This is an ugly merge commit, because the rebase made even less sense.

This should add the old setProperty methods required for the tests. We
decided to not deprecate them.  I don't make a habit of doing merge
commits, due to their destructive nature, but I think I might have
merged too much stuff in.

Merge branch 'pluggable_webview' of https://git-wip-us.apache.org/repos/asf/cordova-android into pluggable_webview

Conflicts:
	framework/src/org/apache/cordova/AndroidChromeClient.java
	framework/src/org/apache/cordova/AndroidWebView.java
	framework/src/org/apache/cordova/CordovaActivity.java
	framework/src/org/apache/cordova/CordovaWebView.java
This commit is contained in:
Joe Bowser 2014-04-30 14:59:40 -07:00
commit 105ccc81a5
9 changed files with 198 additions and 96 deletions

View File

@ -23,6 +23,7 @@
<!-- Preferences for Android --> <!-- Preferences for Android -->
<preference name="loglevel" value="DEBUG" /> <preference name="loglevel" value="DEBUG" />
<preference name="AndroidLaunchMode" value="singleTop" />
<!-- This is required for native Android hooks --> <!-- This is required for native Android hooks -->
<feature name="App"> <feature name="App">

View File

@ -74,7 +74,12 @@ module.exports.run = function(build_type) {
* the script will error out. (should we error or just return undefined?) * the script will error out. (should we error or just return undefined?)
*/ */
module.exports.get_apk = function() { module.exports.get_apk = function() {
var binDir = path.join(ROOT, 'ant-build'); var binDir = '';
if(!hasCustomRules()) {
binDir = path.join(ROOT, 'bin');
} else {
binDir = path.join(ROOT, 'ant-build');
}
if (fs.existsSync(binDir)) { if (fs.existsSync(binDir)) {
var candidates = fs.readdirSync(binDir).filter(function(p) { var candidates = fs.readdirSync(binDir).filter(function(p) {
// Need to choose between release and debug .apk. // Need to choose between release and debug .apk.
@ -87,13 +92,13 @@ module.exports.get_apk = function() {
a.t < b.t ? 1 : 0; a.t < b.t ? 1 : 0;
}); });
if (candidates.length === 0) { if (candidates.length === 0) {
console.error('ERROR : No .apk found in \'ant-build\' directory'); console.error('ERROR : No .apk found in ' + binDir + ' directory');
process.exit(2); process.exit(2);
} }
console.log('Using apk: ' + candidates[0].p); console.log('Using apk: ' + candidates[0].p);
return candidates[0].p; return candidates[0].p;
} else { } else {
console.error('ERROR : unable to find project ant-build directory, could not locate .apk'); console.error('ERROR : unable to find project ' + binDir + ' directory, could not locate .apk');
process.exit(2); process.exit(2);
} }
} }

View File

@ -34,7 +34,7 @@
<application android:icon="@drawable/icon" android:label="@string/app_name" <application android:icon="@drawable/icon" android:label="@string/app_name"
android:hardwareAccelerated="true"> android:hardwareAccelerated="true">
<activity android:name="__ACTIVITY__" android:label="@string/app_name" <activity android:name="__ACTIVITY__" android:label="@string/app_name" android:launchMode="singleTop"
android:theme="@android:style/Theme.Black.NoTitleBar" android:theme="@android:style/Theme.Black.NoTitleBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale"> android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale">
<intent-filter> <intent-filter>

View File

@ -1,5 +1,5 @@
// Platform: android // Platform: android
// 3.5.0-dev-ddf13aa // 3.5.0-dev-81f9a00
/* /*
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file or more contributor license agreements. See the NOTICE file
@ -19,7 +19,7 @@
under the License. under the License.
*/ */
;(function() { ;(function() {
var CORDOVA_JS_BUILD_LABEL = '3.5.0-dev-ddf13aa'; var CORDOVA_JS_BUILD_LABEL = '3.5.0-dev-81f9a00';
// file: src/scripts/require.js // file: src/scripts/require.js
/*jshint -W079 */ /*jshint -W079 */
@ -437,6 +437,16 @@ base64.fromArrayBuffer = function(arrayBuffer) {
return uint8ToBase64(array); return uint8ToBase64(array);
}; };
base64.toArrayBuffer = function(str) {
var decodedStr = typeof atob != 'undefined' ? atob(str) : new Buffer(str,'base64').toString('binary');
var arrayBuffer = new ArrayBuffer(decodedStr.length);
var array = new Uint8Array(arrayBuffer);
for (var i=0, len=decodedStr.length; i < len; i++) {
array[i] = decodedStr.charCodeAt(i);
}
return arrayBuffer;
};
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* This code is based on the performance tests at http://jsperf.com/b64tests /* This code is based on the performance tests at http://jsperf.com/b64tests
@ -919,7 +929,7 @@ function androidExec(success, fail, service, action, args) {
androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT); androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
return; return;
} else { } else {
androidExec.processMessages(messages); androidExec.processMessages(messages, true);
} }
} }
} }
@ -963,7 +973,6 @@ androidExec.nativeToJsModes = nativeToJsModes;
androidExec.setJsToNativeBridgeMode = function(mode) { androidExec.setJsToNativeBridgeMode = function(mode) {
if (mode == jsToNativeModes.JS_OBJECT && !window._cordovaNative) { if (mode == jsToNativeModes.JS_OBJECT && !window._cordovaNative) {
console.log('Falling back on PROMPT mode since _cordovaNative is missing. Expected for Android 3.2 and lower only.');
mode = jsToNativeModes.PROMPT; mode = jsToNativeModes.PROMPT;
} }
nativeApiProvider.setPreferPrompt(mode == jsToNativeModes.PROMPT); nativeApiProvider.setPreferPrompt(mode == jsToNativeModes.PROMPT);
@ -1028,48 +1037,64 @@ function processMessage(message) {
} }
cordova.callbackFromNative(callbackId, success, status, [payload], keepCallback); cordova.callbackFromNative(callbackId, success, status, [payload], keepCallback);
} else { } else {
console.log("processMessage failed: invalid message:" + message); console.log("processMessage failed: invalid message: " + JSON.stringify(message));
} }
} catch (e) { } catch (e) {
console.log("processMessage failed: Message: " + message);
console.log("processMessage failed: Error: " + e); console.log("processMessage failed: Error: " + e);
console.log("processMessage failed: Stack: " + e.stack); console.log("processMessage failed: Stack: " + e.stack);
console.log("processMessage failed: Message: " + message);
} }
} }
var isProcessing = false;
// This is called from the NativeToJsMessageQueue.java. // This is called from the NativeToJsMessageQueue.java.
androidExec.processMessages = function(messages) { androidExec.processMessages = function(messages, opt_useTimeout) {
if (messages) { if (messages) {
messagesFromNative.push(messages); messagesFromNative.push(messages);
// Check for the reentrant case, and enqueue the message if that's the case. }
if (messagesFromNative.length > 1) { // Check for the reentrant case.
return; if (isProcessing) {
} return;
}
if (opt_useTimeout) {
window.setTimeout(androidExec.processMessages, 0);
return;
}
isProcessing = true;
try {
// TODO: add setImmediate polyfill and process only one message at a time.
while (messagesFromNative.length) { while (messagesFromNative.length) {
// Don't unshift until the end so that reentrancy can be detected. var msg = popMessageFromQueue();
messages = messagesFromNative[0];
// The Java side can send a * message to indicate that it // The Java side can send a * message to indicate that it
// still has messages waiting to be retrieved. // still has messages waiting to be retrieved.
if (messages == '*') { if (msg == '*' && messagesFromNative.length === 0) {
messagesFromNative.shift(); setTimeout(pollOnce, 0);
window.setTimeout(pollOnce, 0);
return; return;
} }
processMessage(msg);
var spaceIdx = messages.indexOf(' ');
var msgLen = +messages.slice(0, spaceIdx);
var message = messages.substr(spaceIdx + 1, msgLen);
messages = messages.slice(spaceIdx + msgLen + 1);
processMessage(message);
if (messages) {
messagesFromNative[0] = messages;
} else {
messagesFromNative.shift();
}
} }
} finally {
isProcessing = false;
} }
}; };
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; module.exports = androidExec;
}); });
@ -1191,9 +1216,13 @@ modulemapper.clobbers('cordova/exec', 'Cordova.exec');
// Call the platform-specific initialization. // Call the platform-specific initialization.
platform.bootstrap && platform.bootstrap(); platform.bootstrap && platform.bootstrap();
pluginloader.load(function() { // Wrap in a setTimeout to support the use-case of having plugin JS appended to cordova.js.
channel.onPluginsReady.fire(); // The delay allows the attached modules to be defined before the plugin loader looks for them.
}); setTimeout(function() {
pluginloader.load(function() {
channel.onPluginsReady.fire();
});
}, 0);
/** /**
* Create all cordova objects once native side is ready. * Create all cordova objects once native side is ready.
@ -1444,43 +1473,51 @@ var modulemapper = require('cordova/modulemapper');
var urlutil = require('cordova/urlutil'); var urlutil = require('cordova/urlutil');
// Helper function to inject a <script> tag. // Helper function to inject a <script> tag.
function injectScript(url, onload, onerror) { // Exported for testing.
exports.injectScript = function(url, onload, onerror) {
var script = document.createElement("script"); var script = document.createElement("script");
// onload fires even when script fails loads with an error. // onload fires even when script fails loads with an error.
script.onload = onload; script.onload = onload;
script.onerror = onerror || onload; // onerror fires for malformed URLs.
script.onerror = onerror;
script.src = url; script.src = url;
document.head.appendChild(script); document.head.appendChild(script);
};
function injectIfNecessary(id, url, onload, onerror) {
onerror = onerror || onload;
if (id in define.moduleMap) {
onload();
} else {
exports.injectScript(url, function() {
if (id in define.moduleMap) {
onload();
} else {
onerror();
}
}, onerror);
}
} }
function onScriptLoadingComplete(moduleList, finishPluginLoading) { function onScriptLoadingComplete(moduleList, finishPluginLoading) {
// Loop through all the plugins and then through their clobbers and merges. // Loop through all the plugins and then through their clobbers and merges.
for (var i = 0, module; module = moduleList[i]; i++) { for (var i = 0, module; module = moduleList[i]; i++) {
if (module) { if (module.clobbers && module.clobbers.length) {
try { for (var j = 0; j < module.clobbers.length; j++) {
if (module.clobbers && module.clobbers.length) { modulemapper.clobbers(module.id, module.clobbers[j]);
for (var j = 0; j < module.clobbers.length; j++) {
modulemapper.clobbers(module.id, module.clobbers[j]);
}
}
if (module.merges && module.merges.length) {
for (var k = 0; k < module.merges.length; k++) {
modulemapper.merges(module.id, module.merges[k]);
}
}
// Finally, if runs is truthy we want to simply require() the module.
// This can be skipped if it had any merges or clobbers, though,
// since the mapper will already have required the module.
if (module.runs && !(module.clobbers && module.clobbers.length) && !(module.merges && module.merges.length)) {
modulemapper.runs(module.id);
}
} }
catch(err) { }
// error with module, most likely clobbers, should we continue?
if (module.merges && module.merges.length) {
for (var k = 0; k < module.merges.length; k++) {
modulemapper.merges(module.id, module.merges[k]);
} }
} }
// Finally, if runs is truthy we want to simply require() the module.
if (module.runs) {
modulemapper.runs(module.id);
}
} }
finishPluginLoading(); finishPluginLoading();
@ -1505,26 +1542,10 @@ function handlePluginsObject(path, moduleList, finishPluginLoading) {
} }
for (var i = 0; i < moduleList.length; i++) { for (var i = 0; i < moduleList.length; i++) {
injectScript(path + moduleList[i].file, scriptLoadedCallback); injectIfNecessary(moduleList[i].id, path + moduleList[i].file, scriptLoadedCallback);
} }
} }
function injectPluginScript(pathPrefix, finishPluginLoading) {
var pluginPath = pathPrefix + 'cordova_plugins.js';
injectScript(pluginPath, function() {
try {
var moduleList = require("cordova/plugin_list");
handlePluginsObject(pathPrefix, moduleList, finishPluginLoading);
}
catch (e) {
// Error loading cordova_plugins.js, file not found or something
// this is an acceptable error, pre-3.0.0, so we just move on.
finishPluginLoading();
}
}, finishPluginLoading); // also, add script load error handler for file not found
}
function findCordovaPath() { function findCordovaPath() {
var path = null; var path = null;
var scripts = document.getElementsByTagName('script'); var scripts = document.getElementsByTagName('script');
@ -1548,7 +1569,10 @@ exports.load = function(callback) {
console.log('Could not find cordova.js script tag. Plugin loading may fail.'); console.log('Could not find cordova.js script tag. Plugin loading may fail.');
pathPrefix = ''; pathPrefix = '';
} }
injectPluginScript(pathPrefix, callback); injectIfNecessary('cordova/plugin_list', pathPrefix + 'cordova_plugins.js', function() {
var moduleList = require("cordova/plugin_list");
handlePluginsObject(pathPrefix, moduleList, callback);
}, callback);
}; };

View File

@ -166,8 +166,11 @@ public class AndroidWebView extends WebView implements CordovaWebView {
{ {
Log.d(TAG, "Your activity must implement CordovaInterface to work"); Log.d(TAG, "Your activity must implement CordovaInterface to work");
} }
<<<<<<< HEAD
this.setWebChromeClient(this.makeChromeClient()); this.setWebChromeClient(this.makeChromeClient());
this.initWebViewClient(this.cordova); this.initWebViewClient(this.cordova);
=======
>>>>>>> df05f3a3c07c0630c3d598409289db7a8f3c87e3
this.loadConfiguration(); this.loadConfiguration();
this.setup(); this.setup();
} }
@ -190,7 +193,10 @@ public class AndroidWebView extends WebView implements CordovaWebView {
{ {
Log.d(TAG, "Your activity must implement CordovaInterface to work"); Log.d(TAG, "Your activity must implement CordovaInterface to work");
} }
<<<<<<< HEAD
this.setWebChromeClient(this.makeChromeClient()); this.setWebChromeClient(this.makeChromeClient());
=======
>>>>>>> df05f3a3c07c0630c3d598409289db7a8f3c87e3
this.loadConfiguration(); this.loadConfiguration();
this.setup(); this.setup();
} }
@ -214,17 +220,49 @@ public class AndroidWebView extends WebView implements CordovaWebView {
{ {
Log.d(TAG, "Your activity must implement CordovaInterface to work"); Log.d(TAG, "Your activity must implement CordovaInterface to work");
} }
<<<<<<< HEAD
this.setWebChromeClient(this.makeChromeClient()); this.setWebChromeClient(this.makeChromeClient());
this.initWebViewClient(this.cordova); this.initWebViewClient(this.cordova);
=======
>>>>>>> df05f3a3c07c0630c3d598409289db7a8f3c87e3
this.loadConfiguration(); this.loadConfiguration();
this.setup(); this.setup();
} }
/** /**
<<<<<<< HEAD
* set the WebViewClient, but provide special case handling for IceCreamSandwich. * set the WebViewClient, but provide special case handling for IceCreamSandwich.
*/ */
private void initWebViewClient(CordovaInterface cordova) { private void initWebViewClient(CordovaInterface cordova) {
this.setWebViewClient(this.makeWebViewClient()); this.setWebViewClient(this.makeWebViewClient());
=======
* Create a default WebViewClient object for this webview. This can be overridden by the
* main application's CordovaActivity subclass.
*
* By default, it creates an AndroidWebViewClient, but we provide special case handling for
* IceCreamSandwich.
*/
@Override
public CordovaWebViewClient makeWebViewClient() {
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB ||
android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.JELLY_BEAN_MR1)
{
return (CordovaWebViewClient) new AndroidWebViewClient(this.cordova, this);
}
else
{
return (CordovaWebViewClient) new IceCreamCordovaWebViewClient(this.cordova, this);
}
}
/**
* Create a default WebViewClient object for this webview. This can be overridden by the
* main application's CordovaActivity subclass.
*/
@Override
public CordovaChromeClient makeWebChromeClient() {
return (CordovaChromeClient) new AndroidChromeClient(this.cordova);
>>>>>>> df05f3a3c07c0630c3d598409289db7a8f3c87e3
} }
/** /**
@ -1074,6 +1112,7 @@ public class AndroidWebView extends WebView implements CordovaWebView {
public View getView() { public View getView() {
return this; return this;
} }
<<<<<<< HEAD
@Override @Override
public CordovaWebViewClient makeWebViewClient() { public CordovaWebViewClient makeWebViewClient() {
@ -1088,4 +1127,6 @@ public class AndroidWebView extends WebView implements CordovaWebView {
public CordovaChromeClient makeChromeClient() { public CordovaChromeClient makeChromeClient() {
return (CordovaChromeClient) new AndroidChromeClient(this.cordova, this); return (CordovaChromeClient) new AndroidChromeClient(this.cordova, this);
} }
=======
>>>>>>> df05f3a3c07c0630c3d598409289db7a8f3c87e3
} }

View File

@ -219,22 +219,17 @@ public class CordovaActivity extends Activity implements CordovaInterface {
try { try {
Class webViewClass = Class.forName(r); Class webViewClass = Class.forName(r);
Constructor<CordovaWebView> [] webViewConstructors = webViewClass.getConstructors(); Constructor<CordovaWebView> [] webViewConstructors = webViewClass.getConstructors();
if(CordovaWebView.class.isAssignableFrom(webViewClass)) { if(CordovaWebView.class.isAssignableFrom(webViewClass)) {
for (Constructor<CordovaWebView> constructor : webViewConstructors) { for (Constructor<CordovaWebView> constructor : webViewConstructors) {
try { try {
CordovaWebView webView = (CordovaWebView) constructor.newInstance(this); CordovaWebView webView = (CordovaWebView) constructor.newInstance(this);
return webView; return webView;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
LOG.e(TAG, "Illegal arguments, try next constructor."); LOG.d(TAG, "Illegal arguments; trying next constructor.");
} }
} }
} }
else LOG.e(TAG, "The WebView Engine is NOT a proper WebView, defaulting to system WebView");
{
LOG.e(TAG, "The WebView Engine is NOT a proper WebView, defaulting to system WebView");
}
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
LOG.e(TAG, "The WebView Engine was not found, defaulting to system WebView"); LOG.e(TAG, "The WebView Engine was not found, defaulting to system WebView");
} catch (InstantiationException e) { } catch (InstantiationException e) {
@ -254,25 +249,27 @@ public class CordovaActivity extends Activity implements CordovaInterface {
/** /**
* Construct the client for the default web view object. * Construct the client for the default web view object.
* *
* This is intended to be overridable by subclasses of CordovaIntent which * This is intended to be overridable by subclasses of CordovaActivity which
* require a more specialized web view. * require a more specialized web view. By default, it allows the webView
* to create its own client objects.
* *
* @param webView the default constructed web view object * @param webView the default constructed web view object
*/ */
protected CordovaWebViewClient makeWebViewClient(CordovaWebView webView) { protected CordovaWebViewClient makeWebViewClient(CordovaWebView webView) {
return webView.makeWebViewClient(); return webView.makeWebViewClient();
} }
/** /**
* Construct the chrome client for the default web view object. * Construct the chrome client for the default web view object.
* *
* This is intended to be overridable by subclasses of CordovaIntent which * This is intended to be overridable by subclasses of CordovaActivity which
* require a more specialized web view. * require a more specialized web view. By default, it allows the webView
* to create its own client objects.
* *
* @param webView the default constructed web view object * @param webView the default constructed web view object
*/ */
protected CordovaChromeClient makeChromeClient(CordovaWebView webView) { protected CordovaChromeClient makeChromeClient(CordovaWebView webView) {
return webView.makeChromeClient(); return webView.makeWebChromeClient();
} }
/** /**

View File

@ -45,6 +45,27 @@ import java.net.URL;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.util.Locale; import java.util.Locale;
/**
* What this class provides:
* 1. Helpers for reading & writing to URLs.
* - E.g. handles assets, resources, content providers, files, data URIs, http[s]
* - E.g. Can be used to query for mime-type & content length.
*
* 2. To allow plugins to redirect URLs (via remapUrl).
* - All plugins should call remapUrl() on URLs they receive from JS *before*
* passing the URL onto other utility functions in this class.
* - For an example usage of this, refer to the org.apache.cordova.file plugin.
*
* 3. It exposes a way to use the OkHttp library that ships with Cordova.
* - Through createHttpConnection().
*
* Future Work:
* - Consider using a Cursor to query content URLs for their size (like the file plugin does).
* - Allow plugins to remapUri to "cdv-plugin://plugin-name/$ID", which CordovaResourceApi
* would then delegate to pluginManager.getPlugin(plugin-name).openForRead($ID)
* - Currently, plugins *can* do this by remapping to a data: URL, but it's inefficient
* for large payloads.
*/
public class CordovaResourceApi { public class CordovaResourceApi {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static final String LOG_TAG = "CordovaResourceApi"; private static final String LOG_TAG = "CordovaResourceApi";

View File

@ -18,10 +18,10 @@ public interface CordovaWebView {
Object jsMessageQueue = null; Object jsMessageQueue = null;
View getView(); View getView();
CordovaWebViewClient makeWebViewClient(); CordovaWebViewClient makeWebViewClient();
CordovaChromeClient makeChromeClient(); CordovaChromeClient makeWebChromeClient();
void setWebViewClient(CordovaWebViewClient webViewClient); void setWebViewClient(CordovaWebViewClient webViewClient);

View File

@ -494,9 +494,22 @@ public class NativeToJsMessageQueue {
.append(success) .append(success)
.append(",") .append(",")
.append(status) .append(status)
.append(",[") .append(",[");
.append(pluginResult.getMessage()) switch (pluginResult.getMessageType()) {
.append("],") case PluginResult.MESSAGE_TYPE_BINARYSTRING:
sb.append("atob('")
.append(pluginResult.getMessage())
.append("')");
break;
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
sb.append("cordova.require('cordova/base64').toArrayBuffer('")
.append(pluginResult.getMessage())
.append("')");
break;
default:
sb.append(pluginResult.getMessage());
}
sb.append("],")
.append(pluginResult.getKeepCallback()) .append(pluginResult.getKeepCallback())
.append(");"); .append(");");
} }