Bug 1330411 - 1. Convert ActionBarHandler observers to events; r=sebastian
authorJim Chen <nchen@mozilla.com>
Wed, 25 Jan 2017 18:53:58 -0500
changeset 466658 65b07eac83b329667ae01b036e7e5f5b067c31c4
parent 466657 2740c0e269054538fe69c6ed5b6afe82d9407370
child 466659 14bd5683877aca3615acb06b92edb9ec4bd31eab
push id42948
push userbmo:gasolin@mozilla.com
push dateThu, 26 Jan 2017 07:49:21 +0000
reviewerssebastian
bugs1330411
milestone54.0a1
Bug 1330411 - 1. Convert ActionBarHandler observers to events; r=sebastian Convert observers in ActionBarHandler.js to events that go through WindowEventDispatcher. "TextSelection:Get" now replies through the callback interface rather than a "TextSelection:Data" event. The patch also adds new lazy-loading events in browser.js to replace the lazy-loading observers that ActionBarHandler relied on.
mobile/android/base/java/org/mozilla/gecko/ActionBarTextSelection.java
mobile/android/base/java/org/mozilla/gecko/text/FloatingActionModeCallback.java
mobile/android/base/java/org/mozilla/gecko/text/FloatingToolbarTextSelection.java
mobile/android/chrome/content/ActionBarHandler.js
mobile/android/chrome/content/browser.js
mobile/android/tests/browser/robocop/testAccessibleCarets.js
--- a/mobile/android/base/java/org/mozilla/gecko/ActionBarTextSelection.java
+++ b/mobile/android/base/java/org/mozilla/gecko/ActionBarTextSelection.java
@@ -13,19 +13,16 @@ import org.mozilla.gecko.util.EventCallb
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.ActionModeCompat.Callback;
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.view.MenuItem;
 
-import org.json.JSONException;
-import org.json.JSONObject;
-
 import java.util.Arrays;
 import java.util.Timer;
 import java.util.TimerTask;
 
 import android.util.Log;
 
 class ActionBarTextSelection implements TextSelection, BundleEventListener {
     private static final String LOGTAG = "GeckoTextSelection";
@@ -208,29 +205,26 @@ class ActionBarTextSelection implements 
         public boolean onCreateActionMode(ActionModeCompat mode, GeckoMenu unused) {
             mActionMode = mode;
             return true;
         }
 
         @Override
         public boolean onActionItemClicked(ActionModeCompat mode, MenuItem item) {
             final GeckoBundle obj = mItems[item.getItemId()];
-            GeckoAppShell.notifyObservers("TextSelection:Action", obj.getString("id"));
+            final GeckoBundle data = new GeckoBundle(1);
+            data.putString("id", obj.getString("id"));
+            GeckoApp.getEventDispatcher().dispatch("TextSelection:Action", data);
             return true;
         }
 
         // Called when the user exits the action mode
         @Override
         public void onDestroyActionMode(ActionModeCompat mode) {
             mActionMode = null;
             mCallback = null;
-            final JSONObject args = new JSONObject();
-            try {
-                args.put("selectionID", selectionID);
-            } catch (JSONException e) {
-                Log.e(LOGTAG, "Error building JSON arguments for TextSelection:End", e);
-                return;
-            }
 
-            GeckoAppShell.notifyObservers("TextSelection:End", args.toString());
+            final GeckoBundle data = new GeckoBundle(1);
+            data.putInt("selectionID", selectionID);
+            GeckoApp.getEventDispatcher().dispatch("TextSelection:End", data);
         }
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/text/FloatingActionModeCallback.java
+++ b/mobile/android/base/java/org/mozilla/gecko/text/FloatingActionModeCallback.java
@@ -7,17 +7,18 @@ package org.mozilla.gecko.text;
 import android.annotation.TargetApi;
 import android.graphics.Rect;
 import android.os.Build;
 import android.view.ActionMode;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
 
-import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoApp;
+import org.mozilla.gecko.util.GeckoBundle;
 
 import java.util.List;
 
 @TargetApi(Build.VERSION_CODES.M)
 public class FloatingActionModeCallback extends ActionMode.Callback2 {
     private FloatingToolbarTextSelection textSelection;
     private List<TextAction> actions;
 
@@ -46,17 +47,19 @@ public class FloatingActionModeCallback 
 
         return true;
     }
 
     @Override
     public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
         final TextAction action = actions.get(item.getItemId());
 
-        GeckoAppShell.notifyObservers("TextSelection:Action", action.getId());
+        final GeckoBundle data = new GeckoBundle(1);
+        data.putString("id", action.getId());
+        GeckoApp.getEventDispatcher().dispatch("TextSelection:Action", data);
 
         return true;
     }
 
     @Override
     public void onDestroyActionMode(ActionMode mode) {}
 
     @Override
