From 3439746645df1678bcd8e790ca51689627826fbb Mon Sep 17 00:00:00 2001 From: Jason Chase Date: Wed, 24 Dec 2014 12:33:31 -0500 Subject: [PATCH] CB-8210 Use PluginResult instead of sendJavascript() for keyboard events (close #142) - Initialize a message channel for native -> Javascript in the core App plugin - Change keyboard detection to send events via plugin message channel, instead using eval() (i.e. webView.sendJavascript()) --- framework/assets/www/cordova.js | 69 +++++++++++-------- framework/src/org/apache/cordova/App.java | 30 +++++++- .../org/apache/cordova/CordovaWebView.java | 2 +- .../LinearLayoutSoftKeyboardDetect.java | 25 ++++--- 4 files changed, 87 insertions(+), 39 deletions(-) diff --git a/framework/assets/www/cordova.js b/framework/assets/www/cordova.js index 73805b33..0fdae048 100644 --- a/framework/assets/www/cordova.js +++ b/framework/assets/www/cordova.js @@ -1,5 +1,5 @@ // Platform: android -// 1fc2526faa6197e1637ecb48ebe0f876f008ba0f +// 12489aed3d93ecc98adfc2091fe566067f2d39fc /* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file @@ -509,9 +509,14 @@ function each(objects, func, context) { function clobber(obj, key, value) { exports.replaceHookForTesting(obj, key); - obj[key] = value; + var needsProperty = false; + try { + obj[key] = value; + } catch (e) { + needsProperty = true; + } // Getters can only be overridden by getters. - if (obj[key] !== value) { + if (needsProperty || obj[key] !== value) { utils.defineGetter(obj, key, function() { return value; }); @@ -1027,12 +1032,7 @@ function buildPayload(payload, message) { payload.push(+message.slice(1)); } else if (payloadKind == 'A') { var data = message.slice(1); - var bytes = window.atob(data); - var arraybuffer = new Uint8Array(bytes.length); - for (var i = 0; i < bytes.length; i++) { - arraybuffer[i] = bytes.charCodeAt(i); - } - payload.push(arraybuffer.buffer); + payload.push(base64.toArrayBuffer(data)); } else if (payloadKind == 'S') { payload.push(window.atob(message.slice(1))); } else if (payloadKind == 'M') { @@ -1167,6 +1167,7 @@ var cordova = require('cordova'); var modulemapper = require('cordova/modulemapper'); var platform = require('cordova/platform'); var pluginloader = require('cordova/pluginloader'); +var utils = require('cordova/utils'); var platformInitChannelsArray = [channel.onNativeReady, channel.onPluginsReady]; @@ -1198,21 +1199,19 @@ function replaceNavigator(origNavigator) { for (var key in origNavigator) { if (typeof origNavigator[key] == 'function') { newNavigator[key] = origNavigator[key].bind(origNavigator); - } else { + } + else { (function(k) { - Object.defineProperty(newNavigator, k, { - get: function() { - return origNavigator[k]; - }, - configurable: true, - enumerable: true - }); - })(key); + utils.defineGetterSetter(newNavigator,key,function() { + return origNavigator[k]; + }); + })(key); } } } return newNavigator; } + if (window.navigator) { window.navigator = replaceNavigator(window.navigator); } @@ -1293,6 +1292,7 @@ define("cordova/init_b", function(require, exports, module) { var channel = require('cordova/channel'); var cordova = require('cordova'); var platform = require('cordova/platform'); +var utils = require('cordova/utils'); var platformInitChannelsArray = [channel.onDOMContentLoaded, channel.onNativeReady]; @@ -1327,16 +1327,13 @@ function replaceNavigator(origNavigator) { for (var key in origNavigator) { if (typeof origNavigator[key] == 'function') { newNavigator[key] = origNavigator[key].bind(origNavigator); - } else { + } + else { (function(k) { - Object.defineProperty(newNavigator, k, { - get: function() { - return origNavigator[k]; - }, - configurable: true, - enumerable: true - }); - })(key); + utils.defineGetterSetter(newNavigator,key,function() { + return origNavigator[k]; + }); + })(key); } } } @@ -1385,7 +1382,7 @@ platform.bootstrap && platform.bootstrap(); * Create all cordova objects once native side is ready. */ channel.join(function() { - + platform.initialize && platform.initialize(); // Fire event to notify that all objects are created @@ -1546,11 +1543,27 @@ module.exports = { // Let native code know we are all done on the JS side. // Native code will then un-hide the WebView. channel.onCordovaReady.subscribe(function() { + exec(onMessageFromNative, null, 'App', 'messageChannel', []); exec(null, null, "App", "show", []); }); } }; +function onMessageFromNative(msg) { + var cordova = require('cordova'); + var action = msg.action; + + switch (action) + { + case 'hidekeyboard': + case 'showkeyboard': + cordova.fireDocumentEvent(action); + break; + default: + throw new Error('Unknown event action ' + msg.action); + } +} + }); // file: src/android/plugin/android/app.js diff --git a/framework/src/org/apache/cordova/App.java b/framework/src/org/apache/cordova/App.java index 9e8c1fe0..6affb3d9 100755 --- a/framework/src/org/apache/cordova/App.java +++ b/framework/src/org/apache/cordova/App.java @@ -41,8 +41,19 @@ import java.util.HashMap; */ public class App extends CordovaPlugin { + public static final String PLUGIN_NAME = "App"; protected static final String TAG = "CordovaApp"; private BroadcastReceiver telephonyReceiver; + private CallbackContext messageChannel; + + /** + * Send an event to be fired on the Javascript side. + * + * @param action The name of the event to be fired + */ + public void fireJavascriptEvent(String action) { + sendEventMessage(action); + } /** * Sets the context of the Command. This can then be used to do things like @@ -100,6 +111,11 @@ public class App extends CordovaPlugin { else if (action.equals("exitApp")) { this.exitApp(); } + else if (action.equals("messageChannel")) { + messageChannel = callbackContext; + return true; + } + callbackContext.sendPluginResult(new PluginResult(status, result)); return true; } catch (JSONException e) { @@ -249,7 +265,7 @@ public class App extends CordovaPlugin { public void exitApp() { this.webView.postMessage("exit", null); } - + /** * Listen for telephony events: RINGING, OFFHOOK and IDLE @@ -290,6 +306,18 @@ public class App extends CordovaPlugin { webView.getContext().registerReceiver(this.telephonyReceiver, intentFilter); } + private void sendEventMessage(String action) { + JSONObject obj = new JSONObject(); + try { + obj.put("action", action); + } catch (JSONException e) { + LOG.e(TAG, "Failed to create event message", e); + } + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, obj); + pluginResult.setKeepCallback(true); + messageChannel.sendPluginResult(pluginResult); + } + /* * Unregister the receiver * diff --git a/framework/src/org/apache/cordova/CordovaWebView.java b/framework/src/org/apache/cordova/CordovaWebView.java index feec5a96..7c10e76d 100755 --- a/framework/src/org/apache/cordova/CordovaWebView.java +++ b/framework/src/org/apache/cordova/CordovaWebView.java @@ -155,7 +155,7 @@ public class CordovaWebView extends WebView { bridge = new CordovaBridge(pluginManager, new NativeToJsMessageQueue(this, cordova), this.cordova.getActivity().getPackageName()); resourceApi = new CordovaResourceApi(this.getContext(), pluginManager); - pluginManager.addService("App", "org.apache.cordova.App"); + pluginManager.addService(App.PLUGIN_NAME, "org.apache.cordova.App"); // This will be removed in 4.0.x in favour of the plugin not being bundled. pluginManager.addService(new PluginEntry("SplashScreenInternal", "org.apache.cordova.SplashScreenInternal", true)); pluginManager.init(); diff --git a/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java b/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java index bb6b6a81..6a8a2e34 100755 --- a/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java +++ b/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java @@ -18,10 +18,7 @@ */ package org.apache.cordova; -import org.apache.cordova.LOG; - import android.content.Context; -//import android.view.View.MeasureSpec; import android.widget.LinearLayout; /** @@ -36,6 +33,7 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout { private int screenWidth = 0; private int screenHeight = 0; private CordovaActivity app = null; + private App appPlugin = null; public LinearLayoutSoftKeyboardDetect(Context context, int width, int height) { super(context); @@ -50,7 +48,7 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout { * gets smaller fire a show keyboard event and when height gets bigger fire * a hide keyboard event. * - * Note: We are using app.postMessage so that this is more compatible with the API + * Note: We are using the core App plugin to send events over the bridge to Javascript * * @param widthMeasureSpec * @param heightMeasureSpec @@ -87,14 +85,12 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout { // If the height as gotten bigger then we will assume the soft keyboard has // gone away. else if (height > oldHeight) { - if (app != null) - app.appView.sendJavascript("cordova.fireDocumentEvent('hidekeyboard');"); + sendEvent("hidekeyboard"); } - // If the height as gotten smaller then we will assume the soft keyboard has + // If the height as gotten smaller then we will assume the soft keyboard has // been displayed. else if (height < oldHeight) { - if (app != null) - app.appView.sendJavascript("cordova.fireDocumentEvent('showkeyboard');"); + sendEvent("showkeyboard"); } // Update the old height for the next event @@ -102,4 +98,15 @@ public class LinearLayoutSoftKeyboardDetect extends LinearLayout { oldWidth = width; } + private void sendEvent(String event) { + if (appPlugin == null) { + appPlugin = (App)app.appView.pluginManager.getPlugin(App.PLUGIN_NAME); + } + + if (appPlugin == null) { + LOG.w(TAG, "Unable to fire event without existing plugin"); + return; + } + appPlugin.fireJavascriptEvent(event); + } }