1 // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
2 // Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
3 // Copyright (c) 2008-2012 Kris Maglione <maglione.k@gmail.com>
5 // This work is licensed for reuse under an MIT license. Details are
6 // given in the LICENSE.txt file included with this file.
11 defineModule("prefs", {
12 exports: ["Prefs", "localPrefs", "prefs"],
13 require: ["services", "util"]
16 lazyRequire("messages", ["_"]);
17 lazyRequire("template", ["template"]);
19 var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), {
20 ORIGINAL: "extensions.dactyl.original.",
21 RESTORE: "extensions.dactyl.restore.",
22 SAVED: "extensions.dactyl.saved.",
25 init: function init(branch, defaults) {
26 this._prefContexts = [];
28 this.branch = services.pref[defaults ? "getDefaultBranch" : "getBranch"](branch || "");
29 if ("nsIPrefBranch2" in Ci)
30 this.branch instanceof Ci.nsIPrefBranch2;
32 this.defaults = defaults ? this : this.constructor(branch, true);
34 this.branches = memoize({
36 get original() this.constructor(this.ORIGINAL + this.root),
37 get restore() this.constructor(this.RESTORE + this.root),
38 get saved() this.constructor(this.SAVED + this.root),
47 cleanup: function cleanup(reason) {
48 if (this.defaults != this)
49 this.defaults.cleanup(reason);
53 this.branch.removeObserver("", this);
54 this.observe.unregister();
59 if (~["uninstall", "disable"].indexOf(reason)) {
60 for (let name in values(this.branches.saved.getNames()))
61 this.safeReset(name, null, true);
63 this.branches.original.resetBranch();
64 this.branches.saved.resetBranch();
67 if (reason == "uninstall")
68 localPrefs.resetBranch();
73 * Returns a new Prefs instance for the sub-branch *branch* of this
76 * @param {string} branch The sub-branch to branch to.
79 Branch: function Branch(branch) Prefs(this.root + branch),
82 * Clears the entire branch.
84 * @param {string} name The name of the preference branch to delete.
86 clear: function clear(branch) {
87 this.branch.deleteBranch(branch || "");
91 * Returns the full name of this object's preference branch.
93 get root() this.branch.root,
96 * Returns the value of the preference *name*, or *defaultValue* if
97 * the preference does not exist.
99 * @param {string} name The name of the preference to return.
100 * @param {*} defaultValue The value to return if the preference has no value.
103 get: function get(name, defaultValue) {
104 if (defaultValue == null)
107 name = name.join(".");
109 let type = this.branch.getPrefType(name);
112 case Ci.nsIPrefBranch.PREF_STRING:
113 let value = this.branch.getComplexValue(name, Ci.nsISupportsString).data;
115 if (/^[a-z0-9-]+:/i.test(value))
116 value = this.branch.getComplexValue(name, Ci.nsIPrefLocalizedString).data;
120 case Ci.nsIPrefBranch.PREF_INT:
121 return this.branch.getIntPref(name);
122 case Ci.nsIPrefBranch.PREF_BOOL:
123 return this.branch.getBoolPref(name);
133 getDefault: deprecated("Prefs#defaults.get", function getDefault(name, defaultValue) this.defaults.get(name, defaultValue)),
136 * Returns an array of all preference names in this branch or the
139 * @param {string} branch The sub-branch for which to return preferences.
142 getNames: function getNames(branch) this.branch.getChildList(branch || "", { value: 0 }),
145 * Returns true if the given preference exists in this branch.
147 * @param {string} name The name of the preference to check.
149 has: function has(name) this.branch.getPrefType(name) !== 0,
152 * Returns true if the given preference is set to its default value.
154 * @param {string} name The name of the preference to check.
156 isDefault: function isDefault(name) !this.branch.prefHasUserValue(name),
158 _checkSafe: function _checkSafe(name, message, value) {
159 let curval = this.get(name, null);
161 if (this.branches.original.get(name) == null && !this.branches.saved.has(name))
162 this.branches.original.set(name, curval, true);
164 if (arguments.length > 2 && curval === value)
167 let defval = this.defaults.get(name, null);
168 let saved = this.branches.saved.get(name);
170 if (saved == null && curval != defval || saved != null && curval != saved) {
171 let msg = _("pref.safeSet.warnChanged", name);
173 msg = template.linkifyHelp(msg + " " + message);
174 util.dactyl.warn(msg);
179 * Resets the preference *name* to *value* but warns the user if the value
180 * is changed from its default.
182 * @param {string} name The preference name.
183 * @param {value} value The new preference value.
184 * @param {boolean} silent Ignore errors.
186 safeReset: function safeReset(name, message, silent) {
187 this._checkSafe(name, message);
188 this.set(name, this.branches.original.get(name), silent);
189 this.branches.original.reset(name);
190 this.branches.saved.reset(name);
194 * Sets the preference *name* to *value* but warns the user if the value is
195 * changed from its default.
197 * @param {string} name The preference name.
198 * @param {value} value The new preference value.
200 safeSet: function safeSet(name, value, message, skipSave) {
201 this._checkSafe(name, message, value);
202 this.set(name, value);
203 this.branches.saved[skipSave ? "reset" : "set"](name, value);
207 * Sets the preference *name* to *value*. If the preference already
208 * exists, it must have the same type as the given value.
210 * @param {name} name The name of the preference to change.
211 * @param {string|number|boolean} value The value to set.
212 * @param {boolean} silent Ignore errors.
214 set: function set(name, value, silent) {
215 if (this._prefContexts.length)
216 this._prefContexts[this._prefContexts.length - 1][name] = this.get(name, null);
218 function assertType(needType)
219 util.assert(type === Ci.nsIPrefBranch.PREF_INVALID || type === needType,
220 type === Ci.nsIPrefBranch.PREF_INT
221 ? /*L*/"E521: Number required after =: " + name + "=" + value
222 : /*L*/"E474: Invalid argument: " + name + "=" + value);
224 let type = this.branch.getPrefType(name);
226 switch (typeof value) {
228 assertType(Ci.nsIPrefBranch.PREF_STRING);
230 this.branch.setComplexValue(name, Ci.nsISupportsString, services.String(value));
233 assertType(Ci.nsIPrefBranch.PREF_INT);
235 this.branch.setIntPref(name, value);
238 assertType(Ci.nsIPrefBranch.PREF_BOOL);
240 this.branch.setBoolPref(name, value);
243 if (value == null && this != this.defaults)
246 throw FailedAssertion("Unknown preference type: " + typeof value + " (" + name + "=" + value + ")");
249 catch (e if silent) {}
254 * Saves the current value of a preference to be restored at next
257 * @param {string} name The preference to save.
259 save: function save(name) {
260 let val = this.get(name);
261 this.set(this.RESTORE + name, val);
262 this.safeSet(name, val);
266 * Restores saved preferences in the given branch.
268 * @param {string} branch The branch from which to restore
269 * preferences. @optional
271 restore: function restore(branch) {
272 this.getNames(this.RESTORE + (branch || "")).forEach(function (pref) {
273 this.safeSet(pref.substr(this.RESTORE.length), this.get(pref), null, true);
279 * Resets the preference *name* to its default value.
281 * @param {string} name The name of the preference to reset.
283 reset: function reset(name) {
284 if (this.branch.prefHasUserValue(name))
285 this.branch.clearUserPref(name);
289 * Resets the preference branch *branch* to its default value.
291 * @param {string} branch The preference name. @optional
293 resetBranch: function resetBranch(branch) {
294 this.getNames(branch).forEach(this.closure.reset);
298 * Toggles the value of the boolean preference *name*.
300 * @param {string} name The preference name.
302 toggle: function toggle(name) {
303 util.assert(this.branch.getPrefType(name) === Ci.nsIPrefBranch.PREF_BOOL,
304 _("error.trailingCharacters", name + "!"));
305 this.set(name, !this.get(name));
309 * Pushes a new preference context onto the context stack.
313 pushContext: function pushContext() {
314 this._prefContexts.push({});
318 * Pops the top preference context from the stack.
322 popContext: function popContext() {
323 for (let [k, v] in Iterator(this._prefContexts.pop()))
328 * Executes *func* with a new preference context. When *func* returns, the
329 * context is popped and any preferences set via {@link #setPref} or
330 * {@link #invertPref} are restored to their previous values.
332 * @param {function} func The function to call.
333 * @param {Object} func The 'this' object with which to call *func*
337 withContext: function withContext(func, self) {
340 return func.call(self);
349 "nsPref:changed": function (subject, data) {
350 let observers = this._observers[data];
352 let value = this.get(data, false);
353 this._observers[data] = observers.filter(function (callback) {
356 util.trapErrors(callback.get(), null, value);
364 * Adds a new preference observer for the given preference.
366 * @param {string} pref The preference to observe.
367 * @param {function(object)} callback The callback, called with the
368 * new value of the preference whenever it changes.
370 watch: function watch(pref, callback, strong) {
372 util.addObserver(this);
373 this.branch.addObserver("", this, false);
376 if (!this._observers[pref])
377 this._observers[pref] = [];
378 this._observers[pref].push(!strong ? util.weakReference(callback) : { get: function () callback });
382 * Lists all preferences matching *filter* or only those with changed
383 * values if *onlyNonDefault* is specified.
385 * @param {boolean} onlyNonDefault Limit the list to prefs with a
387 * @param {string} filter The list filter. A null filter lists all
391 list: function list(onlyNonDefault, filter) {
395 let prefArray = this.getNames();
398 for (let [, pref] in Iterator(prefArray)) {
399 let userValue = services.pref.prefHasUserValue(pref);
400 if (onlyNonDefault && !userValue || pref.indexOf(filter) == -1)
403 let value = this.get(pref);
406 isDefault: !userValue,
407 default: this.defaults.get(pref, null),
408 value: ["", "=", template.highlight(value, true, 100)],
410 pre: "\u00a0\u00a0" // Unicode nonbreaking space.
417 return template.options(_("pref.hostPreferences", config.host), prefs.call(this));
421 completion: function init_completion(dactyl, modules) {
422 modules.completion.preference = function preference(context) {
423 context.anchored = false;
424 context.title = [config.host + " Preference", "Value"];
425 context.keys = { text: function (item) item, description: function (item) prefs.get(item) };
426 context.completions = prefs.getNames();
429 javascript: function init_javascript(dactyl, modules) {
430 modules.JavaScript.setCompleter([this.get, this.safeSet, this.set, this.reset, this.toggle],
431 [function (context) (context.anchored=false, this.getNames().map(function (pref) [pref, ""]))]);
435 var localPrefs = Prefs("extensions.dactyl.");
436 defineModule.modules.push(localPrefs);
440 } catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
442 // vim: set fdm=marker sw=4 ts=4 et ft=javascript: