1

I am trying to show my webview only after I inject the CSS file to the HTML. I have tried to put it on onPageCommitVisible function, but it works only 23 apis andd above. Someone knows how can I show the webview only after the CSS has fininshed to load? Now it "jumps" and I see the original CSS for the first one second, before the new one is replaced.

@Override
        public void onPageFinished(WebView view, String url) {
            Utils.logDebug(this, "Page finished");
            if (android.os.Build.VERSION.SDK_INT < 23) {
                injectCSS(view);
            }
            super.onPageFinished(view, url);
            showWebView(true);
           onPageChange();
        }

This is my InjestCSS function:

private void injectCSS(WebView webView) {
            try {
                webView.loadUrl("javascript:(function() {" +
                        "var css = document.createElement(\"style\");\n" +
                        "css.type = \"text/css\";\n" +
                        "css.innerHTML = \"" + readFileAsString() + "\";\n" +
                        "document.body.appendChild(css);" +
                        "})()");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

This function inject the CSS code to the HTML, as you can see in the function.

1 Answers1

2

There are few places where you can handle this.

  • You could use evaluateJavaScript instead of loadUrl (API level 19) and pass callback in which you will set webview visible.
  • You could register your own javascript interface using addJavaScriptInterface and call it on the end of your script
  • You could set WebChromeClient and override onJsAlert then in your script raise alert with specific message.
  • UPDATE: Additionally this could be achieved by intercepting one of 'css' request, and append loaded file with needed content. This will allow you to inject your styles right before onPageFinished. Check this this thread.

All approaches I have combined in following example:

    package com.j2ko.webviewapp;

    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Base64;
    import android.util.Log;
    import android.view.View;
    import android.webkit.JavascriptInterface;
    import android.webkit.JsResult;
    import android.webkit.ValueCallback;
    import android.webkit.WebChromeClient;
    import android.webkit.WebView;
    import android.webkit.WebViewClient;

    import java.io.InputStream;
    import java.io.StringReader;
    import java.io.StringWriter;

    public class MainActivity extends AppCompatActivity {
        private static final String MAIN_FUNC_FMT = "(function() { %s })()";
        private static final String FUNC_BODY_FMT =
                "var parent = document.loadedgetElementsByTagName('head').item(0);" +
                        "var css = document.createElement('style');" +
                        "css.type = 'text/css';" +
                        "css.innerHTML = %s;" +
                        "parent.appendChild(css);";

        private static final String BASE64_DECODE_FMT = "window.atob('%s')";

        WebView mWebView;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            mWebView = (WebView) findViewById(R.id.webview);
            mWebView.getSettings().setJavaScriptEnabled(true);
            mWebView.setWebViewClient(new WebViewClient() {
                @Override
                public void onPageFinished(WebView view, String url) {
                    super.onPageFinished(view, url);
                    //Change it to whatever
                    injectWithEvaluateAndInterface(view);
                }

                @Override
                public void onLoadResource(WebView view, String url) {
                    super.onLoadResource(view, url);
                }
            });
            mWebView.setVisibility(View.INVISIBLE);
            mWebView.loadUrl("http://wiki.org");
        }


        private static class CSSInjectBuilder {
            private final String mOrigin;
            private String mAtEnd = null;
            private boolean mUseBase64 = false;

            public CSSInjectBuilder(String css) {
                mOrigin = css;
            }

            public CSSInjectBuilder withBase64() {
                mUseBase64 = true;
                return this;
            }

            public CSSInjectBuilder withExpressionAtEnd(String expression){
                mAtEnd = expression;
                return this;
            }

            String build() {
                String func_body = FUNC_BODY_FMT;

                if (mAtEnd != null) {
                    func_body += mAtEnd;
                }

                final String css;
                if (mUseBase64) {
                    byte[] buffer = mOrigin.getBytes();
                    css = String.format(BASE64_DECODE_FMT, Base64.encodeToString(buffer, Base64.NO_WRAP));
                } else {
                    css = "'" + mOrigin + "'";
                }

                func_body = String.format(func_body, css);

                return String.format(MAIN_FUNC_FMT, func_body);
            }
        }

        byte[] loadAsset() {
            try {
                InputStream inputStream = getAssets().open("style.css");
                byte[] buffer = new byte[inputStream.available()];
                inputStream.read(buffer);
                inputStream.close();
                return buffer;
            } catch (Exception e) {
            }

            return null;
        }

        String loadCSS() {
            return new String(loadAsset());
        }

        void injectWithEvaluate(final WebView view) {
            view.evaluateJavascript(new CSSInjectBuilder(loadCSS()).withBase64().build(), new ValueCallback<String>() {
                @Override
                public void onReceiveValue(String value) {
                    view.setVisibility(View.VISIBLE);
                }
            });
        }

        void injectWithEvaluateAndInterface(WebView view) {
            view.addJavascriptInterface(new WebViewInterface(), "WebViewBackEnd");
            final String injector = new CSSInjectBuilder(loadCSS())
                    .withBase64()
                    .withExpressionAtEnd("window.WebViewBackEnd.CSSInjectionComplete();")
                    .build();

            view.evaluateJavascript(injector, null);
        }

        void injectWithLoadUrlSimple(WebView view) {
            view.loadUrl("javascript:" + loadCSS());
            view.setVisibility(View.VISIBLE);
        }

        void injectWithLoadUrlAndCheckAlert(final WebView view) {
            view.setWebChromeClient(new WebChromeClient() {
                @Override
                public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
                    if (message.equals("CSSInjectionComplete")) {
                        view.setVisibility(View.VISIBLE);
                        return true;
                    }
                    return super.onJsAlert(view, url, message, result);
                }
            });

            //alert could hang aplying scripts so put it on timeout
            final String injector = new CSSInjectBuilder(loadCSS())
                    .withBase64()
                    .withExpressionAtEnd("setTimeout(function(){alert('CSSInjectionComplete');}, 1);")
                    .build();

            view.loadUrl("javascript:"  + injector);
        }

        private class WebViewInterface {

            @JavascriptInterface
            public void CSSInjectionComplete(){

                mWebView.post(new Runnable() {
                    @Override
                    public void run() {
                        mWebView.setVisibility(View.VISIBLE);
                    }
                });
            }
        }

    }
