Webview shouldOverrideUrlLoading not getting called - javascript

I am making an ebook reader which uses epub format to load books into webviews.
In some of the books there is an anchor link to some portions in the same chapter. Each chapter is loaded as html. This is how the link look like
file:///storage/sdcard0/Android/data/com.abc.reader/files/Download/498935/epub/resources/498935/OEBPS/#footnote-165093-1-backlink
I tried using shouldOverrideUrlLoading() method to get the call back , but it's not getting called and when I press the links in onPageFinished the url shown as about:blank
reader.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Log.w("TESTTESTOVERRIDE "+url);
view.loadUrl(url);
return false;
}
#Override
public void onPageFinished(WebView view, String url) {
// after the data has been loaded, the following is executed
// TODO Auto-generated method stub
super.onPageFinished(view, url);
System.out.println("check.... onPageFinishedEntered.."
+ url.toString());
view.loadUrl(jsfileloadurl);
}
Any ideas?
EDIT: In 4.1 devices I get the anchor links correctly,but in 4.4 or 5.0 it is about:blank. (in both cases shouldOverrideUrlLoading is not called)

I haven't tested this programmatically but I believe you are facing this issue because there was major changes in how webview works post OS 4.4 . You should check this link
https://developer.android.com/guide/webapps/migrating.html#URLs
Under section 'Custom Url Handling' it says that shouldOverrideUrlLoading() will not be invoked for invalid url. Ideally file:// should be treated as valid url but seems like it's not happening here.
One possible solution is to load you main webview content with loadDataWithBaseURL
and provide baseurl as some test url e.g. http://mytestap.testurl , it will guarantee shouldOverrideUrlLoading will get invoked all time. As a next step you need to remove prefix 'http://mytestap.testurl' if exist in the received url in shouldOverrideUrlLoading callback.

In my case it didn't work because of POST requests on web page. shouldOverrideUrlLoading:
Note: This method is not called for POST requests.
Note: This method may be called for subframes and with non-HTTP(S)
schemes; calling WebView#loadUrl(String) with such a URL will fail.
Override shouldInterceptRequest instead (one or both versions). See also https://medium.com/#madmuc/intercept-all-network-traffic-in-webkit-on-android-9c56c9262c85.

Yes. Mr. androgeek answered it rightly.
From Android OS 4.4(KK), if you implement callbacks such as shouldOverrideUrlLoading() or shouldInterceptRequest(), then WebView invokes them only for valid URLs.
If you are using Custom URL and under your control then you need to follow RFC 3986 standard to above methods called. Kindly check RFC 3986 related file:// and correct your URL

I am not sure whether the below will resolve your problem or not.
Please add below code before setting the WebViewClient
reader.getSettings().setLoadWithOverviewMode(true);
reader.getSettings().setUseWideViewPort(true);
/*This makes the layout/page rendering independent of the devices.
I use this to display local HTML pages.*/
reader.getSettings().setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
In addition I have zoom controls enabled. Please note that I have tested my code from API-10 onwards with multiple devices and brands (HTC, Samsung, Nexus etc.) and found that the shouldOverrideUrlLoading works all the time.
If things do not work well, try extending the WebViewClient and Override the shouldOverrideUrlLoading method
class MyWebView extends WebViewClient{
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return false; //THis should be false always
}
}
Now set the WebViewClient as reader.setWebViewClient(new MyWebView());

Related

opening link in external browser from page in WebView

I'm developing an angular website which is loading in an app from a WebView, and there is only one of the links in it that has to be opened outside of the app (external browser)
I need a way to handle this from JavaScript not putting extra work to the android side.
and i have already tried some ways including:
window.open("url","_system")
(navigator as any).app.loadUrl("http://google.com", {openExternal : true});
Well, there is no such thing
instead it must be handled from android application code. you can add a parameter to the url when u need it to open in external browser, ( here it is external=true ) and then check for that parameter in your webview url loading as below:
webView.setWebViewClient(new WebViewClient(){
#RequiresApi(api = Build.VERSION_CODES.N)
#Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
if((String.valueOf(request.getUrl())).contains("external=true")) {
Intent intent = new Intent(Intent.ACTION_VIEW, request.getUrl());
view.getContext().startActivity(intent);
return true;
} else {
view.loadUrl(String.valueOf(request.getUrl()));
}
return true;
}
});

Navigation is blocked error in Chrome 61+ on Android

