Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OnMessage webview not called in physical device android #24143

Closed
longnk-1447 opened this issue Mar 26, 2019 · 5 comments
Closed

OnMessage webview not called in physical device android #24143

longnk-1447 opened this issue Mar 26, 2019 · 5 comments
Labels
Bug Component: WebView Related to the WebView component. Platform: Android Android applications. Ran Commands One of our bots successfully processed a command. Resolution: Locked This issue was locked by the bot.

Comments

@longnk-1447
Copy link

longnk-1447 commented Mar 26, 2019

Hi guys, please give me solution, my webview have javascript code so

  1. iOS working

  2. emulator working

  3. physical device android not working

  4. physical device android 8.1

  5. RN version 57.0.1. I try upgrade 0.59.1 but not working

This is my code:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { WebView, View, Dimensions, Platform, Image, Linking } from 'react-native';
import navigator from '../Lib/navigator';
import templates from './injectedTemplates';
import {
    addImages,
    resetImages,
} from '../Redux/actions';
import { showError } from './toasts';
import { COLORS } from '../Config/colorVars';

const screenHeight = Dimensions.get('window').height;

const wordsPerScreen = screenHeight * 120 / screenHeight;
const charsToScreens = str => str.split(' ').length / wordsPerScreen;

class WebViewAutoHeight extends Component {
    constructor(props) {
        super(props);
        this.state = {
            isRendering: true,
            realContentHeight: charsToScreens(this.props.source.html) * screenHeight,
        };

        this.injectedJavaScript = this.injectedJavaScript.bind(this);
    };

    componentWillMount() {
        if(this.props.fontSize == 'Large' && Platform.OS === 'android') {
            this.setState({
                realContentHeight: charsToScreens(this.props.source.html)*screenHeight*1.3
            });
        }
    };

    shouldComponentUpdate(nextProps, nextState) {
        return this.props.source.html !== nextProps.source.html ||
            this.state.realContentHeight !== nextState.realContentHeight;
    };

    onLoad() {
        this.props.onLoad();
    };

    onMessage(event) {
        const { action, params } = JSON.parse(event.nativeEvent.data);
        switch (action) {
        case 'heightCaculated': {
            return this.setState({
                realContentHeight: params.height,
                isRendering: false,
            });
        };
        case 'addImages': {
            this.props.resetImages();
            params.imgs.map(img => Image.prefetch(img));
            return this.props.addImages(params.imgs);
        };
        case 'openGallery': {
            return navigator.navigate('Gallery', {
                index: params.index,
            })
        };
        case 'openYoutube': {
            return Linking.canOpenURL(params.href).then((supported) => {
                if (!supported) {
                    showError(`Can't handle url: ${params.href}`)
                    return false
                }

                return Linking.openURL(params.href);
            });
        };
        case 'openLink': {
            const checkInteralPostLink = params.href.match(/https:\/\/(?:stg1.)?viblo.asia\/p\/.*-([a-zA-Z0-9]+)/);
            if (checkInteralPostLink && checkInteralPostLink[1]) {
                return navigator.navigate('SinglePost', {
                    post: { slug: checkInteralPostLink[1] },
                }, checkInteralPostLink[1]);
            }
            const checkInteralUserLink = params.href.match(/https:\/\/(?:stg1.)?viblo.asia\/u\/([a-zA-Z0-9.]+)/);
            if (checkInteralUserLink && checkInteralUserLink[1]) {
                return navigator.navigate('UserProfile', {
                    user: checkInteralUserLink[1],
                }, checkInteralUserLink[1]);
            }
            const checkInteralSeriesLink = params.href.match(/https:\/\/(?:stg1.)?viblo.asia\/s\/.*-([a-zA-Z0-9]+)/);
            if (checkInteralSeriesLink && checkInteralSeriesLink[1]) {
                return navigator.navigate('SingleSeries', {
                    series: checkInteralSeriesLink[1],
                }, checkInteralSeriesLink[1]);
            }
            const checkInteralQuestionLink = params.href.match(/https:\/\/(?:stg1.)?viblo.asia\/q\/.*-([a-zA-Z0-9]+)/);
            if (checkInteralQuestionLink && checkInteralQuestionLink[1]) {
                return navigator.navigate('QuestionDetail', {
                    question: { hash_id: checkInteralQuestionLink[1] },
                }, checkInteralQuestionLink[1]);
            }
            return navigator.navigate('Browser', {
                url: params.href,
            });
        };
        default:
            return null;
        };
    };

