From 16e08384c084db071193dffb8f1761b7f0c1cdb8 Mon Sep 17 00:00:00 2001 From: Jeffrey Willms Date: Fri, 21 Jun 2013 18:30:50 -0400 Subject: [PATCH] [CB-3927] Fix start-up race condition that could cause exec() responses to be dropped. Requires a change to the JS as well. (cherry picked from commit 9cb14838e8554ed2d28d317b37f76685fa76432e) --- .../org/apache/cordova/api/PluginEntry.java | 13 +++++ .../org/apache/cordova/api/PluginManager.java | 49 ++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/framework/src/org/apache/cordova/api/PluginEntry.java b/framework/src/org/apache/cordova/api/PluginEntry.java index 9b9af6bc..f66dcda0 100755 --- a/framework/src/org/apache/cordova/api/PluginEntry.java +++ b/framework/src/org/apache/cordova/api/PluginEntry.java @@ -63,6 +63,19 @@ public class PluginEntry { this.onload = onload; } + /** + * Alternate constructor + * + * @param service The name of the service + * @param plugin The plugin associated with this entry + */ + public PluginEntry(String service, CordovaPlugin plugin) { + this.service = service; + this.plugin = plugin; + this.pluginClass = plugin.getClass().getName(); + this.onload = false; + } + /** * Create plugin object. * If plugin is already created, then just return it. diff --git a/framework/src/org/apache/cordova/api/PluginManager.java b/framework/src/org/apache/cordova/api/PluginManager.java index e8e92f89..e565c4f0 100755 --- a/framework/src/org/apache/cordova/api/PluginManager.java +++ b/framework/src/org/apache/cordova/api/PluginManager.java @@ -22,7 +22,9 @@ import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.cordova.CordovaArgs; import org.apache.cordova.CordovaWebView; import org.json.JSONException; import org.xmlpull.v1.XmlPullParserException; @@ -55,6 +57,8 @@ public class PluginManager { // This would allow how all URLs are handled to be offloaded to a plugin protected HashMap urlMap = new HashMap(); + private AtomicInteger numPendingUiExecs; + /** * Constructor. * @@ -65,6 +69,7 @@ public class PluginManager { this.ctx = ctx; this.app = app; this.firstRun = true; + this.numPendingUiExecs = new AtomicInteger(0); } /** @@ -86,6 +91,9 @@ 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(); } @@ -201,8 +209,23 @@ public class PluginManager { * @param rawArgs An Array literal string containing any arguments needed in the * plugin execute method. */ - public void exec(String service, String action, String callbackId, String rawArgs) { - CordovaPlugin plugin = this.getPlugin(service); + 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 { + Log.d(TAG, "running exec normally"); + 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); PluginResult cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION); @@ -396,4 +419,26 @@ public class PluginManager { LOG.e(TAG, "https://git-wip-us.apache.org/repos/asf?p=incubator-cordova-android.git;a=blob;f=framework/res/xml/plugins.xml"); LOG.e(TAG, "====================================================================================="); } + + 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; + } + } }