1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2026-02-10 20:35:46 +01:00

Complete :sanitize and private mode overhaul.

--HG--
rename : common/content/sanitizer.js => common/modules/sanitizer.jsm
This commit is contained in:
Kris Maglione
2010-09-17 06:15:13 -04:00
parent a5213c3760
commit 152e6d5a1f
27 changed files with 1120 additions and 721 deletions

View File

@@ -66,11 +66,12 @@ defmodule("base", this, {
// sed -n 's/^(const|function) ([a-zA-Z0-9_]+).*/ "\2",/p' base.jsm | sort | fmt
exports: [
"Cc", "Ci", "Class", "Cr", "Cu", "Module", "Object", "Runnable",
"Struct", "StructBase", "Timer", "allkeys", "array", "call",
"callable", "curry", "debuggerProperties", "defmodule", "dict",
"Struct", "StructBase", "Timer", "XPCOMUtils", "allkeys", "array",
"call", "callable", "curry", "debuggerProperties", "defmodule", "dict",
"endmodule", "extend", "foreach", "isarray", "isgenerator",
"isinstance", "isobject", "isstring", "issubclass", "iter", "memoize",
"properties", "requiresMainThread", "set", "update", "values",
"isinstance", "isobject", "isstring", "issubclass", "iter", "keys",
"memoize", "properties", "requiresMainThread", "set", "update",
"values",
],
use: ["services"]
});
@@ -117,8 +118,9 @@ function debuggerProperties(obj) {
}
}
let hasOwnProperty = Object.prototype.hasOwnProperty;
if (!Object.keys)
Object.keys = function keys(obj) [k for (k in obj) if (obj.hasOwnProperty(k))];
Object.keys = function keys(obj) [k for (k in obj) if (hasOwnProperty.call(obj, k))];
if (!Object.getOwnPropertyNames)
Object.getOwnPropertyNames = function getOwnPropertyNames(obj) {
@@ -144,9 +146,14 @@ function properties(obj, prototypes) {
}
}
function keys(obj) {
for (var k in obj)
if (hasOwnProperty.call(obj, k))
yield k;
}
function values(obj) {
for (var k in obj)
if (obj.hasOwnProperty(k))
if (hasOwnProperty.call(obj, k))
yield obj[k];
}
function foreach(iter, fn, self) {
@@ -175,7 +182,7 @@ set.add = function (set, key) {
set[key] = true;
return res;
}
set.has = function (set, key) Object.prototype.hasOwnProperty.call(set, key);
set.has = function (set, key) hasOwnProperty.call(set, key);
set.remove = function (set, key) { delete set[key]; }
function iter(obj) {
@@ -204,6 +211,12 @@ function iter(obj) {
for (let i = 0; i < obj.length; i++)
yield [obj.name, obj];
})();
if (obj instanceof Ci.mozIStorageStatement)
return (function (obj) {
while (obj.executeStep())
yield obj.row;
obj.reset();
})(obj);
return Iterator(obj);
}
@@ -542,15 +555,31 @@ Class.prototype = {
* @param {Object} classProperties Properties to be applied to the class constructor.
* @return {Class}
*/
function Module(name, prototype, classProperties, init) {
const module = Class(name, prototype, classProperties);
function Module(name, prototype) {
let init = callable(prototype) ? 4 : 3;
const module = Class.apply(Class, Array.slice(arguments, 0, init));
let instance = module();
module.name = name.toLowerCase();
instance.INIT = init || {};
instance.INIT = arguments[init] || {};
currentModule[module.name] = instance;
defmodule.modules.push(instance);
return module;
}
if (Cu.getGlobalForObject)
Module.callerGlobal = function (caller) {
try {
return Cu.getGlobalForObject(caller);
}
catch (e) {
return null;
}
};
else
Module.callerGlobal = function (caller) {
while (caller.__parent__)
caller = caller.__parent__;
return caller;
};
/**
* @class Struct
@@ -677,7 +706,7 @@ const Timer = Class("Timer", {
*/
const array = Class("util.Array", Array, {
init: function (ary) {
if (isgenerator(ary))
if (isinstance(ary, ["Iterator", "Generator"]))
ary = [k for (k in ary)];
else if (ary.length)
ary = Array.slice(ary);
@@ -688,12 +717,13 @@ const array = Class("util.Array", Array, {
__noSuchMethod__: function (meth, args) {
var res = array[meth].apply(null, [this.__proto__].concat(args));
if (array.isinstance(res))
if (isarray(res))
return array(res);
return res;
},
toString: function () this.__proto__.toString(),
concat: function () this.__proto__.concat.apply(this.__proto__, arguments),
filter: function () this.__noSuchMethod__("filter", Array.slice(arguments)),
map: function () this.__noSuchMethod__("map", Array.slice(arguments))
};
}

View File

@@ -52,7 +52,6 @@ const Highlights = Module("Highlight", {
return "Unknown highlight keyword: " + class_;
let style = this.highlight[key] || Highlight(key);
styles.removeSheet(true, style.selector);
if (append)
newStyle = (style.value || "").replace(/;?\s*$/, "; " + newStyle);
@@ -60,22 +59,23 @@ const Highlights = Module("Highlight", {
newStyle = null;
if (newStyle == null) {
if (style.default == null) {
delete this.highlight[style.class];
styles.removeSheet(true, style.selector);
delete this.highlight[style.class];
return null;
}
newStyle = style.default;
force = true;
}
let css = newStyle.replace(/(?:!\s*important\s*)?(?:;?\s*$|;)/g, "!important;")
.replace(";!important;", ";", "g"); // Seeming Spidermonkey bug
if (!/^\s*(?:!\s*important\s*)?;*\s*$/.test(css)) {
css = style.selector + " { " + css + " }";
if (!style.loaded || style.value != newStyle) {
styles.removeSheet(true, style.selector);
let css = newStyle.replace(/(?:!\s*important\s*)?(?:;?\s*$|;)/g, "!important;")
.replace(";!important;", ";", "g"); // Seeming Spidermonkey bug
if (!/^\s*(?:!\s*important\s*)?;*\s*$/.test(css)) {
css = style.selector + " { " + css + " }";
let error = styles.addSheet(true, "highlight:" + style.class, style.filter, css, true);
if (error)
return error;
styles.addSheet(true, "highlight:" + style.class, style.filter, css, true);
style.loaded = true;
}
}
style.value = newStyle;
this.highlight[style.class] = style;
@@ -120,9 +120,10 @@ const Highlights = Module("Highlight", {
style.selector = this.selector(style.class) + style.selector;
let old = this.highlight[style.class];
this.highlight[style.class] = style;
if (old && old.value != old.default)
style.value = old.value;
if (!old)
this.highlight[style.class] = style;
else if (old.value == old.default)
old.value = style.value;
}, this);
for (let [class_, hl] in Iterator(this.highlight))
if (hl.value == hl.default)

View File

@@ -0,0 +1,330 @@
// Copyright (c) 2009 by Doug Kearns <dougkearns@gmail.com>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
"use strict";
// TODO:
// - fix Sanitize autocommand
// - add warning for TIMESPAN_EVERYTHING?
// - respect privacy.clearOnShutdown et al or recommend Leave autocommand?
// - integrate with the Clear Private Data dialog?
// FIXME:
// - finish 1.9.0 support if we're going to support sanitizing in Xulmus
Components.utils.import("resource://dactyl/base.jsm");
defmodule("sanitizer", this, {
exports: ["Range", "Sanitizer", "sanitizer"],
require: ["services", "storage", "util"]
});
let tmp = {};
services.get("subscriptLoader").loadSubScript("chrome://browser/content/sanitize.js", tmp);
const Range = Struct("min", "max");
Range.prototype.contains = function (date)
date == null || (this.min == null || date >= this.min) && (this.max == null || date <= this.max);
Range.prototype.__defineGetter__("isEternity", function () this.max == null && this.min == null);
Range.prototype.__defineGetter__("isSession", function () this.max == null && this.min == sanitizer.sessionStart);
const Sanitizer = Module("sanitizer", tmp.Sanitizer, {
sessionStart: Date.now() * 1000,
init: function () {
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",
};
// These builtin methods don't support hosts or have
// insufficient granularity
this.addItem("cookies", {
description: "Cookies",
action: function (range, host) {
for (let c in Sanitizer.iterCookies(host))
if (range.contains(c.creationTime) || timespan.isSession && c.isSession)
services.get("cookies").remove(c.host, c.name, c.path, false);
},
override: true
});
this.addItem("sitesettings", {
description: "Site preferences",
action: function (range, host) {
if (host) {
for (let p in Sanitizer.iterPermissions(host)) {
services.get("permissions").remove(util.createURI(p.host), p.type);
services.get("permissions").add(util.createURI(p.host), p.type, 0);
}
for (let p in iter(services.get("contentprefs").getPrefs(util.createURI(host)).enumerator))
services.get("contentprefs").removePref(util.createURI(host), p.QueryInterface(Ci.nsIProperty).name);
}
else {
// "Allow this site to open popups" ...
services.get("permissions").removeAll();
// Zoom level, ...
services.get("contentprefs").removeGroupedPrefs();
}
// "Never remember passwords" ...
for each (let domain in services.get("loginmanager").getAllDisabledHosts())
if (!host || util.isSubdomain(domain, host))
services.get("loginmanager").setLoginSavingEnabled(host, true);
},
override: true
});
util.addObserver(this);
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
addItem: function addItem(name, params) {
if (params.description)
this.itemDescriptions[name] = params.description;
if (params.override)
set.add(this.itemOverrides, name);
name = "clear-" + name;
storage.addObserver("sanitizer",
function (key, event, arg) {
if (event == name)
params.action.apply(params, arg);
}, Module.callerGlobal(params.action));
if (params.privateEnter || params.privateLeave)
storage.addObserver("private-mode",
function (key, event, arg) {
let meth = params[arg ? "privateEnter" : "privateLeave"];
if (meth)
meth.call(params);
}, Module.callerGlobal(params.action));
},
observe: {
"browser:purge-domain-data": function (subject, data) {
storage.fireEvent("sanitize", "domain", data);
// 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.
if (!this.sanitizing)
this.sanitizeItems(null, Range(), data);
},
"browser:purge-session-history": function (subject, data) {
// See above.
if (!this.sanitizing)
this.sanitizeItems(null, Range(this.sessionStart/1000), null);
},
"private-browsing": function (subject, data) {
if (data == "enter")
storage.privateMode = true;
else if (data == "exit")
storage.privateMode = false;
storage.fireEvent("private-mode", "change", storage.privateMode);
}
},
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();
}
}
catch (e) {
errors = errors || {};
errors[itemName] = e;
dump("Error sanitizing " + itemName + ": " + e + "\n" + e.stack + "\n");
}
}
this.sanitizing = false;
return errors;
},
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;
dump("Error sanitizing " + itemName + ": " + e + "\n" + e.stack + "\n");
}
return errors;
}
}, {
argPrefMap: {
commandline: "commandLine",
offlineapps: "offlineApps",
sitesettings: "siteSettings",
},
argToPref: function (arg) Sanitizer.argPrefMap[arg] || arg,
prefToArg: function (pref) pref.replace(/.*\./, "").toLowerCase(),
iterCookies: function iterCookies(host) {
for (let c in iter(services.get("cookies").enumerator))
if (!host || util.isSubdomain(c.QueryInterface(Ci.nsICookie2).rawHost, host))
yield c;
},
iterPermissions: function iterPermissions(host) {
for (let p in iter(services.get("permissions").enumerator))
if (p.QueryInterface(Ci.nsIPermission) && (!host || util.isSubdomain(p.host, host)))
yield p;
}
}, {
autocommands: function (dactyl, modules, window) {
storage.addObserver("private-mode",
function (key, event, value) {
modules.autocommands.trigger("PrivateMode", { state: value });
}, window);
storage.addObserver("sanitizer",
function (key, event, value) {
if (event == "domain")
modules.autocommands.trigger("SanitizeDomain", { domain: value });
else if (!value[1])
modules.autocommands.trigger("Sanitize", { name: event.substr("clear-".length), domain: value[1] });
}, window);
},
commands: function (dactyl, modules, window) {
const commands = modules.commands;
commands.add(["sa[nitize]"],
"Clear private data",
function (args) {
dactyl.assert(!modules.options['private'], "Cannot sanitize items in private mode");
let timespan = args["-timespan"] || modules.options["sanitizetimespan"];
let range = Range(), match = /^(\d+)([mhdw])$/.exec(timespan);
range[args["-older"] ? "max" : "min"] =
match ? 1000 * (Date.now() - 1000 * parseInt(match[1], 10) * { m: 60, h: 3600, d: 3600 * 24, w: 3600 * 24 * 7 }[match[2]])
: (timespan[0] == "s" ? sanitizer.sessionStart : null);
let items = args.slice();
if (args.bang) {
dactyl.assert(args.length == 0, "E488: Trailing characters");
items = modules.options.get("sanitizeitems").values;
}
else
dactyl.assert(modules.options.get("sanitizeitems").validator(items), "Valid items required");
if (items[0] == "all")
items = Object.keys(sanitizer.itemDescriptions);
sanitizer.range = range;
sanitizer.ignoreTimespan = range.min == null;
sanitizer.sanitizing = true;
if (args["-host"]) {
args["-host"].forEach(function (host) {
sanitizer.sanitizing = true;
if (items.indexOf("history") > -1)
services.get("privateBrowsing").removeDataFromDomain(host);
sanitizer.sanitizeItems(items, range, host)
});
}
else
sanitizer.sanitize(items, range);
},
{
argCount: "*", // FIXME: should be + and 0
bang: true,
completer: function (context) {
context.title = ["Privacy Item", "Description"];
context.completions = modules.options.get("sanitizeitems").completer();
},
domains: function (args) args["-host"] || [],
options: [
{
names: ["-host", "-h"],
description: "Only sanitize items referring to listed host or hosts",
completer: function (context, args) {
let hosts = context.filter.split(",");
context.advance(context.filter.length - hosts.pop().length);
context.filters.push(function (item)
!hosts.some(function (host) util.isSubdomain(item.text, host)));
modules.completion.domain(context);
},
type: modules.CommandOption.LIST,
}, {
names: ["-older", "-o"],
description: "Sanitize items older than timespan",
type: modules.CommandOption.NOARG
}, {
names: ["-timespan", "-t"],
description: "Timespan for which to sanitize items",
completer: function (context) modules.options.get("sanitizetimespan").completer(context),
type: modules.CommandOption.STRING,
validator: function (arg) modules.options.get("sanitizetimespan").validator(arg),
}
],
privateData: true
});
},
options: function (dactyl, modules) {
const options = modules.options;
if (services.get("privateBrowsing"))
options.add(["private", "pornmode"],
"Set the 'private browsing' option",
"boolean", false,
{
setter: function (value) services.get("privateBrowsing").privateBrowsingEnabled = value,
getter: function () services.get("privateBrowsing").privateBrowsingEnabled
});
options.add(["sanitizeitems", "si"],
"The default list of private items to sanitize",
"stringlist", "all",
{
completer: function (value) Iterator(sanitizer.itemDescriptions),
validator: function (values) values.length &&
values.every(function (val) set.has(sanitizer.itemDescriptions, val)) &&
(values.length == 1 || !values.some(function (val) val == "all"))
});
options.add(["sanitizetimespan", "sts"],
"The default sanitizer time span",
"string", "all",
{
completer: function (context) {
context.compare = context.constructor.Sort.Unsorted;
return [
["all", "Everything"],
["session", "The current session"],
["10m", "Last ten minutes"],
["1h", "Past hour"],
["1d", "Past day"],
["1w", "Past week"],
]
},
validator: function (value) /^(a(ll)?|s(ession)|\d+[mhdw])$/.test(value)
});
}
});
endmodule();
// catch(e){dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack);}
// vim: set fdm=marker sw=4 ts=4 et ft=javascript:

View File

@@ -27,12 +27,14 @@ const Services = Module("Services", {
this.add("environment", "@mozilla.org/process/environment;1", Ci.nsIEnvironment);
this.add("extensionManager", "@mozilla.org/extensions/manager;1", Ci.nsIExtensionManager);
this.add("favicon", "@mozilla.org/browser/favicon-service;1", Ci.nsIFaviconService);
this.add("history", "@mozilla.org/browser/global-history;2", [Ci.nsIBrowserHistory, Ci.nsIGlobalHistory3, Ci.nsINavHistoryService]);
this.add("history", "@mozilla.org/browser/global-history;2", [Ci.nsIBrowserHistory, Ci.nsIGlobalHistory3,
Ci.nsINavHistoryService, Ci.nsPIPlacesDatabase]);
this.add("io", "@mozilla.org/network/io-service;1", Ci.nsIIOService);
this.add("json", "@mozilla.org/dom/json;1", Ci.nsIJSON, "createInstance");
this.add("livemark", "@mozilla.org/browser/livemark-service;2", Ci.nsILivemarkService);
this.add("observer", "@mozilla.org/observer-service;1", Ci.nsIObserverService);
this.add("pref", "@mozilla.org/preferences-service;1", [Ci.nsIPrefBranch, Ci.nsIPrefBranch2, Ci.nsIPrefService]);
this.add("privateBrowsing", "@mozilla.org/privatebrowsing;1", Ci.nsIPrivateBrowsingService);
this.add("profile", "@mozilla.org/toolkit/profile-service;1", Ci.nsIToolkitProfileService);
this.add("runtime", "@mozilla.org/xre/runtime;1", [Ci.nsIXULAppInfo, Ci.nsIXULRuntime]);
this.add("rdf", "@mozilla.org/rdf/rdf-service;1", Ci.nsIRDFService);
@@ -41,9 +43,9 @@ const Services = Module("Services", {
this.add("subscriptLoader", "@mozilla.org/moz/jssubscript-loader;1", Ci.mozIJSSubScriptLoader);
this.add("tagging", "@mozilla.org/browser/tagging-service;1", Ci.nsITaggingService);
this.add("threadManager", "@mozilla.org/thread-manager;1", Ci.nsIThreadManager);
this.add("urifixup", "@mozilla.org/docshell/urifixup;1", Ci.nsIURIFixup);
this.add("windowMediator", "@mozilla.org/appshell/window-mediator;1", Ci.nsIWindowMediator);
this.add("windowWatcher", "@mozilla.org/embedcomp/window-watcher;1", Ci.nsIWindowWatcher);
this.add("xulAppInfo", "@mozilla.org/xre/app-info;1", Ci.nsIXULAppInfo);
this.addClass("file", "@mozilla.org/file/local;1", Ci.nsILocalFile);
this.addClass("file:", "@mozilla.org/network/protocol;1?name=file", Ci.nsIFileProtocolHandler);
@@ -128,6 +130,6 @@ const Services = Module("Services", {
endmodule();
// catch(e){dump(e.fileName+":"+e.lineNumber+": "+e+"\n");}
// catch(e){dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack);}
// vim: set fdm=marker sw=4 sts=4 et ft=javascript:

View File

@@ -21,6 +21,7 @@
}}} ***** END LICENSE BLOCK *****/
"use strict";
const myObject = Object;
Components.utils.import("resource://dactyl/base.jsm");
defmodule("storage", this, {
exports: ["File", "storage"],
@@ -76,9 +77,9 @@ function savePref(obj) {
}
const StoreBase = Class("StoreBase", {
OPTIONS: ["privateData"],
OPTIONS: ["privateData", "replacer"],
fireEvent: function (event, arg) { storage.fireEvent(this.name, event, arg); },
get serial() services.get("json").encode(this._object),
get serial() JSON.stringify(this._object, this.replacer),
save: function () { savePref(this); },
init: function (name, store, load, options) {
this._load = load;
@@ -97,7 +98,7 @@ const StoreBase = Class("StoreBase", {
});
const ObjectStore = Class("ObjectStore", StoreBase, {
_constructor: Object,
_constructor: myObject,
set: function set(key, val) {
var defined = key in this._object;
@@ -120,6 +121,7 @@ const ObjectStore = Class("ObjectStore", StoreBase, {
clear: function () {
this._object = {};
this.fireEvent("clear", key);
},
__iterator__: function () Iterator(this._object),
@@ -181,7 +183,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 || Object);
let load = function () loadPref(key, params.store, params.type || myObject);
keys[key] = new constructor(key, params.store, load, params);
timers[key] = new Timer(1000, 10000, function () storage.save(key));
this.__defineGetter__(key, function () keys[key]);
@@ -234,14 +236,13 @@ const Storage = Module("Storage", {
get observers() observers,
fireEvent: function fireEvent(key, event, arg) {
if (!(key in this))
return;
this.removeDeadObservers();
// Safe, since we have our own Array object here.
if (key in observers)
for each (let observer in observers[key])
observer.callback.get()(key, event, arg);
timers[key].tell();
if (timers[key])
timers[key].tell();
},
load: function load(key) {
@@ -261,6 +262,8 @@ const Storage = Module("Storage", {
_privateMode: false,
get privateMode() this._privateMode,
set privateMode(val) {
if (val && !this._privateMode)
this.saveAll();
if (!val && this._privateMode)
for (let key in keys)
this.load(key);

View File

@@ -31,6 +31,13 @@ Sheet.prototype.__defineGetter__("fullCSS", function wrapCSS() {
.join(", ");
return "/* Dactyl style #" + this.id + " */ " + namespace + " @-moz-document " + selectors + "{\n" + css + "\n}\n";
});
Sheet.prototype.__defineGetter__("css", function () this[3]);
Sheet.prototype.__defineSetter__("css", function (val) {
this.enabled = false;
this[3] = val;
this.enabled = true;
return val;
});
Sheet.prototype.__defineGetter__("enabled", function () this._enabled);
Sheet.prototype.__defineSetter__("enabled", function (on) {
this._enabled = Boolean(on);

View File

@@ -33,6 +33,41 @@ const Util = Module("Util", {
}
},
/**
* Registers a obj as a new observer with the observer service. obj.observe
* must be an object where each key is the name of a target to observe and
* each value is a function(subject, data) to be called when the given
* target is broadcast. obj.observe will be replaced with a new opaque
* function. The observer is automatically unregistered on application
* shutdown.
*
* @param {object} obj
*/
addObserver: function (obj) {
let observers = obj.observe;
function register(meth) {
services.get("observer")[meth](obj, "quit-application", true);
for (let target in keys(observers))
services.get("observer")[meth](obj, target, true);
}
Class.replaceProperty(obj, "observe",
function (subject, target, data) {
if (target == "quit-application")
register("removeObserver");
if (observers[target])
observers[target].call(obj, subject, data);
});
register("addObserver");
},
/**
* Calls a function synchronously in the main thread. Return values are not
* preserved.
*
* @param {function} callback
* @param {object} self The this object for the call.
* @returns {function}
*/
callInMainThread: function (callback, self) {
let mainThread = services.get("threadManager").mainThread;
if (services.get("threadManager").isMainThread)
@@ -123,30 +158,19 @@ const Util = Module("Util", {
},
/**
* Copies a string to the system clipboard. If <b>verbose</b> is specified
* the copied string is also echoed to the command line.
* Converts any arbitrary string into an URI object. Returns null on
* failure.
*
* @param {string} str
* @param {boolean} verbose
* @returns {nsIURI|null}
*/
copyToClipboard: function copyToClipboard(str, verbose) {
const clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
clipboardHelper.copyString(str);
if (verbose)
util.dactyl.echomsg("Yanked " + str);
},
/**
* Converts any arbitrary string into an URI object.
*
* @param {string} str
* @returns {Object}
*/
// FIXME: newURI needed too?
createURI: function createURI(str) {
const fixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup);
return fixup.createFixupURI(str, fixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP);
try {
return services.get("urifixup").createFixupURI(str, services.get("urifixup").FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP);
}
catch (e) {
return null;
}
},
/**
@@ -336,6 +360,20 @@ const Util = Module("Util", {
return strNum[0] + " " + unitVal[unitIndex];
},
/**
* Returns the host for the given URL, or null if invalid.
*
* @param {string} url
* @returns {string|null}
*/
getHost: function (url) {
try {
return util.createURI(url).host;
}
catch (e) {}
return null;
},
/**
* Sends a synchronous or asynchronous HTTP request to <b>url</b> and
* returns the XMLHttpRequest object. If <b>callback</b> is specified the
@@ -390,6 +428,29 @@ const Util = Module("Util", {
bottom: Math.min(r1.bottom, r2.bottom)
}),
/**
* Returns true if 'url' is in the domain 'domain'.
*
* @param {string} url
* @param {string} domain
* @returns {boolean}
*/
isDomainURL: function isDomainURL(url, domain) util.isSubdomain(util.getHost(url), domain),
/**
* Returns true if 'host' is a subdomain of 'domain'.
*
* @param {string} host The host to check.
* @param {string} domain The base domain to check the host agains.
* @returns {boolean}
*/
isSubdomain: function isSubdomain(host, domain) {
if (host == null)
return false;
let idx = host.lastIndexOf(domain);
return idx > -1 && idx + domain.length == host.length && (idx == 0 || host[idx-1] == ".");
},
/**
* Returns an XPath union expression constructed from the specified node
* tests. An expression is built with node tests for both the null and
@@ -589,43 +650,6 @@ const Util = Module("Util", {
}
},
/**
* Reads a string from the system clipboard.
*
* This is same as Firefox's readFromClipboard function, but is needed for
* apps like Thunderbird which do not provide it.
*
* @returns {string}
*/
readFromClipboard: function readFromClipboard() {
let str;
try {
const clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
const transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
transferable.addDataFlavor("text/unicode");
if (clipboard.supportsSelectionClipboard())
clipboard.getData(transferable, clipboard.kSelectionClipboard);
else
clipboard.getData(transferable, clipboard.kGlobalClipboard);
let data = {};
let dataLen = {};
transferable.getTransferData("text/unicode", data, dataLen);
if (data) {
data = data.value.QueryInterface(Ci.nsISupportsString);
str = data.data.substring(0, dataLen.value / 2);
}
}
catch (e) {}
return str;
},
/**
* Scrolls an element into view if and only if it's not already
* fully visible.