11

I have a WebView in my app that displays a page not belonging to me. My desired behavior is, if any link is tapped by the user, the device's Browser app is launched, and the resulting page is loaded there. Unfortunately, this page is doing some weird things, so shouldOverrideUrlLoading() does not fire.

My attempted solution is to hook some javascript into pushState and use an interface to run Android code to launch the browser app.

Here is my interface:

public class LaunchExternalBrowserHack {
    Context mContext;

    LaunchExternalBrowserHack(Context c) {
        mContext = c;
    }

    @JavascriptInterface
    public void launchExternalBrowser(String url) {
        openUrl(url);
    }
}

I'm injecting some javascript into the page in onPageFinished():

public void onPageFinished(WebView view, String url) {
     mWebView.loadUrl(javascript);
}

Here is my javascript:

private final String javascript = "javascript:history.pushState = function (state, title, url) { console.log(url); console.log(location.href); Uphoria.launchExternalBrowser(location.origin, url); };";

And, of course, I'm adding the interface to the WebView:

mWebView.addJavascriptInterface(new LaunchExternalBrowserHack(getContext()), "Android");

So this seems to work. The Browser app is launching and the next page is opening.

However, the WebView is also moving forward, too. I want to prevent this, but I cannot find a way to prevent the WebView moving forward while still allowing me to capture the forwarding url and launch the Browser app. As I mentioned earlier, with this webpage, shouldOverrideUrlLoading is not firing.

Ideas?

Andrew
  • 20,756
  • 32
  • 99
  • 177
  • You mention not getting a result in `onReceiveValue(...)`. Can you see whether changing the javascript into a function call would work? Like [this answer](http://stackoverflow.com/a/19790911/503508) suggests. Specifically, in this style: `(function() { return { var1: \"variable1\", var2: \"variable2\" }; })();` – Knossos Apr 05 '16 at 09:22

2 Answers2

5

The user is trying to move away from the page. to stop him from moving out, return false. to let him move away, don't return anything but you can execute any code that you may want. This event is fired immediately when navigation event occurs including page refresh.

private final String javascript = "window.onbeforeunload = function(){ dosomething(); }; window.unload = function(){ dosomething(); }; function dosomething() { /* Write code here */  };"

You need to include the entire javascript code above, since some browsers use onbeforeunload and some use unload

EDIT

After careful reading of your problem I found my solution to be of little use.

Here's another approach using history. If your history is blank, that means its the first page. but if you have some items in history, it means that navigation has occurred.

You can check it as following

private final String javascript = "javascript:"+"if(window.history.length>=1 && document.referrer=='') Android.launchExternalBrowser(location.href);";
Rohit Agre
  • 1,528
  • 1
  • 14
  • 25
1

This answer is wrong. Kept here because it has importantly relevant comments.

The reason that you are not getting a result from your Javascript, is that you are not calling a function directly. You are assigning a function in your Javascript that returns a value, but it will not return that to your code, it will return that to whatever calls history.pushState().

private final String javascript = "javascript:" + "var pushState = history.pushState; history.pushState = function () " +
        "{ pushState.apply(history, arguments); console.log(arguments); return location.href; " +
        "};";

Instead, you can test whether something like this returns you a value:

private final String javascript = "(function() { return location.href; })();";
Knossos
  • 15,802
  • 10
  • 54
  • 91
  • Won't this return right away? I want the handler to fire when the user taps on something that causes him/her to navigate away. – Andrew Apr 05 '16 at 13:35
  • Yes it would return right away. I don't think that you can run a callback in the style you are thinking of. You are directly evaluating Javascript through that function. You will send the JS immediately, it will be evaluated and a value will be returned (if given). However, in your code you are evaluating the setting of a function, and that is that. You could conceivably use `webView.addJavascriptInterface`. Have you investigated that? – Knossos Apr 05 '16 at 13:41
  • Yea, but my interface doesn't seem to fire on Android 4.4+. It works fine pre-4.4. – Andrew Apr 05 '16 at 13:46
  • Ah yes, of course you have. Hmm. You appear to be right about [evaluateJavascript](https://developer.android.com/guide/webapps/migrating.html). Needs more investigation. – Knossos Apr 05 '16 at 13:54
  • It appears `addJavascriptInterface` does work in 4.4+. If you look at the js above where I'm calling `apply`, I think that's turning my function into a recursive function. It wasn't working because I was filling up the stack. I now have it properly launching the Browser app and opening the url there for all versions of Android (4.1+). However, I need to find a way to stop the WebView from navigating forward, too. So far, I haven't been successful in figuring that out. – Andrew Apr 06 '16 at 13:00
  • Stopping the page redirecting, perhaps if you push this Javascript to the page: `window.onbeforeunload = function(){ return false; };` then you can stop it changing page? – Knossos Apr 07 '16 at 09:44
  • Unfortunately, I've already tried this and it doesn't seem to work. About the only thing I've tried that "works" is calling `myWebView.goBack();` once the javascript triggers my interface. I don't like this though because the user can still see it happening. – Andrew Apr 07 '16 at 12:33
  • What if you set the `WebView` to `View.INVISIBLE` and `goBack();` whilst you show an indeterminate progress bar? Since you intend to start the main browser from it. Then once you return to your `Activity` ensure it is back to `View.VISIBLE`. – Knossos Apr 07 '16 at 13:15
  • I'm not sure this will work for me. I'm calling `myWebView.goBack()` before I call `startActivity` to send the user to the Browser app. When they hit back from the Browser app and my WebView shows again, it is still on the second page. The back functionality doesn't seem to trigger until the user is back on my Activity. Ideally, it would be a seamless transition. – Andrew Apr 07 '16 at 13:40