PSA: WebView URL-Handling Regression
About 40 minutes ago, a developer posted a question on Stack Overflow that made my heart sink:
I noticed that with the last update of Google System WebView, all the links in my WebViews are opened in the view itself.
While we generally applauded the move to decouple WebView
from firmware
updates, so security fixes can get rolled out quicker, that switch
has its downsides. One is that bugs or other regressions in WebView
behavior roll out to a lot of people before we as developers necessarily
have time to adjust.
In this case, WebView
(from Andoid System WebView 43.0.2357.121)
now behaves as noted above:
-
All links and redirected-to URLs open in the
WebView
by default -
If you return
false
from ashouldOverrideUrlLoading()
in yourWebViewClient
, the URL will be loaded into theWebView
Before, both scenarios would trigger an ACTION_VIEW
Intent
on the URL, typically bringing up a Web browser.
This runs counter to what is documented for shouldOverrideUrlLoading()
and past seven years’ of WebView
behavior.
If your app is using a WebViewClient
to keep all links within the WebView
anyway, you should see no net change in behavior.
However, if your app relies on WebView
opening up some URLs in a browser on its own,
you have a problem.
Right now, though, we do not know if this is an accidental regression or a permanent change in behavior. I filed a bug report on it, but I have little hope that it will ever get addressed. I have had less luck with bug reports on the Chromium project than I have on the Android project, and that’s saying a lot.
Hence, my recommendation at the moment is:
-
Always attach a
WebViewClient
to yourWebView
-
Always implement
shouldOverrideUrlLoading()
on theWebViewClient
-
Always return
true
to indicate that you are handling the event -
Always do what your app needs to have done, whether that is loading the URL into the
WebView
or launching a browser on the URL (rather than returningfalse
and relying on stock behavior)
Something like this static inner class appears to do the trick —
create an instance and pass it to setWebViewClient()
on your
WebView
:
private static class URLHandler extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (shouldKeepInWebView(url)) {
view.loadUrl(url);
}
else {
Intent i=new Intent(Intent.ACTION_VIEW, Uri.parse(url))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
view.getContext().startActivity(i);
}
return(true);
}
private boolean shouldKeepInWebView(String url) {
return(true); // or false, or use regex, or whatever
}
}
(where you would put your business logic in shouldKeepInWebView()
to
determine whether or not a given URL should stay in the WebView
or launch a browser)
Unfortunately, since I do not see a getWebViewClient()
counterpart to setWebViewClient()
, we cannot use a chaining pattern
here easily. If you already have a WebViewClient
for other
reasons, you would want to blend in something akin to the code
shown above.
With luck, we will get more clarity on the situation in the coming days (weeks? months?).