Merge branch 'master' into 4.0.x (Bridge fixes)

Conflicts:
	framework/src/org/apache/cordova/CordovaChromeClient.java
	framework/src/org/apache/cordova/CordovaUriHelper.java
	framework/src/org/apache/cordova/CordovaWebView.java
	framework/src/org/apache/cordova/CordovaWebViewClient.java
	framework/src/org/apache/cordova/ExposedJsApi.java
	framework/src/org/apache/cordova/NativeToJsMessageQueue.java
	framework/src/org/apache/cordova/PluginManager.java
This commit is contained in:
Andrew Grieve 2014-07-03 23:02:02 -04:00
commit 4ca2305693
12 changed files with 193 additions and 380 deletions

View File

@ -44,6 +44,9 @@ import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.util.Log;
/**
* This class is the WebChromeClient that implements callbacks for our web view.
@ -60,7 +63,6 @@ public class AndroidChromeClient extends WebChromeClient implements CordovaChrom
public static final int FILECHOOSER_RESULTCODE = 5173;
private static final String LOG_TAG = "CordovaChromeClient";
private String TAG = "CordovaLog";
private long MAX_QUOTA = 100 * 1024 * 1024;
protected CordovaInterface cordova;
protected CordovaWebView appView;
@ -201,68 +203,75 @@ public class AndroidChromeClient extends WebChromeClient implements CordovaChrom
* Since we are hacking prompts for our own purposes, we should not be using them for
* this purpose, perhaps we should hack console.log to do this instead!
*
* @param view
* @param url
* @param message
* @param defaultValue
* @param result
* @see Other implementation in the Dialogs plugin.
*/
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
// Security check to make sure any requests are coming from the page initially
// loaded in webview and not another loaded in an iframe.
boolean reqOk = false;
if (url.startsWith("file://") || Config.isUrlWhiteListed(url)) {
reqOk = true;
}
// Calling PluginManager.exec() to call a native service using
// prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true]));
if (reqOk && defaultValue != null && defaultValue.length() > 3 && defaultValue.substring(0, 4).equals("gap:")) {
public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, JsPromptResult result) {
// Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
if (defaultValue != null && defaultValue.length() > 3 && defaultValue.startsWith("gap:")) {
JSONArray array;
try {
array = new JSONArray(defaultValue.substring(4));
String service = array.getString(0);
String action = array.getString(1);
String callbackId = array.getString(2);
//String r = this.appView.exposedJsApi.exec(service, action, callbackId, message);
String r = this.appView.exec(service, action, callbackId, message);
int bridgeSecret = array.getInt(0);
String service = array.getString(1);
String action = array.getString(2);
String callbackId = array.getString(3);
String r = appView.exposedJsApi.exec(bridgeSecret, service, action, callbackId, message);
result.confirm(r == null ? "" : r);
} catch (JSONException e) {
e.printStackTrace();
return false;
result.cancel();
} catch (IllegalAccessException e) {
e.printStackTrace();
result.cancel();
}
}
// Sets the native->JS bridge mode.
else if (reqOk && defaultValue != null && defaultValue.equals("gap_bridge_mode:")) {
try {
//this.appView.exposedJsApi.setNativeToJsBridgeMode(Integer.parseInt(message));
this.appView.setNativeToJsBridgeMode(Integer.parseInt(message));
result.confirm("");
} catch (NumberFormatException e){
result.confirm("");
else if (defaultValue != null && defaultValue.startsWith("gap_bridge_mode:")) {
try {
int bridgeSecret = Integer.parseInt(defaultValue.substring(16));
appView.exposedJsApi.setNativeToJsBridgeMode(bridgeSecret, Integer.parseInt(message));
result.cancel();
} catch (NumberFormatException e){
e.printStackTrace();
}
result.cancel();
} catch (IllegalAccessException e) {
e.printStackTrace();
result.cancel();
}
}
// Polling for JavaScript messages
else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) {
//String r = this.appView.exposedJsApi.retrieveJsMessages("1".equals(message));
String r = this.appView.retrieveJsMessages("1".equals(message));
result.confirm(r == null ? "" : r);
else if (defaultValue != null && defaultValue.startsWith("gap_poll:")) {
int bridgeSecret = Integer.parseInt(defaultValue.substring(9));
try {
String r = appView.exposedJsApi.retrieveJsMessages(bridgeSecret, "1".equals(message));
result.confirm(r == null ? "" : r);
} catch (IllegalAccessException e) {
e.printStackTrace();
result.cancel();
}
}
// Do NO-OP so older code doesn't display dialog
else if (defaultValue != null && defaultValue.equals("gap_init:")) {
result.confirm("OK");
}
// Show dialog
else {
else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {
String startUrl = Config.getStartUrl();
// Protect against random iframes being able to talk through the bridge.
// Trust only file URLs and the start URL's domain.
// The extra origin.startsWith("http") is to protect against iframes with data: having "" as origin.
if (origin.startsWith("file:") || (origin.startsWith("http") && startUrl.startsWith(origin))) {
// Enable the bridge
int bridgeMode = Integer.parseInt(defaultValue.substring(9));
appView.jsMessageQueue.setBridgeMode(bridgeMode);
// Tell JS the bridge secret.
int secret = appView.exposedJsApi.generateBridgeSecret();
result.confirm(""+secret);
} else {
Log.e(LOG_TAG, "gap_init called from restricted origin: " + origin);
result.cancel();
}
} else {
// Returning false would also show a dialog, but the default one shows the origin (ugly).
final JsPromptResult res = result;
AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
dlg.setMessage(message);
@ -297,7 +306,7 @@ public class AndroidChromeClient extends WebChromeClient implements CordovaChrom
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater)
{
LOG.d(TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
LOG.d(LOG_TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
quotaUpdater.updateQuota(MAX_QUOTA);
}
@ -310,7 +319,7 @@ public class AndroidChromeClient extends WebChromeClient implements CordovaChrom
//This is only for Android 2.1
if(android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.ECLAIR_MR1)
{
LOG.d(TAG, "%s: Line %d : %s", sourceID, lineNumber, message);
LOG.d(LOG_TAG, "%s: Line %d : %s", sourceID, lineNumber, message);
super.onConsoleMessage(message, lineNumber, sourceID);
}
}
@ -320,7 +329,7 @@ public class AndroidChromeClient extends WebChromeClient implements CordovaChrom
public boolean onConsoleMessage(ConsoleMessage consoleMessage)
{
if (consoleMessage.message() != null)
LOG.d(TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message());
LOG.d(LOG_TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message());
return super.onConsoleMessage(consoleMessage);
}
@ -342,10 +351,10 @@ public class AndroidChromeClient extends WebChromeClient implements CordovaChrom
this.appView.showCustomView(view, callback);
}
@Override
public void onHideCustomView() {
this.appView.hideCustomView();
}
@Override
public void onHideCustomView() {
this.appView.hideCustomView();
}
@Override
/**
@ -355,31 +364,31 @@ public class AndroidChromeClient extends WebChromeClient implements CordovaChrom
*/
public View getVideoLoadingProgressView() {
if (mVideoProgressView == null) {
// Create a new Loading view programmatically.
// create the linear layout
LinearLayout layout = new LinearLayout(this.appView.getContext());
layout.setOrientation(LinearLayout.VERTICAL);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
layout.setLayoutParams(layoutParams);
// the proress bar
ProgressBar bar = new ProgressBar(this.appView.getContext());
LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
barLayoutParams.gravity = Gravity.CENTER;
bar.setLayoutParams(barLayoutParams);
layout.addView(bar);
mVideoProgressView = layout;
}
if (mVideoProgressView == null) {
// Create a new Loading view programmatically.
// create the linear layout
LinearLayout layout = new LinearLayout(this.appView.getContext());
layout.setOrientation(LinearLayout.VERTICAL);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
layout.setLayoutParams(layoutParams);
// the proress bar
ProgressBar bar = new ProgressBar(this.appView.getContext());
LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
barLayoutParams.gravity = Gravity.CENTER;
bar.setLayoutParams(barLayoutParams);
layout.addView(bar);
mVideoProgressView = layout;
}
return mVideoProgressView;
}
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
this.openFileChooser(uploadMsg, "*/*");
}
public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) {
this.openFileChooser(uploadMsg, acceptType, null);
}

View File

@ -27,18 +27,20 @@ import org.json.JSONException;
* an equivalent entry in CordovaChromeClient.java, and be added to
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
*/
public /* package */ class AndroidExposedJsApi implements ExposedJsApi {
class AndroidExposedJsApi implements ExposedJsApi {
private PluginManager pluginManager;
private NativeToJsMessageQueue jsMessageQueue;
private volatile int bridgeSecret = -1; // written by UI thread, read by JS thread.
public AndroidExposedJsApi(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
this.pluginManager = pluginManager;
this.jsMessageQueue = jsMessageQueue;
}
@JavascriptInterface
public String exec(String service, String action, String callbackId, String arguments) throws JSONException {
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
verifySecret(bridgeSecret);
// If the arguments weren't received, send a message back to JS. It will switch bridge modes and try again. See CB-2666.
// We send a message meant specifically for this case. It starts with "@" so no other message can be encoded into the same string.
if (arguments == null) {
@ -49,7 +51,7 @@ public /* package */ class AndroidExposedJsApi implements ExposedJsApi {
try {
// Tell the resourceApi what thread the JS is running on.
CordovaResourceApi.jsThread = Thread.currentThread();
pluginManager.exec(service, action, callbackId, arguments);
String ret = "";
if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING) {
@ -63,14 +65,33 @@ public /* package */ class AndroidExposedJsApi implements ExposedJsApi {
jsMessageQueue.setPaused(false);
}
}
@JavascriptInterface
public void setNativeToJsBridgeMode(int value) {
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
verifySecret(bridgeSecret);
jsMessageQueue.setBridgeMode(value);
}
@JavascriptInterface
public String retrieveJsMessages(boolean fromOnlineEvent) {
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
verifySecret(bridgeSecret);
return jsMessageQueue.popAndEncode(fromOnlineEvent);
}
private void verifySecret(int value) throws IllegalAccessException {
if (bridgeSecret < 0 || value != bridgeSecret) {
throw new IllegalAccessException();
}
}
/** Called on page transitions */
void clearBridgeSecret() {
bridgeSecret = -1;
}
/** Called by cordova.js to initialize the bridge. */
int generateBridgeSecret() {
bridgeSecret = (int)(Math.random() * Integer.MAX_VALUE);
return bridgeSecret;
}
}

View File

@ -412,17 +412,7 @@ public class AndroidWebView extends WebView implements CordovaWebView {
this.loadUrlNow(url);
}
else {
String initUrl = this.getProperty("url", null);
// If first page of app, then set URL to load to be the one passed in
if (initUrl == null) {
this.loadUrlIntoView(url);
}
// Otherwise use the URL specified in the activity's extras bundle
else {
this.loadUrlIntoView(initUrl);
}
this.loadUrlIntoView(url);
}
}
@ -433,16 +423,15 @@ public class AndroidWebView extends WebView implements CordovaWebView {
* @param url
* @param time The number of ms to wait before loading webview
*/
@Deprecated
public void loadUrl(final String url, int time) {
String initUrl = this.getProperty("url", null);
// If first page of app, then set URL to load to be the one passed in
if (initUrl == null) {
this.loadUrlIntoView(url, time);
if(url == null)
{
this.loadUrlIntoView(Config.getStartUrl());
}
// Otherwise use the URL specified in the activity's extras bundle
else {
this.loadUrlIntoView(initUrl);
else
{
this.loadUrlIntoView(url);
}
}
@ -1027,6 +1016,7 @@ public class AndroidWebView extends WebView implements CordovaWebView {
boundKeyCodes.clear();
pluginManager.onReset();
jsMessageQueue.reset();
exposedJsApi.clearBridgeSecret();
}
@Override

View File

@ -21,8 +21,6 @@ package org.apache.cordova;
import java.io.ByteArrayInputStream;
import java.util.Hashtable;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.LOG;
import org.json.JSONException;
import org.json.JSONObject;
@ -32,16 +30,15 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.http.SslError;
import android.util.Log;
import android.view.View;
import android.webkit.HttpAuthHandler;
import android.webkit.SslErrorHandler;
import android.webkit.WebResourceResponse;
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
@ -56,8 +53,8 @@ import android.webkit.WebViewClient;
*/
public class AndroidWebViewClient extends WebViewClient implements CordovaWebViewClient{
private static final String TAG = "AndroidWebViewClient";
private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
private static final String TAG = "AndroidWebViewClient";
private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
CordovaInterface cordova;
AndroidWebView appView;
CordovaUriHelper helper;
@ -98,30 +95,6 @@ public class AndroidWebViewClient extends WebViewClient implements CordovaWebVie
helper = new CordovaUriHelper(cordova, view);
}
// Parses commands sent by setting the webView's URL to:
// cdvbrg:service/action/callbackId#jsonArgs
private void handleExecUrl(String url) {
int idx1 = CORDOVA_EXEC_URL_PREFIX.length();
int idx2 = url.indexOf('#', idx1 + 1);
int idx3 = url.indexOf('#', idx2 + 1);
int idx4 = url.indexOf('#', idx3 + 1);
if (idx1 == -1 || idx2 == -1 || idx3 == -1 || idx4 == -1) {
Log.e(TAG, "Could not decode URL command: " + url);
return;
}
String service = url.substring(idx1, idx2);
String action = url.substring(idx2 + 1, idx3);
String callbackId = url.substring(idx3 + 1, idx4);
String jsonArgs = url.substring(idx4 + 1);
try {
appView.exec(service, action, callbackId, jsonArgs);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* Give the host application a chance to take over the control when a new url
* is about to be loaded in the current WebView.
@ -132,115 +105,7 @@ public class AndroidWebViewClient extends WebViewClient implements CordovaWebVie
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// Check if it's an exec() bridge command message.
/*
if (NativeToJsMessageQueue.ENABLE_LOCATION_CHANGE_EXEC_MODE && url.startsWith(CORDOVA_EXEC_URL_PREFIX)) {
handleExecUrl(url);
}
// Give plugins the chance to handle the url
else if (this.appView.onOverrideUrlLoading(url)) {
}
// If dialing phone (tel:5551212)
else if (url.startsWith(WebView.SCHEME_TEL)) {
try {
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse(url));
this.cordova.getActivity().startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error dialing " + url + ": " + e.toString());
}
}
// If displaying map (geo:0,0?q=address)
else if (url.startsWith("geo:")) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
this.cordova.getActivity().startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error showing map " + url + ": " + e.toString());
}
}
// If sending email (mailto:abc@corp.com)
else if (url.startsWith(WebView.SCHEME_MAILTO)) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
this.cordova.getActivity().startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error sending email " + url + ": " + e.toString());
}
}
// If sms:5551212?body=This is the message
else if (url.startsWith("sms:")) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
// Get address
String address = null;
int parmIndex = url.indexOf('?');
if (parmIndex == -1) {
address = url.substring(4);
}
else {
address = url.substring(4, parmIndex);
// If body, then set sms body
Uri uri = Uri.parse(url);
String query = uri.getQuery();
if (query != null) {
if (query.startsWith("body=")) {
intent.putExtra("sms_body", query.substring(5));
}
}
}
intent.setData(Uri.parse("sms:" + address));
intent.putExtra("address", address);
intent.setType("vnd.android-dir/mms-sms");
this.cordova.getActivity().startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error sending sms " + url + ":" + e.toString());
}
}
//Android Market
else if(url.startsWith("market:")) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
this.cordova.getActivity().startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error loading Google Play Store: " + url, e);
}
}
// All else
else {
// If our app or file:, then load into a new Cordova webview container by starting a new instance of our activity.
// Our app continues to run. When BACK is pressed, our app is redisplayed.
if (url.startsWith("file://") || url.startsWith("data:") || Config.isUrlWhiteListed(url)) {
return false;
}
// If not our application, let default viewer handle
else {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
this.cordova.getActivity().startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error loading url " + url, e);
}
}
}
return true;
*/
return helper.shouldOverrideUrlLoading(view, url);
return helper.shouldOverrideUrlLoading(view, url);
}
/**

View File

@ -20,21 +20,16 @@
package org.apache.cordova;
import java.io.IOException;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.cordova.LOG;
import org.xmlpull.v1.XmlPullParserException;
import android.app.Activity;
import android.content.res.XmlResourceParser;
import android.graphics.Color;
import android.util.Log;
public class Config {
@ -44,6 +39,8 @@ public class Config {
private Whitelist whitelist = new Whitelist();
private String startUrl;
private static String errorUrl;
private static Config self = null;
public static void init(Activity action) {
@ -156,6 +153,10 @@ public class Config {
boolean value = xml.getAttributeValue(null, "value").equals("true");
action.getIntent().putExtra(name, value);
}
else if(name.equalsIgnoreCase("errorurl"))
{
errorUrl = xml.getAttributeValue(null, "value");
}
else
{
String value = xml.getAttributeValue(null, "value");
@ -230,4 +231,8 @@ public class Config {
}
return self.startUrl;
}
public static String getErrorUrl() {
return errorUrl;
}
}

View File

@ -608,7 +608,7 @@ public class CordovaActivity extends Activity implements CordovaInterface {
//Code to test CB-3064
String errorUrl = this.getStringProperty("ErrorUrl", null);
String errorUrl = Config.getErrorUrl();
LOG.d(TAG, "CB-3064: The errorUrl is " + errorUrl);
if (this.activityState == ACTIVITY_STARTING) {

View File

@ -22,11 +22,6 @@ import android.net.Uri;
import android.webkit.ValueCallback;
public interface CordovaChromeClient {
int FILECHOOSER_RESULTCODE = 0;
void setWebView(CordovaWebView appView);
ValueCallback<Uri> getValueCallback();
}

View File

@ -19,17 +19,13 @@
package org.apache.cordova;
import org.json.JSONException;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import android.webkit.WebView;
public class CordovaUriHelper {
private static final String TAG = "CordovaUriHelper";
private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
private CordovaWebView appView;
private CordovaInterface cordova;
@ -40,32 +36,6 @@ public class CordovaUriHelper {
cordova = cdv;
}
// Parses commands sent by setting the webView's URL to:
// cdvbrg:service/action/callbackId#jsonArgs
void handleExecUrl(String url) {
int idx1 = CORDOVA_EXEC_URL_PREFIX.length();
int idx2 = url.indexOf('#', idx1 + 1);
int idx3 = url.indexOf('#', idx2 + 1);
int idx4 = url.indexOf('#', idx3 + 1);
if (idx1 == -1 || idx2 == -1 || idx3 == -1 || idx4 == -1) {
Log.e(TAG, "Could not decode URL command: " + url);
return;
}
String service = url.substring(idx1, idx2);
String action = url.substring(idx2 + 1, idx3);
String callbackId = url.substring(idx3 + 1, idx4);
String jsonArgs = url.substring(idx4 + 1);
try {
appView.exec(service, action, callbackId, jsonArgs);
//There is no reason to not send this directly to the pluginManager
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* Give the host application a chance to take over the control when a new url
* is about to be loaded in the current WebView.
@ -76,14 +46,10 @@ public class CordovaUriHelper {
*/
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// The WebView should support http and https when going on the Internet
if(url.startsWith("http"))
if(url.startsWith("http:") || url.startsWith("https:"))
{
// Check if it's an exec() bridge command message.
if (NativeToJsMessageQueue.ENABLE_LOCATION_CHANGE_EXEC_MODE && url.startsWith(CORDOVA_EXEC_URL_PREFIX)) {
handleExecUrl(url);
}
// We only need to whitelist sites on the Internet!
else if(Config.isUrlWhiteListed(url))
if(Config.isUrlWhiteListed(url))
{
return false;
}
@ -94,7 +60,9 @@ public class CordovaUriHelper {
}
else if(url.startsWith("file://") | url.startsWith("data:"))
{
return false;
//This directory on WebKit/Blink based webviews contains SQLite databases!
//DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING!
return url.contains("app_webview");
}
else
{
@ -109,5 +77,4 @@ public class CordovaUriHelper {
//Default behaviour should be to load the default intent, let's see what happens!
return true;
}
}

View File

@ -2,16 +2,11 @@ package org.apache.cordova;
import org.json.JSONException;
import android.webkit.JavascriptInterface;
/*
* Any exposed Javascript API MUST implement these three things!
*/
public interface ExposedJsApi {
@JavascriptInterface
public String exec(String service, String action, String callbackId, String arguments) throws JSONException;
public void setNativeToJsBridgeMode(int value);
public String retrieveJsMessages(boolean fromOnlineEvent);
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException;
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException;
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException;
}

View File

@ -35,6 +35,7 @@ import android.webkit.WebView;
public class IceCreamCordovaWebViewClient extends AndroidWebViewClient implements CordovaWebViewClient{
private static final String TAG = "IceCreamCordovaWebViewClient";
private CordovaUriHelper helper;
public IceCreamCordovaWebViewClient(CordovaInterface cordova) {
super(cordova);
@ -47,8 +48,9 @@ public class IceCreamCordovaWebViewClient extends AndroidWebViewClient implement
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
try {
// Check the against the white-list.
if ((url.startsWith("http:") || url.startsWith("https:")) && !Config.isUrlWhiteListed(url)) {
// Check the against the whitelist and lock out access to the WebView directory
// Changing this will cause problems for your application
if (isUrlHarmful(url)) {
LOG.w(TAG, "URL blocked by whitelist: " + url);
// Results in a 404.
return new WebResourceResponse("text/plain", "UTF-8", null);
@ -74,6 +76,11 @@ public class IceCreamCordovaWebViewClient extends AndroidWebViewClient implement
}
}
private boolean isUrlHarmful(String url) {
return ((url.startsWith("http:") || url.startsWith("https:")) && !Config.isUrlWhiteListed(url))
|| url.contains("app_webview");
}
private static boolean needsKitKatContentUrlFix(Uri uri) {
return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && "content".equals(uri.getScheme());
}

View File

@ -35,31 +35,19 @@ import android.webkit.WebView;
public class NativeToJsMessageQueue {
private static final String LOG_TAG = "JsMessageQueue";
// This must match the default value in cordova-js/lib/android/exec.js
private static final int DEFAULT_BRIDGE_MODE = 2;
// Set this to true to force plugin results to be encoding as
// JS instead of the custom format (useful for benchmarking).
private static final boolean FORCE_ENCODE_USING_EVAL = false;
// Disable URL-based exec() bridge by default since it's a bit of a
// security concern.
public static final boolean ENABLE_LOCATION_CHANGE_EXEC_MODE = false;
// Disable sending back native->JS messages during an exec() when the active
// exec() is asynchronous. Set this to true when running bridge benchmarks.
public static final boolean DISABLE_EXEC_CHAINING = false;
static final boolean DISABLE_EXEC_CHAINING = false;
// Arbitrarily chosen upper limit for how much data to send to JS in one shot.
// This currently only chops up on message boundaries. It may be useful
// to allow it to break up messages.
private static int MAX_PAYLOAD_SIZE = 50 * 1024 * 10240;
/**
* The index into registeredListeners to treat as active.
*/
private int activeListenerIndex;
/**
* When true, the active listener is not fired upon enqueue. When set to false,
* the active listener will be fired if the queue is non-empty.
@ -76,6 +64,13 @@ public class NativeToJsMessageQueue {
*/
private final BridgeMode[] registeredListeners;
/**
* When null, the bridge is disabled. This occurs during page transitions.
* When disabled, all callbacks are dropped since they are assumed to be
* relevant to the previous page.
*/
private BridgeMode activeBridgeMode;
private final CordovaInterface cordova;
private final CordovaWebView webView;
@ -94,17 +89,19 @@ public class NativeToJsMessageQueue {
* Changes the bridge mode.
*/
public void setBridgeMode(int value) {
if (value < 0 || value >= registeredListeners.length) {
if (value < -1 || value >= registeredListeners.length) {
Log.d(LOG_TAG, "Invalid NativeToJsBridgeMode: " + value);
} else {
if (value != activeListenerIndex) {
Log.d(LOG_TAG, "Set native->JS mode to " + value);
BridgeMode newMode = value < 0 ? null : registeredListeners[value];
if (newMode != activeBridgeMode) {
Log.d(LOG_TAG, "Set native->JS mode to " + (newMode == null ? "null" : newMode.getClass().getSimpleName()));
synchronized (this) {
activeListenerIndex = value;
BridgeMode activeListener = registeredListeners[value];
activeListener.reset();
if (!paused && !queue.isEmpty()) {
activeListener.onNativeToJsMessageAvailable();
activeBridgeMode = newMode;
if (newMode != null) {
newMode.reset();
if (!paused && !queue.isEmpty()) {
newMode.onNativeToJsMessageAvailable();
}
}
}
}
@ -117,8 +114,7 @@ public class NativeToJsMessageQueue {
public void reset() {
synchronized (this) {
queue.clear();
setBridgeMode(DEFAULT_BRIDGE_MODE);
registeredListeners[activeListenerIndex].reset();
setBridgeMode(-1);
}
}
@ -142,7 +138,10 @@ public class NativeToJsMessageQueue {
*/
public String popAndEncode(boolean fromOnlineEvent) {
synchronized (this) {
registeredListeners[activeListenerIndex].notifyOfFlush(fromOnlineEvent);
if (activeBridgeMode == null) {
return null;
}
activeBridgeMode.notifyOfFlush(fromOnlineEvent);
if (queue.isEmpty()) {
return null;
}
@ -247,16 +246,20 @@ public class NativeToJsMessageQueue {
enqueueMessage(message);
}
private void enqueueMessage(JsMessage message) {
synchronized (this) {
if (activeBridgeMode == null) {
Log.d(LOG_TAG, "Dropping Native->JS message due to disabled bridge");
return;
}
queue.add(message);
if (!paused) {
registeredListeners[activeListenerIndex].onNativeToJsMessageAvailable();
activeBridgeMode.onNativeToJsMessageAvailable();
}
}
}
}
public void setPaused(boolean value) {
if (paused && value) {
// This should never happen. If a use-case for it comes up, we should
@ -266,16 +269,12 @@ public class NativeToJsMessageQueue {
paused = value;
if (!value) {
synchronized (this) {
if (!queue.isEmpty()) {
registeredListeners[activeListenerIndex].onNativeToJsMessageAvailable();
if (!queue.isEmpty() && activeBridgeMode != null) {
activeBridgeMode.onNativeToJsMessageAvailable();
}
}
}
}
public boolean getPaused() {
return paused;
}
private abstract class BridgeMode {
abstract void onNativeToJsMessageAvailable();
@ -308,23 +307,28 @@ public class NativeToJsMessageQueue {
/** Uses online/offline events to tell the JS when to poll for messages. */
private class OnlineEventsBridgeMode extends BridgeMode {
private boolean online;
final Runnable runnable = new Runnable() {
private boolean ignoreNextFlush;
final Runnable toggleNetworkRunnable = new Runnable() {
public void run() {
if (!queue.isEmpty()) {
ignoreNextFlush = false;
webView.setNetworkAvailable(online);
}
}
}
};
@Override void reset() {
online = false;
// If the following call triggers a notifyOfFlush, then ignore it.
ignoreNextFlush = true;
webView.setNetworkAvailable(true);
}
@Override void onNativeToJsMessageAvailable() {
cordova.getActivity().runOnUiThread(runnable);
cordova.getActivity().runOnUiThread(toggleNetworkRunnable);
}
// Track when online/offline events are fired so that we don't fire excess events.
@Override void notifyOfFlush(boolean fromOnlineEvent) {
if (fromOnlineEvent) {
if (fromOnlineEvent && !ignoreNextFlush) {
online = !online;
}
}

View File

@ -23,10 +23,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.cordova.CordovaArgs;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
@ -65,8 +62,6 @@ public class PluginManager {
// Using <url-filter> is deprecated.
protected HashMap<String, List<String>> urlMap = new HashMap<String, List<String>>();
private AtomicInteger numPendingUiExecs;
private Set<String> pluginIdWhitelist;
/**
@ -79,7 +74,6 @@ public class PluginManager {
this.ctx = ctx;
this.app = app;
this.firstRun = true;
this.numPendingUiExecs = new AtomicInteger(0);
}
public void setPluginIdWhitelist(Set<String> pluginIdWhitelist) {
@ -105,9 +99,6 @@ public class PluginManager {
this.clearPluginObjects();
}
// Insert PluginManager service
this.addService(new PluginEntry("PluginManager", new PluginManagerService()));
// Start up all plugins that have onload specified
this.startupPlugins();
}
@ -224,20 +215,6 @@ public class PluginManager {
* plugin execute method.
*/
public void exec(final String service, final String action, final String callbackId, final String rawArgs) {
if (numPendingUiExecs.get() > 0) {
numPendingUiExecs.getAndIncrement();
this.ctx.getActivity().runOnUiThread(new Runnable() {
public void run() {
execHelper(service, action, callbackId, rawArgs);
numPendingUiExecs.getAndDecrement();
}
});
} else {
execHelper(service, action, callbackId, rawArgs);
}
}
private void execHelper(final String service, final String action, final String callbackId, final String rawArgs) {
CordovaPlugin plugin = getPlugin(service);
if (plugin == null) {
Log.d(TAG, "exec() call to unknown plugin: " + service);
@ -449,26 +426,4 @@ public class PluginManager {
}
return null;
}
private class PluginManagerService extends CordovaPlugin {
@Override
public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException {
if ("startup".equals(action)) {
// The onPageStarted event of CordovaWebViewClient resets the queue of messages to be returned to javascript in response
// to exec calls. Since this event occurs on the UI thread and exec calls happen on the WebCore thread it is possible
// that onPageStarted occurs after exec calls have started happening on a new page, which can cause the message queue
// to be reset between the queuing of a new message and its retrieval by javascript. To avoid this from happening,
// javascript always sends a "startup" exec upon loading a new page which causes all future exec calls to happen on the UI
// thread (and hence after onPageStarted) until there are no more pending exec calls remaining.
numPendingUiExecs.getAndIncrement();
ctx.getActivity().runOnUiThread(new Runnable() {
public void run() {
numPendingUiExecs.getAndDecrement();
}
});
return true;
}
return false;
}
}
}