Bug 1414398 - Read default preferences supplied by add-ons. r=jorgk
authorPhilipp Kewisch <philipp@bugzilla.kewis.ch>, Frank-Rainer Grahl <frgrahl@gmx.net> and aceman <acelists@atlas.sk>
Mon, 11 Dec 2017 15:34:00 +0100
changeset 29756 60d8f821b8b32ecd87a54e952a3d855e1273e529
parent 29755 8cc636483513cc4e3fdbb6c951dc0b7aa81f5008
child 29757 8ecd82f4b211d07a1a55f1e204efec38eb414ddc
push id2108
push userclokep@gmail.com
push dateMon, 22 Jan 2018 17:53:55 +0000
treeherdercomm-beta@c44930d8ad9b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorgk
bugs1414398
Bug 1414398 - Read default preferences supplied by add-ons. r=jorgk
common/src/extensionSupport.jsm
common/src/moz.build
mail/components/mailGlue.js
mail/moz.build
suite/common/src/nsSuiteGlue.js
suite/moz.build
new file mode 100644
--- /dev/null
+++ b/common/src/extensionSupport.jsm
@@ -0,0 +1,141 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = [ "extensionDefaults" ];
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+// Cu.import("resource://gre/modules/Deprecated.jsm") - needed for warning.
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+
+Cu.import("resource:///modules/iteratorUtils.jsm");
+Cu.import("resource:///modules/IOUtils.js");
+
+/**
+ * Reads preferences from addon provided locations (defaults/preferences/*.js)
+ * and stores them in the default preferences branch.
+ */
+function extensionDefaults() {
+
+  function setPref(preferDefault, name, value) {
+    let branch = Services.prefs.getBranch("");
+    if (preferDefault) {
+      let defaultBranch = Services.prefs.getDefaultBranch("");
+      if (defaultBranch.getPrefType(name) == Ci.nsIPrefBranch.PREF_INVALID) {
+        // Only use the default branch if it doesn't already have the pref set.
+        // If there is already a pref with this value on the default branch, the
+        // extension wants to override a built-in value.
+        branch = defaultBranch;
+      } else if (defaultBranch.prefHasUserValue(name)) {
+        // If a pref already has a user-set value it proper type
+        // will be returned (not PREF_INVALID). In that case keep the user's
+        // value and overwrite the default.
+        branch = defaultBranch;
+      }
+    }
+
+    if (typeof value == "boolean") {
+      branch.setBoolPref(name, value);
+    } else if (typeof value == "string") {
+      if (value.startsWith("chrome://") && value.endsWith(".properties")) {
+        let valueLocal = Cc["@mozilla.org/pref-localizedstring;1"]
+                         .createInstance(Ci.nsIPrefLocalizedString);
+        valueLocal.data = value;
+        branch.setComplexValue(name, Ci.nsIPrefLocalizedString, valueLocal);
+      } else {
+        branch.setStringPref(name, value);
+      }
+    } else if (typeof value == "number" && Number.isInteger(value)) {
+      branch.setIntPref(name, value);
+    } else if (typeof value == "number" && Number.isFloat(value)) {
+      // Floats are set as char prefs, then retrieved using getFloatPref
+      branch.setCharPref(name, value);
+    }
+  }
+
+  function walkExtensionPrefs(addon) {
+    let foundPrefStrings = [];
+    let prefPath = addon.path;
+    let prefFile = new FileUtils.File(prefPath);
+    if (!prefFile.exists())
+      return [];
+
+    if (prefFile.isDirectory()) {
+      prefFile.append("defaults");
+      prefFile.append("preferences");
+      if (!prefFile.exists() || !prefFile.isDirectory())
+        return [];
+
+      for (let file of fixIterator(prefFile.directoryEntries, Components.interfaces.nsIFile)) {
+        if (file.isFile() && file.leafName.toLowerCase().endsWith(".js")) {
+          foundPrefStrings.push(IOUtils.loadFileToString(file));
+        }
+      }
+    } else if (prefFile.isFile() && prefFile.leafName.endsWith("xpi")) {
+      let zipReader = Components.classes["@mozilla.org/libjar/zip-reader;1"]
+                                .createInstance(Components.interfaces.nsIZipReader);
+      zipReader.open(prefFile);
+      let entries = zipReader.findEntries("defaults/preferences/*.js");
+
+      while (entries.hasMore()) {
+        let entryName = entries.getNext();
+        let stream = zipReader.getInputStream(entryName);
+        let entrySize = zipReader.getEntry(entryName).realSize;
+        if (entrySize > 0) {
+          let content = NetUtil.readInputStreamToString(stream, entrySize, { charset: "utf-8", replacement: "?" });
+          foundPrefStrings.push(content);
+        }
+      }
+    }
+
+    return foundPrefStrings;
+  }
+
+  function loadAddonPrefs(addon) {
+    let sandbox = new Components.utils.Sandbox(null);
+    sandbox.pref = setPref.bind(undefined, true);
+    sandbox.user_pref = setPref.bind(undefined, false);
+
+    let prefDataStrings = walkExtensionPrefs(addon);
+    for (let prefDataString of prefDataStrings) {
+      try {
+        Components.utils.evalInSandbox(prefDataString, sandbox);
+      } catch (e) {
+        Components.utils.reportError("Error reading default prefs of addon " + addon.defaultLocale.name + ": " + e);
+      }
+    }
+
+    /*
+    TODO: decide whether we need to warn the user/make addon authors to migrate away from these pref files.
+    if (prefDataStrings.length > 0) {
+      Deprecated.warning(addon.defaultLocale.name + " uses defaults/preferences/*.js files to load prefs",
+                         "https://bugzilla.mozilla.org/show_bug.cgi?id=1414398");
+    }
+    */
+  }
+
+  let addonsFile = Services.dirsvc.get("ProfDS", Ci.nsIFile);
+  addonsFile.append("extensions.json");
+
+  if (addonsFile.exists() && addonsFile.isFile()) {
+    let fileData = IOUtils.loadFileToString(addonsFile);
+    let addonsData;
+    if (fileData) {
+      try {
+        addonsData = JSON.parse(fileData);
+      } catch (e) {
+        Components.utils.reportError("Parsing of extensions.json failed!");
+      }
+    }
+
+    for (let addon of addonsData.addons) {
+      if (addon.type == "extension" && addon.active && !addon.userDisabled && !addon.appDisabled && !addon.bootstrap)
+        loadAddonPrefs(addon);
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/common/src/moz.build
@@ -0,0 +1,8 @@
+# vim: set filetype=python:
+# 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/.
+
+EXTRA_JS_MODULES += [
+    'extensionSupport.jsm',
+]
--- a/mail/components/mailGlue.js
+++ b/mail/components/mailGlue.js
@@ -7,32 +7,34 @@ var Ci = Components.interfaces;
 var Cc = Components.classes;
 var Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/AddonManager.jsm");
 Cu.import("resource:///modules/distribution.js");
 Cu.import("resource:///modules/mailMigrator.js");
+Cu.import("resource:///modules/extensionSupport.jsm");
 
 /**
  * Glue code that should be executed before any windows are opened. Any
  * window-independent helper methods (a la nsBrowserGlue.js) should go in
  * MailUtils.js instead.
  */
 
 function MailGlue() {
   XPCOMUtils.defineLazyGetter(this, "_sanitizer",
     function() {
       let sanitizerScope = {};
       Services.scriptloader.loadSubScript("chrome://messenger/content/sanitize.js", sanitizerScope);
       return sanitizerScope.Sanitizer;
     });
 
   this._init();
+  extensionDefaults(); // extensionSupport.jsm
 }
 
 MailGlue.prototype = {
   // init (called at app startup)
   _init: function MailGlue__init() {
     Services.obs.addObserver(this, "xpcom-shutdown");
     Services.obs.addObserver(this, "final-ui-startup");
     Services.obs.addObserver(this, "mail-startup-done");
--- a/mail/moz.build
+++ b/mail/moz.build
@@ -7,16 +7,17 @@ CONFIGURE_SUBST_FILES += ['installer/Mak
 
 # app is always last as it packages up the built files on mac.
 DIRS += [
     'base',
     'locales',
     'extensions',
     'themes',
     'app',
+    '../common/src',
 ]
 
 if CONFIG['MAKENSISU']:
     DIRS += ['installer/windows']
 
 if CONFIG['MOZ_BUNDLED_FONTS']:
     DIRS += ['/%s/browser/fonts' % CONFIG['mozreltopsrcdir']]
 
--- a/suite/common/src/nsSuiteGlue.js
+++ b/suite/common/src/nsSuiteGlue.js
@@ -6,16 +6,17 @@ const XULNS = "http://www.mozilla.org/ke
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/osfile.jsm");
 Components.utils.import("resource://gre/modules/AddonManager.jsm");
 Components.utils.import("resource://gre/modules/LoginManagerParent.jsm");
 Components.utils.import("resource:///modules/Sanitizer.jsm");
 Components.utils.import("resource:///modules/mailnewsMigrator.js");
+Components.utils.import("resource:///modules/extensionSupport.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
@@ -125,16 +126,17 @@ function onSummaryChanged()
 // Constructor
 
 function SuiteGlue() {
   XPCOMUtils.defineLazyServiceGetter(this, "_idleService",
                                      "@mozilla.org/widget/idleservice;1",
                                      "nsIIdleService");
 
   this._init();
+  extensionDefaults(); // extensionSupport.jsm
 }
 
 SuiteGlue.prototype = {
   _saveSession: false,
   _sound: null,
   _isIdleObserver: false,
   _isPlacesDatabaseLocked: false,
   _migrationImportsDefaultBookmarks: false,
--- a/suite/moz.build
+++ b/suite/moz.build
@@ -16,22 +16,22 @@ DIRS += [
     'modules',
     'themes/classic',
     'themes/modern',
     'profile',
     'security',
     'shell/public',
     'shell/src',
     'smile',
+    '../common/src',
 ]
 
 if CONFIG['MAKENSISU']:
     DIRS += ['installer/windows']
 
 if CONFIG['MOZ_BUNDLED_FONTS']:
     DIRS += ['/%s/browser/fonts' % CONFIG['mozreltopsrcdir']]
 
 # app is always last as it packages up the built files on mac.
 DIRS += [
     'build',
     'app',
 ]
-