Bug 1330439 - 4. Convert home panel observers to events; r=sebastian
authorJim Chen <nchen@mozilla.com>
Wed, 25 Jan 2017 18:53:58 -0500
changeset 466663 3c1089284c7596a93ea6fd2a87e6dd2546ea2ac5
parent 466662 904d17b3ff1e65af007e338f54c797ba0802103b
child 466664 174bd8a438db50a4274fbb94ea0d35c34fad793b
push id42948
push userbmo:gasolin@mozilla.com
push dateThu, 26 Jan 2017 07:49:21 +0000
reviewerssebastian
bugs1330439
milestone54.0a1
Bug 1330439 - 4. Convert home panel observers to events; r=sebastian Convert nsIObserverService observers used in home panels to events that go through EventDispatcher.
mobile/android/base/java/org/mozilla/gecko/home/HomeBanner.java
mobile/android/base/java/org/mozilla/gecko/home/HomeConfig.java
mobile/android/base/java/org/mozilla/gecko/home/PanelAuthLayout.java
mobile/android/base/java/org/mozilla/gecko/home/PanelInfoManager.java
mobile/android/base/java/org/mozilla/gecko/home/PanelRefreshLayout.java
mobile/android/chrome/content/browser.js
mobile/android/modules/Home.jsm
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeBanner.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeBanner.java
@@ -149,17 +149,17 @@ public class HomeBanner extends LinearLa
             mOnDismissListener.onDismiss();
         }
     }
 
     /**
      * Sends a message to gecko to request a new banner message. UI is updated in handleMessage.
      */
     public void update() {
-        GeckoAppShell.notifyObservers("HomeBanner:Get", null);
+        EventDispatcher.getInstance().dispatch("HomeBanner:Get", null);
     }
 
     @Override // BundleEventListener
     public void handleMessage(final String event, final GeckoBundle message,
                               final EventCallback callback) {
         final String id = message.getString("id");
         final String text = message.getString("text");
         final String iconURI = message.getString("iconURI");
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeConfig.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeConfig.java
@@ -13,18 +13,19 @@ import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.Context;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Pair;
 
@@ -1146,19 +1147,19 @@ public final class HomeConfig {
      * exception if you don't follow this invariant.
      */
     public static class Editor implements Iterable<PanelConfig> {
         private final HomeConfig mHomeConfig;
         private final Map<String, PanelConfig> mConfigMap;
         private final List<String> mConfigOrder;
         private final Thread mOriginalThread;
 
-        // Each Pair represents parameters to a GeckoAppShell.notifyObservers call;
-        // the first String is the observer topic and the second string is the notification data.
-        private List<Pair<String, String>> mNotificationQueue;
+        // Each Pair represents parameters to a EventDispatcher.dispatch call;
+        // the String is the event name and the GeckoBundle is the event data.
+        private List<Pair<String, GeckoBundle>> mNotificationQueue;
         private PanelConfig mDefaultPanel;
         private int mEnabledCount;
 
         private boolean mHasChanged;
         private final boolean mIsFromDefault;
 
         private Editor(HomeConfig homeConfig, State configState) {
             mHomeConfig = homeConfig;
@@ -1372,18 +1373,20 @@ public final class HomeConfig {
                 mEnabledCount++;
                 if (mEnabledCount == 1 || panelConfig.isDefault()) {
                     setDefault(panelConfig.getId());
                 }
 
                 installed = true;
 
                 // Add an event to the queue if a new panel is successfully installed.
-                mNotificationQueue.add(new Pair<String, String>(
-                        "HomePanels:Installed", panelConfig.getId()));
+                final GeckoBundle data = new GeckoBundle(1);
+                data.putString("id", panelConfig.getId());
+                mNotificationQueue.add(new Pair<String, GeckoBundle>(
+                        "HomePanels:Installed", data));
             }
 
             mHasChanged = true;
             return installed;
         }
 
         /**
          * Removes an existing panel.
@@ -1409,17 +1412,20 @@ public final class HomeConfig {
                 mEnabledCount--;
             }
 
             if (isCurrentDefaultPanel(panelConfig)) {
                 findNewDefault();
             }
 
             // Add an event to the queue if a panel is successfully uninstalled.
-            mNotificationQueue.add(new Pair<String, String>("HomePanels:Uninstalled", panelId));
+            final GeckoBundle data = new GeckoBundle(1);
+            data.putString("id", panelId);
+            mNotificationQueue.add(new Pair<String, GeckoBundle>(
+                    "HomePanels:Uninstalled", data));
 
             mHasChanged = true;
             return true;
         }
 
         /**
          * Moves panel associated with panelId to the specified position.
          *
@@ -1483,17 +1489,17 @@ public final class HomeConfig {
             // We're about to save the current state in the background thread
             // so we should use a deep copy of the PanelConfig instances to
             // avoid saving corrupted state.
             final State newConfigState =
                     new State(mHomeConfig, makeOrderedCopy(true), isDefault());
 
             // Copy the event queue to a new list, so that we only modify mNotificationQueue on
             // the original thread where it was created.
-            final List<Pair<String, String>> copiedQueue = mNotificationQueue;
+            final List<Pair<String, GeckoBundle>> copiedQueue = mNotificationQueue;
             mNotificationQueue = new ArrayList<>();
 
             ThreadUtils.getBackgroundHandler().post(new Runnable() {
                 @Override
                 public void run() {
                     mHomeConfig.save(newConfigState);
 
                     // Send pending events after the new config is saved.
@@ -1536,19 +1542,20 @@ public final class HomeConfig {
 
             return (!mHasChanged && mIsFromDefault);
         }
 
         public boolean isEmpty() {
             return mConfigMap.isEmpty();
         }
 
-        private void sendNotificationsToGecko(List<Pair<String, String>> notifications) {
-            for (Pair<String, String> p : notifications) {
-                GeckoAppShell.notifyObservers(p.first, p.second);
+        private void sendNotificationsToGecko(List<Pair<String, GeckoBundle>> notifications) {
+            final EventDispatcher dispatcher = EventDispatcher.getInstance();
+            for (final Pair<String, GeckoBundle> p : notifications) {
+                dispatcher.dispatch(p.first, p.second);
             }
         }
 
         private class EditorIterator implements Iterator<PanelConfig> {
             private final Iterator<String> mOrderIterator;
 
             public EditorIterator() {
                 mOrderIterator = mConfigOrder.iterator();
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelAuthLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/PanelAuthLayout.java
@@ -1,19 +1,20 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.home;
 
-import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.home.HomeConfig.AuthConfig;
 import org.mozilla.gecko.home.HomeConfig.PanelConfig;
+import org.mozilla.gecko.util.GeckoBundle;
 
 import android.content.Context;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -39,17 +40,19 @@ class PanelAuthLayout extends LinearLayo
 
         final Button buttonView = (Button) findViewById(R.id.button);
         buttonView.setText(authConfig.getButtonText());
 
         final String panelId = panelConfig.getId();
         buttonView.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                GeckoAppShell.notifyObservers("HomePanels:Authenticate", panelId);
+                final GeckoBundle data = new GeckoBundle(1);
+                data.putString("id", panelId);
+                EventDispatcher.getInstance().dispatch("HomePanels:Authenticate", data);
             }
         });
 
         final ImageView imageView = (ImageView) findViewById(R.id.image);
         final String imageUrl = authConfig.getImageUrl();
 
         if (TextUtils.isEmpty(imageUrl)) {
             // Use a default image if an image URL isn't specified.
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelInfoManager.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/PanelInfoManager.java
@@ -5,19 +5,17 @@
 
 package org.mozilla.gecko.home;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import org.json.JSONArray;
 import org.json.JSONException;
-import org.json.JSONObject;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.home.HomeConfig.PanelConfig;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 
@@ -80,34 +78,24 @@ public class PanelInfoManager implements
             // If there are no pending callbacks, register the event listener.
             if (sCallbacks.size() == 0) {
                 EventDispatcher.getInstance().registerUiThreadListener(this,
                     "HomePanels:Data");
             }
             sCallbacks.put(requestId, callback);
         }
 
-        final JSONObject message = new JSONObject();
-        try {
-            message.put("requestId", requestId);
+        final GeckoBundle message = new GeckoBundle(2);
+        message.putInt("requestId", requestId);
 
-            if (ids != null && ids.size() > 0) {
-                JSONArray idsArray = new JSONArray();
-                for (String id : ids) {
-                    idsArray.put(id);
-                }
-
-                message.put("ids", idsArray);
-            }
-        } catch (JSONException e) {
-            Log.e(LOGTAG, "Failed to build event to request panels by id", e);
-            return;
+        if (ids != null && ids.size() > 0) {
+            message.putStringArray("ids", ids.toArray(new String[ids.size()]));
         }
 
-        GeckoAppShell.notifyObservers("HomePanels:Get", message.toString());
+        EventDispatcher.getInstance().dispatch("HomePanels:Get", message);
     }
 
     /**
      * Asynchronously fetches list of available panels from Gecko.
      *
      * @param callback onComplete will be called on the UI thread.
      */
     public void requestAvailablePanels(RequestCallback callback) {
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelRefreshLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/PanelRefreshLayout.java
@@ -2,22 +2,20 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.home;
 
 import org.mozilla.gecko.R;
 
-import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.home.PanelLayout.DatasetBacked;
 import org.mozilla.gecko.home.PanelLayout.FilterManager;
-
-import org.json.JSONException;
-import org.json.JSONObject;
+import org.mozilla.gecko.util.GeckoBundle;
 
 import android.content.Context;
 import android.database.Cursor;
 import android.support.v4.widget.SwipeRefreshLayout;
 import android.util.Log;
 import android.view.View;
 
 /**
@@ -25,18 +23,18 @@ import android.view.View;
  * capabilities.
  *
  * This view acts as a decorator to forward the {@code DatasetBacked} methods to the child view
  * while providing the refresh gesture support on top of it.
  */
 class PanelRefreshLayout extends SwipeRefreshLayout implements DatasetBacked {
     private static final String LOGTAG = "GeckoPanelRefreshLayout";
 
-    private static final String JSON_KEY_PANEL_ID = "panelId";
-    private static final String JSON_KEY_VIEW_INDEX = "viewIndex";
+    private static final String BUNDLE_KEY_PANEL_ID = "panelId";
+    private static final String BUNDLE_KEY_VIEW_INDEX = "viewIndex";
 
     private final String panelId;
     private final int viewIndex;
     private final DatasetBacked datasetBacked;
 
     /**
      * @param context Android context.
      * @param childView ListView or GridView. Must implement {@code DatasetBacked}.
@@ -70,21 +68,15 @@ class PanelRefreshLayout extends SwipeRe
     @Override
     public void setFilterManager(FilterManager manager) {
         datasetBacked.setFilterManager(manager);
     }
 
     private class RefreshListener implements OnRefreshListener {
         @Override
         public void onRefresh() {
-            final JSONObject response = new JSONObject();
-            try {
-                response.put(JSON_KEY_PANEL_ID, panelId);
-                response.put(JSON_KEY_VIEW_INDEX, viewIndex);
-            } catch (JSONException e) {
-                Log.e(LOGTAG, "Could not create refresh message", e);
-                return;
-            }
-
-            GeckoAppShell.notifyObservers("HomePanels:RefreshView", response.toString());
+            final GeckoBundle response = new GeckoBundle(2);
+            response.putString(BUNDLE_KEY_PANEL_ID, panelId);
+            response.putInt(BUNDLE_KEY_VIEW_INDEX, viewIndex);
+            EventDispatcher.getInstance().dispatch("HomePanels:RefreshView", response);
         }
     }
 }
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -226,46 +226,37 @@ lazilyLoadedObserverScripts.forEach(func
   };
 
   messages.forEach((message) => {
     let [name, listenAfterClose] = message;
     mm.addMessageListener(name, listener, listenAfterClose);
   });
 });
 
-// Lazily-loaded JS modules that use observer notifications
-[
-  ["Home", ["HomeBanner:Get", "HomePanels:Get", "HomePanels:Authenticate", "HomePanels:RefreshView",
-            "HomePanels:Installed", "HomePanels:Uninstalled"], "resource://gre/modules/Home.jsm"],
-].forEach(module => {
-  let [name, notifications, resource] = module;
-  XPCOMUtils.defineLazyModuleGetter(this, name, resource);
-  let observer = (s, t, d) => {
-    Services.obs.removeObserver(observer, t);
-    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.
+// Lazily-loaded JS subscripts and modules that use global/window EventDispatcher.
 [
   ["ActionBarHandler", WindowEventDispatcher,
    ["TextSelection:Get", "TextSelection:Action", "TextSelection:End"],
    "chrome://browser/content/ActionBarHandler.js"],
   ["FindHelper", GlobalEventDispatcher,
    ["FindInPage:Opened", "FindInPage:Closed"],
    "chrome://browser/content/FindHelper.js"],
+  ["Home", GlobalEventDispatcher,
+   ["HomeBanner:Get", "HomePanels:Get", "HomePanels:Authenticate",
+    "HomePanels:RefreshView", "HomePanels:Installed", "HomePanels:Uninstalled"],
+   "resource://gre/modules/Home.jsm"],
 ].forEach(module => {
   let [name, dispatcher, events, script] = module;
   XPCOMUtils.defineLazyGetter(window, name, function() {
     let sandbox = {};
-    Services.scriptloader.loadSubScript(script, sandbox);
+    if (script.endsWith(".jsm")) {
+      Cu.import(script, sandbox);
+    } else {
+      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);
--- a/mobile/android/modules/Home.jsm
+++ b/mobile/android/modules/Home.jsm
@@ -119,24 +119,24 @@ var HomeBanner = (function () {
 
   let _handleDismiss = function(id) {
     let message = _messages[id];
     if (message.ondismiss)
       message.ondismiss();
   };
 
   return Object.freeze({
-    onEvent: function(event, message, callback) {
-      switch(topic) {
+    onEvent: function(event, data, callback) {
+      switch(event) {
         case "HomeBanner:Click":
-          _handleClick(message.id);
+          _handleClick(data.id);
           break;
 
         case "HomeBanner:Dismiss":
-          _handleDismiss(message.id);
+          _handleDismiss(data.id);
           break;
       }
     },
 
     /**
      * Adds a new banner message to the rotation.
      *
      * @return id Unique identifer for the message.
@@ -190,18 +190,16 @@ var HomeBanner = (function () {
 // private members without leaking it outside Home.jsm.
 var HomePanelsMessageHandlers;
 
 var HomePanels = (function () {
   // Functions used to handle messages sent from Java.
   HomePanelsMessageHandlers = {
 
     "HomePanels:Get": function handlePanelsGet(data) {
-      data = JSON.parse(data);
-
       let requestId = data.requestId;
       let ids = data.ids || null;
 
       let panels = [];
       for (let id in _registeredPanels) {
         // Null ids means we want to fetch all available panels
         if (ids == null || ids.indexOf(id) >= 0) {
           try {
@@ -214,61 +212,62 @@ var HomePanels = (function () {
 
       EventDispatcher.instance.sendRequest({
         type: "HomePanels:Data",
         panels: panels,
         requestId: requestId
       });
     },
 
-    "HomePanels:Authenticate": function handlePanelsAuthenticate(id) {
+    "HomePanels:Authenticate": function handlePanelsAuthenticate(data) {
       // Generate panel options to get auth handler.
+      let id = data.id;
       let options = _registeredPanels[id]();
       if (!options.auth) {
         throw "Home.panels: Invalid auth for panel.id = " + id;
       }
       if (!options.auth.authenticate || typeof options.auth.authenticate !== "function") {
         throw "Home.panels: Invalid auth authenticate function: panel.id = " + this.id;
       }
       options.auth.authenticate();
     },
 
     "HomePanels:RefreshView": function handlePanelsRefreshView(data) {
-      data = JSON.parse(data);
-
       let options = _registeredPanels[data.panelId]();
       let view = options.views[data.viewIndex];
 
       if (!view) {
         throw "Home.panels: Invalid view for panel.id = " + data.panelId
             + ", view.index = " + data.viewIndex;
       }
 
       if (!view.onrefresh || typeof view.onrefresh !== "function") {
         throw "Home.panels: Invalid onrefresh for panel.id = " + data.panelId
             + ", view.index = " + data.viewIndex;
       }
 
       view.onrefresh();
     },
 
-    "HomePanels:Installed": function handlePanelsInstalled(id) {
+    "HomePanels:Installed": function handlePanelsInstalled(data) {
+      let id = data.id;
       _assertPanelExists(id);
 
       let options = _registeredPanels[id]();
       if (!options.oninstall) {
         return;
       }
       if (typeof options.oninstall !== "function") {
         throw "Home.panels: Invalid oninstall function: panel.id = " + this.id;
       }
       options.oninstall();
     },
 
-    "HomePanels:Uninstalled": function handlePanelsUninstalled(id) {
+    "HomePanels:Uninstalled": function handlePanelsUninstalled(data) {
+      let id = data.id;
       _assertPanelExists(id);
 
       let options = _registeredPanels[id]();
       if (!options.onuninstall) {
         return;
       }
       if (typeof options.onuninstall !== "function") {
         throw "Home.panels: Invalid onuninstall function: panel.id = " + this.id;
@@ -468,18 +467,18 @@ var HomePanels = (function () {
 })();
 
 // Public API
 this.Home = Object.freeze({
   banner: HomeBanner,
   panels: HomePanels,
 
   // Lazy notification observer registered in browser.js
-  observe: function(subject, topic, data) {
-    if (topic in HomeBannerMessageHandlers) {
-      HomeBannerMessageHandlers[topic](data);
-    } else if (topic in HomePanelsMessageHandlers) {
-      HomePanelsMessageHandlers[topic](data);
+  onEvent: function (event, data, callback) {
+    if (event in HomeBannerMessageHandlers) {
+      HomeBannerMessageHandlers[event](data);
+    } else if (event in HomePanelsMessageHandlers) {
+      HomePanelsMessageHandlers[event](data);
     } else {
-      Cu.reportError("Home.observe: message handler not found for topic: " + topic);
+      Cu.reportError("Home.observe: message handler not found for event: " + event);
     }
   }
 });