mirror of
https://github.com/apache/cordova-android.git
synced 2025-02-20 23:56:20 +08:00
Backport CordovaBridge from 4.0.x -> master
This commit is contained in:
parent
d8a19b5565
commit
cc860804f6
@ -669,7 +669,7 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
||||
@Deprecated // Call method on appView directly.
|
||||
public void sendJavascript(String statement) {
|
||||
if (this.appView != null) {
|
||||
this.appView.jsMessageQueue.addJavaScript(statement);
|
||||
this.appView.bridge.getMessageQueue().addJavaScript(statement);
|
||||
}
|
||||
}
|
||||
|
||||
|
185
framework/src/org/apache/cordova/CordovaBridge.java
Normal file
185
framework/src/org/apache/cordova/CordovaBridge.java
Normal file
@ -0,0 +1,185 @@
|
||||
/*
|
||||
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 org.apache.cordova.PluginManager;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Contains APIs that the JS can call. All functions in here should also have
|
||||
* an equivalent entry in CordovaChromeClient.java, and be added to
|
||||
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
|
||||
*/
|
||||
public class CordovaBridge {
|
||||
private static final String LOG_TAG = "CordovaBridge";
|
||||
private PluginManager pluginManager;
|
||||
private NativeToJsMessageQueue jsMessageQueue;
|
||||
private volatile int bridgeSecret = -1; // written by UI thread, read by JS thread.
|
||||
private String loadedUrl;
|
||||
|
||||
public CordovaBridge(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
|
||||
this.pluginManager = pluginManager;
|
||||
this.jsMessageQueue = jsMessageQueue;
|
||||
}
|
||||
|
||||
private final boolean checkBridgeEnabled(String action) {
|
||||
if (!jsMessageQueue.isBridgeEnabled()) {
|
||||
if (bridgeSecret == -1) {
|
||||
Log.d(LOG_TAG, action + " call made before bridge was enabled.");
|
||||
} else {
|
||||
Log.d(LOG_TAG, "Ignoring " + action + " from previous page load.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String jsExec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
|
||||
if (!checkBridgeEnabled("exec()")) {
|
||||
return "";
|
||||
}
|
||||
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) {
|
||||
return "@Null arguments.";
|
||||
}
|
||||
|
||||
jsMessageQueue.setPaused(true);
|
||||
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) {
|
||||
ret = jsMessageQueue.popAndEncode(false);
|
||||
}
|
||||
return ret;
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
} finally {
|
||||
jsMessageQueue.setPaused(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void jsSetNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
|
||||
verifySecret(bridgeSecret);
|
||||
jsMessageQueue.setBridgeMode(value);
|
||||
}
|
||||
|
||||
public String jsRetrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
|
||||
if (!checkBridgeEnabled("retrieveJsMessages()")) {
|
||||
return "";
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
public void reset(String loadedUrl) {
|
||||
jsMessageQueue.reset();
|
||||
clearBridgeSecret();
|
||||
this.loadedUrl = loadedUrl;
|
||||
}
|
||||
|
||||
public String promptOnJsPrompt(String origin, String message, String defaultValue) {
|
||||
if (defaultValue != null && defaultValue.length() > 3 && defaultValue.startsWith("gap:")) {
|
||||
JSONArray array;
|
||||
try {
|
||||
array = new JSONArray(defaultValue.substring(4));
|
||||
int bridgeSecret = array.getInt(0);
|
||||
String service = array.getString(1);
|
||||
String action = array.getString(2);
|
||||
String callbackId = array.getString(3);
|
||||
String r = jsExec(bridgeSecret, service, action, callbackId, message);
|
||||
return r == null ? "" : r;
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
// Sets the native->JS bridge mode.
|
||||
else if (defaultValue != null && defaultValue.startsWith("gap_bridge_mode:")) {
|
||||
try {
|
||||
int bridgeSecret = Integer.parseInt(defaultValue.substring(16));
|
||||
jsSetNativeToJsBridgeMode(bridgeSecret, Integer.parseInt(message));
|
||||
} catch (NumberFormatException e){
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
// Polling for JavaScript messages
|
||||
else if (defaultValue != null && defaultValue.startsWith("gap_poll:")) {
|
||||
int bridgeSecret = Integer.parseInt(defaultValue.substring(9));
|
||||
try {
|
||||
String r = jsRetrieveJsMessages(bridgeSecret, "1".equals(message));
|
||||
return r == null ? "" : r;
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {
|
||||
// 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") && loadedUrl.startsWith(origin))) {
|
||||
// Enable the bridge
|
||||
int bridgeMode = Integer.parseInt(defaultValue.substring(9));
|
||||
jsMessageQueue.setBridgeMode(bridgeMode);
|
||||
// Tell JS the bridge secret.
|
||||
int secret = generateBridgeSecret();
|
||||
return ""+secret;
|
||||
} else {
|
||||
Log.e(LOG_TAG, "gap_init called from restricted origin: " + origin);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public NativeToJsMessageQueue getMessageQueue() {
|
||||
return jsMessageQueue;
|
||||
}
|
||||
}
|
@ -20,15 +20,12 @@ package org.apache.cordova;
|
||||
|
||||
import org.apache.cordova.CordovaInterface;
|
||||
import org.apache.cordova.LOG;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
@ -60,7 +57,6 @@ import android.widget.RelativeLayout;
|
||||
public class CordovaChromeClient extends WebChromeClient {
|
||||
|
||||
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;
|
||||
@ -193,67 +189,9 @@ public class CordovaChromeClient extends WebChromeClient {
|
||||
@Override
|
||||
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));
|
||||
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();
|
||||
result.cancel();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
result.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the native->JS bridge mode.
|
||||
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 (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();
|
||||
}
|
||||
}
|
||||
|
||||
else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {
|
||||
// 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") && appView.loadedUrl.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();
|
||||
}
|
||||
String handledRet = appView.bridge.promptOnJsPrompt(origin, message, defaultValue);
|
||||
if (handledRet != null) {
|
||||
result.confirm(handledRet);
|
||||
} else {
|
||||
// Returning false would also show a dialog, but the default one shows the origin (ugly).
|
||||
final JsPromptResult res = result;
|
||||
|
@ -81,8 +81,7 @@ public class CordovaWebView extends WebView {
|
||||
|
||||
private long lastMenuEventTime = 0;
|
||||
|
||||
NativeToJsMessageQueue jsMessageQueue;
|
||||
ExposedJsApi exposedJsApi;
|
||||
CordovaBridge bridge;
|
||||
|
||||
/** custom view created by the browser (a video player for example) */
|
||||
private View mCustomView;
|
||||
@ -149,8 +148,7 @@ public class CordovaWebView extends WebView {
|
||||
super.setWebViewClient(webViewClient);
|
||||
|
||||
pluginManager = new PluginManager(this, this.cordova, pluginEntries);
|
||||
jsMessageQueue = new NativeToJsMessageQueue(this, cordova);
|
||||
exposedJsApi = new ExposedJsApi(pluginManager, jsMessageQueue);
|
||||
bridge = new CordovaBridge(pluginManager, new NativeToJsMessageQueue(this, cordova));
|
||||
resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);
|
||||
|
||||
pluginManager.addService("App", "org.apache.cordova.App");
|
||||
@ -300,7 +298,7 @@ public class CordovaWebView extends WebView {
|
||||
// use the prompt bridge instead.
|
||||
return;
|
||||
}
|
||||
this.addJavascriptInterface(exposedJsApi, "_cordovaNative");
|
||||
this.addJavascriptInterface(new ExposedJsApi(bridge), "_cordovaNative");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -497,7 +495,7 @@ public class CordovaWebView extends WebView {
|
||||
*/
|
||||
@Deprecated
|
||||
public void sendJavascript(String statement) {
|
||||
this.jsMessageQueue.addJavaScript(statement);
|
||||
this.bridge.getMessageQueue().addJavaScript(statement);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -508,7 +506,7 @@ public class CordovaWebView extends WebView {
|
||||
* @param callbackId
|
||||
*/
|
||||
public void sendPluginResult(PluginResult result, String callbackId) {
|
||||
this.jsMessageQueue.addPluginResult(result, callbackId);
|
||||
this.bridge.getMessageQueue().addPluginResult(result, callbackId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -141,8 +141,7 @@ public class CordovaWebViewClient extends WebViewClient {
|
||||
isCurrentlyLoading = true;
|
||||
LOG.d(TAG, "onPageStarted(" + url + ")");
|
||||
// Flush stale messages.
|
||||
this.appView.jsMessageQueue.reset();
|
||||
this.appView.exposedJsApi.clearBridgeSecret();
|
||||
this.appView.bridge.reset(url);
|
||||
|
||||
// Broadcast message that page has loaded
|
||||
this.appView.postMessage("onPageStarted", url);
|
||||
|
@ -20,7 +20,6 @@ package org.apache.cordova;
|
||||
|
||||
import android.webkit.JavascriptInterface;
|
||||
|
||||
import org.apache.cordova.PluginManager;
|
||||
import org.json.JSONException;
|
||||
|
||||
/**
|
||||
@ -30,69 +29,24 @@ import org.json.JSONException;
|
||||
*/
|
||||
/* package */ class ExposedJsApi {
|
||||
|
||||
private PluginManager pluginManager;
|
||||
private NativeToJsMessageQueue jsMessageQueue;
|
||||
private volatile int bridgeSecret = -1; // written by UI thread, read by JS thread.
|
||||
private CordovaBridge bridge;
|
||||
|
||||
public ExposedJsApi(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
|
||||
this.pluginManager = pluginManager;
|
||||
this.jsMessageQueue = jsMessageQueue;
|
||||
public ExposedJsApi(CordovaBridge bridge) {
|
||||
this.bridge = bridge;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
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) {
|
||||
return "@Null arguments.";
|
||||
}
|
||||
|
||||
jsMessageQueue.setPaused(true);
|
||||
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) {
|
||||
ret = jsMessageQueue.popAndEncode(false);
|
||||
}
|
||||
return ret;
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
} finally {
|
||||
jsMessageQueue.setPaused(false);
|
||||
}
|
||||
return bridge.jsExec(bridgeSecret, service, action, callbackId, arguments);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
|
||||
verifySecret(bridgeSecret);
|
||||
jsMessageQueue.setBridgeMode(value);
|
||||
bridge.jsSetNativeToJsBridgeMode(bridgeSecret, value);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
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;
|
||||
return bridge.jsRetrieveJsMessages(bridgeSecret, fromOnlineEvent);
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +84,11 @@ public class NativeToJsMessageQueue {
|
||||
registeredListeners[3] = new PrivateApiBridgeMode();
|
||||
reset();
|
||||
}
|
||||
|
||||
|
||||
public boolean isBridgeEnabled() {
|
||||
return activeBridgeMode != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the bridge mode.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user