Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merge pull request #270 from erikvold/prefs

fix bug 645207 - implement high-level preferences API
  • Loading branch information...
commit 5e5abf56321539af169b7d3639da63507e47eb60 2 parents b9e53cc + b7a357a
Myk Melez mykmelez authored
5 doc/dev-guide-source/addon-development/package-spec.md
Source Rendered
@@ -48,6 +48,11 @@ called `package.json`. This file is also referred to as the
48 48 [`icon64URL` entry in the Install Manifest](https://developer.mozilla.org/en/install_manifests#icon64URL),
49 49 so the icon should be 64x64 pixels in size.
50 50
  51 +* `preferences` - *experimental*
  52 + An array of JSON objects that use the following keys `name`, `type`, `value`,
  53 + `title`, and `description`. These JSON objects will be used to automatically
  54 + create a preferences interface for the addon in the Add-ons Manager.
  55 + For more information see the documentation of [simple-prefs](packages/addon-kit/docs/simple-prefs.html).
51 56
52 57 * `license` - the name of the license as a String, with an optional
53 58 URL in parentheses.
70 packages/addon-kit/docs/simple-prefs.md
Source Rendered
... ... @@ -0,0 +1,70 @@
  1 +<!-- contributed by Erik Vold [erikvvold@gmail.com] -->
  2 +
  3 +#### *Experimental*
  4 +
  5 +The `simple-prefs` module lets you easily and persistently store preferences
  6 +across application restarts, which can be configured by users in the
  7 +Add-ons Manager.
  8 +
  9 +Introduction
  10 +------------
  11 +
  12 +With the simple preferences module you can store booleans, integers, and string
  13 +values.
  14 +
  15 +
  16 +Inline Options & Default Values
  17 +-------------------------------
  18 +
  19 +In order to have a `options.xul` (for inline options) generated, or a
  20 +`defaults/preferences/prefs.js` for default preferences, you will need to
  21 +define the preferences in your `package.json`, like so:
  22 +
  23 + {
  24 + "fullName": "Example Add-on",
  25 + ...
  26 + "preferences": [{
  27 + "name": "somePreference",
  28 + "title": "Some preference title",
  29 + "type": "string",
  30 + "value": "this is the default string value"
  31 + }]
  32 + }
  33 +
  34 +
  35 +<api name="prefs">
  36 +@property {object}
  37 + *experimental* A persistent object private to your add-on. Properties with boolean,
  38 + number, and string values will be persisted in the Mozilla preferences system.
  39 +</api>
  40 +
  41 +
  42 +<api name="on">
  43 +@function
  44 + *experimental* Registers an event `listener` that will be called when a preference is changed.
  45 +
  46 +**Example:**
  47 +
  48 + function onPrefChange(prefName) {
  49 + console.log("The " + prefName + " preference changed.");
  50 + }
  51 + require("simple-prefs").on("somePreference", onPrefChange);
  52 + require("simple-prefs").on("someOtherPreference", onPrefChange);
  53 +
  54 +
  55 +@param prefName {String}
  56 + The name of the preference to watch for changes.
  57 +@param listener {Function}
  58 + The listener function that processes the event.
  59 +</api>
  60 +
  61 +<api name="removeListener">
  62 +@function
  63 + *experimental* Unregisters an event `listener` for the specified preference.
  64 +
  65 +@param prefName {String}
  66 + The name of the preference to watch for changes.
  67 +@param listener {Function}
  68 + The listener function that processes the event.
  69 +</api>
  70 +
107 packages/addon-kit/lib/simple-prefs.js
... ... @@ -0,0 +1,107 @@
  1 +/* ***** BEGIN LICENSE BLOCK *****
  2 + * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3 + *
  4 + * The contents of this file are subject to the Mozilla Public License Version
  5 + * 1.1 (the "License"); you may not use this file except in compliance with
  6 + * the License. You may obtain a copy of the License at
  7 + * http://www.mozilla.org/MPL/
  8 + *
  9 + * Software distributed under the License is distributed on an "AS IS" basis,
  10 + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11 + * for the specific language governing rights and limitations under the
  12 + * License.
  13 + *
  14 + * The Original Code is Jetpack.
  15 + *
  16 + * The Initial Developer of the Original Code is
  17 + * Mozilla Foundation.
  18 + * Portions created by the Initial Developer are Copyright (C) 2010
  19 + * the Initial Developer. All Rights Reserved.
  20 + *
  21 + * Contributor(s):
  22 + * Hernan Rodriguez Colmeiro <colmeiro@gmail.com> (Original Author)
  23 + * Erik Vold <erikvvold@gmail.com>
  24 + *
  25 + * Alternatively, the contents of this file may be used under the terms of
  26 + * either the GNU General Public License Version 2 or later (the "GPL"), or
  27 + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28 + * in which case the provisions of the GPL or the LGPL are applicable instead
  29 + * of those above. If you wish to allow use of your version of this file only
  30 + * under the terms of either the GPL or the LGPL, and not to allow others to
  31 + * use your version of this file under the terms of the MPL, indicate your
  32 + * decision by deleting the provisions above and replace them with the notice
  33 + * and other provisions required by the GPL or the LGPL. If you do not delete
  34 + * the provisions above, a recipient may use your version of this file under
  35 + * the terms of any one of the MPL, the GPL or the LGPL.
  36 + *
  37 + * ***** END LICENSE BLOCK ***** */
  38 +
  39 +const { Cc, Ci } = require("chrome");
  40 +const observers = require("observer-service");
  41 +const { EventEmitter } = require("events");
  42 +const unload = require("unload");
  43 +const prefService = require("preferences-service");
  44 +const { jetpackID } = require("@packaging");
  45 +
  46 +const ADDON_BRANCH = "extensions." + jetpackID + ".";
  47 +const BUTTON_PRESSED = jetpackID + "-cmdPressed";
  48 +
  49 +// XXX Currently, only Firefox implements the inline preferences.
  50 +if (!require("xul-app").is("Firefox"))
  51 + throw Error("This API is only supported in Firefox");
  52 +
  53 +let branch = Cc["@mozilla.org/preferences-service;1"].
  54 + getService(Ci.nsIPrefService).
  55 + getBranch(ADDON_BRANCH).
  56 + QueryInterface(Ci.nsIPrefBranch2);
  57 +
  58 +const events = EventEmitter.compose({
  59 + constructor: function Prefs() {
  60 + // Log unhandled errors.
  61 + this.on("error", console.exception.bind(console));
  62 +
  63 + // Make sure we remove all the listeners
  64 + unload.ensure(this);
  65 +
  66 + this._prefObserver = this._prefObserver.bind(this);
  67 + this._buttonObserver = this._buttonObserver.bind(this);
  68 +
  69 + // Listen to changes in the preferences
  70 + branch.addObserver("", this._prefObserver, false);
  71 +
  72 + // Listen to clicks on buttons
  73 + observers.add(BUTTON_PRESSED, this._buttonObserver, this);
  74 + },
  75 + _prefObserver: function PrefsPrefObserver(subject, topic, prefName) {
  76 + if (topic == "nsPref:changed") {
  77 + this._emit(prefName, prefName);
  78 + }
  79 + },
  80 + _buttonObserver: function PrefsButtonObserver(subject, data) {
  81 + this._emit(data);
  82 + },
  83 + unload: function manager_unload() {
  84 + this._removeAllListeners("error");
  85 + branch.removeObserver("", this._prefObserver);
  86 + },
  87 +})();
  88 +
  89 +const simple = Proxy.create({
  90 + get: function(receiver, pref) {
  91 + return prefService.get(ADDON_BRANCH + pref);
  92 + },
  93 + set: function(receiver, pref, val) {
  94 + prefService.set(ADDON_BRANCH + pref, val);
  95 + },
  96 + delete: function(pref) {
  97 + prefService.reset(ADDON_BRANCH + pref);
  98 + return true;
  99 + },
  100 + has: function(pref) {
  101 + return prefService.has(ADDON_BRANCH + pref);
  102 + }
  103 +});
  104 +
  105 +exports.on = events.on;
  106 +exports.removeListener = events.removeListener;
  107 +exports.prefs = simple;
180 packages/addon-kit/tests/test-simple-prefs.js
... ... @@ -0,0 +1,180 @@
  1 +/* ***** BEGIN LICENSE BLOCK *****
  2 + * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3 + *
  4 + * The contents of this file are subject to the Mozilla Public License Version
  5 + * 1.1 (the "License"); you may not use this file except in compliance with
  6 + * the License. You may obtain a copy of the License at
  7 + * http://www.mozilla.org/MPL/
  8 + *
  9 + * Software distributed under the License is distributed on an "AS IS" basis,
  10 + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11 + * for the specific language governing rights and limitations under the
  12 + * License.
  13 + *
  14 + * The Original Code is Jetpack.
  15 + *
  16 + * The Initial Developer of the Original Code is
  17 + * the Mozilla Foundation.
  18 + * Portions created by the Initial Developer are Copyright (C) 2011
  19 + * the Initial Developer. All Rights Reserved.
  20 + *
  21 + * Contributor(s):
  22 + * Erik Vold <erikvvold@gmail.com> (Original Author)
  23 + *
  24 + * Alternatively, the contents of this file may be used under the terms of
  25 + * either the GNU General Public License Version 2 or later (the "GPL"), or
  26 + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27 + * in which case the provisions of the GPL or the LGPL are applicable instead
  28 + * of those above. If you wish to allow use of your version of this file only
  29 + * under the terms of either the GPL or the LGPL, and not to allow others to
  30 + * use your version of this file under the terms of the MPL, indicate your
  31 + * decision by deleting the provisions above and replace them with the notice
  32 + * and other provisions required by the GPL or the LGPL. If you do not delete
  33 + * the provisions above, a recipient may use your version of this file under
  34 + * the terms of any one of the MPL, the GPL or the LGPL.
  35 + *
  36 + * ***** END LICENSE BLOCK ***** */
  37 +
  38 +
  39 +const { Loader } = require("./helpers");
  40 +const setTimeout = require("timers").setTimeout;
  41 +const notify = require("observer-service").notify;
  42 +const { jetpackID } = require("@packaging");
  43 +
  44 +exports.testSetGetBool = function(test) {
  45 + test.waitUntilDone();
  46 +
  47 + let loader = Loader(module);
  48 + let sp = loader.require("simple-prefs").prefs;
  49 +
  50 + test.assertEqual(sp.test, undefined, "Value should not exist");
  51 + sp.test = true;
  52 + test.assert(sp.test, "Value read should be the value previously set");
  53 +
  54 + loader.unload();
  55 + test.done();
  56 +};
  57 +
  58 +exports.testSetGetInt = function(test) {
  59 + test.waitUntilDone();
  60 +
  61 + // Load the module once, set a value.
  62 + let loader = Loader(module);
  63 + let sp = loader.require("simple-prefs").prefs;
  64 +
  65 + test.assertEqual(sp["test-int"], undefined, "Value should not exist");
  66 + sp["test-int"] = 1;
  67 + test.assertEqual(sp["test-int"], 1, "Value read should be the value previously set");
  68 +
  69 + loader.unload();
  70 + test.done();
  71 +};
  72 +
  73 +exports.testSetComplex = function(test) {
  74 + test.waitUntilDone();
  75 +
  76 + let loader = Loader(module);
  77 + let sp = loader.require("simple-prefs").prefs;
  78 +
  79 + try {
  80 + sp["test-complex"] = {test: true};
  81 + test.fail("Complex values are not allowed");
  82 + }
  83 + catch (e) {
  84 + test.pass("Complex values are not allowed");
  85 + }
  86 +
  87 + loader.unload();
  88 + test.done();
  89 +};
  90 +
  91 +exports.testSetGetString = function(test) {
  92 + test.waitUntilDone();
  93 +
  94 + let loader = Loader(module);
  95 + let sp = loader.require("simple-prefs").prefs;
  96 +
  97 + test.assertEqual(sp["test-string"], undefined, "Value should not exist");
  98 + sp["test-string"] = "test";
  99 + test.assertEqual(sp["test-string"], "test", "Value read should be the value previously set");
  100 +
  101 + loader.unload();
  102 + test.done();
  103 +};
  104 +
  105 +exports.testHasAndRemove = function(test) {
  106 + test.waitUntilDone();
  107 +
  108 + let loader = Loader(module);
  109 + let sp = loader.require("simple-prefs").prefs;
  110 +
  111 + sp.test = true;
  112 + test.assert(("test" in sp), "Value exists");
  113 + delete sp.test;
  114 + test.assertEqual(sp.test, undefined, "Value should be undefined");
  115 +
  116 + loader.unload();
  117 + test.done();
  118 +
  119 +};
  120 +
  121 +exports.testPrefListener = function(test) {
  122 + test.waitUntilDone();
  123 +
  124 + let loader = Loader(module);
  125 + let sp = loader.require("simple-prefs");
  126 +
  127 + let listener = function(prefName) {
  128 + test.assertEqual(prefName, "test-listen", "The prefs listener heard the right event");
  129 + test.done();
  130 + };
  131 +
  132 + sp.on("test-listen", listener);
  133 +
  134 + sp.prefs["test-listen"] = true;
  135 + loader.unload();
  136 +};
  137 +
  138 +exports.testBtnListener = function(test) {
  139 + test.waitUntilDone();
  140 +
  141 + let loader = Loader(module);
  142 + let sp = loader.require("simple-prefs");
  143 +
  144 + sp.on("test-btn-listen", function() {
  145 + test.pass("Button press event was heard");
  146 + test.done();
  147 + });
  148 + notify((jetpackID + "-cmdPressed"), "", "test-btn-listen");
  149 +
  150 + loader.unload();
  151 +};
  152 +
  153 +exports.testPrefRemoveListener = function(test) {
  154 + test.waitUntilDone();
  155 +
  156 + let loader = Loader(module);
  157 + let sp = loader.require("simple-prefs");
  158 + let counter = 0;
  159 +
  160 + let listener = function() {
  161 + test.pass("The prefs listener was not removed yet");
  162 +
  163 + if (++counter > 1)
  164 + test.fail("The prefs listener was not removed");
  165 +
  166 + sp.removeListener("test-listen2", listener);
  167 +
  168 + sp.prefs["test-listen2"] = false;
  169 +
  170 + setTimeout(function() {
  171 + test.pass("The prefs listener was removed");
  172 + loader.unload();
  173 + test.done();
  174 + }, 250);
  175 + };
  176 +
  177 + sp.on("test-listen2", listener);
  178 +
  179 + sp.prefs["test-listen2"] = true;
  180 +};
4 python-lib/cuddlefish/__init__.py
@@ -728,6 +728,10 @@ def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None,
728 728 mydir = os.path.dirname(os.path.abspath(__file__))
729 729 app_extension_dir = os.path.join(mydir, "app-extension")
730 730
  731 +
  732 + if target_cfg.get('preferences'):
  733 + harness_options['preferences'] = target_cfg.get('preferences')
  734 +
731 735 harness_options['manifest'] = manifest.get_harness_options_manifest(uri_prefix)
732 736 harness_options['allTestModules'] = manifest.get_all_test_modules()
733 737
63 python-lib/cuddlefish/app-extension/bootstrap.js
@@ -21,6 +21,7 @@
21 21 * Contributor(s):
22 22 * Irakli Gozalishvili <gozala@mozilla.com> (Original Author)
23 23 * Matteo Ferretti <zer0@mozilla.com>
  24 + * Erik Vold <erikvvold@gmail.com>
24 25 *
25 26 * Alternatively, the contents of this file may be used under the terms of
26 27 * either the GNU General Public License Version 2 or later (the "GPL"), or
@@ -48,12 +49,50 @@ const resourceHandler = ioService.getProtocolHandler('resource')
48 49 .QueryInterface(Ci.nsIResProtocolHandler);
49 50 const XMLHttpRequest = CC('@mozilla.org/xmlextras/xmlhttprequest;1',
50 51 'nsIXMLHttpRequest');
  52 +const prefs = Cc["@mozilla.org/preferences-service;1"].
  53 + getService(Ci.nsIPrefService).
  54 + QueryInterface(Ci.nsIPrefBranch2);
  55 +const mozIJSSubScriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
  56 + getService(Ci.mozIJSSubScriptLoader);
51 57
52 58 const REASON = [ 'unknown', 'startup', 'shutdown', 'enable', 'disable',
53 59 'install', 'uninstall', 'upgrade', 'downgrade' ];
54 60
55 61 let loader = null;
56 62
  63 +const URI = __SCRIPT_URI_SPEC__.replace(/bootstrap\.js$/, "");
  64 +
  65 +// Initializes default preferences
  66 +function setDefaultPrefs() {
  67 + let branch = prefs.getDefaultBranch("");
  68 + let prefLoaderScope = {
  69 + pref: function(key, val) {
  70 + switch (typeof val) {
  71 + case "boolean":
  72 + branch.setBoolPref(key, val);
  73 + break;
  74 + case "number":
  75 + if (value % 1 == 0) // number must be a integer, otherwise ignore it
  76 + branch.setIntPref(key, val);
  77 + break;
  78 + case "string":
  79 + branch.setCharPref(key, val);
  80 + break;
  81 + }
  82 + }
  83 + };
  84 +
  85 + let uri = ioService.newURI(
  86 + "defaults/preferences/prefs.js",
  87 + null,
  88 + ioService.newURI(URI, null, null));
  89 +
  90 + // if there is a prefs.js file, then import the default prefs
  91 + if (uri.QueryInterface(Ci.nsIFileURL).file.exists()) {
  92 + // setup default prefs
  93 + mozIJSSubScriptLoader.loadSubScript(uri.spec, prefLoaderScope);
  94 + }
  95 +}
57 96
58 97 // Gets the topic that fit best as application startup event, in according with
59 98 // the current application (e.g. Firefox, Fennec, Thunderbird...)
@@ -94,17 +133,6 @@ function readURI(uri) {
94 133 return request.responseText;
95 134 }
96 135
97   -// Shim function to get `resourceURI` in pre Gecko 7.0.
98   -// https://developer.mozilla.org/en/Extensions/Bootstrapped_extensions#Bootstrap_data
99   -function resourceURI(file) {
100   - // First creating "file:" URI.
101   - let uri = ioService.newFileURI(file);
102   - if (uri.spec.substr(-4) === '.xpi') // `unpack` is `false`
103   - uri = ioService.newURI('jar:' + uri.spec + '!/', null, null);
104   -
105   - return uri;
106   -}
107   -
108 136 // Function takes `topic` to be observer via `nsIObserverService` and returns
109 137 // promise that will be delivered once notification is published.
110 138 function on(topic) {
@@ -126,10 +154,10 @@ function on(topic) {
126 154 * handler with an associated key. Each path is resolved relative to the given
127 155 * `root` path.
128 156 */
129   -function mapResources(root, resources) {
  157 +function mapResources(resources) {
130 158 Object.keys(resources).forEach(function(id) {
131 159 let path = resources[id];
132   - let uri = Array.isArray(path) ? root + '/' + path.join('/')
  160 + let uri = Array.isArray(path) ? URI + '/' + path.join('/')
133 161 : 'file://' + path;
134 162 uri = ioService.newURI(uri + '/', null, null);
135 163 resourceHandler.setSubstitution(id, uri);
@@ -142,16 +170,19 @@ function install(data, reason) {}
142 170 function uninstall(data, reason) {}
143 171
144 172 function startup(data, reason) {
145   - let uri = (data.resourceURI || resourceURI(data.installPath)).spec;
  173 + // TODO: When bug 564675 is implemented this will no longer be needed
  174 + // Always set the default prefs, because they disappear on restart
  175 + setDefaultPrefs();
  176 +
146 177 // TODO: Maybe we should perform read harness-options.json asynchronously,
147 178 // since we can't do anything until 'sessionstore-windows-restored' anyway.
148   - let options = JSON.parse(readURI(uri + './harness-options.json'));
  179 + let options = JSON.parse(readURI(URI + './harness-options.json'));
149 180 options.loadReason = REASON[reason];
150 181
151 182 // TODO: This is unnecessary overhead per add-on instance. Manifest should
152 183 // probably contain paths relative to add-on root to avoid this, but that
153 184 // requires simpler package layout that is being worked under the bug-660629.
154   - mapResources(uri, options.resources);
  185 + mapResources(options.resources);
155 186
156 187 // Import loader module using `Cu.imports` and bootstrap module loader.
157 188 loader = Cu.import(options.loader).Loader.new(options);
2  python-lib/cuddlefish/app-extension/install.rdf
@@ -25,7 +25,7 @@
25 25 <em:iconURL></em:iconURL>
26 26 <em:icon64URL></em:icon64URL>
27 27 <em:homepageURL></em:homepageURL>
28   - <em:optionsURL></em:optionsURL>
  28 + <em:optionsType></em:optionsType>
29 29 <em:updateURL></em:updateURL>
30 30 </Description>
31 31 </RDF>
22 python-lib/cuddlefish/options_defaults.py
... ... @@ -0,0 +1,22 @@
  1 +def parse_options_defaults(options, jetpack_id):
  2 + pref_list = []
  3 +
  4 + for pref in options:
  5 + if ('value' in pref):
  6 + value = pref["value"]
  7 + vtype = str(type(value))
  8 +
  9 + if ("<type 'float'>" == vtype):
  10 + continue
  11 + elif ("<type 'bool'>" == vtype):
  12 + value = str(pref["value"]).lower()
  13 + elif ("<type 'str'>" == vtype):
  14 + value = "\"" + str(pref["value"]) + "\""
  15 + elif ("<type 'unicode'>" == vtype):
  16 + value = "\"" + str(pref["value"]) + "\""
  17 + else:
  18 + value = str(pref["value"])
  19 +
  20 + pref_list.append("pref(\"extensions." + jetpack_id + "." + pref["name"] + "\", " + value + ");")
  21 +
  22 + return "\n".join(pref_list) + "\n"
60 python-lib/cuddlefish/options_xul.py
... ... @@ -0,0 +1,60 @@
  1 +from xml.dom.minidom import Document
  2 +
  3 +VALID_PREF_TYPES = ['bool', 'boolint', 'integer', 'string', 'color', 'file',
  4 + 'directory', 'control']
  5 +
  6 +class Error(Exception):
  7 + pass
  8 +
  9 +class BadPrefTypeError(Error):
  10 + pass
  11 +
  12 +class MissingPrefAttr(Error):
  13 + pass
  14 +
  15 +def validate_prefs(options):
  16 + for pref in options:
  17 + # Make sure there is a 'title'
  18 + if ("title" not in pref):
  19 + raise MissingPrefAttr("The '%s' pref requires a 'title'" % (pref["name"]))
  20 +
  21 + # Make sure that the pref type is a valid inline pref type
  22 + if (pref["type"] not in VALID_PREF_TYPES):
  23 + raise BadPrefTypeError('%s is not a valid inline pref type' % (pref["type"]))
  24 +
  25 + # Make sure the 'control' type has a 'label'
  26 + if (pref["type"] == "control"):
  27 + if ("label" not in pref):
  28 + raise MissingPrefAttr("The 'control' inline pref type requires a 'label'")
  29 +
  30 + # TODO: Check that pref["type"] matches default value type
  31 +
  32 +def parse_options(options, jetpack_id):
  33 + doc = Document()
  34 + root = doc.createElement("vbox")
  35 + root.setAttribute("xmlns", "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul")
  36 + doc.appendChild(root)
  37 +
  38 + for pref in options:
  39 + setting = doc.createElement("setting")
  40 + setting.setAttribute("pref", "extensions." + jetpack_id + "." + pref["name"])
  41 + setting.setAttribute("type", pref["type"])
  42 + setting.setAttribute("title", pref["title"])
  43 +
  44 + if ("description" in pref):
  45 + setting.appendChild(doc.createTextNode(pref["description"]))
  46 +
  47 + if (pref["type"] == "control"):
  48 + button = doc.createElement("button")
  49 + button.setAttribute("label", pref["label"])
  50 + button.setAttribute("oncommand", "Services.obs.notifyObservers(null, '" +
  51 + jetpack_id + "-cmdPressed', '" +
  52 + pref["name"] + "');");
  53 + setting.appendChild(button)
  54 + elif (pref["type"] == "boolint"):
  55 + setting.setAttribute("on", pref["on"])
  56 + setting.setAttribute("off", pref["off"])
  57 +
  58 + root.appendChild(setting)
  59 +
  60 + return doc.toprettyxml(indent=" ")
7 python-lib/cuddlefish/packaging.py
@@ -318,6 +318,7 @@ def add_dep_to_build(dep):
318 318 dep_cfg.loader)
319 319
320 320 target_cfg = pkg_cfg.packages[target]
  321 +
321 322 if include_tests and not include_dep_tests:
322 323 add_section_to_build(target_cfg, "tests", is_code=True)
323 324
@@ -335,6 +336,12 @@ def add_dep_to_build(dep):
335 336 build['icon64'] = os.path.join(target_cfg.root_dir, target_cfg.icon64)
336 337 del target_cfg['icon64']
337 338
  339 + if ('preferences' in target_cfg):
  340 + build['preferences'] = target_cfg.preferences
  341 +
  342 + if ('id' in target_cfg):
  343 + build['jetpackID'] = target_cfg.id
  344 +
338 345 return build
339 346
340 347 def _get_files_in_dir(path):
6 python-lib/cuddlefish/rdf.py
@@ -124,11 +124,17 @@ def gen_manifest(template_root_dir, target_cfg, bundle_id,
124 124 target_cfg.get("author", ""))
125 125 manifest.set("em:bootstrap", str(bootstrap).lower())
126 126 manifest.set("em:unpack", "true")
  127 +
127 128 if update_url:
128 129 manifest.set("em:updateURL", update_url)
129 130 else:
130 131 manifest.remove("em:updateURL")
131 132
  133 + if target_cfg.get("preferences"):
  134 + manifest.set("em:optionsType", "2")
  135 + else:
  136 + manifest.remove("em:optionsType")
  137 +
132 138 if enable_mobile:
133 139 dom = manifest.dom
134 140 target_app = dom.createElement("em:targetApplication")
0  python-lib/cuddlefish/tests/preferences-files/packages/no-prefs/lib/main.js
No changes.
3  python-lib/cuddlefish/tests/preferences-files/packages/no-prefs/package.json
... ... @@ -0,0 +1,3 @@
  1 +{
  2 + "loader": "lib/main.js"
  3 +}
0  python-lib/cuddlefish/tests/preferences-files/packages/simple-prefs/lib/main.js
No changes.
11 python-lib/cuddlefish/tests/preferences-files/packages/simple-prefs/package.json
... ... @@ -0,0 +1,11 @@
  1 +{
  2 + "id": "jid1-fZHqN9JfrDBa8A",
  3 + "fullName": "Simple Prefs Test",
  4 + "author": "Erik Vold",
  5 + "preferences": [{
  6 + "name": "test",
  7 + "type": "bool",
  8 + "title": "test"
  9 + }],
  10 + "loader": "lib/main.js"
  11 +}
31 python-lib/cuddlefish/tests/test_xpi.py
@@ -14,6 +14,35 @@
14 14
15 15 fake_manifest = '<RDF><!-- Extension metadata is here. --></RDF>'
16 16
  17 +class PrefsTests(unittest.TestCase):
  18 + def makexpi(self, pkg_name):
  19 + self.xpiname = "%s.xpi" % pkg_name
  20 + create_xpi(self.xpiname, pkg_name, 'preferences-files')
  21 + self.xpi = zipfile.ZipFile(self.xpiname, 'r')
  22 + options = self.xpi.read('harness-options.json')
  23 + self.xpi_harness_options = json.loads(options)
  24 +
  25 + def setUp(self):
  26 + self.xpiname = None
  27 + self.xpi = None
  28 +
  29 + def tearDown(self):
  30 + if self.xpi:
  31 + self.xpi.close()
  32 + if self.xpiname and os.path.exists(self.xpiname):
  33 + os.remove(self.xpiname)
  34 +
  35 + def testPackageWithSimplePrefs(self):
  36 + self.makexpi('simple-prefs')
  37 + assert 'options.xul' in self.xpi.namelist()
  38 + assert 'defaults/preferences/prefs.js' in self.xpi.namelist()
  39 +
  40 + def testPackageWithNoPrefs(self):
  41 + self.makexpi('no-prefs')
  42 + assert 'options.xul' not in self.xpi.namelist()
  43 + assert 'defaults/preferences/prefs.js' not in self.xpi.namelist()
  44 +
  45 +
17 46 class Bug588119Tests(unittest.TestCase):
18 47 def makexpi(self, pkg_name):
19 48 self.xpiname = "%s.xpi" % pkg_name
@@ -25,7 +54,7 @@ def makexpi(self, pkg_name):
25 54 def setUp(self):
26 55 self.xpiname = None
27 56 self.xpi = None
28   -
  57 +
29 58 def tearDown(self):
30 59 if self.xpi:
31 60 self.xpi.close()
15 python-lib/cuddlefish/xpi.py
@@ -33,6 +33,21 @@ def build_xpi(template_root_dir, manifest, xpi_path,
33 33 zf.write(str(harness_options['icon64']), 'icon64.png')
34 34 del harness_options['icon64']
35 35
  36 + if 'preferences' in harness_options:
  37 + from options_xul import parse_options, validate_prefs
  38 +
  39 + validate_prefs(harness_options["preferences"])
  40 +
  41 + open('.options.xul', 'w').write(parse_options(harness_options["preferences"], harness_options["jetpackID"]))
  42 + zf.write('.options.xul', 'options.xul')
  43 + os.remove('.options.xul')
  44 +
  45 + from options_defaults import parse_options_defaults
  46 + open('.prefs.js', 'w').write(parse_options_defaults(harness_options["preferences"], harness_options["jetpackID"]))
  47 + zf.write('.prefs.js', 'defaults/preferences/prefs.js')
  48 + os.remove('.prefs.js')
  49 +
  50 +
36 51 IGNORED_FILES = [".hgignore", ".DS_Store", "install.rdf",
37 52 "application.ini", xpi_path]
38 53

0 comments on commit 5e5abf5

Please sign in to comment.
Something went wrong with that request. Please try again.