mirror of
https://github.com/gryf/pentadactyl-pm.git
synced 2026-01-05 03:24:13 +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:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
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)),
|
||||
|
||||
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();
|
||||
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"],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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