mirror of
https://github.com/gryf/pentadactyl-pm.git
synced 2025-12-23 07:17:59 +01:00
Integrate sanitizer with host UI, sanitize at shutdown support, and control which items are sanitized when more thoroughly. Closes issue #70.
This commit is contained in:
@@ -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.
|
||||
@@ -881,6 +958,22 @@ const Util = Module("Util", {
|
||||
while (flush === true && mainThread.hasPendingEvents());
|
||||
},
|
||||
|
||||
/**
|
||||
* 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,
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user