Bug 1330439 - 4. Convert home panel observers to events; r=sebastian
Convert nsIObserverService observers used in home panels to events that
go through EventDispatcher.
--- 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);
}
}
});