question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

OnMessage webview not called in physical device android

See original GitHub issue

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!

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:5 (1 by maintainers)

github_iconTop GitHub Comments

1reaction
guhungrycommented, Mar 29, 2019
0reactions
longnk-1447commented, 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)} />
)
Read more comments on GitHub >

github_iconTop Results From Across the Web

OnMessage webview not called in physical device android #463
Hi guys, please give me solution, my webview have javascript code so iOS working emulator working physical device android not working ...
Read more >
onMessage android Webview React Native not working in ...
My webview contain (embed, code, ...) so because i used onMessage and injectedJavascript, i'm try console event in onMessage in ios and ...
Read more >
HTML : React Native WebView onMessage doesn't do anything
HTML : React Native WebView onMessage doesn't do anything [ Gift : Animated ... provided in this video is as it is with...
Read more >
App security best practices - Android Developers
If your app uses Google Play services, make sure that it's updated on the device where your app is installed. Perform the check...
Read more >
Using the TomTom Maps Web SDK in a React Native Web ...
React Native WebView helps your JavaScript application's maps look great across ... look across iOS and Android devices — without coding twice.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found