Bug 1328684 - Convert SharedPreferencesHelper events to bundle events; r=sebastian
authorJim Chen <nchen@mozilla.com>
Tue, 10 Jan 2017 23:01:28 -0500
changeset 328900 4d1775e57bcce9e4475c95552cc41f630b31810b
parent 328899 3a5de4214c9ec365478afce5a32a73009c5b747e
child 328901 a365beac4010f8addfb04aa6a37bb40ef1fec118
push id31191
push usercbook@mozilla.com
push dateWed, 11 Jan 2017 15:24:19 +0000
treeherdermozilla-central@63ad56438630 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssebastian
bugs1328684
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1328684 - Convert SharedPreferencesHelper events to bundle events; r=sebastian Convert events used in SharedPreferencesHelper to GeckoBundle/BundleEventListener events. Gecko thread events are used because the listeners are expected to be synchronous, especially for the SharedPreferences:Get event, which also requires a synchronous callback.
mobile/android/base/java/org/mozilla/gecko/SharedPreferencesHelper.java
mobile/android/modules/SharedPreferences.jsm
--- a/mobile/android/base/java/org/mozilla/gecko/SharedPreferencesHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/SharedPreferencesHelper.java
@@ -1,34 +1,33 @@
 /* -*- 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;
 
 import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
+import org.mozilla.gecko.util.BundleEventListener;
+import org.mozilla.gecko.util.EventCallback;
+import org.mozilla.gecko.util.GeckoBundle;
 
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.preference.PreferenceManager;
 import android.util.Log;
 
 import java.util.Map;
 import java.util.HashMap;
 
 /**
  * Helper class to get, set, and observe Android Shared Preferences.
  */
 public final class SharedPreferencesHelper