    hackBefore() {
        return Platform.OS === 'ios' ?
        `
            (function(){
                var originalPostMessage = window.postMessage;
                var patchedPostMessage = function(message, targetOrigin, transfer) {
                    originalPostMessage(message, targetOrigin, transfer);
                };
                patchedPostMessage.toString = function() {
                    return String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage');
                };
                window.postMessage = patchedPostMessage; `
        :
        `
            if (window.postMessage.length !== 1) {
                window.postMessage = function(msg) {
                    setTimeout(function () {
                    window.postMessage(msg);
                    }, 1000);
                }
            }
        `
    }

    hackAfter() {
        return Platform.OS === 'ios' ? '})();' : ''
    }

    injectedJavaScript() {
        return ` 
            ${this.hackBefore()}
            NodeList.prototype.forEach = Array.prototype.forEach;
            
            if (!Element.prototype.matches)
            Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
        
            if (!Element.prototype.closest)
            Element.prototype.closest = function(s) {
                var el = this;
                if (!document.documentElement.contains(el)) return null;
                do {
                    if (el.matches(s)) return el;
                    el = el.parentElement || el.parentNode;
                } while (el !== null && el.nodeType === 1); 
                return null;
            };

            function dispatchAction(action, params) {
                window.postMessage(JSON.stringify({
                    action,
                    params,
                }));
            };

            var contentDiv = document.querySelector('.md-contents');    

            dispatchAction('heightCaculated', {
                height: contentDiv ? contentDiv.clientHeight : 0,
            });
            
            var imgs = [];
            
            document.querySelectorAll('img:not(.emoji):not(.embedded-img):not(.embedded-btn)').forEach(function (img, index) {
                var src = img.getAttribute('src');
                imgs.push(src);

                img.addEventListener('click', function (event) {
                    dispatchAction('openGallery', {
                        index: index,
                    });
                });
            });

            dispatchAction('addImages', {
                imgs: imgs,
            });

            document.querySelectorAll('a:not(.embedded-a)').forEach(function (a) {
                a.addEventListener('click', function (event){
                    event.preventDefault();
                    dispatchAction('openLink', {
                        href: event.target.closest('a').getAttribute('href'),
                    });
                    return false;
                });
            });


            document.querySelectorAll('a.embedded-a').forEach(function (a) {
                a.addEventListener('click', function (event){
                    event.preventDefault();
                    dispatchAction('openYoutube', {
                        href: event.target.closest('a').getAttribute('href'),
                    });
                    return false;
                });
            });

            document.querySelectorAll('img.embedded-btn').forEach(function (img) {
                var a = img.previousElementSibling;
                
                img.addEventListener('click', function (event){
                    dispatchAction('openYoutube', {
                        href: a.getAttribute('href'),
                    });
                    return false;
                });

            return false;
            });

            ${this.hackAfter()}
        `
    };

    render() {
        const { source, fontSizeContent, fontSizeContentPre, highlightThemes, enableNightMode, ...otherProps } = this.props;
        const { realContentHeight } = this.state;
        const html = source.html;
        return (
            <View style={{ flex: 1 ,backgroundColor: enableNightMode ? COLORS.COLOR_NIGHT_MODE : COLORS.COLOR_UN_NIGHT_MODE}}>
                <View 
                    style={{
                        height: Platform.OS === 'ios' ? realContentHeight : realContentHeight < screenHeight ? screenHeight*70/100 : realContentHeight+screenHeight,
                        paddingHorizontal: 5,
                        backgroundColor: enableNightMode ? COLORS.COLOR_NIGHT_MODE : COLORS.COLOR_UN_NIGHT_MODE
                    }}
                >
                {
                    Platform.OS === 'android' ? 
                         <WebView
                            {...otherProps}
                            javaScriptEnable={true}
                            domStorageEnabled={true}
                            mixedContentMode={'compatibility'}
                            source={{ baseUrl: '', html: templates[this.props.template]({html, fontSizeContent, fontSizeContentPre, highlightThemes, enableNightMode}) }}
                            onLoad={this.onLoad.bind(this)}
                            injectedJavaScript={this.injectedJavaScript()}
                            onMessage = {
                                (event) => {
                                    let message = event.nativeEvent.data;
                                    console.log(message);
                                    console.log('message');
                                }
                            }
                            scrollEnabled={false}
                            style={{
                                height: realContentHeight, 
                                backgroundColor: enableNightMode ? COLORS.COLOR_NIGHT_MODE : COLORS.COLOR_UN_NIGHT_MODE
                            }}
                        />
                    :  <WebView
                            {...otherProps}
                            javaScriptEnable={true}
                            domStorageEnabled={true}
                            mixedContentMode={'compatibility'}
                            source={{ html: templates[this.props.template]({html, fontSizeContent, fontSizeContentPre, highlightThemes, enableNightMode}) }}
                            onLoad={this.onLoad.bind(this)}
                            injectedJavaScript={this.injectedJavaScript()}
                            onMessage={this.onMessage.bind(this)}
                            scrollEnabled={false}
                            style={{
                                height: realContentHeight, 
                                backgroundColor: enableNightMode ? COLORS.COLOR_NIGHT_MODE : COLORS.COLOR_UN_NIGHT_MODE
                            }}
                        />
                }             
                </View>
            </View>
        );
    }
}