--- a/mobile/android/base/java/org/mozilla/gecko/text/FloatingToolbarTextSelection.java
+++ b/mobile/android/base/java/org/mozilla/gecko/text/FloatingToolbarTextSelection.java
@@ -7,19 +7,16 @@ package org.mozilla.gecko.text;
 import android.annotation.TargetApi;
 import android.app.Activity;
 import android.graphics.Rect;
 import android.os.Build;
 import android.util.Log;
 import android.util.TypedValue;
 import android.view.ActionMode;
 
-import org.json.JSONException;
-import org.json.JSONObject;
-
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
@@ -68,25 +65,19 @@ public class FloatingToolbarTextSelectio
         return false;
     }
 
     private void endTextSelection() {
         if (selectionID == 0) {
             return;
         }
 
-        final JSONObject args = new JSONObject();
-        try {
-            args.put("selectionID", selectionID);
-        } catch (JSONException e) {
-            Log.e(LOGTAG, "Error building JSON arguments for TextSelection:End", e);
-            return;
-        }
-
-        GeckoAppShell.notifyObservers("TextSelection:End", args.toString());
+        final GeckoBundle data = new GeckoBundle(1);
+        data.putInt("selectionID", selectionID);
+        GeckoApp.getEventDispatcher().dispatch("TextSelection:End", data);
     }
 
     @Override
     public void create() {
         registerForEvents();
     }
 
     @Override
--- a/mobile/android/chrome/content/ActionBarHandler.js
+++ b/mobile/android/chrome/content/ActionBarHandler.js
@@ -83,49 +83,48 @@ var ActionBarHandler = {
         this._init(e.boundingClientRect);
       }
     }
   },
 
   /**
    * ActionBarHandler notification observers.
    */