I'm building a login page that, upon submitting and validation of the user credentials, opens up a native mobile application. Up till last week, I had this working cross mobile OS by using a custom scheme URI, something like:
function onLoginCallback() {
const redirectUri = 'customscheme:/action?param1=val1&param2=val2';
window.location.replace(redirectUri);
}
The login page is displayed in an IABT, short for In App Browser Tab.
However, since the release of version 61 of Chrome, this is approach is broken on Android. Chrome blocks the redirect because there's no apparent user action related to the redirect (see here for more information on the matter).
As a consequence, when executing the code above, I'll end up with a warning in the console:
Navigation is blocked: customscheme:/action?param1=val1&param2=val2
I've also tried updating the custom scheme url to an intent url but to no avail. Googling about this issue doesn't readily provide a clear solution, so I'm hoping anyone on can help me out.
Edit: Tried to reproduce the issue with the following scenario (as close as possible to the real life scenario):
IABT displays a page with a single button
Clicking the button fires an jsonp call to a mock endpoint
The JSONP callback is executed and fires off a custom event
An event handler for the custom event is triggered and redirects the browser to another mock endpoint
That mock endpoint responds with a 302 to the custom deeplink scheme.
Alas, this seems to be working. I would have expected that the inclusion of the jsonp call would cause Chrome to block the final redirect as it would not be able to identify it as a user initiated action.
Edit 2: Managed to get a reproducible scenario. We've set up a dummy endpoint, that upon request simply returns a 302 with the custom scheme in the Location header. This is blocked on all tries, except for the first one. That fact still boggles the mind. We're using the AppAuth for Android application to test the setup.
I'm opening a custom tab to the endpoint as shown below. The code is taken from this answer.
void launchTab(Context context, Uri uri){
final CustomTabsServiceConnection connection = new CustomTabsServiceConnection() {
#Override
public void onCustomTabsServiceConnected(ComponentName componentName, CustomTabsClient client) {
final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
final CustomTabsIntent intent = builder.build();
client.warmup(0L); // This prevents backgrounding after redirection
intent.launchUrl(context, uri);
}
#Override
public void onServiceDisconnected(ComponentName name) {
}
};
CustomTabsClient.bindCustomTabsService(context, "com.android.chrome", connection);
}
We ended up implementing our login and registration forms with a classic post-redirect-get pattern.
The server responds with a 302 to the custom URI scheme. Because in this setup there's no asynchronous execution between the user submitting the form and the browser receiving a redirect, Chrome correctly identifies the chain of actions as trusted and thus will not block the navigation.
I realise this might not be the preferred solution for everyone. A possible alternative to support asynchronous execution flows is the use of universal links as these use regular http(s) schemes, to which redirects were (at the time of posting my question) not considered harmful by Chrome.
For those who use App Auth client and Identity Server:
Startup.cs
services.AddTransient<IAuthorizeResponseGenerator, AuthorizeRG>();
AuthorizeRG.cs
public class AuthorizeRG: AuthorizeResponseGenerator
{
public override async Task<AuthorizeResponse> CreateResponseAsync(ValidatedAuthorizeRequest request)
{
var response = await base.CreateResponseAsync(request);
if (response.RedirectUri != null && request.IsNativeClient())
//this fix chrome navigation blocked on native clients https://bugs.chromium.org/p/chromium/issues/detail?id=738724
response.Request.RedirectUri = $"/native/redirect/{HttpUtility.UrlEncode(response.RedirectUri)}";
return response;
}
}
NativeController.cs
[Route("[controller]")]
public class NativeController : Controller
{
[HttpGet("Redirect/{redirectUri}")]
public IActionResult Redirect([FromRoute] string redirectUri)
{
redirectUri = HttpUtility.UrlDecode(redirectUri);
redirectUri += HttpContext.Request.QueryString.ToUriComponent();
return this.LoadingPage("Redirect", redirectUri);
}
}
Extensions.cs
/// <summary>
/// Checks if the redirect URI is for a native client.
/// </summary>
/// <returns></returns>
public static bool IsNativeClient(this AuthorizationRequest context)
{
return !context.RedirectUri.StartsWith("https", StringComparison.Ordinal)
&& !context.RedirectUri.StartsWith("http", StringComparison.Ordinal);
}
public static bool IsNativeClient(this ValidatedAuthorizeRequest context)
{
return !context.RedirectUri.StartsWith("https", StringComparison.Ordinal)
&& !context.RedirectUri.StartsWith("http", StringComparison.Ordinal);
}
public static IActionResult LoadingPage(this Controller controller, string viewName, string redirectUri)
{
controller.HttpContext.Response.StatusCode = 200;
controller.HttpContext.Response.Headers["Location"] = "";
return controller.View(viewName, new RedirectViewModel { RedirectUrl = redirectUri });
}
This works for me, but please comment if it broke smth in your authorization flow

