-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CB-8417 moved platform specific js into platforms
- Loading branch information
1 parent
4cb6458
commit 828edb3
Showing
5 changed files
with
557 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
/** | ||
* Exports the ExposedJsApi.java object if available, otherwise exports the PromptBasedNativeApi. | ||
*/ | ||
|
||
var nativeApi = this._cordovaNative || require('cordova/android/promptbasednativeapi'); | ||
var currentApi = nativeApi; | ||
|
||
module.exports = { | ||
get: function() { return currentApi; }, | ||
setPreferPrompt: function(value) { | ||
currentApi = value ? require('cordova/android/promptbasednativeapi') : nativeApi; | ||
}, | ||
// Used only by tests. | ||
set: function(value) { | ||
currentApi = value; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
/** | ||
* Implements the API of ExposedJsApi.java, but uses prompt() to communicate. | ||
* This is used pre-JellyBean, where addJavascriptInterface() is disabled. | ||
*/ | ||
|
||
module.exports = { | ||
exec: function(bridgeSecret, service, action, callbackId, argsJson) { | ||
return prompt(argsJson, 'gap:'+JSON.stringify([bridgeSecret, service, action, callbackId])); | ||
}, | ||
setNativeToJsBridgeMode: function(bridgeSecret, value) { | ||
prompt(value, 'gap_bridge_mode:' + bridgeSecret); | ||
}, | ||
retrieveJsMessages: function(bridgeSecret, fromOnlineEvent) { | ||
return prompt(+fromOnlineEvent, 'gap_poll:' + bridgeSecret); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,287 @@ | ||
/* | ||
* | ||
* 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. | ||
* | ||
*/ | ||
|
||
/** | ||
* Execute a cordova command. It is up to the native side whether this action | ||
* is synchronous or asynchronous. The native side can return: | ||
* Synchronous: PluginResult object as a JSON string | ||
* Asynchronous: Empty string "" | ||
* If async, the native side will cordova.callbackSuccess or cordova.callbackError, | ||
* depending upon the result of the action. | ||
* | ||
* @param {Function} success The success callback | ||
* @param {Function} fail The fail callback | ||
* @param {String} service The name of the service to use | ||
* @param {String} action Action to be run in cordova | ||
* @param {String[]} [args] Zero or more arguments to pass to the method | ||
*/ | ||
var cordova = require('cordova'), | ||
nativeApiProvider = require('cordova/android/nativeapiprovider'), | ||
utils = require('cordova/utils'), | ||
base64 = require('cordova/base64'), | ||
channel = require('cordova/channel'), | ||
jsToNativeModes = { | ||
PROMPT: 0, | ||
JS_OBJECT: 1 | ||
}, | ||
nativeToJsModes = { | ||
// Polls for messages using the JS->Native bridge. | ||
POLLING: 0, | ||
// For LOAD_URL to be viable, it would need to have a work-around for | ||
// the bug where the soft-keyboard gets dismissed when a message is sent. | ||
LOAD_URL: 1, | ||
// For the ONLINE_EVENT to be viable, it would need to intercept all event | ||
// listeners (both through addEventListener and window.ononline) as well | ||
// as set the navigator property itself. | ||
ONLINE_EVENT: 2, | ||
// Uses reflection to access private APIs of the WebView that can send JS | ||
// to be executed. | ||
// Requires Android 3.2.4 or above. | ||
PRIVATE_API: 3 | ||
}, | ||
jsToNativeBridgeMode, // Set lazily. | ||
nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT, | ||
pollEnabled = false, | ||
bridgeSecret = -1; | ||
|
||
var messagesFromNative = []; | ||
var isProcessing = false; | ||
var resolvedPromise = typeof Promise == 'undefined' ? null : Promise.resolve(); | ||
var nextTick = resolvedPromise ? function(fn) { resolvedPromise.then(fn); } : function(fn) { setTimeout(fn); }; | ||
|
||
function androidExec(success, fail, service, action, args) { | ||
if (bridgeSecret < 0) { | ||
// If we ever catch this firing, we'll need to queue up exec()s | ||
// and fire them once we get a secret. For now, I don't think | ||
// it's possible for exec() to be called since plugins are parsed but | ||
// not run until until after onNativeReady. | ||
throw new Error('exec() called without bridgeSecret'); | ||
} | ||
// Set default bridge modes if they have not already been set. | ||
// By default, we use the failsafe, since addJavascriptInterface breaks too often | ||
if (jsToNativeBridgeMode === undefined) { | ||
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT); | ||
} | ||
|
||
// Process any ArrayBuffers in the args into a string. | ||
for (var i = 0; i < args.length; i++) { | ||
if (utils.typeName(args[i]) == 'ArrayBuffer') { | ||
args[i] = base64.fromArrayBuffer(args[i]); | ||
} | ||
} | ||
|
||
var callbackId = service + cordova.callbackId++, | ||
argsJson = JSON.stringify(args); | ||
|
||
if (success || fail) { | ||
cordova.callbacks[callbackId] = {success:success, fail:fail}; | ||
} | ||
|
||
var msgs = nativeApiProvider.get().exec(bridgeSecret, service, action, callbackId, argsJson); | ||
// If argsJson was received by Java as null, try again with the PROMPT bridge mode. | ||
// This happens in rare circumstances, such as when certain Unicode characters are passed over the bridge on a Galaxy S2. See CB-2666. | ||
if (jsToNativeBridgeMode == jsToNativeModes.JS_OBJECT && msgs === "@Null arguments.") { | ||
androidExec.setJsToNativeBridgeMode(jsToNativeModes.PROMPT); | ||
androidExec(success, fail, service, action, args); | ||
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT); | ||
} else if (msgs) { | ||
messagesFromNative.push(msgs); | ||
// Always process async to avoid exceptions messing up stack. | ||
nextTick(processMessages); | ||
} | ||
} | ||
|
||
androidExec.init = function() { | ||
bridgeSecret = +prompt('', 'gap_init:' + nativeToJsBridgeMode); | ||
channel.onNativeReady.fire(); | ||
}; | ||
|
||
function pollOnceFromOnlineEvent() { | ||
pollOnce(true); | ||
} | ||
|
||
function pollOnce(opt_fromOnlineEvent) { | ||
if (bridgeSecret < 0) { | ||
// This can happen when the NativeToJsMessageQueue resets the online state on page transitions. | ||
// We know there's nothing to retrieve, so no need to poll. | ||
return; | ||
} | ||
var msgs = nativeApiProvider.get().retrieveJsMessages(bridgeSecret, !!opt_fromOnlineEvent); | ||
if (msgs) { | ||
messagesFromNative.push(msgs); | ||
// Process sync since we know we're already top-of-stack. | ||
processMessages(); | ||
} | ||
} | ||
|
||
function pollingTimerFunc() { | ||
if (pollEnabled) { | ||
pollOnce(); | ||
setTimeout(pollingTimerFunc, 50); | ||
} | ||
} | ||
|
||
function hookOnlineApis() { | ||
function proxyEvent(e) { | ||
cordova.fireWindowEvent(e.type); | ||
} | ||
// The network module takes care of firing online and offline events. | ||
// It currently fires them only on document though, so we bridge them | ||
// to window here (while first listening for exec()-releated online/offline | ||
// events). | ||
window.addEventListener('online', pollOnceFromOnlineEvent, false); | ||
window.addEventListener('offline', pollOnceFromOnlineEvent, false); | ||
cordova.addWindowEventHandler('online'); | ||
cordova.addWindowEventHandler('offline'); | ||
document.addEventListener('online', proxyEvent, false); | ||
document.addEventListener('offline', proxyEvent, false); | ||
} | ||
|
||
hookOnlineApis(); | ||
|
||
androidExec.jsToNativeModes = jsToNativeModes; | ||
androidExec.nativeToJsModes = nativeToJsModes; | ||
|
||
androidExec.setJsToNativeBridgeMode = function(mode) { | ||
if (mode == jsToNativeModes.JS_OBJECT && !window._cordovaNative) { | ||
mode = jsToNativeModes.PROMPT; | ||
} | ||
nativeApiProvider.setPreferPrompt(mode == jsToNativeModes.PROMPT); | ||
jsToNativeBridgeMode = mode; | ||
}; | ||
|
||
androidExec.setNativeToJsBridgeMode = function(mode) { | ||
if (mode == nativeToJsBridgeMode) { | ||
return; | ||
} | ||
if (nativeToJsBridgeMode == nativeToJsModes.POLLING) { | ||
pollEnabled = false; | ||
} | ||
|
||
nativeToJsBridgeMode = mode; | ||
// Tell the native side to switch modes. | ||
// Otherwise, it will be set by androidExec.init() | ||
if (bridgeSecret >= 0) { | ||
nativeApiProvider.get().setNativeToJsBridgeMode(bridgeSecret, mode); | ||
} | ||
|
||
if (mode == nativeToJsModes.POLLING) { | ||
pollEnabled = true; | ||
setTimeout(pollingTimerFunc, 1); | ||
} | ||
}; | ||
|
||
function buildPayload(payload, message) { | ||
var payloadKind = message.charAt(0); | ||
if (payloadKind == 's') { | ||
payload.push(message.slice(1)); | ||
} else if (payloadKind == 't') { | ||
payload.push(true); | ||
} else if (payloadKind == 'f') { | ||
payload.push(false); | ||
} else if (payloadKind == 'N') { | ||
payload.push(null); | ||
} else if (payloadKind == 'n') { | ||
payload.push(+message.slice(1)); | ||
} else if (payloadKind == 'A') { | ||
var data = message.slice(1); | ||
payload.push(base64.toArrayBuffer(data)); | ||
} else if (payloadKind == 'S') { | ||
payload.push(window.atob(message.slice(1))); | ||
} else if (payloadKind == 'M') { | ||
var multipartMessages = message.slice(1); | ||
while (multipartMessages !== "") { | ||
var spaceIdx = multipartMessages.indexOf(' '); | ||
var msgLen = +multipartMessages.slice(0, spaceIdx); | ||
var multipartMessage = multipartMessages.substr(spaceIdx + 1, msgLen); | ||
multipartMessages = multipartMessages.slice(spaceIdx + msgLen + 1); | ||
buildPayload(payload, multipartMessage); | ||
} | ||
} else { | ||
payload.push(JSON.parse(message)); | ||
} | ||
} | ||
|
||
// Processes a single message, as encoded by NativeToJsMessageQueue.java. | ||
function processMessage(message) { | ||
var firstChar = message.charAt(0); | ||
if (firstChar == 'J') { | ||
// This is deprecated on the .java side. It doesn't work with CSP enabled. | ||
eval(message.slice(1)); | ||
} else if (firstChar == 'S' || firstChar == 'F') { | ||
var success = firstChar == 'S'; | ||
var keepCallback = message.charAt(1) == '1'; | ||
var spaceIdx = message.indexOf(' ', 2); | ||
var status = +message.slice(2, spaceIdx); | ||
var nextSpaceIdx = message.indexOf(' ', spaceIdx + 1); | ||
var callbackId = message.slice(spaceIdx + 1, nextSpaceIdx); | ||
var payloadMessage = message.slice(nextSpaceIdx + 1); | ||
var payload = []; | ||
buildPayload(payload, payloadMessage); | ||
cordova.callbackFromNative(callbackId, success, status, payload, keepCallback); | ||
} else { | ||
console.log("processMessage failed: invalid message: " + JSON.stringify(message)); | ||
} | ||
} | ||
|
||
function processMessages() { | ||
// Check for the reentrant case. | ||
if (isProcessing) { | ||
return; | ||
} | ||
if (messagesFromNative.length === 0) { | ||
return; | ||
} | ||
isProcessing = true; | ||
try { | ||
var msg = popMessageFromQueue(); | ||
// The Java side can send a * message to indicate that it | ||
// still has messages waiting to be retrieved. | ||
if (msg == '*' && messagesFromNative.length === 0) { | ||
nextTick(pollOnce); | ||
return; | ||
} | ||
processMessage(msg); | ||
} finally { | ||
isProcessing = false; | ||
if (messagesFromNative.length > 0) { | ||
nextTick(processMessages); | ||
} | ||
} | ||
} | ||
|
||
function popMessageFromQueue() { | ||
var messageBatch = messagesFromNative.shift(); | ||
if (messageBatch == '*') { | ||
return '*'; | ||
} | ||
|
||
var spaceIdx = messageBatch.indexOf(' '); | ||
var msgLen = +messageBatch.slice(0, spaceIdx); | ||
var message = messageBatch.substr(spaceIdx + 1, msgLen); | ||
messageBatch = messageBatch.slice(spaceIdx + msgLen + 1); | ||
if (messageBatch) { | ||
messagesFromNative.unshift(messageBatch); | ||
} | ||
return message; | ||
} | ||
|
||
module.exports = androidExec; |
Oops, something went wrong.