-             implements GeckoEventListener
+             implements BundleEventListener
 {
     public static final String LOGTAG = "GeckoAndSharedPrefs";
 
     // Calculate this once, at initialization. isLoggable is too expensive to
     // have in-line in each log call.
     private static final boolean logVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
 
     private enum Scope {
@@ -72,30 +71,30 @@ public final class SharedPreferencesHelp
 
     public synchronized void uninit() {
         EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
             "SharedPreferences:Set",
             "SharedPreferences:Get",
             "SharedPreferences:Observe");
     }
 
-    private SharedPreferences getSharedPreferences(JSONObject message) throws JSONException {
+    private SharedPreferences getSharedPreferences(final GeckoBundle message) {
         final Scope scope = Scope.forKey(message.getString("scope"));
         switch (scope) {
             case APP:
                 return GeckoSharedPrefs.forApp(mContext);
             case PROFILE:
-                final String profileName = message.optString("profileName", null);
+                final String profileName = message.getString("profileName", null);
                 if (profileName == null) {
                     return GeckoSharedPrefs.forProfile(mContext);
                 } else {
                     return GeckoSharedPrefs.forProfileName(mContext, profileName);
                 }
             case GLOBAL:
-                final String branch = message.optString("branch", null);
+                final String branch = message.getString("branch", null);
                 if (branch == null) {
                     return PreferenceManager.getDefaultSharedPreferences(mContext);
                 } else {
                     return mContext.getSharedPreferences(branch, Context.MODE_PRIVATE);
                 }
         }
 
         return null;
@@ -122,135 +121,150 @@ public final class SharedPreferencesHelp
      * Set many SharedPreferences in Android.
      *
      * message.branch must exist, and should be a String SharedPreferences
      * branch name, or null for the default branch.
      * message.preferences should be an array of preferences.  Each preference
      * must include a String name, a String type in ["bool", "int", "string"],
      * and an Object value.
      */
-    private void handleSet(JSONObject message) throws JSONException {
+    private void handleSet(final GeckoBundle message) {
         SharedPreferences.Editor editor = getSharedPreferences(message).edit();
 
-        JSONArray jsonPrefs = message.getJSONArray("preferences");
+        final GeckoBundle[] bundlePrefs = message.getBundleArray("preferences");
 
-        for (int i = 0; i < jsonPrefs.length(); i++) {
-            JSONObject pref = jsonPrefs.getJSONObject(i);
-            String name = pref.getString("name");
-            String type = pref.getString("type");
+        for (int i = 0; i < bundlePrefs.length; i++) {
+            final GeckoBundle pref = bundlePrefs[i];
+            final String name = pref.getString("name");
+            final String type = pref.getString("type");
             if ("bool".equals(type)) {
                 editor.putBoolean(name, pref.getBoolean("value"));
             } else if ("int".equals(type)) {
                 editor.putInt(name, pref.getInt("value"));
             } else if ("string".equals(type)) {
                 editor.putString(name, pref.getString("value"));
             } else {
                 Log.w(LOGTAG, "Unknown pref value type [" + type + "] for pref [" + name + "]");
             }
-            editor.apply();
         }
+        editor.apply();
     }
 
     /**
      * Get many SharedPreferences from Android.
      *
      * message.branch must exist, and should be a String SharedPreferences
      * branch name, or null for the default branch.
      * message.preferences should be an array of preferences.  Each preference
      * must include a String name, and a String type in ["bool", "int",
      * "string"].
      */
-    private JSONArray handleGet(JSONObject message) throws JSONException {
-        SharedPreferences prefs = getSharedPreferences(message);
-        JSONArray jsonPrefs = message.getJSONArray("preferences");
-        JSONArray jsonValues = new JSONArray();
+    private GeckoBundle[] handleGet(final GeckoBundle message) {
+        final SharedPreferences prefs = getSharedPreferences(message);
+        final GeckoBundle[] bundlePrefs = message.getBundleArray("preferences");
+        final GeckoBundle[] bundleValues = new GeckoBundle[bundlePrefs.length];
 
-        for (int i = 0; i < jsonPrefs.length(); i++) {
-            JSONObject pref = jsonPrefs.getJSONObject(i);
-            String name = pref.getString("name");
-            String type = pref.getString("type");
-            JSONObject jsonValue = new JSONObject();
-            jsonValue.put("name", name);
-            jsonValue.put("type", type);
+        for (int i = 0; i < bundlePrefs.length; i++) {
+            final GeckoBundle pref = bundlePrefs[i];
+            final String name = pref.getString("name");
+            final String type = pref.getString("type");
+            final GeckoBundle bundleValue = new GeckoBundle(3);
+            bundleValue.put("name", name);
+            bundleValue.put("type", type);
             try {
                 if ("bool".equals(type)) {
-                    boolean value = prefs.getBoolean(name, false);
-                    jsonValue.put("value", value);
+                    bundleValue.put("value", prefs.getBoolean(name, false));
                 } else if ("int".equals(type)) {
-                    int value = prefs.getInt(name, 0);
-                    jsonValue.put("value", value);
+                    bundleValue.put("value", prefs.getInt(name, 0));
                 } else if ("string".equals(type)) {
-                    String value = prefs.getString(name, "");
-                    jsonValue.put("value", value);
+                    bundleValue.put("value", prefs.getString(name, ""));
                 } else {
                     Log.w(LOGTAG, "Unknown pref value type [" + type + "] for pref [" + name + "]");
                 }
-            } catch (ClassCastException e) {
+            } catch (final ClassCastException e) {
                 // Thrown if there is a preference with the given name that is
                 // not the right type.
-                Log.w(LOGTAG, "Wrong pref value type [" + type + "] for pref [" + name + "]");
+                Log.w(LOGTAG, "Wrong pref value type [" + type + "] for pref [" + name + "]", e);
             }
-            jsonValues.put(jsonValue);
+            bundleValues[i] = bundleValue;
         }
 
-        return jsonValues;
+        return bundleValues;
     }
 
     private static class ChangeListener
         implements SharedPreferences.OnSharedPreferenceChangeListener {
         public final Scope scope;
         public final String branch;
         public final String profileName;
 
         public ChangeListener(final Scope scope, final String branch, final String profileName) {
             this.scope = scope;
             this.branch = branch;
             this.profileName = profileName;
         }
 
+        private static void putSharedPreference(final GeckoBundle msg,
+                                                final SharedPreferences sharedPreferences,
+                                                final String key) {
+            // Truly, this is awful, but the API impedance is strong: there is no way to
+            // get a single untyped value from a SharedPreferences instance.
+
+            try {
+                msg.putBoolean("value", sharedPreferences.getBoolean(key, false));
+                return;
+            } catch (final ClassCastException e) {
+            }
+
+            try {
+                msg.putInt("value", sharedPreferences.getInt(key, 0));
+                return;
+            } catch (final ClassCastException e) {
+            }
+
+            try {
+                msg.putString("value", sharedPreferences.getString(key, ""));
+                return;
+            } catch (final ClassCastException e) {
+            }
+        }
+
         @Override
-        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+        public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
+                                              final String key) {
             if (logVerbose) {
                 Log.v(LOGTAG, "Got onSharedPreferenceChanged");
             }
-            try {
-                final JSONObject msg = new JSONObject();
-                msg.put("scope", this.scope.key);
-                msg.put("branch", this.branch);
-                msg.put("profileName", this.profileName);
-                msg.put("key", key);
 
-                // Truly, this is awful, but the API impedance is strong: there
-                // is no way to get a single untyped value from a
-                // SharedPreferences instance.
-                msg.put("value", sharedPreferences.getAll().get(key));
+            final GeckoBundle msg = new GeckoBundle(5);
+            msg.putString("scope", scope.key);
+            msg.putString("branch", branch);
+            msg.putString("profileName", profileName);
+            msg.putString("key", key);
+            putSharedPreference(msg, sharedPreferences, key);
 
-                GeckoAppShell.notifyObservers("SharedPreferences:Changed", msg.toString());
-            } catch (JSONException e) {
-                Log.e(LOGTAG, "Got exception creating JSON object", e);
-                return;
-            }
+            EventDispatcher.getInstance().dispatch("SharedPreferences:Changed", msg);
         }
     }
 
     /**
      * Register or unregister a SharedPreferences.OnSharedPreferenceChangeListener.
      *
      * message.branch must exist, and should be a String SharedPreferences
      * branch name, or null for the default branch.
      * message.enable should be a boolean: true to enable listening, false to
      * disable listening.
      */
-    private void handleObserve(JSONObject message) throws JSONException {
+    private void handleObserve(final GeckoBundle message) {
         final SharedPreferences prefs = getSharedPreferences(message);
         final boolean enable = message.getBoolean("enable");
 
         final Scope scope = Scope.forKey(message.getString("scope"));
-        final String profileName = message.optString("profileName", null);
-        final String branch = getBranch(scope, profileName, message.optString("branch", null));
+        final String profileName = message.getString("profileName", null);
+        final String branch = getBranch(scope, profileName, message.getString("branch", null));
 
         if (branch == null) {
             Log.e(LOGTAG, "No branch specified for SharedPreference:Observe; aborting.");
             return;
         }
 
         // mListeners is only modified in this one observer, which is called
         // from Gecko serially.
@@ -262,40 +276,34 @@ public final class SharedPreferencesHelp
         }
         if (!enable && this.mListeners.containsKey(branch)) {
             SharedPreferences.OnSharedPreferenceChangeListener listener
                 = this.mListeners.remove(branch);
             prefs.unregisterOnSharedPreferenceChangeListener(listener);
         }
     }
 
-    @Override
-    public void handleMessage(String event, JSONObject message) {
+    @Override // BundleEventListener
+    public void handleMessage(final String event, final GeckoBundle message,
+                              final EventCallback callback) {
         // Everything here is synchronous and serial, so we need not worry about
         // overwriting an in-progress response.
-        try {
-            if (event.equals("SharedPreferences:Set")) {
-                if (logVerbose) {
-                    Log.v(LOGTAG, "Got SharedPreferences:Set message.");
-                }
-                handleSet(message);
-            } else if (event.equals("SharedPreferences:Get")) {
-                if (logVerbose) {
-                    Log.v(LOGTAG, "Got SharedPreferences:Get message.");
-                }
-                JSONObject obj = new JSONObject();
-                obj.put("values", handleGet(message));
-                EventDispatcher.sendResponse(message, obj);
-            } else if (event.equals("SharedPreferences:Observe")) {
-                if (logVerbose) {
-                    Log.v(LOGTAG, "Got SharedPreferences:Observe message.");
-                }
-                handleObserve(message);
-            } else {
-                Log.e(LOGTAG, "SharedPreferencesHelper got unexpected message " + event);
-                return;
+        if (event.equals("SharedPreferences:Set")) {
+            if (logVerbose) {
+                Log.v(LOGTAG, "Got SharedPreferences:Set message.");
+            }
+            handleSet(message);
+        } else if (event.equals("SharedPreferences:Get")) {
+            if (logVerbose) {
+                Log.v(LOGTAG, "Got SharedPreferences:Get message.");
             }
-        } catch (JSONException e) {
-            Log.e(LOGTAG, "Got exception in handleMessage handling event " + event, e);
+            callback.sendSuccess(handleGet(message));
+        } else if (event.equals("SharedPreferences:Observe")) {
+            if (logVerbose) {
+                Log.v(LOGTAG, "Got SharedPreferences:Observe message.");
+            }
+            handleObserve(message);
+        } else {
+            Log.e(LOGTAG, "SharedPreferencesHelper got unexpected message " + event);
             return;
         }
     }
 }
--- a/mobile/android/modules/SharedPreferences.jsm
+++ b/mobile/android/modules/SharedPreferences.jsm
@@ -71,17 +71,17 @@ function SharedPreferencesImpl(options =
   this._scope = options.scope;
   this._profileName = options.profileName;
   this._branch = options.branch;
   this._observers = {};
 }
 
 SharedPreferencesImpl.prototype = Object.freeze({
   _set: function _set(prefs) {
-    Messaging.sendRequest({
+    EventDispatcher.instance.sendRequest({
       type: "SharedPreferences:Set",
       preferences: prefs,
       scope: this._scope,
       profileName: this._profileName,
       branch: this._branch,
     });
   },
 
@@ -104,39 +104,38 @@ SharedPreferencesImpl.prototype = Object
   },
 
   setIntPref: function setIntPref(prefName, value) {
     this._setOne(prefName, value, "int");
   },
 
   _get: function _get(prefs, callback) {
     let result = null;
-    Messaging.sendRequestForResult({
-      type: "SharedPreferences:Get",
+
+    // Use dispatch instead of sendRequestForResult because callbacks for
+    // Gecko thread events are synchronous when used with dispatch(), so we
+    // don't have to spin the event loop here to wait for a result.
+    EventDispatcher.instance.dispatch("SharedPreferences:Get", {
       preferences: prefs,
       scope: this._scope,
       profileName: this._profileName,
       branch: this._branch,
-    }).then((data) => {
-      result = data.values;
+    }, {
+      onSuccess: values => { result = values },
+      onError: msg => { throw new Error("Cannot get preference: " + msg); },
     });
 
-    let thread = Services.tm.currentThread;
-    while (result == null)
-      thread.processNextEvent(true);
-
     return result;
   },
 
   _getOne: function _getOne(prefName, type) {
-    let prefs = [];
-    prefs.push({
+    let prefs = [{
       name: prefName,
       type: type,
-    });
+    }];
     let values = this._get(prefs);
     if (values.length != 1) {
       throw new Error("Got too many values: " + values.length);
     }
     return values[0].value;
   },
 
   getBoolPref: function getBoolPref(prefName) {
@@ -200,32 +199,32 @@ SharedPreferencesImpl.prototype = Object
       this._installAndroidListener();
   },
 
   _installAndroidListener: function _installAndroidListener() {
     if (this._listening)
       return;
     this._listening = true;
 
-    Services.obs.addObserver(this, "SharedPreferences:Changed", false);
-    Messaging.sendRequest({
+    EventDispatcher.instance.registerListener(this, "SharedPreferences:Changed");
+
+    EventDispatcher.instance.sendRequest({
       type: "SharedPreferences:Observe",
       enable: true,
       scope: this._scope,
       profileName: this._profileName,
       branch: this._branch,
     });
   },
 
-  observe: function observe(subject, topic, data) {
-    if (topic != "SharedPreferences:Changed") {
+  onEvent: function _onEvent(event, msg, callback) {
+    if (event !== "SharedPreferences:Changed") {
       return;
     }
 
-    let msg = JSON.parse(data);
     if (msg.scope !== this._scope ||
         ((this._scope === Scope.PROFILE) && (msg.profileName !== this._profileName)) ||
         ((this._scope === Scope.GLOBAL)  && (msg.branch !== this._branch))) {
       return;
     }
 
     if (!this._observers.hasOwnProperty(msg.key)) {
       return;
@@ -237,18 +236,19 @@ SharedPreferencesImpl.prototype = Object
     }
   },
 
   _uninstallAndroidListener: function _uninstallAndroidListener() {
     if (!this._listening)
       return;
     this._listening = false;
 
-    Services.obs.removeObserver(this, "SharedPreferences:Changed");
-    Messaging.sendRequest({
+    EventDispatcher.instance.unregisterListener(this, "SharedPreferences:Changed");
+
+    EventDispatcher.instance.sendRequest({
       type: "SharedPreferences:Observe",
       enable: false,
       scope: this._scope,
       profileName: this._profileName,
       branch: this._branch,
     });
   },
 });