Displaying a part of a webpage in webview

Please note that this question is not a duplicate of How to display some part of webpage in android webview? or Android WebView: display only some part of website as they exclude some element whereas i want to include only one.
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
boolean validate=checkIfNet();
if(!validate){
finish();
}
setContentView(R.layout.activity_main);
WebView wb = (WebView) findViewById(R.id.webview);
wb.getSettings().setJavaScriptEnabled(true);
wb.getSettings().setLoadWithOverviewMode(true);
wb.getSettings().setUseWideViewPort(true);
wb.getSettings().setJavaScriptEnabled(true);
wb.getSettings().setBuiltInZoomControls(false);
wb.getSettings().setPluginState(WebSettings.PluginState.ON);
//wb.getSettings().setPluginsEnabled(true);
//wb.setWebViewClient(new HelloWebViewClient());
wb.loadUrl("http://www.dota2.com/leaderboards#europe");
}
private boolean checkIfNet() {
boolean connected = false;
ConnectivityManager connectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
if(connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).getState() == NetworkInfo.State.CONNECTED ||
connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState() == NetworkInfo.State.CONNECTED) {
//we are connected to a network
connected = true;
}
else
connected = false;
return connected;
}
}
Now while getting http://www.dota2.com/leaderboards#europe it gets the full page. I only want the table in there. The one with tbody id="leaderboard_body". I am kind of new so pls chill on me. Thanks in advance.
Actually what you want to achieve technically is to remove some parts of the html.
What happens is that your webview will get all of the content of the URL you provided to it, this is inherent on how http works. The webview class doesn't let you choose to show only a specific part of the html, so what you must do is to remove what you don't want before rendering using javascript.
If you want to show it using a webview then the solutions in your post are what you want, if you want to parse the html and render it using Android UI classes then the jsoup solutions are what you want(however this is a bit overkill).
If you're really new to this my tip is: use an API. Probably something like https://docs.opendota.com/. The way virtually all apps(and websites) that are not static works is by using APIs. Here is an introduction on why you would want to do so instead of parsing with jsoup.

Unable to execute Js code in WebView in Cordova Project

I have two activity, one main activity(A) is an CordovaActivity, then I use intent to start another activity(B), in B i have an WebView(not CordovaActivity), and after I use this webview to load a simple webpage (alert something), I found the js code is not executed at all, even if I enable javascript by calling setttings.setJsenabel(true);
I start activity B from A
Load Url from webview in Activity B
simple web page
in the device, it does not alert anything
However, if I change the webview to CordovaWebView instead of the original Android native one, it works.....
That's because plain WebView doesn't support showing alerts by itself. You need to provide a WebChromeClient to it that implements WebChromeClient.onJsAlert method. For example:
mywebView.setWebChromeClient(new WebChromeClient() {
#Override
public boolean onJsAlert(
WebView view, String url, String message, final JsResult result) {
new AlertDialog.Builder(view.getContext())
.setTitle("Alert")
.setMessage(message)
.setPositiveButton("Ok",
new AlertDialog.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
}).setCancelable(false).create().show();
return true;
}
});

Android/WebView doesn't load javascript gallery images when navigated to

I have a website/web app built with jquery mobile that I am trying to package into a webview for android; I am able to load up the pages locally by putting the whole site into the assets folder and loading the url like so:
mWebView.loadUrl("file:///android_asset/www/btec/index.html");
One of the pages is a gallery/slideshow of images that flips through automatically using javascript. However, if I navigate to my gallery from index.html, the images dont load. If I load it directly (i.e. load it directly into the webview with:
mWebView.loadUrl("file:///android_asset/www/btec/gallery.html");
the images appear and scroll no problem! I'm not sure why this is happening. Is there a setting I need to enable/disable? I have the following settings enabled already:
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setAllowFileAccess(true);
mWebView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
mWebView.getSettings().setLoadsImagesAutomatically(true);
mWebView.getSettings().setPluginsEnabled(true);
This a trick that probably solves your problem.
1) Create a WebViewClient class, overriding shouldOverrideUrlLoading method:
private class MyClient extends WebViewClient {
private WebView mwv;
public MyClient(WebView v) {
mwv = v;
}
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if(url.equals("file:///android_asset/www/btec/gallery.html")) {
mwv.loadUrl(url);
return true;
}
return false;
}
}
2) Associate this client to your webview:
mWebView.setWebViewClient(new MyClient(mWebView));
Probably not the best way; but I think it works... ;)
EDIT: for other details, you can see this: http://developer.android.com/reference/android/webkit/WebViewClient.html

Categories