WebViewAutoHeight.propTypes = {
    source: PropTypes.object.isRequired,
    injectedJavaScript: PropTypes.string,
    onLoad: PropTypes.func,
    onMessage: PropTypes.func,
    onNavigationStateChange: PropTypes.func,
    template: PropTypes.string.isRequired,
    style: WebView.propTypes.style
}

WebViewAutoHeight.defaultProps = {
    onLoad: () => {
    },
    onMessage: () => {
    },
    onNavigationStateChange: () => {
    },
}

const mapStateToProps = (state) => {
    return {
        fontSize: state.fontSize.fontSize,
        highlightThemes: state.fontSize.highLight,
        enableNightMode: state.fontSize.enable
    }
}

export default connect(mapStateToProps, {
    addImages,
    resetImages,
})(WebViewAutoHeight)

and file template.js

import vibloStyle from './assets/viblo.css'
import darkStyle from './assets/dark.css'
import grayStyle from './assets/gray.css'
import {Dimensions} from 'react-native'
import {COLORS} from '../../Config/colorVars'

const screenWidth = Dimensions.get('window').width

const transferHtml = ({html, fontSizeContent, fontSizeContentPre, highlightThemes, enableNightMode}) => {
    const highlight = highlightThemes === 'dark' ? darkStyle : grayStyle
    return `
        <html lang='en'>
            <head>
                <meta charset='utf-8'>
                <meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=yes'>
                <style>${highlight}</style>
                <style>${vibloStyle}</style>
                <style>
                ::-webkit-scrollbar { 
                    display: none; 
                }

                * { 
                    line-height: normal;
                    font-size: ${fontSizeContent};
                }

                body {
                    text-align: left;
                    padding: 5;
                    border: none;
                    background-color: ${enableNightMode ? COLORS.COLOR_NIGHT_MODE : COLORS.COLOR_UN_NIGHT_MODE};
                    color: ${enableNightMode ? COLORS.COLOR_UN_NIGHT_MODE : COLORS.COLOR_NIGHT_MODE};
                }

                p {
                    color: ${enableNightMode ? COLORS.COLOR_UN_NIGHT_MODE : COLORS.COLOR_NIGHT_MODE} !important;
                }

                blockquote > p {
                    color: ${COLORS.COLOR_DESCRIPTION} !important;
                }

                p > code {
                    background: #EBEBEB !important;
                    color: black !important;
                }

                table {
                    max-width: 100px
                }

                td {
                    max-width: ${screenWidth*50/100};
                    font-size: ${screenWidth*3/100};
                }

                td > code {
                    font-size: ${screenWidth*3/100};
                }

                code {
                    padding: 0;
                    color: ${highlightThemes === 'dark' ? 'white' : 'black'} !important;
                    background: ${enableNightMode && COLORS.COLOR_ICON_NIGHT_MODE} !important;
                }

                pre > code {
                    background: ${highlightThemes === 'dark' ? 'black' : '#f1f2f3'} !important;
                    border-width: 1px;
                    border-style: solid;
                    border-color: white;
                }
                
                iframe {
                    width: 100%;
                    height: 250px;
                }

                ul {
                    padding-left: 20px;
                }

                ol {
                    padding-left: 0px;
                }

                li {
                    line-height: 30px;
                    padding: 10px;
                }

                li > code {
                    background: ${ enableNightMode ? COLORS.COLOR_ICON_NIGHT_MODE : '#EBEBEB'} !important;
                    color: black !important;
                }

                blockquote{
                    padding-top: 1px !important;
                    padding-bottom: 1px !important;
                    margin-bottom: 1px !important;
                }

                span {
                    font-size: ${fontSizeContentPre};
                }

                pre {
                    font-size: ${fontSizeContentPre};
                }

                .embedded-container {
                    position: relative;
                }

                .embedded-img {
                    width: 100%;
                    min-height: 180px;
                    opacity: 0.3;
                }

                .embedded-btn {
                    width: 50px;
                    height: 50px;
                    position: absolute;
                    top: 50%;
                    left: 50%;
                    transform: translate(-50%, -50%);
                }
                </style>
            </head>
            <body>
                <div class='post-content-wrapper'>
                <div class='md-contents post-content'>
                    ${html}
                </div>
                </div>
                <script>
                    var wideTables = Array.from(document.querySelectorAll('table')).filter(function(table) {
                        return table.rows[0].cells.length > 2;
                    });

                    wideTables.forEach(function(table) {
                        var hiddenThs = [];
                        table.querySelectorAll("th:nth-child(n+3)").forEach(function(th) {
                            hiddenThs.push(th.innerHTML);
                            th.remove();
                        });
                        table.querySelectorAll("tbody>tr").forEach(function(tr) {
                            var newTr = tr.cloneNode(true);
                            var expandedContent = Array.from(newTr.querySelectorAll("td:nth-child(n+3)")).map(function(td, index) {
                                var newTd = '<li><b>' + hiddenThs[index] + '</b>:' + td.innerHTML + '</li>';
                                td.remove();
                                return newTd;
                            }).join('');
                            var toggleIcon = document.createElement('span');
                            toggleIcon.style.color = '#5488c7';
                            toggleIcon.style.margin = '0 10px';

                            var textNode = document.createTextNode('\u25BC');
                            toggleIcon.appendChild(textNode);

                            newTr.children[0].insertBefore(toggleIcon, newTr.children[0].firstChild);
                            newTr.addEventListener('click', function() {
                                if (newTr.nextSibling.nodeType === 3) {
                                    toggleIcon.innerHTML = '\u25B2';
                                    newTr.insertAdjacentHTML('afterEnd', '<tr><td colspan="2">' + '<ul class="row-more-detail">' + expandedContent + '</ul>' + '</td></tr>');
                                } else {
                                    toggleIcon.innerHTML = '\u25BC';
                                    newTr.nextSibling.remove();
                                }
                                var contentDiv = document.querySelector('.md-contents'); 
                                if (contentDiv) {
                                window.postMessage(JSON.stringify({
                                    action: 'heightCaculated',
                                    params: {
                                    height: contentDiv.clientHeight
                                    },
                                }));
                                };
                            }, false);
                            tr.parentNode.replaceChild(newTr, tr);
                        });
                    });
                </script>
            </body>
        </html>`
}