-  observe: function(subject, topic, data) {
-    switch (topic) {
+  onEvent: function(event, data, callback) {
+    switch (event) {
       // User click an ActionBar button.
       case "TextSelection:Action": {
         if (!this._selectionID) {
           break;
         }
         for (let type in this.actions) {
           let action = this.actions[type];
-          if (action.id == data) {
+          if (action.id == data.id) {
             action.action(this._targetElement, this._contentWindow);
             break;
           }
         }
         break;
       }
 
       // Provide selected text to FindInPageBar on request.
       case "TextSelection:Get": {
-        Messaging.sendRequest({
-          type: "TextSelection:Data",
-          requestId: data,
-          text: this._getSelectedText(),
-        });
-
+        try {
+          callback.onSuccess(this._getSelectedText());
+        } catch (e) {
+          callback.onError(e.toString());
+        }
         this._uninit();
         break;
       }
 
       // User closed ActionBar by clicking "checkmark" button.
       case "TextSelection:End": {
         // End the requested selection only.
-        if (this._selectionID == JSON.parse(data).selectionID) {
+        if (this._selectionID == data.selectionID) {
           this._uninit();
         }
         break;
       }
     }
   },
 
   /**
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -4,22 +4,23 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cu = Components.utils;
 var Cr = Components.results;
 
+Cu.import("resource://gre/modules/AddonManager.jsm");
 Cu.import("resource://gre/modules/AppConstants.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/AddonManager.jsm");
 Cu.import("resource://gre/modules/AsyncPrefs.jsm");
 Cu.import("resource://gre/modules/DelayedInit.jsm");
+Cu.import("resource://gre/modules/Messaging.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 if (AppConstants.ACCESSIBILITY) {
   XPCOMUtils.defineLazyModuleGetter(this, "AccessFu",
                                     "resource://gre/modules/accessibility/AccessFu.jsm");
 }
 
 XPCOMUtils.defineLazyModuleGetter(this, "Manifest",
                                   "resource://gre/modules/Manifest.jsm");
@@ -40,22 +41,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/UITelemetry.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                   "resource://gre/modules/Downloads.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "EventDispatcher",
-                                  "resource://gre/modules/Messaging.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Messaging",
-                                  "resource://gre/modules/Messaging.jsm");
-
 XPCOMUtils.defineLazyModuleGetter(this, "UserAgentOverrides",
                                   "resource://gre/modules/UserAgentOverrides.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
                                   "resource://gre/modules/LoginManagerContent.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent",
                                   "resource://gre/modules/LoginManagerParent.jsm");
@@ -125,16 +120,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "RuntimePermissions", "resource://gre/modules/RuntimePermissions.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "WebsiteMetadata", "resource://gre/modules/WebsiteMetadata.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "FontEnumerator",
   "@mozilla.org/gfx/fontenumerator;1",
   "nsIFontEnumerator");
 
+var GlobalEventDispatcher = EventDispatcher.instance;
+var WindowEventDispatcher = EventDispatcher.for(window);
+
 var lazilyLoadedBrowserScripts = [
   ["SelectHelper", "chrome://browser/content/SelectHelper.js"],
   ["InputWidgetHelper", "chrome://browser/content/InputWidgetHelper.js"],
   ["MasterPassword", "chrome://browser/content/MasterPassword.js"],
   ["PluginHelper", "chrome://browser/content/PluginHelper.js"],
   ["OfflineApps", "chrome://browser/content/OfflineApps.js"],
   ["Linkifier", "chrome://browser/content/Linkify.js"],
   ["CastingApps", "chrome://browser/content/CastingApps.js"],
@@ -161,21 +159,16 @@ var lazilyLoadedObserverScripts = [
   ["PermissionsHelper", ["Permissions:Check", "Permissions:Get", "Permissions:Clear"], "chrome://browser/content/PermissionsHelper.js"],
   ["FeedHandler", ["Feeds:Subscribe"], "chrome://browser/content/FeedHandler.js"],
   ["Feedback", ["Feedback:Show"], "chrome://browser/content/Feedback.js"],
   ["EmbedRT", ["GeckoView:ImportScript"], "chrome://browser/content/EmbedRT.js"],
   ["Reader", ["Reader:AddToCache", "Reader:RemoveFromCache"], "chrome://browser/content/Reader.js"],
   ["PrintHelper", ["Print:PDF"], "chrome://browser/content/PrintHelper.js"],
 ];
 
-lazilyLoadedObserverScripts.push(
-["ActionBarHandler", ["TextSelection:Get", "TextSelection:Action", "TextSelection:End"],
-  "chrome://browser/content/ActionBarHandler.js"]
-);
-
 if (AppConstants.MOZ_WEBRTC) {
   lazilyLoadedObserverScripts.push(
     ["WebrtcUI", ["getUserMedia:request",
                   "PeerConnection:request",
                   "recording-device-events",
                   "VideoCapture:Paused",
                   "VideoCapture:Resumed"], "chrome://browser/content/WebrtcUI.js"])
 }
@@ -251,16 +244,36 @@ lazilyLoadedObserverScripts.forEach(func
     Services.obs.addObserver(this[name], t, false);
     this[name].observe(s, t, d); // Explicitly notify new observer
   };
   notifications.forEach(notification => {
     Services.obs.addObserver(observer, notification, false);
   });
 });
 
+// Lazily-loaded JS subscripts that use global/window EventDispatcher.
+[
+  ["ActionBarHandler", WindowEventDispatcher,
+   ["TextSelection:Get", "TextSelection:Action", "TextSelection:End"],
+   "chrome://browser/content/ActionBarHandler.js"],
+].forEach(module => {
+  let [name, dispatcher, events, script] = module;
+  XPCOMUtils.defineLazyGetter(window, name, function() {
+    let sandbox = {};
+    Services.scriptloader.loadSubScript(script, sandbox);
+    return sandbox[name];
+  });
+  let listener = (event, message, callback) => {
+    dispatcher.unregisterListener(listener, event);
+    dispatcher.registerListener(window[name], event);
+    window[name].onEvent(event, message, callback); // Explicitly notify new listener
+  };
+  dispatcher.registerListener(listener, events);
+});
+
 XPCOMUtils.defineLazyServiceGetter(this, "Haptic",
   "@mozilla.org/widget/hapticfeedback;1", "nsIHapticFeedback");
 
 XPCOMUtils.defineLazyServiceGetter(this, "ParentalControls",
   "@mozilla.org/parental-controls-service;1", "nsIParentalControlsService");
 
 XPCOMUtils.defineLazyServiceGetter(this, "DOMUtils",
   "@mozilla.org/inspector/dom-utils;1", "inIDOMUtils");
@@ -335,19 +348,16 @@ const kFormHelperModeDisabled = 0;
 const kFormHelperModeEnabled = 1;
 const kFormHelperModeDynamic = 2;   // disabled on tablets
 const kMaxHistoryListSize = 50;
 
 function InitLater(fn, object, name) {
   return DelayedInit.schedule(fn, object, name, 15000 /* 15s max wait */);
 }
 
-XPCOMUtils.defineLazyGetter(this, "GlobalEventDispatcher", () => EventDispatcher.instance);
-XPCOMUtils.defineLazyGetter(this, "WindowEventDispatcher", () => EventDispatcher.for(window));
-
 var BrowserApp = {
   _tabs: [],
   _selectedTab: null,
 
   get isTablet() {
     let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
     delete this.isTablet;
     return this.isTablet = sysInfo.get("tablet");
--- a/mobile/android/tests/browser/robocop/testAccessibleCarets.js
+++ b/mobile/android/tests/browser/robocop/testAccessibleCarets.js
@@ -139,18 +139,18 @@ function UIhasActionByID(expectedActionI
     return action.id === expectedActionID;
   });
 }
 
 /**
  * Messages the ActionBarHandler to close the Selection UI.
  */
 function closeSelectionUI() {
-  Services.obs.notifyObservers(null, "TextSelection:End",
-    JSON.stringify({selectionID: gChromeWin.ActionBarHandler._selectionID}));
+  gChromeWin.WindowEventDispatcher.dispatch("TextSelection:End",
+    {selectionID: gChromeWin.ActionBarHandler._selectionID});
 }
 
 /**
  * Main test method.
  */
 add_task(function* testAccessibleCarets() {
   // Wait to start loading our test page until after the initial browser tab is
   // completely loaded. This allows each tab to complete its layer initialization,