Android Web-View : Inject local Javascript file to Remote Webpage

58,273

Solution 1

There is a way to 'force' the injection of your local Javascript files from local assets (e.g., assets/js/script.js), and to circumvent the 'Not allowed to load local resource : file:///android_assets/js/script.js ...' issue.

It is similar to what described in another thread (Android webview, loading javascript file in assets folder), with additional BASE64 encoding/decoding for representing your Javascript file as a printable string.

I am using an Android 4.4.2, API level 19 Virtual Device.

Here are some code snippets:

[assets/js/script.js]:

    'use strict';

    function test() {
       // ... do something
    }

    // more Javascript

[MainActivity.java]:

    ...

    WebView myWebView = (WebView) findViewById(R.id.webView);
    WebSettings webSettings = myWebView.getSettings();

    webSettings.setJavaScriptEnabled(true);
    webSettings.setAllowUniversalAccessFromFileURLs(true);
    myWebView.setWebViewClient(new WebViewClient() {
       @Override
       public boolean shouldOverrideUrlLoading(WebView view, String url) {
          return false;
       }

       @Override
       public void onPageFinished(WebView view, String url) {
          super.onPageFinished(view, url);

          injectScriptFile(view, "js/script.js"); // see below ...

          // test if the script was loaded
          view.loadUrl("javascript:setTimeout(test(), 500)");
       }

       private void injectScriptFile(WebView view, String scriptFile) {
          InputStream input;
          try {
             input = getAssets().open(scriptFile);
             byte[] buffer = new byte[input.available()];
             input.read(buffer);
             input.close();

             // String-ify the script byte-array using BASE64 encoding !!!
             String encoded = Base64.encodeToString(buffer, Base64.NO_WRAP);
             view.loadUrl("javascript:(function() {" +
                          "var parent = document.getElementsByTagName('head').item(0);" +
                          "var script = document.createElement('script');" +
                          "script.type = 'text/javascript';" +
             // Tell the browser to BASE64-decode the string into your script !!!
                          "script.innerHTML = window.atob('" + encoded + "');" +
                          "parent.appendChild(script)" +
                          "})()");
          } catch (IOException e) {
             // TODO Auto-generated catch block
             e.printStackTrace();
          }
       }
    });

    myWebView.loadUrl("http://www.example.com");

    ...

Solution 2

loadUrl will work only in old version use evaluateJavascript

webview.evaluateJavascript("(function() { document.getElementsByName('username')[0].value='USERNAME';document.getElementsByName('password')[0].value='PASSWORD'; "+
"return { var1: \"variable1\", var2: \"variable2\" }; })();", new ValueCallback<String>() {
                @Override
                public void onReceiveValue(String s) {
                    Log.d("LogName", s); // Prints: {"var1":"variable1","var2":"variable2"}
                }
            });

Solution 3

Yes, you could use shouldInterceptRequest() to intercept remote url loading and return local stored content.

WebView webview = (WebView) findViewById(R.id.webview);

webview.setWebViewClient(new WebViewClient() {
    @Override
    public WebResourceResponse shouldInterceptRequest (final WebView view, String url) {
       if (url.equals("script_url_to_load_local")) {
           return new WebResourceResponse("text/javascript", "UTF-8", new FileInputStream("local_url")));
       } else {
           return super.shouldInterceptRequest(view, url);
       }
    }
});

Solution 4

Be careful using evaluateJavascript: if there is a syntax error or exception thrown in your javascript it will call your onReceiveValue with a null. The most common way to support both SDK 19 as well as lower seems to be like this:Fill form in WebView with Javascript

Also if you get terribly desperate for some kind of browser functionality (in my case, never could figure out how to get DRM to work well) you could use a bookmarklet within normal chrome, which works only if you type the bookmark name into the omnibox but does work and does inject javascript.

Also be aware that with the default WebView you can't use javascript alerts to test anything, they don't show. Also be aware that "video" by default (like html <video> tags) doesn't "really work" by default and also DRM video doesn't work by default, they're all configure options :\

Share:
58,273
sumit
Author by

sumit

Updated on July 09, 2022

Comments

  • sumit
    sumit almost 2 years

    It has been asked many times before, I browsed through everything, no clear answers yet.

    Question simplified: Is it possible to inject local Javascript file (from asset or storage) to remote webpage loaded in an Android Web-View? I know that it is possible to inject such files to local Webpages (Assets HTML) loaded in a Web-View.

    Why do I need this to work? : To make browsing experience faster, by avoiding downloading of bigger files such as Js and CSS files every time. I want to avoid Web-View Caching.

  • sumit
    sumit over 10 years
    Thanks a lot, that worked. However it works only for android version upto 4.3. For Android 4.4, it says I have to run the loading thing (loadUrl) inside the UI thread only and when I do that using RunOnUIThread() it does nothing, I mean it loads nothing. Do you know what can be the problem?
  • Raffaele N.
    Raffaele N. over 10 years
    @sumit In Android 4.4 I use Runnable objects as follows: first, I define a Handler in class MainActivity ... { final Handler handler = new Handler(); .... Second, in my method I use a Runnable object, e.g. void loadWebPage() { final Runnable r = new Runnable() { public void run() { WebView myWebView = ... } }; handler.post(r) }. I hope it helps.
  • sumit
    sumit over 10 years
    I was doing the same but it didnt work. The reason is, in Kitkat version it is not possible to inject js file locally using view.loadUrl(). I had to use evaluateJavaScript() method and it worked. To inject file CSS files one can use view.loadUrl(), but for js, evaluateJavaScript() has to be used. Welcome to ChromeView :)
  • Jon Hargett
    Jon Hargett almost 10 years
    This is an awesome answer! I only have one problem with it, it doesn't work with the asset you want to inject is in UTF-8 and outside of the ANSI character range. I was able to fix it by changing the decode to this: "script.innerHTML = decodeURIComponent(escape(window.atob('" + encoded + "')));" +
  • dasmikko
    dasmikko about 8 years
    This is a better solution, as you can run Javascript more consistently.
  • Roel
    Roel about 7 years
    What is clientLogin? aha... the webview
  • James Moore
    James Moore almost 7 years
    This requires minSdkVersion 19 or higher.
  • Zohab Ali
    Zohab Ali almost 6 years
    Genius genius!!! you saved my day bro! I have posted a question with 200 bounty. If you post answer under that I can accept your answer. Here is the link: stackoverflow.com/questions/51570906/…
  • corwin.amber
    corwin.amber about 5 years
    Note that shouldInterceptRequest(WebView, String) has been superseded by shouldInterceptRequest(WebView, WebResourceRequest). This solution still works (except that you have to check request.getUrl() and remember that its type is Uri). Also note that if you use WebView.loadData() without a base URL, you cannot use relative paths in <script src="...">, seems that WebView ignores them. Use loadDataWithBaseURL() instead.
  • Eilon
    Eilon about 4 years
    Worked for me in a modified way. I changed setTimeout(test(), 500) to setTimeout(test, 0). First, you don't want to call the test function immediately, you want setTimeout to call it (though it works both ways!). Second, why wait 500ms; You can just queue the function call to run immediately when ready.
  • user7637745
    user7637745 over 2 years
    This worked for me with some minor changes and with setTimeout too. Thanks!