Community
  • 1
  • 1
j2ko
  • 2,479
  • 1
  • 16
  • 29
  • Do I have to implement all approaches or just one? – Brudus Feb 27 '17 at 21:11
  • I've posted all of them just for illustration purpose - choose which one suits more – j2ko Feb 27 '17 at 21:12
  • So I tried just one for now, but with the WebViewInterface I still have a delay between the visible webview and the injection taking place. – Brudus Feb 27 '17 at 21:42
  • Try to delay call to `WebViewInterface` as I've did for alert – j2ko Feb 27 '17 at 21:44
  • You mean like this: `.withExpresionAtEnd("setTimeout(function(){window.WebViewBackEnd.CSSInjectionComplete();}, 1);")` ? – Brudus Feb 27 '17 at 21:52
  • something like that, you could even increase delay if needed. – j2ko Feb 27 '17 at 21:54
  • Doesn't work for me either. I have take 3000 but even with that, it doesn't work. I will try the alert approach from you. – Brudus Feb 27 '17 at 22:01
  • Put log on java side - and take a look if it get executed – j2ko Feb 27 '17 at 22:12
  • Finally! It works with the alert method! Thank you so much! I've worked 2 days on that without a working solution! Saved my day...or well saved my night! – Brudus Feb 27 '17 at 22:36
  • @Brudus glad to help – j2ko Feb 27 '17 at 22:37
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/136854/discussion-between-brudus-and-j2ko). – Brudus Feb 28 '17 at 12:13
  • Thanks, I used shouldInterceptRequest, from the link in the update you gave and it worked. Thanks again. –  Feb 28 '17 at 21:26