Bug 943251 - [app-manager] Add preference actor. r=jryans
authorShih-Chiang Chien <schien@mozilla.com>
Mon, 03 Mar 2014 09:41:36 +0800
changeset 173456 762cdef7ecb4dd33ca57c7fe2b8fbcf38c20426a
parent 173455 6213d1669c1a9cf09afb34295fe2c4899198707c
child 173457 187f55d9c2e589cf3a6993cc743ba06710ae655c
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersjryans
bugs943251
milestone30.0a1
Bug 943251 - [app-manager] Add preference actor. r=jryans
b2g/chrome/content/shell.js
toolkit/devtools/server/actors/preference.js
toolkit/devtools/server/main.js
toolkit/devtools/server/tests/mochitest/chrome.ini
toolkit/devtools/server/tests/mochitest/test_preference.html
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -1141,17 +1141,18 @@ let RemoteDebugger = {
             getList: function() {
               return promise.resolve([]);
             }
           },
           // Use an explicit global actor list to prevent exposing
           // unexpected actors
           globalActorFactories: restrictPrivileges ? {
             webappsActor: DebuggerServer.globalActorFactories.webappsActor,
-            deviceActor: DebuggerServer.globalActorFactories.deviceActor
+            deviceActor: DebuggerServer.globalActorFactories.deviceActor,
+            preferenceActor: DebuggerServer.globalActorFactories.preferenceActor,
           } : DebuggerServer.globalActorFactories
         };
         let root = new DebuggerServer.RootActor(connection, parameters);
         root.applicationType = "operating-system";
         return root;
       };
 
 #ifdef MOZ_WIDGET_GONK
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/actors/preference.js
@@ -0,0 +1,164 @@
+/* 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/. */
+
+const {Cc, Ci, Cu, CC} = require("chrome");
+const protocol = require("devtools/server/protocol");
+const {Arg, method, RetVal} = protocol;
+const promise = require("sdk/core/promise");
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import('resource://gre/modules/devtools/dbg-server.jsm');
+
+exports.register = function(handle) {
+  handle.addGlobalActor(PreferenceActor, "preferenceActor");
+};
+
+exports.unregister = function(handle) {
+};
+
+let PreferenceActor = protocol.ActorClass({
+  typeName: "preference",
+
+  _arePrefsAccessible: function() {
+    // We are using the authorization to debug certified apps as a proxy for the
+    // authorization to read and write preferences.  We also perform this check
+    // inside every operation.
+    return !Services.prefs.getBoolPref("devtools.debugger.forbid-certified-apps");
+  },
+  arePrefsAccessible: method(function() {
+    return this._arePrefsAccessible();
+  }, {
+    request: {},
+    response: { value: RetVal("boolean") },
+  }),
+
+  getBoolPref: method(function(name) {
+    if (!this._arePrefsAccessible()) {
+      throw new Error("Operation not permitted");
+    }
+    return Services.prefs.getBoolPref(name);
+  }, {
+    request: { value: Arg(0) },
+    response: { value: RetVal("boolean") }
+  }),
+
+  getCharPref: method(function(name) {
+    if (!this._arePrefsAccessible()) {
+      throw new Error("Operation not permitted");
+    }
+    return Services.prefs.getCharPref(name);
+  }, {
+    request: { value: Arg(0) },
+    response: { value: RetVal("string") }
+  }),
+
+  getIntPref: method(function(name) {
+    if (!this._arePrefsAccessible()) {
+      throw new Error("Operation not permitted");
+    }
+    return Services.prefs.getIntPref(name);
+  }, {
+    request: { value: Arg(0) },
+    response: { value: RetVal("number") }
+  }),
+
+  getAllPrefs: method(function() {
+    if (!this._arePrefsAccessible()) {
+      throw new Error("Operation not permitted");
+    }
+    let prefs = {};
+    Services.prefs.getChildList("").forEach(function(name, index) {
+      // append all key/value pairs into a huge json object.
+      try {
+        let value;
+        switch (Services.prefs.getPrefType(name)) {
+          case Ci.nsIPrefBranch.PREF_STRING:
+            value = Services.prefs.getCharPref(name);
+            break;
+          case Ci.nsIPrefBranch.PREF_INT:
+            value = Services.prefs.getIntPref(name);
+            break;
+          case Ci.nsIPrefBranch.PREF_BOOL:
+            value = Services.prefs.getBoolPref(name);
+            break;
+          default:
+        }
+        prefs[name] = {
+          value: value,
+          hasUserValue: Services.prefs.prefHasUserValue(name)
+        };
+      } catch (e) {
+        // pref exists but has no user or default value
+      }
+    });
+    return prefs;
+  }, {
+    request: {},
+    response: { value: RetVal("json") }
+  }),
+
+  setBoolPref: method(function(name, value) {
+    if (!this._arePrefsAccessible()) {
+      throw new Error("Operation not permitted");
+    }
+    Services.prefs.setBoolPref(name, value);
+    Services.prefs.savePrefFile(null);
+  }, {
+    request: { name: Arg(0), value: Arg(1) },
+    response: {}
+  }),
+
+  setCharPref: method(function(name, value) {
+    if (!this._arePrefsAccessible()) {
+      throw new Error("Operation not permitted");
+    }
+    Services.prefs.setCharPref(name, value);
+    Services.prefs.savePrefFile(null);
+  }, {
+    request: { name: Arg(0), value: Arg(1) },
+    response: {}
+  }),
+
+  setIntPref: method(function(name, value) {
+    if (!this._arePrefsAccessible()) {
+      throw new Error("Operation not permitted");
+    }
+    Services.prefs.setIntPref(name, value);
+    Services.prefs.savePrefFile(null);
+  }, {
+    request: { name: Arg(0), value: Arg(1) },
+    response: {}
+  }),
+
+  clearUserPref: method(function(name) {
+    if (!this._arePrefsAccessible()) {
+      throw new Error("Operation not permitted");
+    }
+    Services.prefs.clearUserPref(name);
+    Services.prefs.savePrefFile(null);
+  }, {
+    request: { name: Arg(0) },
+    response: {}
+  }),
+});
+
+let PreferenceFront = protocol.FrontClass(PreferenceActor, {
+  initialize: function(client, form) {
+    protocol.Front.prototype.initialize.call(this, client);
+    this.actorID = form.preferenceActor;
+    client.addActorPool(this);
+    this.manage(this);
+  },
+});
+
+const _knownPreferenceFronts = new WeakMap();
+
+exports.getPreferenceFront = function(client, form) {
+  if (_knownPreferenceFronts.has(client))
+    return _knownPreferenceFronts.get(client);
+
+  let front = new PreferenceFront(client, form);
+  _knownPreferenceFronts.set(client, front);
+  return front;
+};
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -355,16 +355,17 @@ var DebuggerServer = {
 
     if (!restrictPrivileges) {
       this.addTabActors();
       this.addGlobalActor(this.ChromeDebuggerActor, "chromeDebugger");
     }
 
     this.addActors("resource://gre/modules/devtools/server/actors/webapps.js");
     this.registerModule("devtools/server/actors/device");
+    this.registerModule("devtools/server/actors/preference");
   },
 
   /**
    * Install tab actors in documents loaded in content childs
    */
   addChildActors: function () {
     // In case of apps being loaded in parent process, DebuggerServer is already
     // initialized and browser actors are already loaded,
--- a/toolkit/devtools/server/tests/mochitest/chrome.ini
+++ b/toolkit/devtools/server/tests/mochitest/chrome.ini
@@ -37,8 +37,9 @@ support-files =
 [test_styles-computed.html]
 [test_styles-matched.html]
 [test_styles-modify.html]
 [test_styles-svg.html]
 [test_unsafeDereference.html]
 [test_evalInGlobal-outerized_this.html]
 [test_inspector_getImageData.html]
 [test_memory.html]
+[test_preference.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/mochitest/test_preference.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 943251 - Allow accessing about:config from app-manager
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test Preference Actor</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+function runTests() {
+  var Cu = Components.utils;
+  var Cc = Components.classes;
+  var Ci = Components.interfaces;
+
+  Cu.import("resource://gre/modules/devtools/Loader.jsm");
+  Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
+  Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
+  Cu.import("resource://gre/modules/Services.jsm");
+
+  SimpleTest.waitForExplicitFinish();
+
+  var {getPreferenceFront} = devtools.require("devtools/server/actors/preference");
+
+  DebuggerServer.init(function () { return true; });
+  DebuggerServer.addBrowserActors();
+
+  var client = new DebuggerClient(DebuggerServer.connectPipe());
+  client.connect(function onConnect() {
+    client.listTabs(function onListTabs(aResponse) {
+      var p = getPreferenceFront(client, aResponse);
+
+      var prefs = {};
+
+      var localPref = {
+        accessible: true,
+        boolPref: true,
+        intPref: 0x1234,
+        charPref: "Hello World",
+      };
+
+
+      function checkValues() {
+        is(prefs.accessible, localPref.accessible, "preference is accessible");
+        is(prefs.boolPref, localPref.boolPref, "read/write bool pref");
+        is(prefs.intPref, localPref.intPref, "read/write int pref");
+        is(prefs.charPref, localPref.charPref, "read/write string pref");
+
+        for (var key in prefs.allPrefs) {
+          var expectedValue;
+          switch(Services.prefs.getPrefType(key)) {
+            case Ci.nsIPrefBranch.PREF_STRING:
+              expectedValue = Services.prefs.getCharPref(key);
+              break;
+            case Ci.nsIPrefBranch.PREF_INT:
+              expectedValue = Services.prefs.getIntPref(key);
+              break;
+            case Ci.nsIPrefBranch.PREF_BOOL:
+              expectedValue = Services.prefs.getBoolPref(key);
+              break;
+            default:
+              ok(false, "unexpected pref type (" + key + ")");
+              break;
+          }
+
+          is(prefs.allPrefs[key].value, expectedValue, "valid preference value (" + key + ")");
+          is(prefs.allPrefs[key].hasUserValue, Services.prefs.prefHasUserValue(key), "valid hasUserValue (" + key + ")");
+        }
+
+        ["test.bool", "test.int", "test.string"].forEach(function(key) {
+          is(Services.prefs.getPrefType(key), Ci.nsIPrefBranch.PREF_INVALID, "pref (" + key + ") is clear");
+        });
+
+        client.close(() => {
+          DebuggerServer.destroy();
+          SimpleTest.finish()
+        });
+      }
+
+
+      p.arePrefsAccessible().then((value) => prefs["accessible"] = value)
+      .then(() => p.getAllPrefs()).then((json) => prefs["allPrefs"]  = json)
+      .then(() => p.setBoolPref("test.bool", localPref.boolPref))
+      .then(() => p.setIntPref("test.int", localPref.intPref))
+      .then(() => p.setCharPref("test.string", localPref.charPref))
+      .then(() => p.getBoolPref("test.bool")).then((value) => prefs["boolPref"] = value)
+      .then(() => p.getIntPref("test.int")).then((value) => prefs["intPref"] = value)
+      .then(() => p.getCharPref("test.string")).then((value) => prefs["charPref"] = value)
+      .then(() => p.clearUserPref("test.bool"))
+      .then(() => p.clearUserPref("test.int"))
+      .then(() => p.clearUserPref("test.string"))
+      .then(checkValues);
+
+    });
+  });
+
+}
+
+window.onload = function () {
+  SpecialPowers.pushPrefEnv({
+    "set": [
+      ["devtools.debugger.forbid-certified-apps", false],
+    ]
+  }, runTests);
+}
+</script>
+</pre>
+</body>
+</html>