Merging latest master, including new tests
Project: http://git-wip-us.apache.org/repos/asf/cordova-android/repo Commit: http://git-wip-us.apache.org/repos/asf/cordova-android/commit/a7ccb924 Tree: http://git-wip-us.apache.org/repos/asf/cordova-android/tree/a7ccb924 Diff: http://git-wip-us.apache.org/repos/asf/cordova-android/diff/a7ccb924 Branch: refs/heads/4.0.x Commit: a7ccb9243daf3dfaa0f2538492dccaa4d140ba58 Parents: f9b8f9a 320e31b Author: Joe Bowser <[email protected]> Authored: Thu Aug 14 14:20:49 2014 -0700 Committer: Joe Bowser <[email protected]> Committed: Thu Aug 14 14:20:49 2014 -0700 ---------------------------------------------------------------------- .../apache/cordova/AndroidWebViewClient.java | 14 +++- .../src/org/apache/cordova/CordovaActivity.java | 3 + .../org/apache/cordova/CordovaUriHelper.java | 9 +++ .../apache/cordova/NativeToJsMessageQueue.java | 13 ++-- framework/src/org/apache/cordova/Whitelist.java | 8 +-- test/assets/www/error.html | 8 +++ .../test/CordovaWebViewTestActivity.java | 9 ++- .../apache/cordova/test/SabotagedActivity.java | 73 ++++++++++++++++++++ .../test/junit/IntentUriOverrideTest.java | 73 ++++++++++++++++++++ 9 files changed, 198 insertions(+), 12 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-android/blob/a7ccb924/framework/src/org/apache/cordova/AndroidWebViewClient.java ---------------------------------------------------------------------- diff --cc framework/src/org/apache/cordova/AndroidWebViewClient.java index 44862a6,0000000..c017177 mode 100755,000000..100755 --- a/framework/src/org/apache/cordova/AndroidWebViewClient.java +++ b/framework/src/org/apache/cordova/AndroidWebViewClient.java @@@ -1,318 -1,0 +1,330 @@@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ +package org.apache.cordova; + +import java.util.Hashtable; + +import org.json.JSONException; +import org.json.JSONObject; + +import android.annotation.TargetApi; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.Bitmap; +import android.net.http.SslError; +import android.view.View; +import android.webkit.HttpAuthHandler; +import android.webkit.SslErrorHandler; +import android.webkit.WebView; +import android.webkit.WebViewClient; + + +/** + * This class is the WebViewClient that implements callbacks for our web view. + * The kind of callbacks that happen here are regarding the rendering of the + * document instead of the chrome surrounding it, such as onPageStarted(), + * shouldOverrideUrlLoading(), etc. Related to but different than + * CordovaChromeClient. + * + * @see <a href="http://developer.android.com/reference/android/webkit/WebViewClient.html">WebViewClient</a> + * @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a> + * @see CordovaChromeClient + * @see CordovaWebView + */ +public class AndroidWebViewClient extends WebViewClient { + + private static final String TAG = "AndroidWebViewClient"; + protected final CordovaInterface cordova; + protected final AndroidWebView appView; + protected final CordovaUriHelper helper; + private boolean doClearHistory = false; + boolean isCurrentlyLoading; + + /** The authorization tokens. */ + private Hashtable<String, AuthenticationToken> authenticationTokens = new Hashtable<String, AuthenticationToken>(); + + public AndroidWebViewClient(CordovaInterface cordova, AndroidWebView view) { + this.cordova = cordova; + this.appView = view; + helper = new CordovaUriHelper(cordova, view); + } + + /** + * Give the host application a chance to take over the control when a new url + * is about to be loaded in the current WebView. + * + * @param view The WebView that is initiating the callback. + * @param url The url to be loaded. + * @return true to override, false for default behavior + */ + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + return helper.shouldOverrideUrlLoading(url); + } + + /** + * On received http auth request. + * The method reacts on all registered authentication tokens. There is one and only one authentication token for any host + realm combination + */ + @Override + public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { + + // Get the authentication token + AuthenticationToken token = this.getAuthenticationToken(host, realm); + if (token != null) { + handler.proceed(token.getUserName(), token.getPassword()); + } + else { + // Handle 401 like we'd normally do! + super.onReceivedHttpAuthRequest(view, handler, host, realm); + } + } + + /** + * Notify the host application that a page has started loading. + * This method is called once for each main frame load so a page with iframes or framesets will call onPageStarted + * one time for the main frame. This also means that onPageStarted will not be called when the contents of an + * embedded frame changes, i.e. clicking a link whose target is an iframe. + * + * @param view The webview initiating the callback. + * @param url The url of the page. + */ + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + super.onPageStarted(view, url, favicon); + isCurrentlyLoading = true; + LOG.d(TAG, "onPageStarted(" + url + ")"); + // Flush stale messages & reset plugins. + this.appView.onPageReset(); + + // Broadcast message that page has loaded + this.appView.getPluginManager().postMessage("onPageStarted", url); + } + + /** + * Notify the host application that a page has finished loading. + * This method is called only for main frame. When onPageFinished() is called, the rendering picture may not be updated yet. + * + * + * @param view The webview initiating the callback. + * @param url The url of the page. + */ + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + // Ignore excessive calls. + if (!isCurrentlyLoading) { + return; + } + isCurrentlyLoading = false; + LOG.d(TAG, "onPageFinished(" + url + ")"); + + /** + * Because of a timing issue we need to clear this history in onPageFinished as well as + * onPageStarted. However we only want to do this if the doClearHistory boolean is set to + * true. You see when you load a url with a # in it which is common in jQuery applications + * onPageStared is not called. Clearing the history at that point would break jQuery apps. + */ + if (this.doClearHistory) { + view.clearHistory(); + this.doClearHistory = false; + } + + // Clear timeout flag + appView.loadUrlTimeout++; + + // Broadcast message that page has loaded + this.appView.getPluginManager().postMessage("onPageFinished", url); + + // Make app visible after 2 sec in case there was a JS error and Cordova JS never initialized correctly + if (this.appView.getVisibility() == View.INVISIBLE) { + Thread t = new Thread(new Runnable() { + public void run() { + try { + Thread.sleep(2000); + cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + appView.getPluginManager().postMessage("spinner", "stop"); + } + }); + } catch (InterruptedException e) { + } + } + }); + t.start(); + } + + // Shutdown if blank loaded + if (url.equals("about:blank")) { + appView.getPluginManager().postMessage("exit", null); + } + } + + /** + * Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable). + * The errorCode parameter corresponds to one of the ERROR_* constants. + * + * @param view The WebView that is initiating the callback. + * @param errorCode The error code corresponding to an ERROR_* value. + * @param description A String describing the error. + * @param failingUrl The url that failed to load. + */ + @Override + public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { + // Ignore error due to stopLoading(). + if (!isCurrentlyLoading) { + return; + } + LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl); + + // Clear timeout flag + appView.loadUrlTimeout++; + - // Handle error ++ // If this is a "Protocol Not Supported" error, then revert to the previous ++ // page. If there was no previous page, then punt. The application's config ++ // is likely incorrect (start page set to sms: or something like that) ++ if (errorCode == WebViewClient.ERROR_UNSUPPORTED_SCHEME) { ++ if (view.canGoBack()) { ++ view.goBack(); ++ return; ++ } else { ++ super.onReceivedError(view, errorCode, description, failingUrl); ++ } ++ } ++ ++ // Handle other errors by passing them to the webview in JS + JSONObject data = new JSONObject(); + try { + data.put("errorCode", errorCode); + data.put("description", description); + data.put("url", failingUrl); + } catch (JSONException e) { + e.printStackTrace(); + } + this.appView.getPluginManager().postMessage("onReceivedError", data); + } + + /** + * Notify the host application that an SSL error occurred while loading a resource. + * The host application must call either handler.cancel() or handler.proceed(). + * Note that the decision may be retained for use in response to future SSL errors. + * The default behavior is to cancel the load. + * + * @param view The WebView that is initiating the callback. + * @param handler An SslErrorHandler object that will handle the user's response. + * @param error The SSL error object. + */ + @TargetApi(8) + @Override + public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { + + final String packageName = this.cordova.getActivity().getPackageName(); + final PackageManager pm = this.cordova.getActivity().getPackageManager(); + + ApplicationInfo appInfo; + try { + appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); + if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { + // debug = true + handler.proceed(); + return; + } else { + // debug = false + super.onReceivedSslError(view, handler, error); + } + } catch (NameNotFoundException e) { + // When it doubt, lock it out! + super.onReceivedSslError(view, handler, error); + } + } + + + /** + * Sets the authentication token. + * + * @param authenticationToken + * @param host + * @param realm + */ + public void setAuthenticationToken(AuthenticationToken authenticationToken, String host, String realm) { + if (host == null) { + host = ""; + } + if (realm == null) { + realm = ""; + } + this.authenticationTokens.put(host.concat(realm), authenticationToken); + } + + /** + * Removes the authentication token. + * + * @param host + * @param realm + * + * @return the authentication token or null if did not exist + */ + public AuthenticationToken removeAuthenticationToken(String host, String realm) { + return this.authenticationTokens.remove(host.concat(realm)); + } + + /** + * Gets the authentication token. + * + * In order it tries: + * 1- host + realm + * 2- host + * 3- realm + * 4- no host, no realm + * + * @param host + * @param realm + * + * @return the authentication token + */ + public AuthenticationToken getAuthenticationToken(String host, String realm) { + AuthenticationToken token = null; + token = this.authenticationTokens.get(host.concat(realm)); + + if (token == null) { + // try with just the host + token = this.authenticationTokens.get(host); + + // Try the realm + if (token == null) { + token = this.authenticationTokens.get(realm); + } + + // if no host found, just query for default + if (token == null) { + token = this.authenticationTokens.get(""); + } + } + + return token; + } + + /** + * Clear all authentication tokens. + */ + public void clearAuthenticationTokens() { + this.authenticationTokens.clear(); + } +} http://git-wip-us.apache.org/repos/asf/cordova-android/blob/a7ccb924/framework/src/org/apache/cordova/CordovaActivity.java ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-android/blob/a7ccb924/framework/src/org/apache/cordova/CordovaUriHelper.java ---------------------------------------------------------------------- diff --cc framework/src/org/apache/cordova/CordovaUriHelper.java index 077e425,bb78592..f7d22da --- a/framework/src/org/apache/cordova/CordovaUriHelper.java +++ b/framework/src/org/apache/cordova/CordovaUriHelper.java @@@ -19,10 -19,13 +19,13 @@@ package org.apache.cordova; + import android.annotation.TargetApi; import android.content.Intent; import android.net.Uri; + import android.os.Build; + import android.webkit.WebView; -class CordovaUriHelper { +public class CordovaUriHelper { private static final String TAG = "CordovaUriHelper"; @@@ -43,7 -46,8 +46,8 @@@ * @param url The url to be loaded. * @return true to override, false for default behavior */ + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) - boolean shouldOverrideUrlLoading(WebView view, String url) { + public boolean shouldOverrideUrlLoading(String url) { // The WebView should support http and https when going on the Internet if(url.startsWith("http:") || url.startsWith("https:")) { http://git-wip-us.apache.org/repos/asf/cordova-android/blob/a7ccb924/framework/src/org/apache/cordova/NativeToJsMessageQueue.java ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-android/blob/a7ccb924/test/assets/www/error.html ---------------------------------------------------------------------- diff --cc test/assets/www/error.html index 0000000,0000000..aad167d new file mode 100644 --- /dev/null +++ b/test/assets/www/error.html @@@ -1,0 -1,0 +1,8 @@@ ++<html> ++ <head> ++ <title>OH NOES!</title> ++ </head> ++ <body> ++ <h1>Things went terribly wrong!</h1> ++ </body> ++</html> http://git-wip-us.apache.org/repos/asf/cordova-android/blob/a7ccb924/test/src/org/apache/cordova/test/CordovaWebViewTestActivity.java ---------------------------------------------------------------------- diff --cc test/src/org/apache/cordova/test/CordovaWebViewTestActivity.java index 6f96e83,1c1789e..b143521 --- a/test/src/org/apache/cordova/test/CordovaWebViewTestActivity.java +++ b/test/src/org/apache/cordova/test/CordovaWebViewTestActivity.java @@@ -22,7 -22,8 +22,9 @@@ package org.apache.cordova.test import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; ++import org.apache.cordova.AndroidChromeClient; ++import org.apache.cordova.AndroidWebViewClient; import org.apache.cordova.Config; -import org.apache.cordova.CordovaChromeClient; import org.apache.cordova.CordovaWebView; import org.apache.cordova.CordovaInterface; import org.apache.cordova.CordovaPlugin; @@@ -45,10 -47,12 +47,11 @@@ public class CordovaWebViewTestActivit setContentView(R.layout.main); - cordovaWebView = (CordovaWebView) findViewById(R.id.cordovaWebView); + //CB-7238: This has to be added now, because it got removed from somewhere else Config.init(this); - cordovaWebView.init(this, - Config.getPluginEntries(), Config.getWhitelist(), Config.getPreferences()); + + cordovaWebView = (CordovaWebView) findViewById(R.id.cordovaWebView); - cordovaWebView.init(this, new CordovaWebViewClient(this, cordovaWebView), new CordovaChromeClient(this, cordovaWebView), - Config.getPluginEntries(), Config.getWhitelist(), Config.getPreferences()); ++ cordovaWebView.init(this, Config.getPluginEntries(), Config.getWhitelist(), Config.getPreferences()); cordovaWebView.loadUrl("file:///android_asset/www/index.html");
