diff options
-rw-r--r-- | common/content/buffer.js | 37 | ||||
-rw-r--r-- | common/content/commandline.js | 1 | ||||
-rw-r--r-- | common/content/dactyl.js | 47 | ||||
-rw-r--r-- | common/content/events.js | 3 | ||||
-rw-r--r-- | common/content/finder.js | 4 | ||||
-rw-r--r-- | common/content/javascript.js | 4 | ||||
-rw-r--r-- | common/content/marks.js | 2 | ||||
-rw-r--r-- | common/content/modes.js | 42 | ||||
-rw-r--r-- | common/content/modules.js | 4 | ||||
-rw-r--r-- | common/content/options.js | 359 | ||||
-rw-r--r-- | common/content/tabs.js | 20 | ||||
-rw-r--r-- | common/locale/en-US/options.xml | 10 | ||||
-rw-r--r-- | common/modules/base.jsm | 30 | ||||
-rw-r--r-- | common/modules/bookmarkcache.jsm | 2 | ||||
-rw-r--r-- | common/modules/sanitizer.jsm | 287 | ||||
-rw-r--r-- | common/modules/storage.jsm | 39 | ||||
-rw-r--r-- | common/modules/util.jsm | 406 | ||||
-rw-r--r-- | pentadactyl/NEWS | 7 |
18 files changed, 782 insertions, 522 deletions
diff --git a/common/content/buffer.js b/common/content/buffer.js index 630de47a..8c64b00a 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -289,19 +289,16 @@ const Buffer = Module("buffer", { }, setOverLink: function setOverLink(link, b) { setOverLink.superapply(this, arguments); - let ssli = options["showstatuslinks"]; - if (link && ssli) { - if (ssli == 1) - statusline.updateUrl("Link: " + link); - else if (ssli == 2) + switch (options["showstatuslinks"]) { + case 1: + statusline.updateUrl(link && "Link: " + link); + break; + case 2: + if (link) dactyl.echo("Link: " + link, commandline.DISALLOW_MULTILINE); - } - - if (link == "") { - if (ssli == 1) - statusline.updateUrl(); - else if (ssli == 2) + else commandline.clear(); + break; } }, }), @@ -644,8 +641,8 @@ const Buffer = Module("buffer", { buffer.focusElement(elem); - options.withContext(function () { - options.setPref("browser.tabs.loadInBackground", true); + prefs.withContext(function () { + prefs.set("browser.tabs.loadInBackground", true); ["mousedown", "mouseup", "click"].forEach(function (event) { elem.dispatchEvent(events.create(doc, event, { screenX: offsetX, screenY: offsetY, @@ -690,7 +687,7 @@ const Buffer = Module("buffer", { try { window.urlSecurityCheck(url, doc.nodePrincipal); // we always want to save that link relative to the current working directory - options.setPref("browser.download.lastDir", io.cwd); + prefs.set("browser.download.lastDir", io.cwd); window.saveURL(url, text, null, true, skipPrompt, makeURI(url, doc.characterSet)); } catch (e) { @@ -1262,17 +1259,17 @@ const Buffer = Module("buffer", { dactyl.assert(!arg || arg[0] == ">" && !dactyl.has("WINNT"), "E488: Trailing characters"); - options.withContext(function () { + prefs.withContext(function () { if (arg) { - options.setPref("print.print_to_file", "true"); - options.setPref("print.print_to_filename", io.File(arg.substr(1)).path); + prefs.set("print.print_to_file", "true"); + prefs.set("print.print_to_filename", io.File(arg.substr(1)).path); dactyl.echomsg("Printing to file: " + arg.substr(1)); } else dactyl.echomsg("Sending to printer..."); - options.setPref("print.always_print_silent", args.bang); - options.setPref("print.show_print_progress", !args.bang); + prefs.set("print.always_print_silent", args.bang); + prefs.set("print.show_print_progress", !args.bang); config.browser.contentWindow.print(); }); @@ -1371,7 +1368,7 @@ const Buffer = Module("buffer", { // if browser.download.useDownloadDir = false then the "Save As" // dialog is used with this as the default directory // TODO: if we're going to do this shouldn't it be done in setCWD or the value restored? - options.setPref("browser.download.lastDir", io.cwd); + prefs.set("browser.download.lastDir", io.cwd); try { var contentDisposition = window.content diff --git a/common/content/commandline.js b/common/content/commandline.js index 413a55a5..9f71909b 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -1690,6 +1690,7 @@ const CommandLine = Module("commandline", { sanitizer: function () { sanitizer.addItem("commandline", { description: "Command-line and search history", + persistent: true, action: function (timespan, host) { if (!host) storage["history-search"].mutate("filter", function (item) !timespan.contains(item.timestamp)); diff --git a/common/content/dactyl.js b/common/content/dactyl.js index 58fa4b83..71979f4e 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -19,12 +19,6 @@ const EVAL_ERROR = "__dactyl_eval_error"; const EVAL_RESULT = "__dactyl_eval_result"; const EVAL_STRING = "__dactyl_eval_string"; -const FailedAssertion = Class("FailedAssertion", Error, { - init: function (message) { - this.message = message; - } -}); - function deprecated(reason, fn) { let name, func = callable(fn) ? fn : function () this[fn].apply(this, arguments); function deprecatedMethod() { @@ -633,13 +627,13 @@ const Dactyl = Module("dactyl", { <spec>{spec((obj.specs || obj.names)[0])}</spec>{ !obj.type ? "" : <> <type>{obj.type}</type> - <default>{opt.stringify(obj.defaultValue)}</default></>} + <default>{obj.stringDefaultValue}</default></>} <description>{ obj.description ? br+<p>{obj.description.replace(/\.?$/, ".")}</p> : "" }{ extraHelp ? br+extraHelp : "" }{ !(extraHelp || obj.description) ? br+<p>Sorry, no help available.</p> : "" } </description> - </item></>.toXMLString(), true); + </item></>.toXMLString().replace(/^ {12}/gm, ""), true); }, /** @@ -734,7 +728,7 @@ const Dactyl = Module("dactyl", { // options does not exist at the very beginning if (modules.options) - verbose = options.getPref("extensions.dactyl.loglevel", 0); + verbose = prefs.get("extensions.dactyl.loglevel", 0); if (level > verbose) return; @@ -821,8 +815,8 @@ const Dactyl = Module("dactyl", { if (!dactyl.has("tabs")) return open(urls, dactyl.NEW_WINDOW); - options.withContext(function () { - options.setPref("browser.tabs.loadInBackground", true); + prefs.withContext(function () { + prefs.set("browser.tabs.loadInBackground", true); browser.loadOneTab(url, null, null, postdata, background); }); break; @@ -870,9 +864,9 @@ const Dactyl = Module("dactyl", { quit: function (saveSession, force) { // TODO: Use safeSetPref? if (saveSession) - options.setPref("browser.startup.page", 3); // start with saved session + prefs.set("browser.startup.page", 3); // start with saved session else - options.setPref("browser.startup.page", 1); // start with default homepage session + prefs.set("browser.startup.page", 1); // start with default homepage session if (force) services.get("appStartup").quit(Ci.nsIAppStartup.eForceQuit); @@ -939,18 +933,7 @@ const Dactyl = Module("dactyl", { }); }, - /* - * Tests a condition and throws a FailedAssertion error on - * failure. - * - * @param {boolean} condition The condition to test. - * @param {string} message The message to present to the - * user on failure. - */ - assert: function (condition, message) { - if (!condition) - throw new FailedAssertion(message); - }, + get assert() util.assert, /** * Traps errors in the called function, possibly reporting them. @@ -1171,8 +1154,8 @@ const Dactyl = Module("dactyl", { class_.length ? class_.join(", ") + " { visibility: collapse !important; }" : "", true); - options.safeSetPref("layout.scrollbar.side", opts.indexOf("l") >= 0 ? 3 : 2, - "See 'guioptions' scrollbar flags."); + prefs.safeSet("layout.scrollbar.side", opts.indexOf("l") >= 0 ? 3 : 2, + "See 'guioptions' scrollbar flags."); }, validator: function (opts) Option.validIf(!(opts.indexOf("l") >= 0 && opts.indexOf("r") >= 0), UTF8("Only one of ‘l’ or ‘r’ allowed")) @@ -1264,8 +1247,8 @@ const Dactyl = Module("dactyl", { "boolean", false, { setter: function (value) { - options.safeSetPref("accessibility.typeaheadfind.enablesound", !value, - "See 'visualbell' option"); + prefs.safeSet("accessibility.typeaheadfind.enablesound", !value, + "See 'visualbell' option"); return value; } }); @@ -1950,7 +1933,7 @@ const Dactyl = Module("dactyl", { AddonManager.getAddonByID(services.get("dactyl:").addonID, function (addon) { // @DATE@ token replaced by the Makefile // TODO: Find it automatically - options.setPref("extensions.dactyl.version", addon.version); + prefs.set("extensions.dactyl.version", addon.version); dactyl.version = addon.version + " (created: @DATE@)"; }); @@ -1970,10 +1953,10 @@ const Dactyl = Module("dactyl", { // first time intro message const firstTime = "extensions." + config.name + ".firsttime"; - if (options.getPref(firstTime, true)) { + if (prefs.get(firstTime, true)) { util.timeout(function () { dactyl.help(); - options.setPref(firstTime, false); + prefs.set(firstTime, false); }, 1000); } diff --git a/common/content/events.js b/common/content/events.js index 188e2dc7..8069da69 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -756,7 +756,7 @@ const Events = Module("events", { dactyl.echomsg("Recorded macro '" + this._currentMacro + "'"); return killEvent(); } - else if (!mappings.hasMap(mode, this._input.buffer + key)) + else if (!mappings.hasMap(modes.main, this._input.buffer + key)) this._macros.set(this._currentMacro, { keys: this._macros.get(this._currentMacro, {}).keys + key, timeRecorded: Date.now() @@ -1087,6 +1087,7 @@ const Events = Module("events", { sanitizer: function () { sanitizer.addItem("macros", { description: "Saved macros", + persistent: true, action: function (timespan, host) { if (!host) for (let [k, m] in events._macros) diff --git a/common/content/finder.js b/common/content/finder.js index 9981f1e1..0153c52d 100644 --- a/common/content/finder.js +++ b/common/content/finder.js @@ -200,9 +200,9 @@ const RangeFinder = Module("rangefinder", { }, options: function () { - // options.safeSetPref("accessibility.typeaheadfind.autostart", false); + // prefs.safeSet("accessibility.typeaheadfind.autostart", false); // The above should be sufficient, but: https://bugzilla.mozilla.org/show_bug.cgi?id=348187 - options.safeSetPref("accessibility.typeaheadfind", false); + prefs.safeSet("accessibility.typeaheadfind", false); options.add(["hlsearch", "hls"], "Highlight previous search pattern matches", diff --git a/common/content/javascript.js b/common/content/javascript.js index 5bf6c230..d8bc0a0d 100644 --- a/common/content/javascript.js +++ b/common/content/javascript.js @@ -42,13 +42,13 @@ const JavaScript = Module("javascript", { let seen = isinstance(obj, ["Sandbox"]) ? set(JavaScript.magicalNames) : {}; let globals = values(toplevel && window === obj ? JavaScript.globalNames : []); - for (let key in iterAll(globals, properties(obj, !toplevel))) + for (let key in iterAll(globals, properties(obj, !toplevel, true))) if (!set.add(seen, key)) yield key; // Properties aren't visible in an XPCNativeWrapper until // they're accessed. - for (let key in properties(this.getKey(obj, "wrappedJSObject"), !toplevel)) + for (let key in properties(this.getKey(obj, "wrappedJSObject"), !toplevel, true)) try { if (key in obj && !set.has(seen, key)) yield key; diff --git a/common/content/marks.js b/common/content/marks.js index ac99c9c5..be179543 100644 --- a/common/content/marks.js +++ b/common/content/marks.js @@ -288,6 +288,8 @@ const Marks = Module("marks", { sanitizer: function () { sanitizer.addItem("marks", { description: "Local and URL marks", + persistent: true, + contains: ["history"], action: function (timespan, host) { function matchhost(url) !host || util.isDomainURL(url, host); function match(marks) (k for ([k, v] in Iterator(marks)) if (timespan.contains(v.timestamp) && matchhost(v.location))); diff --git a/common/content/modes.js b/common/content/modes.js index 643d5b77..e4319258 100644 --- a/common/content/modes.js +++ b/common/content/modes.js @@ -54,8 +54,8 @@ const Modes = Module("modes", { this.addMode("COMMAND_LINE", { char: "c", input: true, display: function () modes.extended & modes.OUTPUT_MULTILINE ? null : this.disp }); this.addMode("CARET", {}, { - get pref() options.getPref("accessibility.browsewithcaret"), - set pref(val) options.setPref("accessibility.browsewithcaret", val), + get pref() prefs.get("accessibility.browsewithcaret"), + set pref(val) prefs.set("accessibility.browsewithcaret", val), enter: function (stack) { if (stack.pop && !this.pref) modes.pop(); @@ -88,8 +88,8 @@ const Modes = Module("modes", { this.push(this.NORMAL, 0, { enter: function (stack, prev) { - if (options.getPref("accessibility.browsewithcaret")) - options.setPref("accessibility.browsewithcaret", false); + if (prefs.get("accessibility.browsewithcaret")) + prefs.set("accessibility.browsewithcaret", false); statusline.updateUrl(); if (prev.mainMode.input || prev.mainMode.ownsFocus) @@ -180,7 +180,7 @@ const Modes = Module("modes", { [m for (m in values(this._modeMap)) if (Object.keys(obj).every(function (k) obj[k] == (m[k] || false)))], // show the current mode string in the command line - show: function () { + show: function show() { let msg = null; if (options["showmode"]) msg = this._getModeMessage(); @@ -189,7 +189,7 @@ const Modes = Module("modes", { }, // add/remove always work on the this._extended mode only - add: function (mode) { + add: function add(mode) { this._extended |= mode; this.show(); }, @@ -197,7 +197,7 @@ const Modes = Module("modes", { delayed: [], delay: function (callback, self) { this.delayed.push([callback, self]) }, - save: function (id, obj, prop) { + save: function save(id, obj, prop) { if (!(id in this.boundProperties)) for (let elem in array.iterValues(this._modeStack)) elem.saved[id] = { obj: obj, prop: prop, value: obj[prop] }; @@ -206,7 +206,7 @@ const Modes = Module("modes", { // helper function to set both modes in one go // if silent == true, you also need to take care of the mode handling changes yourself - set: function (mainMode, extendedMode, params, stack) { + set: function set(mainMode, extendedMode, params, stack) { params = params || this.getMode(mainMode || this.main).params; if (!stack && mainMode != null && this._modeStack.length > 1) @@ -258,11 +258,18 @@ const Modes = Module("modes", { this.show(); }, - push: function (mainMode, extendedMode, params) { + push: function push(mainMode, extendedMode, params) { this.set(mainMode, extendedMode, params, { push: this.topOfStack }); }, - pop: function (mode) { + onCaretChange: function onPrefChange(value) { + if (!value && modes.main === modes.CARET) + modes.pop(); + if (value && modes.main === modes.NORMAL) + modes.push(modes.CARET); + }, + + pop: function pop(mode) { while (this._modeStack.length > 1 && this.main != mode) { let a = this._modeStack.pop(); this.set(this.topOfStack.main, this.topOfStack.extended, this.topOfStack.params, @@ -273,7 +280,7 @@ const Modes = Module("modes", { } }, - replace: function (mode, oldMode) { + replace: function replace(mode, oldMode) { while (oldMode && this._modeStack.length > 1 && this.main != oldMode) this.pop(); @@ -282,14 +289,14 @@ const Modes = Module("modes", { this.push(mode); }, - reset: function () { + reset: function reset() { if (this._modeStack.length == 1 && this.topOfStack.params.enter) this.topOfStack.params.enter({}, this.topOfStack); while (this._modeStack.length > 1) this.pop(); }, - remove: function (mode) { + remove: function remove(mode) { if (this._extended & mode) { this._extended &= ~mode; this.show(); @@ -342,13 +349,8 @@ const Modes = Module("modes", { }, desc)); } }, { - options: function () { - options.observePref("accessibility.browsewithcaret", function (value) { - if (!value && modes.main === modes.CARET) - modes.pop(); - if (value && modes.main === modes.NORMAL) - modes.push(modes.CARET); - }); + prefs: function () { + prefs.watch("accessibility.browsewithcaret", modes.closure.onCaretChange); } }); diff --git a/common/content/modules.js b/common/content/modules.js index bf0acd60..a1ed1640 100644 --- a/common/content/modules.js +++ b/common/content/modules.js @@ -91,8 +91,8 @@ window.addEventListener("load", function onLoad() { function init(module) { function init(func, mod) function () defineModule.time(module.className || module.constructor.className, mod, - func, module, - dactyl, modules, window); + func, module, + dactyl, modules, window); set.add(loaded, module.constructor.className); for (let [mod, func] in Iterator(module.INIT)) { diff --git a/common/content/options.js b/common/content/options.js index 2b3605d2..b6da0b34 100644 --- a/common/content/options.js +++ b/common/content/options.js @@ -76,7 +76,7 @@ const Option = Class("Option", { dactyl.trapErrors(function () this.value = this.value, this); }, - get isDefault() this.stringify(this.value) === this.stringify(this.defaultValue), + get isDefault() this.stringValue === this.stringDefaultValue, /** @property {value} The option's global value. @see #scope */ get globalValue() options.store.get(this.name, {}).value, @@ -174,6 +174,11 @@ const Option = Class("Option", { get value() this.get(), set value(val) this.set(val), + get stringValue() this.stringify(this.value), + set stringValue(value) this.value = this.parse(value), + + get stringDefaultValue() this.stringify(this.defaultValue), + getKey: function (key) undefined, /** @@ -610,28 +615,6 @@ const Options = Module("options", { this.needInit = []; this._options = []; this._optionMap = {}; - this._prefContexts = []; - - for (let [, pref] in Iterator(this.allPrefs(Options.OLD_SAVED))) { - let saved = Options.SAVED + pref.substr(Options.OLD_SAVED.length); - if (!this.getPref(saved)) - this.setPref(saved, this.getPref(pref)); - this.resetPref(pref); - } - - // Host application preferences which need to be changed to work well with - // - - // Work around the popup blocker - // TODO: Make this work like safeSetPref - var popupAllowedEvents = this._loadPreference("dom.popup_allowed_events", "change click dblclick mouseup reset submit"); - if (!/keypress/.test(popupAllowedEvents)) { - this._storePreference("dom.popup_allowed_events", popupAllowedEvents + " keypress"); - dactyl.registerObserver("shutdown", function () { - if (this._loadPreference("dom.popup_allowed_events", "") == popupAllowedEvents + " keypress") - this._storePreference("dom.popup_allowed_events", popupAllowedEvents); - }); - } storage.newMap("options", { store: false }); storage.addObserver("options", function optionObserver(key, event, option) { @@ -640,34 +623,12 @@ const Options = Module("options", { if (event == "change" && opt) opt.set(opt.globalValue, Option.SCOPE_GLOBAL, true); }, window); - - this._branch = services.get("pref").getBranch("").QueryInterface(Ci.nsIPrefBranch2); - this._branch.addObserver("", this, false); - }, - - destroy: function () { - this._branch.removeObserver("", this); }, /** @property {Iterator(Option)} @private */ __iterator__: function () values(this._options.sort(function (a, b) String.localeCompare(a.name, b.name))), - observe: function (subject, topic, data) { - if (topic == "nsPref:changed") { - let observers = this._observers[data]; - if (observers) { - let value = options.getPref(data, false); - this._observers[data] = observers.filter(function (callback) { - if (!callback.get()) - return false; - dactyl.trapErrors(callback.get(), null, value); - return true; - }); - } - } - }, - /** * Adds a new option. * @@ -705,13 +666,18 @@ const Options = Module("options", { this.__defineSetter__(name, function (value) { this._optionMap[name].value = value; }); }, - /** - * Returns the names of all preferences. - * - * @param {string} branch The branch in which to search preferences. - * @default "" - */ - allPrefs: function (branch) services.get("pref").getChildList(branch || "", { value: 0 }), + allPrefs: deprecated("Please use prefs.getNames", function allPrefs() prefs.getNames.apply(prefs, arguments)), + getPref: deprecated("Please use prefs.get", function getPref() prefs.get.apply(prefs, arguments)), + invertPref: deprecated("Please use prefs.invert", function invertPref() prefs.invert.apply(prefs, arguments)), + listPrefs: deprecated("Please use prefs.list", function listPrefs() { commandline.commandOutput(prefs.list.apply(prefs, arguments)) }), + observePref: deprecated("Please use prefs.observe", function observePref() prefs.observe.apply(prefs, arguments)), + popContext: deprecated("Please use prefs.popContext", function popContext() prefs.popContext.apply(prefs, arguments)), + pushContext: deprecated("Please use prefs.pushContext", function pushContext() prefs.pushContext.apply(prefs, arguments)), + resetPref: deprecated("Please use prefs.reset", function resetPref() prefs.reset.apply(prefs, arguments)), + safeResetPref: deprecated("Please use prefs.safeReset", function safeResetPref() prefs.safeReset.apply(prefs, arguments)), + safeSetPref: deprecated("Please use prefs.safeSet", function safeSetPref() prefs.safeSet.apply(prefs, arguments)), + setPref: deprecated("Please use prefs.set", function setPref() prefs.set.apply(prefs, arguments)), + withContext: deprecated("Please use prefs.withContext", function withContext() prefs.withContext.apply(prefs, arguments)), /** * Returns the option with <b>name</b> in the specified <b>scope</b>. @@ -748,7 +714,7 @@ const Options = Module("options", { let option = { isDefault: opt.isDefault, name: opt.name, - default: opt.stringify(opt.defaultValue), + default: opt.stringDefaultValue, pre: "\u00a0\u00a0", // Unicode nonbreaking space. value: <></> }; @@ -764,7 +730,7 @@ const Options = Module("options", { option.default = (option.default ? "" : "no") + opt.name; } else - option.value = <>={template.highlight(opt.stringify(opt.value))}</>; + option.value = <>={template.highlight(opt.stringValue)}</>; yield option; } }; @@ -773,60 +739,6 @@ const Options = Module("options", { }, /** - * Lists all preferences matching <b>filter</b> or only those with - * changed values if <b>onlyNonDefault</b> is specified. - * - * @param {boolean} onlyNonDefault Limit the list to prefs with a - * non-default value. - * @param {string} filter The list filter. A null filter lists all - * prefs. - * @optional - */ - listPrefs: function (onlyNonDefault, filter) { - if (!filter) - filter = ""; - - let prefArray = options.allPrefs(); - prefArray.sort(); - function prefs() { - for (let [, pref] in Iterator(prefArray)) { - let userValue = services.get("pref").prefHasUserValue(pref); - if (onlyNonDefault && !userValue || pref.indexOf(filter) == -1) - continue; - - let value = options.getPref(pref); - - let option = { - isDefault: !userValue, - default: options._loadPreference(pref, null, true), - value: <>={template.highlight(value, true, 100)}</>, - name: pref, - pre: "\u00a0\u00a0" // Unicode nonbreaking space. - }; - - yield option; - } - }; - - commandline.commandOutput( - template.options(config.host + " Options", prefs())); - }, - - _observers: Class.memoize(function () ({})), - /** - * Adds a new preference observer for the given preference. - * - * @param {string} pref The preference to observe. - * @param {function(object)} callback The callback, called with the - * new value of the preference whenever it changes. - */ - observePref: function (pref, callback, weak) { - if (!this._observers[pref]) - this._observers[pref] = []; - this._observers[pref].push(weak ? Cu.getWeakReference(callback) : { get: function () callback }); - }, - - /** * Parses a :set command's argument string. * * @param {string} args The :set command's argument string. @@ -895,204 +807,8 @@ const Options = Module("options", { }, /** @property {Object} The options store. */ - get store() storage.options, - - /** - * Returns the value of the preference <b>name</b>. - * - * @param {string} name The preference name. - * @param {value} forcedDefault The default value for this - * preference. Used for internal dactyl preferences. - */ - getPref: function (name, forcedDefault) { - return this._loadPreference(name, forcedDefault); - }, - - _checkPrefSafe: function (name, message, value) { - let curval = this._loadPreference(name, null, false); - if (arguments.length > 2 && curval === value) - return; - let defval = this._loadPreference(name, null, true); - let saved = this._loadPreference(Options.SAVED + name); - - if (saved == null && curval != defval || curval != saved) { - let msg = "Warning: setting preference " + name + ", but it's changed from its default value."; - if (message) - msg += " " + message; - dactyl.echomsg(msg); - } - }, - - /** - * Resets the preference <b>name</b> to </b>value</b> but warns the user - * if the value is changed from its default. - * - * @param {string} name The preference name. - * @param {value} value The new preference value. - */ - safeResetPref: function (name, message) { - this._checkPrefSafe(name, message); - this.resetPref(name); - this.resetPref(Options.SAVED + name); - }, - - /** - * Sets the preference <b>name</b> to </b>value</b> but warns the user - * if the value is changed from its default. - * - * @param {string} name The preference name. - * @param {value} value The new preference value. - */ - safeSetPref: function (name, value, message) { - this._checkPrefSafe(name, message, value); - this._storePreference(name, value); - this._storePreference(Options.SAVED + name, value); - }, - - /** - * Sets the preference <b>name</b> to </b>value</b>. - * - * @param {string} name The preference name. - * @param {value} value The new preference value. - */ - setPref: function (name, value) { - this._storePreference(name, value); - }, - - /** - * Resets the preference <b>name</b> to its default value. - * - * @param {string} name The preference name. - */ - resetPref: function (name) { - try { - services.get("pref").clearUserPref(name); - } - catch (e) {} // ignore - thrown if not a user set value - }, - - /** - * Toggles the value of the boolean preference <b>name</b>. - * - * @param {string} name The preference name. - */ - invertPref: function (name) { - if (services.get("pref").getPrefType(name) == Ci.nsIPrefBranch.PREF_BOOL) - this.setPref(name, !this.getPref(name)); - else - dactyl.echoerr("E488: Trailing characters: " + name + "!"); - }, - - /** - * Pushes a new preference context onto the context stack. - * - * @see #withContext - */ - pushContext: function () { - this._prefContexts.push({}); - }, - - /** - * Pops the top preference context from the stack. - * - * @see #withContext - */ - popContext: function () { - for (let [k, v] in Iterator(this._prefContexts.pop())) - this._storePreference(k, v); - }, - - /** - * Executes <b>func</b> with a new preference context. When <b>func</b> - * returns, the context is popped and any preferences set via - * {@link #setPref} or {@link #invertPref} are restored to their - * previous values. - * - * @param {function} func The function to call. - * @param {Object} func The 'this' object with which to call <b>func</b> - * @see #pushContext - * @see #popContext - */ - withContext: function (func, self) { - try { - this.pushContext(); - return func.call(self); - } - finally { - this.popContext(); - } - }, - - _storePreference: function (name, value) { - if (this._prefContexts.length) { - let val = this._loadPreference(name, null); - if (val != null) - this._prefContexts[this._prefContexts.length - 1][name] = val; - } - - let type = services.get("pref").getPrefType(name); - switch (typeof value) { - case "string": - if (type == Ci.nsIPrefBranch.PREF_INVALID || type == Ci.nsIPrefBranch.PREF_STRING) { - let supportString = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString); - supportString.data = value; - services.get("pref").setComplexValue(name, Ci.nsISupportsString, supportString); - } - else if (type == Ci.nsIPrefBranch.PREF_INT) - dactyl.echoerr("E521: Number required after =: " + name + "=" + value); - else - dactyl.echoerr("E474: Invalid argument: " + name + "=" + value); - break; - case "number": - if (type == Ci.nsIPrefBranch.PREF_INVALID || type == Ci.nsIPrefBranch.PREF_INT) - services.get("pref").setIntPref(name, value); - else - dactyl.echoerr("E474: Invalid argument: " + name + "=" + value); - break; - case "boolean": - if (type == Ci.nsIPrefBranch.PREF_INVALID || type == Ci.nsIPrefBranch.PREF_BOOL) - services.get("pref").setBoolPref(name, value); - else if (type == Ci.nsIPrefBranch.PREF_INT) - dactyl.echoerr("E521: Number required after =: " + name + "=" + value); - else - dactyl.echoerr("E474: Invalid argument: " + name + "=" + value); - break; - default: - dactyl.echoerr("Unknown preference type: " + typeof value + " (" + name + "=" + value + ")"); - } - }, - - _loadPreference: function (name, forcedDefault, defaultBranch) { - let defaultValue = null; // XXX - if (forcedDefault != null) // this argument sets defaults for non-user settable options (like extensions.history.comp_history) - defaultValue = forcedDefault; - - let branch = defaultBranch ? services.get("pref").getDefaultBranch("") : services.get("pref"); - let type = services.get("pref").getPrefType(name); - try { - switch (type) { - case Ci.nsIPrefBranch.PREF_STRING: - let value = branch.getComplexValue(name, Ci.nsISupportsString).data; - // try in case it's a localized string (will throw an exception if not) - if (!services.get("pref").prefIsLocked(name) && !services.get("pref").prefHasUserValue(name) && - RegExp("chrome://.+/locale/.+\\.properties").test(value)) - value = branch.getComplexValue(name, Ci.nsIPrefLocalizedString).data; - return value; - case Ci.nsIPrefBranch.PREF_INT: - return branch.getIntPref(name); - case Ci.nsIPrefBranch.PREF_BOOL: - return branch.getBoolPref(name); - default: - return defaultValue; - } - } - catch (e) { - return defaultValue; - } - } + get store() storage.options }, { - SAVED: "extensions.dactyl.saved.", - OLD_SAVED: "dactyl.saved." }, { commands: function () { function setAction(args, modifiers) { @@ -1121,16 +837,16 @@ const Options = Module("options", { commandline.input("Warning: Resetting all preferences may make " + config.host + " unusable. Continue (yes/[no]): ", function (resp) { if (resp == "yes") - for (let pref in values(options.allPrefs())) - options.resetPref(pref); + for (let pref in values(prefs.getNames())) + prefs.reset(pref); }, { promptHighlight: "WarningMsg" }); else if (name == "all") - options.listPrefs(onlyNonDefault, ""); + command.commandOutput(prefs.list(onlyNonDefault, "")); else if (reset) - options.resetPref(name); + prefs.reset(name); else if (invertBoolean) - options.invertPref(name); + prefs.toggle(name); else if (valueGiven) { if (value == undefined) value = ""; @@ -1140,10 +856,10 @@ const Options = Module("options", { value = false; else if (/^\d+$/.test(value)) value = parseInt(value, 10); - options.setPref(name, value); + prefs.set(name, value); } else - options.listPrefs(onlyNonDefault, name); + command.commandOutput(prefs.list(onlyNonDefault, "")); return; } @@ -1208,8 +924,8 @@ const Options = Module("options", { context.advance(filter.length); filter = filter.substr(0, filter.length - 1); context.completions = [ - [options._loadPreference(filter, null, false), "Current Value"], - [options._loadPreference(filter, null, true), "Default Value"] + [prefs.get(filter), "Current Value"], + [prefs.getDefault(filter), "Default Value"] ].filter(function ([k]) k != null && k.length < 200); return null; } @@ -1241,8 +957,8 @@ const Options = Module("options", { context.fork("default", 0, this, function (context) { context.title = ["Extra Completions"]; context.completions = [ - [option.value, "Current value"], - [option.defaultValue, "Default value"] + [option.stringValue, "Current value"], + [option.stringValue, "Default value"] ].filter(function (f) f[0] !== "" && String(f[0]).length < 200); context.quote = ["", util.identity, ""]; }); @@ -1359,7 +1075,7 @@ const Options = Module("options", { { command: this.name, literalArg: [opt.type == "boolean" ? (opt.value ? "" : "no") + opt.name - : opt.name + "=" + opt.stringify(opt.value)] + : opt.name + "=" + opt.stringValue] } for (opt in options) if (!opt.getter && !opt.isDefault && (opt.scope & Option.SCOPE_GLOBAL)) @@ -1491,18 +1207,9 @@ const Options = Module("options", { if (res) context.completions = res; }; - - completion.preference = function preference(context) { - context.anchored = false; - context.title = [config.host + " Preference", "Value"]; - context.keys = { text: function (item) item, description: function (item) options.getPref(item) }; - context.completions = options.allPrefs(); - }; }, javascript: function () { JavaScript.setCompleter(this.get, [function () ([o.name, o.description] for (o in options))]); - JavaScript.setCompleter([this.getPref, this.safeSetPref, this.setPref, this.resetPref, this.invertPref], - [function (context) (context.anchored=false, options.allPrefs().map(function (pref) [pref, ""]))]); }, sanitizer: function () { sanitizer.addItem("options", { diff --git a/common/content/tabs.js b/common/content/tabs.js index 64cfbac5..b7010bc5 100644 --- a/common/content/tabs.js +++ b/common/content/tabs.js @@ -993,8 +993,8 @@ const Tabs = Module("tabs", { if (value == 0) tabs.tabStyle.enabled = true; else { - options.safeSetPref("browser.tabs.autoHide", value == 1, - "See 'showtabline' option."); + prefs.safeSet("browser.tabs.autoHide", value == 1, + "See 'showtabline' option."); tabs.tabStyle.enabled = false; } @@ -1025,7 +1025,7 @@ const Tabs = Module("tabs", { ]; options.add(["activate", "act"], "Define when tabs are automatically activated", - "stringlist", [g[0] for (g in values(activateGroups.slice(1))) if (!g[2] || !options.getPref("browser.tabs." + g[2]))].join(","), + "stringlist", [g[0] for (g in values(activateGroups.slice(1))) if (!g[2] || !prefs.get("browser.tabs." + g[2]))].join(","), { completer: function (context) activateGroups, has: Option.has.toggleAll, @@ -1033,9 +1033,9 @@ const Tabs = Module("tabs", { let valueSet = set(newValues); for (let group in values(activateGroups)) if (group[2]) - options.safeSetPref("browser.tabs." + group[2], - !(valueSet["all"] ^ valueSet[group[0]]), - "See the 'activate' option"); + prefs.safeSet("browser.tabs." + group[2], + !(valueSet["all"] ^ valueSet[group[0]]), + "See the 'activate' option"); return newValues; } }); @@ -1072,10 +1072,10 @@ const Tabs = Module("tabs", { restriction = 2; } - options.safeSetPref("browser.link.open_newwindow", open, - "See 'popups' option."); - options.safeSetPref("browser.link.open_newwindow.restriction", restriction, - "See 'popups' option."); + prefs.safeSet("browser.link.open_newwindow", open, + "See 'popups' option."); + prefs.safeSet("browser.link.open_newwindow.restriction", restriction, + "See 'popups' option."); return values; }, completer: function (context) [ diff --git a/common/locale/en-US/options.xml b/common/locale/en-US/options.xml index a291b6f5..6dfe5823 100644 --- a/common/locale/en-US/options.xml +++ b/common/locale/en-US/options.xml @@ -1175,6 +1175,16 @@ </item> <item> + <tags>'sanitizeshutdown' 'ss'</tags> + <spec>'sanitizeshutdown'</spec> + <type>stringlist</type> + <default/> + <description> + <p>The items to sanitize automatically at shutdown.</p> + </description> +</item> + +<item> <tags>'sts' 'sanitizetimespan'</tags> <spec>'sanitizetimespan' 'sts'</spec> <strut/> diff --git a/common/modules/base.jsm b/common/modules/base.jsm index 4077d9c4..20c3b538 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -129,7 +129,11 @@ defineModule.modules = []; defineModule.times = { all: 0 }; defineModule.time = function time(major, minor, func, self) { let time = Date.now(); - let res = func.apply(self, Array.slice(arguments, 4)); + try { + var res = func.apply(self, Array.slice(arguments, 4)); + } catch (e) { + loaded.util && util.reportError(e); + } let delta = Date.now() - time; defineModule.times.all += delta; defineModule.times[major] = (defineModule.times[major] || 0) + delta; @@ -157,7 +161,7 @@ function require(obj, name, from) { if (loaded.util) util.reportError(e); else - defineModule.dump(" " + e.fileName + ":" + e.lineNumber + ": " + e +"\n"); + defineModule.dump(" " + (e.filename || e.fileName) + ":" + e.lineNumber + ": " + e +"\n"); } } @@ -169,7 +173,8 @@ defineModule("base", { "call", "callable", "ctypes", "curry", "debuggerProperties", "defineModule", "endModule", "forEach", "isArray", "isGenerator", "isinstance", "isObject", "isString", "isSubclass", "iter", "iterAll", "keys", - "memoize", "properties", "requiresMainThread", "set", "update", "values" + "memoize", "properties", "requiresMainThread", "set", "update", "values", + "withCallerGlobal" ], use: ["services", "util"] }); @@ -217,7 +222,7 @@ function properties(obj, prototypes, debugger_) { for (; obj; obj = prototypes && prototype(obj)) { try { - if (!debugger_ || !services.get("debugger").isOn) + if (sandbox.Object.getOwnPropertyNames || !debugger_ || !services.get("debugger").isOn) var iter = values(Object.getOwnPropertyNames(obj)); } catch (e) {} @@ -588,6 +593,21 @@ function requiresMainThread(callback) mainThread.dispatch(Runnable(this, callback, arguments), mainThread.DISPATCH_NORMAL); } +let sandbox = Cu.Sandbox(this); +sandbox.__proto__ = this; +/** + * Wraps a function so that when called, the global object of the caller + * is prepended to its arguments. + */ +// Hack to get around lack of access to caller in strict mode. +const withCallerGlobal = Cu.evalInSandbox(<![CDATA[ + (function withCallerGlobal(fn) + function withCallerGlobal_wrapped() + fn.apply(this, + [Class.objectGlobal(withCallerGlobal_wrapped.caller)] + .concat(Array.slice(arguments)))) +]]>, Cu.Sandbox(this), "1.8"); + /** * Updates an object with the properties of another object. Getters * and setters are copied as expected. Moreover, any function @@ -895,6 +915,8 @@ let StructBase = Class("StructBase", Array, { clone: function clone() this.constructor.apply(null, this.slice()), + toString: function () Class.prototype.toString.apply(this, arguments), + // Iterator over our named members __iterator__: function () { let self = this; diff --git a/common/modules/bookmarkcache.jsm b/common/modules/bookmarkcache.jsm index cf73277f..a376eb05 100644 --- a/common/modules/bookmarkcache.jsm +++ b/common/modules/bookmarkcache.jsm @@ -41,7 +41,7 @@ const BookmarkCache = Module("BookmarkCache", XPCOM(Ci.nsINavBookmarkObserver), .map(function (s) bookmarks[s]), _deleteBookmark: function deleteBookmark(id) { - let result = this.bookmarks[item.id] || null; + let result = this.bookmarks[id] || null; delete this.bookmarks[id]; return result; }, diff --git a/common/modules/sanitizer.jsm b/common/modules/sanitizer.jsm index c43d57b3..4e8a743f 100644 --- a/common/modules/sanitizer.jsm +++ b/common/modules/sanitizer.jsm @@ -17,11 +17,12 @@ Components.utils.import("resource://dactyl/base.jsm"); defineModule("sanitizer", { exports: ["Range", "Sanitizer", "sanitizer"], - require: ["services", "storage", "util"] + require: ["services", "storage", "template", "util"] }); let tmp = {}; services.get("subscriptLoader").loadSubScript("chrome://browser/content/sanitize.js", tmp); +tmp.Sanitizer.prototype.__proto__ = Class.prototype; const Range = Struct("min", "max"); Range.prototype.contains = function (date) @@ -29,32 +30,67 @@ Range.prototype.contains = function (date) Range.prototype.__defineGetter__("isEternity", function () this.max == null && this.min == null); Range.prototype.__defineGetter__("isSession", function () this.max == null && this.min == sanitizer.sessionStart); +const Item = Class("Item", { + init: function (name) { + this.name = name; + }, + + // Hack for completion: + "0": Class.Property({ get: function () this.name }), + "1": Class.Property({ get: function () this.description }), + + get cpdPref() (this.builtin ? "" : Item.PREFIX) + Item.BRANCH + Sanitizer.argToPref(this.name), + get shutdownPref() (this.builtin ? "" : Item.PREFIX) + Item.SHUTDOWN_BRANCH + Sanitizer.argToPref(this.name), + get cpd() prefs.get(this.cpdPref), + get shutdown() prefs.get(this.shutdownPref), + + shouldSanitize: function (shutdown) (!shutdown || this.builtin || this.persistent) && + prefs.get(shutdown ? this.shutdownPref : this.pref) +}, { + PREFIX: "extensions.dactyl.", + BRANCH: "privacy.cpd.", + SHUTDOWN_BRANCH: "privacy.clearOnShutdown." +}); + const Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference], tmp.Sanitizer), { sessionStart: Date.now() * 1000, init: function () { + const self = this; + + util.addObserver(this); + services.add("contentprefs", "@mozilla.org/content-pref/service;1", Ci.nsIContentPrefService); services.add("cookies", "@mozilla.org/cookiemanager;1", [Ci.nsICookieManager, Ci.nsICookieManager2, Ci.nsICookieService]); services.add("loginmanager", "@mozilla.org/login-manager;1", Ci.nsILoginManager); services.add("permissions", "@mozilla.org/permissionmanager;1", Ci.nsIPermissionManager); - this.itemOverrides = {}; - this.itemDescriptions = { - all: "Sanitize all items", - // Builtin items - cache: "Cache", - downloads: "Download history", - formdata: "Saved form and search history", - history: "Browsing history", - offlineapps: "Offline website data", - passwords: "Saved passwords", - sessions: "Authenticated sessions", + this.itemMap = { + __iterator__: function () { + // For platforms without getOwnPropertyNames :( + for (let p in properties(this)) + if (p !== "__iterator__") + yield this[p] + } }; + + this.addItem("all", { description: "Sanitize all items", shouldSanitize: function () false }); + // Builtin items + this.addItem("cache", { builtin: true, description: "Cache" }); + this.addItem("downloads", { builtin: true, description: "Download history" }); + this.addItem("formdata", { builtin: true, description: "Saved form and search history" }); + this.addItem("history", { builtin: true, description: "Browsing history", sessionHistory: true }); + this.addItem("offlineapps", { builtin: true, description: "Offline website data" }); + this.addItem("passwords", { builtin: true, description: "Saved passwords" }); + this.addItem("sessions", { builtin: true, description: "Authenticated sessions" }); + // These builtin methods don't support hosts or otherwise have // insufficient granularity this.addItem("cookies", { + builtin: true, description: "Cookies", + persistent: true, action: function (range, host) { for (let c in Sanitizer.iterCookies(host)) if (range.contains(c.creationTime) || timespan.isSession && c.isSession) @@ -63,7 +99,9 @@ const Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakR override: true }); this.addItem("sitesettings", { + builtin: true, description: "Site preferences", + persistent: true, action: function (range, host) { if (range.isSession) return; @@ -89,21 +127,95 @@ const Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakR }, override: true }); - util.addObserver(this); + + function ourItems(persistent) [ + item for (item in self.itemMap) + if (!item.builtin && (!persistent || item.persistent)) + ]; + + function prefOverlay(branch, persistent, local) update(Object.create(local), { + before: array.toObject([ + [branch.substr(Item.PREFIX.length) + "history", + <preferences xmlns={XUL}>{ + template.map(ourItems(persistent), function (item) + <preference type="bool" id={branch + item.name} name={branch + item.name}/>) + }</preferences>.*::*] + ]), + init: function init(win) { + let pane = win.document.getElementById("SanitizeDialogPane"); + for (let [,pref] in iter(pane.preferences)) + pref.updateElements(); + init.superapply(this, arguments); + } + }); + + let (branch = Item.PREFIX + Item.SHUTDOWN_BRANCH) { + util.overlayWindow("chrome://browser/content/preferences/sanitize.xul", + function (win) prefOverlay(branch, true, { + append: { + SanitizeDialogPane: + <groupbox orient="horizontal" xmlns={XUL}> + <caption label={services.get("dactyl:").appName + " (see :help privacy)"}/> + <grid flex="1"> + <columns><column flex="1"/><column flex="1"/></columns> + <rows>{ + let (items = ourItems(true)) + template.map(util.range(0, Math.ceil(items.length/2)), function (i) + <row xmlns={XUL}>{ + template.map(items.slice(i*2, i*2+2), function (item) + <checkbox xmlns={XUL} label={item.description} preference={branch + item.name}/>) + }</row>) + }</rows> + </grid> + </groupbox> + }, + })); + } + let (branch = Item.PREFIX + Item.BRANCH) { + util.overlayWindow("chrome://browser/content/sanitize.xul", + function (win) prefOverlay(branch, false, { + append: { + itemList: <> + <listitem xmlns={XUL} label="See :help privacy for the following:" disabled="true" style="font-style: italic; font-weight: bold;"/> + { + template.map(ourItems(), function ([item, desc]) + <listitem xmlns={XUL} type="checkbox" + label={services.get("dactyl:").appName + " " + desc} + preference={branch + item} + onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>) + } + </> + }, + init: function (win) { + let elem = win.document.getElementById("itemList"); + elem.setAttribute("rows", elem.itemCount); + win.Sanitizer = Class("Sanitizer", win.Sanitizer, { + sanitize: function sanitize() { + self.withSavedValues(["sanitizing"], function () { + self.sanitizing = true; + sanitize.superapply(this, arguments); + sanitizer.sanitizeItems([item.name for (item in self.itemMap) if (item.shouldSanitize(false))], + Range.fromArray(this.range || [])); + }, this); + } + }); + } + })); + } }, addItem: function addItem(name, params) { - if (params.description) - this.itemDescriptions[name] = params.description; - if (params.override) - set.add(this.itemOverrides, name); + this.itemMap[name] = update(this.itemMap[name] || Item(name), + array([k, v] for ([k, v] in Iterator(params)) if (!callable(v))).toObject()); - name = "clear-" + name; - storage.addObserver("sanitizer", - function (key, event, arg) { - if (event == name) - params.action.apply(params, arg); - }, Class.objectGlobal(params.action)); + let names = set([name].concat(params.contains || []).map(function (e) "clear-" + e)); + if (params.action) + storage.addObserver("sanitizer", + function (key, event, arg) { + if (event in names) + params.action.apply(params, arg); + }, + Class.objectGlobal(params.action)); if (params.privateEnter || params.privateLeave) storage.addObserver("private-mode", @@ -111,12 +223,13 @@ const Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakR let meth = params[arg ? "privateEnter" : "privateLeave"]; if (meth) meth.call(params); - }, Class.objectGlobal(params.action)); + }, + Class.objectGlobal(params.action)); }, observe: { - "browser:purge-domain-data": function (subject, data) { - storage.fireEvent("sanitize", "domain", data); + "browser:purge-domain-data": function (subject, host) { + storage.fireEvent("sanitize", "domain", host); // If we're sanitizing, our own sanitization functions will already // be called, and with much greater granularity. Only process this // event if it's triggered externally. @@ -126,7 +239,11 @@ const Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakR "browser:purge-session-history": function (subject, data) { // See above. if (!this.sanitizing) - this.sanitizeItems(null, Range(this.sessionStart), null); + this.sanitizeItems(null, Range(this.sessionStart), null, "sessionHistory"); + }, + "quit-application-granted": function (subject, data) { + if (!this.sanitizeItems(null, Range(), null, "shutdown")) + this.ranAtShutdown = true; }, "private-browsing": function (subject, data) { if (data == "enter") @@ -137,50 +254,57 @@ const Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakR } }, - sanitize: function (items, range) { - this.sanitizing = true; - let errors = this.sanitizeItems(items, range, null); - - for (let itemName in values(items)) { - try { - let item = this.items[Sanitizer.argToPref(itemName)]; - if (item && !this.itemOverrides[itemName]) { - item.range = range; - if ("clear" in item && item.canClear) - item.clear(); + get ranAtShutdown() prefs.get(Item.PREFIX + "didSanitizeOnShutdown"), + set ranAtShutdown(val) prefs.set(Item.PREFIX + "didSanitizeOnShutdown", Boolean(val)), + get runAtShutdown() prefs.get("privacy.sanitize.sanitizeOnShutdown"), + set runAtShutdown(val) prefs.set("privacy.sanitize.sanitizeOnShutdown", Boolean(val)), + + sanitize: function (items, range) + this.withSavedValues(["sanitizing"], function () { + this.sanitizing = true; + let errors = this.sanitizeItems(items, range, null); + + for (let itemName in values(items)) { + try { + let item = this.items[Sanitizer.argToPref(itemName)]; + if (item && !this.itemMap[itemName].override) { + item.range = range; + if ("clear" in item && item.canClear) + item.clear(); + } + } + catch (e) { + errors = errors || {}; + errors[itemName] = e; + util.dump("Error sanitizing " + itemName); + util.reportError(e); } } - catch (e) { - errors = errors || {}; - errors[itemName] = e; - util.dump("Error sanitizing " + itemName); - util.reportError(e); - } - } + return errors; + }), - this.sanitizing = false; - return errors; - }, + sanitizeItems: function (items, range, host, key) + this.withSavedValues(["sanitizing"], function () { + this.sanitizing = true; + if (items == null) + items = Object.keys(this.itemMap); - sanitizeItems: function (items, range, host) { - if (items == null) - items = Object.keys(this.itemDescriptions); - let errors; - for (let itemName in values(items)) - try { - storage.fireEvent("sanitizer", "clear-" + itemName, [range, host]); - } - catch (e) { - errors = errors || {}; - errors[itemName] = e; - util.dump("Error sanitizing " + itemName); - util.reportError(e); - } - return errors; - } + let errors; + for (let itemName in values(items)) + try { + if (!key || this.itemMap[itemName][key]) + storage.fireEvent("sanitizer", "clear-" + itemName, [range, host]); + } + catch (e) { + errors = errors || {}; + errors[itemName] = e; + util.dump("Error sanitizing " + itemName); + util.reportError(e); + } + return errors; + }) }, { argPrefMap: { - commandline: "commandLine", offlineapps: "offlineApps", sitesettings: "siteSettings", }, @@ -198,6 +322,11 @@ const Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakR yield p; } }, { + load: function (dactyl, modules, window) { + if (sanitizer.runAtShutdown && !sanitizer.ranAtShutdown) + sanitizer.sanitizeItems(null, Range(), null, "shutdown"); + sanitizer.ranAtShutdown = false; + }, autocommands: function (dactyl, modules, window) { storage.addObserver("private-mode", function (key, event, value) { @@ -305,10 +434,34 @@ const Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakR "The default list of private items to sanitize", "stringlist", "all", { - completer: function (value) Iterator(sanitizer.itemDescriptions), + completer: function (value) sanitizer.itemMap, has: modules.Option.has.toggleAll, validator: function (values) values.length && - values.every(function (val) val === "all" || set.has(sanitizer.itemDescriptions, val)) + values.every(function (val) val === "all" || set.has(sanitizer.itemMap, val)) + }); + + options.add(["sanitizeshutdown", "ss"], + "The items to sanitize automatically at shutdown", + "stringlist", "", + { + initialValue: true, + completer: function () [i for (i in sanitizer.itemMap) if (i.persistent || i.builtin)], + getter: function () !sanitizer.runAtShutdown ? [] : [ + item.name for (item in sanitizer.itemMap) + if (item.shouldSanitize(true)) + ], + setter: function (values) { + if (values.length === 0) + sanitizer.runAtShutdown = false; + else { + sanitizer.runAtShutdown = true; + let have = set(values); + for (let item in sanitizer.itemMap) + prefs.set(item.shutdownPref, + Boolean(set.has(have, item.name) ^ set.has(have, "all"))); + } + return values; + } }); options.add(["sanitizetimespan", "sts"], diff --git a/common/modules/storage.jsm b/common/modules/storage.jsm index 74b78c46..0cd800e6 100644 --- a/common/modules/storage.jsm +++ b/common/modules/storage.jsm @@ -11,8 +11,6 @@ defineModule("storage", { require: ["services", "util"] }); -var prefService = services.get("pref").getBranch("extensions.dactyl.datastore."); - const win32 = /^win(32|nt)$/i.test(services.get("runtime").OS); function getFile(name) { @@ -21,38 +19,19 @@ function getFile(name) { return File(file); } -function getCharPref(name) { +function loadData(name, store, type) { try { - return prefService.getComplexValue(name, Ci.nsISupportsString).data; - } - catch (e) {} -} - -function setCharPref(name, value) { - var str = Cc['@mozilla.org/supports-string;1'].createInstance(Ci.nsISupportsString); - str.data = value; - return prefService.setComplexValue(name, Ci.nsISupportsString, str); -} - -function loadPref(name, store, type) { - try { - if (store) - var pref = getCharPref(name); - if (!pref && storage.infoPath) + if (storage.infoPath) var file = getFile(name).read(); - if (pref || file) - var result = services.get("json").decode(pref || file); - if (pref) { - prefService.clearUserPref(name); - savePref({ name: name, store: true, serial: pref }); - } + if (file) + var result = services.get("json").decode(file); if (result instanceof type) return result; } catch (e) {} } -function savePref(obj) { +function saveData(obj) { if (obj.privateData && storage.privateMode) return; if (obj.store && storage.infoPath) @@ -78,7 +57,7 @@ const StoreBase = Class("StoreBase", { this._object = this._load() || this._constructor(); this.fireEvent("change", null); }, - save: function () { savePref(this); }, + save: function () { saveData(this); }, }); const ArrayStore = Class("ArrayStore", StoreBase, { @@ -175,7 +154,7 @@ const Storage = Module("Storage", { if (!(key in keys) || params.reload || this.alwaysReload[key]) { if (key in this && !(params.reload || this.alwaysReload[key])) throw Error(); - let load = function () loadPref(key, params.store, params.type || myObject); + let load = function () loadData(key, params.store, params.type || myObject); keys[key] = new constructor(key, params.store, load, params); keys[key].timer = new Timer(1000, 10000, function () storage.save(key)); this.__defineGetter__(key, function () keys[key]); @@ -243,12 +222,12 @@ const Storage = Module("Storage", { }, save: function save(key) { - savePref(keys[key]); + saveData(keys[key]); }, saveAll: function storeAll() { for each (let obj in keys) - savePref(obj); + saveData(obj); }, _privateMode: false, diff --git a/common/modules/util.jsm b/common/modules/util.jsm index 830f3508..7bc801bc 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -6,9 +6,11 @@ // given in the LICENSE.txt file included with this file. "use strict"; +try { + Components.utils.import("resource://dactyl/base.jsm"); defineModule("util", { - exports: ["Math", "NS", "Util", "XHTML", "XUL", "util"], + exports: ["FailedAssertion", "Math", "NS", "Prefs", "Util", "XHTML", "XUL", "prefs", "util"], require: ["services"], use: ["highlight", "template"] }); @@ -18,12 +20,33 @@ const XUL = Namespace("xul", "http://www.mozilla.org/keymaster/gatekeeper/there. const NS = Namespace("dactyl", "http://vimperator.org/namespaces/liberator"); default xml namespace = XHTML; -const Util = Module("Util", { +const FailedAssertion = Class("FailedAssertion", Error, { + init: function (message) { + this.message = message; + } +}); + +function wrapCallback(fn) + fn.wrapper = function wrappedCallback () { + try { + return fn.apply(this, arguments); + } + catch (e) { + util.reportError(e); + return undefined; + } + } + +const Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), { init: function () { this.Array = array; + + this.addObserver(this); + this.overlays = {}; }, - get activeWindow() services.get("windowWatcher").activeWindow, + // FIXME: Only works for Pentadactyl + get activeWindow() services.get("windowMediator").getMostRecentWindow("navigator:browser"), dactyl: { __noSuchMethod__: function (meth, args) { let win = util.activeWindow; @@ -61,6 +84,19 @@ const Util = Module("Util", { register("addObserver"); }, + /* + * Tests a condition and throws a FailedAssertion error on + * failure. + * + * @param {boolean} condition The condition to test. + * @param {string} message The message to present to the + * user on failure. + */ + assert: function (condition, message) { + if (!condition) + throw new FailedAssertion(message); + }, + /** * Calls a function synchronously in the main thread. Return values are not * preserved. @@ -644,6 +680,41 @@ const Util = Module("Util", { return color ? string : [s for each (s in string)].join(""); }, + observe: { + "toplevel-window-ready": function (window, data) { + window.addEventListener("DOMContentLoaded", wrapCallback(function listener(event) { + window.removeEventListener("DOMContentLoaded", listener.wrapper, true); + + if (event.originalTarget !== window.document) + return; + + let obj = util.overlays[window.document.documentURI]; + if (obj) { + obj = obj(window); + + function overlay(key, fn) { + for (let [elem, xml] in Iterator(obj[key] || {})) + if (elem = window.document.getElementById(elem)) + fn(elem, util.xmlToDom(xml, window.document)); + } + + overlay("before", function (elem, dom) elem.parentNode.insertBefore(dom, elem)); + overlay("after", function (elem, dom) elem.parentNode.insertBefore(dom, elem.nextSibling)); + overlay("append", function (elem, dom) elem.appendChild(dom)); + overlay("prepend", function (elem, dom) elem.insertBefore(dom, elem.firstChild)); + if (obj.init) + obj.init(window, event); + + if (obj.load) + window.document.addEventListener("load", function (event) { + if (event.originalTarget === event.target) + obj.load(window, event); + }, true); + } + }), true) + } + }, + /** * Parses the fields of a form and returns a URL/POST-data pair * that is the equivalent of submitting the form. @@ -756,6 +827,12 @@ const Util = Module("Util", { } }, + overlayWindow: function (url, fn) { + Array.concat(url).forEach(function (url) { + this.overlays[url] = fn; + }, this); + }, + /** * Scrolls an element into view if and only if it's not already * fully visible. @@ -882,6 +959,22 @@ const Util = Module("Util", { }, /** + * Traps errors in the called function, possibly reporting them. + * + * @param {function} func The function to call + * @param {object} self The 'this' object for the function. + */ + trapErrors: function trapErrors(func, self) { + try { + return func.apply(self || this, Array.slice(arguments, 2)); + } + catch (e) { + util.reportError(e); + return undefined; + } + }, + + /** * Converts an E4X XML literal to a DOM node. Any attribute named * highlight is present, it is transformed into dactyl:highlight, * and the named highlight groups are guaranteed to be loaded. @@ -927,6 +1020,309 @@ const Util = Module("Util", { Array: array }); +const Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), { + SAVED: "extensions.dactyl.saved.", + + init: function () { + this._prefContexts = []; + + util.addObserver(this); + this._branch = services.get("pref").getBranch("").QueryInterface(Ci.nsIPrefBranch2); + this._branch.addObserver("", this, false); + this._observers = {}; + }, + + observe: { + "nsPref:changed": function (subject, data) { + let observers = this._observers[data]; + if (observers) { + let value = this.get(data, false); + this._observers[data] = observers.filter(function (callback) { + if (!callback.get()) + return false; + util.trapErrors(callback.get(), null, value); + return true; + }); + } + } + }, + + /** + * Adds a new preference observer for the given preference. + * + * @param {string} pref The preference to observe. + * @param {function(object)} callback The callback, called with the + * new value of the preference whenever it changes. + */ + watch: function (pref, callback, strong) { + if (!this._observers[pref]) + this._observers[pref] = []; + this._observers[pref].push(!strong ? Cu.getWeakReference(callback) : { get: function () callback }); + }, + + /** + * Lists all preferences matching <b>filter</b> or only those with + * changed values if <b>onlyNonDefault</b> is specified. + * + * @param {boolean} onlyNonDefault Limit the list to prefs with a + * non-default value. + * @param {string} filter The list filter. A null filter lists all + * prefs. + * @optional + */ + list: function list(onlyNonDefault, filter) { + if (!filter) + filter = ""; + + let prefArray = this.getNames(); + prefArray.sort(); + function prefs() { + for (let [, pref] in Iterator(prefArray)) { + let userValue = services.get("pref").prefHasUserValue(pref); + if (onlyNonDefault && !userValue || pref.indexOf(filter) == -1) + continue; + + let value = this.get(pref); + + let option = { + isDefault: !userValue, + default: this._load(pref, null, true), + value: <>={template.highlight(value, true, 100)}</>, + name: pref, + pre: "\u00a0\u00a0" // Unicode nonbreaking space. + }; + + yield option; + } + }; + + return template.options(services.get("dactyl:").host + " Preferences", prefs.call(this)); + }, + + /** + * Returns the value of a preference. + * + * @param {string} name The preference name. + * @param {value} defaultValue The value to return if the preference + * is unset. + */ + get: function (name, defaultValue) { + return this._load(name, defaultValue); + }, + + /** + * Returns the default value of a preference + * + * @param {string} name The preference name. + * @param {value} defaultValue The value to return if the preference + * has no default value. + */ + getDefault: function (name, defaultValue) { + return this._load(name, defaultValue, true); + }, + + /** + * Returns the names of all preferences. + * + * @param {string} branch The branch in which to search preferences. + * @default "" + */ + getNames: function (branch) services.get("pref").getChildList(branch || "", { value: 0 }), + + _checkSafe: function (name, message, value) { + let curval = this._load(name, null, false); + if (arguments.length > 2 && curval === value) + return; + let defval = this._load(name, null, true); + let saved = this._load(this.SAVED + name); + + if (saved == null && curval != defval || curval != saved) { + let msg = "Warning: setting preference " + name + ", but it's changed from its default value."; + if (message) + msg += " " + message; + util.dactyl.echomsg(msg); + } + }, + + /** + * Resets the preference <b>name</b> to </b>value</b> but warns the user + * if the value is changed from its default. + * + * @param {string} name The preference name. + * @param {value} value The new preference value. + */ + safeReset: function (name, message) { + this._checkSafe(name, message); + this.reset(name); + this.reset(this.SAVED + name); + }, + + /** + * Sets the preference <b>name</b> to </b>value</b> but warns the user + * if the value is changed from its default. + * + * @param {string} name The preference name. + * @param {value} value The new preference value. + */ + safeSet: function (name, value, message) { + this._checkSafe(name, message, value); + this._store(name, value); + this._store(this.SAVED + name, value); + }, + + /** + * Sets the preference <b>name</b> to </b>value</b>. + * + * @param {string} name The preference name. + * @param {value} value The new preference value. + */ + set: function (name, value) { + this._store(name, value); + }, + + /** + * Resets the preference <b>name</b> to its default value. + * + * @param {string} name The preference name. + */ + reset: function (name) { + try { + services.get("pref").clearUserPref(name); + } + catch (e) {} // ignore - thrown if not a user set value + }, + + /** + * Toggles the value of the boolean preference <b>name</b>. + * + * @param {string} name The preference name. + */ + toggle: function (name) { + util.assert(services.get("pref").getPrefType(name) === Ci.nsIPrefBranch.PREF_BOOL, + "E488: Trailing characters: " + name + "!"); + this.set(name, !this.get(name)); + }, + + /** + * Pushes a new preference context onto the context stack. + * + * @see #withContext + */ + pushContext: function () { + this._prefContexts.push({}); + }, + + /** + * Pops the top preference context from the stack. + * + * @see #withContext + */ + popContext: function () { + for (let [k, v] in Iterator(this._prefContexts.pop())) + this._store(k, v); + }, + + /** + * Executes <b>func</b> with a new preference context. When <b>func</b> + * returns, the context is popped and any preferences set via + * {@link #setPref} or {@link #invertPref} are restored to their + * previous values. + * + * @param {function} func The function to call. + * @param {Object} func The 'this' object with which to call <b>func</b> + * @see #pushContext + * @see #popContext + */ + withContext: function (func, self) { + try { + this.pushContext(); + return func.call(self); + } + finally { + this.popContext(); + } + }, + + _store: function (name, value) { + if (this._prefContexts.length) { + let val = this._load(name, null); + if (val != null) + this._prefContexts[this._prefContexts.length - 1][name] = val; + } + + function assertType(needType) + util.assert(type === Ci.nsIPrefBranch.PREF_INVALID || type === needType, + type === Ci.nsIPrefBranch.PREF_INT + ? "E521: Number required after =: " + name + "=" + value + : "E474: Invalid argument: " + name + "=" + value); + + let type = services.get("pref").getPrefType(name); + switch (typeof value) { + case "string": + assertType(Ci.nsIPrefBranch.PREF_STRING); + + let supportString = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString); + supportString.data = value; + services.get("pref").setComplexValue(name, Ci.nsISupportsString, supportString); + break; + case "number": + assertType(Ci.nsIPrefBranch.PREF_INT); + + services.get("pref").setIntPref(name, value); + break; + case "boolean": + assertType(Ci.nsIPrefBranch.PREF_BOOL); + + services.get("pref").setBoolPref(name, value); + break; + default: + throw FailedAssertion("Unknown preference type: " + typeof value + " (" + name + "=" + value + ")"); + } + }, + + _load: function (name, defaultValue, defaultBranch) { + if (defaultValue == null) + defaultValue = null; + + let branch = defaultBranch ? services.get("pref").getDefaultBranch("") : services.get("pref"); + let type = services.get("pref").getPrefType(name); + try { + switch (type) { + case Ci.nsIPrefBranch.PREF_STRING: + let value = branch.getComplexValue(name, Ci.nsISupportsString).data; + // try in case it's a localized string (will throw an exception if not) + if (!services.get("pref").prefIsLocked(name) && !services.get("pref").prefHasUserValue(name) && + RegExp("chrome://.+/locale/.+\\.properties").test(value)) + value = branch.getComplexValue(name, Ci.nsIPrefLocalizedString).data; + return value; + case Ci.nsIPrefBranch.PREF_INT: + return branch.getIntPref(name); + case Ci.nsIPrefBranch.PREF_BOOL: + return branch.getBoolPref(name); + default: + return defaultValue; + } + } + catch (e) { + return defaultValue; + } + } +}, { +}, { + completion: function (dactyl, modules) { + modules.completion.preference = function preference(context) { + context.anchored = false; + context.title = [services.get("dactyl:").host + " Preference", "Value"]; + context.keys = { text: function (item) item, description: function (item) prefs.get(item) }; + context.completions = prefs.getNames(); + }; + }, + javascript: function (dactyl, modules) { + modules.JavaScript.setCompleter([this.get, this.safeSet, this.set, this.reset, this.toggle], + [function (context) (context.anchored=false, prefs.getNames().map(function (pref) [pref, ""]))]); + } +}); + /** * Math utility methods. * @singleton @@ -945,8 +1341,8 @@ var Math = update(Object.create(GlobalMath), { constrain: function constrain(value, min, max) Math.min(Math.max(min, value), max) }); -// catch(e){dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack);} - endModule(); +} catch(e){dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack);} + // vim: set fdm=marker sw=4 ts=4 et ft=javascript: diff --git a/pentadactyl/NEWS b/pentadactyl/NEWS index 57f59f4b..f8c7b909 100644 --- a/pentadactyl/NEWS +++ b/pentadactyl/NEWS @@ -8,6 +8,13 @@ * Significant completion speed improvements, especially for JavaScript. * Greatly improved private mode support and :sanitize command. + - Full integration with Firefox data clearing dialogs. + - Support for sanitizing items at shutdown. + - Fine-grained control over what data is sanitized and for + what timespan. + - Support for sanitizing reference to particular hosts, + everywhere from command-line and message history to option + values and cookies. * New and much more powerful incremental search implementation. Improvements over the standard Firefox find include: - Starts at the cursor position in the currently selected |