export default transferHtml

Please help me, Thanks!

@react-native-bot react-native-bot added Component: WebView Related to the WebView component. Platform: Android Android applications. labels Mar 26, 2019
@react-native-bot
Copy link
Collaborator

Thanks for submitting your issue. Can you take another look at your description and make sure the issue template has been filled in its entirety?

👉 Click here if you want to take another look at the Bug Report issue template.

@react-native-bot react-native-bot added Ran Commands One of our bots successfully processed a command. Resolution: Needs More Information labels Mar 26, 2019
@moxorama
Copy link

Maybe in would be better to open this issue in react-native-community repository? Webview is deprecated

@longnk-1447
Copy link
Author

Maybe in would be better to open this issue in react-native-community repository? Webview is deprecated

Thank moxorama, but I try install react-native-webview so it's not working!

@longnk-1447
Copy link
Author

longnk-1447 commented Mar 28, 2019

My solution:
I'm assign a references in Webview and setTimeout 3s call this references, but sometime working error!

render() {
if (Platform.OS === 'android') {
            setTimeout(() => {
                if (this.webref) {
                    this.webref.injectJavaScript(this.injectedJavaScript());
                }
            }, 3000);
        }
}
return(
<Webview ref={r => (this.webref = r)} />
)

@guhungry
Copy link
Contributor

Since WebView was remove from React Native @6345fcf. This issue should be migrate to https://github.com/react-native-community/react-native-webview and close.

@facebook facebook locked as resolved and limited conversation to collaborators Apr 1, 2020
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Apr 1, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Bug Component: WebView Related to the WebView component. Platform: Android Android applications. Ran Commands One of our bots successfully processed a command. Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests

5 participants