1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2025-12-21 07:07:58 +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

@@ -280,6 +280,7 @@ const Bookmarks = Module("bookmarks", {
names: ["-tags", "-T"],
description: "A comma-separated list of tags",
completer: function tags(context, args) {
// TODO: Move the bulk of this to parseArgs.
let filter = context.filter;
let have = filter.split(",");

View File

@@ -52,45 +52,6 @@ const Browser = Module("browser", {
completer: function (context) completion.charset(context)
});
// only available in FF 3.5
services.add("privateBrowsing", "@mozilla.org/privatebrowsing;1", Ci.nsIPrivateBrowsingService);
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
});
let services = modules.services; // Storage objects are global to all windows, 'modules' isn't.
storage.newObject("private-mode", function () {
({
init: function () {
services.get("observer").addObserver(this, "private-browsing", false);
services.get("observer").addObserver(this, "quit-application", false);
this.private = services.get("privateBrowsing").privateBrowsingEnabled;
},
observe: function (subject, topic, data) {
if (topic == "private-browsing") {
if (data == "enter")
storage.privateMode = true;
else if (data == "exit")
storage.privateMode = false;
storage.fireEvent("private-mode", "change", storage.privateMode);
}
else if (topic == "quit-application") {
services.get("observer").removeObserver(this, "quit-application");
services.get("observer").removeObserver(this, "private-browsing");
}
}
}).init();
}, { store: false });
storage.addObserver("private-mode",
function (key, event, value) {
autocommands.trigger("PrivateMode", { state: value });
}, window);
}
options.add(["urlseparator"],
"Set the separator regex used to separate multiple URL args",
"string", ",\\s");
@@ -99,7 +60,7 @@ const Browser = Module("browser", {
mappings: function () {
mappings.add([modes.NORMAL],
["y"], "Yank current location to the clipboard",
function () { util.copyToClipboard(buffer.URL, true); });
function () { dactyl.clipboardWrite(buffer.URL, true); });
// opening websites
mappings.add([modes.NORMAL],
@@ -218,6 +179,8 @@ const Browser = Module("browser", {
dactyl.open("about:blank");
}, {
completer: function (context) completion.url(context),
domains: function (args) array.compact(dactyl.stringToURLArray(args[0] || "").map(
function (url) util.getHost(url))),
literal: 0,
privateData: true
});

View File

@@ -1517,14 +1517,14 @@ const Buffer = Module("buffer", {
mappings.add(myModes, ["gP"],
"Open (put) a URL based on the current clipboard contents in a new buffer",
function () {
dactyl.open(util.readFromClipboard(),
dactyl.open(dactyl.clipboardRead(),
dactyl[options.get("activate").has("paste") ? "NEW_BACKGROUND_TAB" : "NEW_TAB"]);
});
mappings.add(myModes, ["p", "<MiddleMouse>"],
"Open (put) a URL based on the current clipboard contents in the current buffer",
function () {
let url = util.readFromClipboard();
let url = dactyl.clipboardRead();
dactyl.assert(url);
dactyl.open(url);
});
@@ -1532,7 +1532,7 @@ const Buffer = Module("buffer", {
mappings.add(myModes, ["P"],
"Open (put) a URL based on the current clipboard contents in a new buffer",
function () {
let url = util.readFromClipboard();
let url = dactyl.clipboardRead();
dactyl.assert(url);
dactyl.open(url, { from: "paste", where: dactyl.NEW_TAB });
});
@@ -1552,7 +1552,7 @@ const Buffer = Module("buffer", {
function () {
let sel = buffer.getCurrentWord();
dactyl.assert(sel);
util.copyToClipboard(sel, true);
dactyl.clipboardWrite(sel, true);
});
// zooming

View File

@@ -23,36 +23,6 @@ const CommandLine = Module("commandline", {
storage.newArray("history-search", { store: true, privateData: true });
storage.newArray("history-command", { store: true, privateData: true });
// Really inideal.
let services = modules.services; // Storage objects are global to all windows, 'modules' isn't.
storage.newObject("sanitize", function () {
({
CLEAR: "browser:purge-session-history",
QUIT: "quit-application",
init: function () {
services.get("observer").addObserver(this, this.CLEAR, false);
services.get("observer").addObserver(this, this.QUIT, false);
},
observe: function (subject, topic, data) {
switch (topic) {
case this.CLEAR:
["search", "command"].forEach(function (mode) {
CommandLine.History(null, mode).sanitize();
});
break;
case this.QUIT:
services.get("observer").removeObserver(this, this.CLEAR);
services.get("observer").removeObserver(this, this.QUIT);
break;
}
}
}).init();
}, { store: false });
storage.addObserver("sanitize",
function (key, event, value) {
autocommands.trigger("Sanitize", {});
}, window);
this._messageHistory = { //{{{
_messages: [],
get messages() {
@@ -71,6 +41,10 @@ const CommandLine = Module("commandline", {
this._messages = [];
},
filter: function filter(fn, self) {
this._messages = this._messages.filter(fn, self);
},
add: function add(message) {
if (!message)
return;
@@ -78,7 +52,9 @@ const CommandLine = Module("commandline", {
if (this._messages.length >= options["messages"])
this._messages.shift();
this._messages.push(message);
this._messages.push(update({
timestamp: Date.now()
}, message));
}
}; //}}}
@@ -103,21 +79,25 @@ const CommandLine = Module("commandline", {
});
this._autocompleteTimer = Timer(200, 500, function autocompleteTell(tabPressed) {
dactyl.trapErrors(function () {
if (!events.feedingKeys && self._completions && options.get("autocomplete").values.length) {
self._completions.complete(true, false);
if (self._completions)
self._completions.itemList.show();
}
});
});
// This timer just prevents <Tab>s from queueing up when the
// system is under load (and, thus, giving us several minutes of
// the completion list scrolling). Multiple <Tab> presses are
// still processed normally, as the timer is flushed on "keyup".
this._tabTimer = Timer(0, 0, function tabTell(event) {
dactyl.trapErrors(function () {
if (self._completions)
self._completions.tab(event.shiftKey);
});
});
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// VARIABLES ///////////////////////////////////////////////
@@ -213,7 +193,7 @@ const CommandLine = Module("commandline", {
*
* @returns {boolean}
*/
_commandShown: function () modes.main == modes.COMMAND_LINE &&
get commandVisible() modes.main == modes.COMMAND_LINE &&
!(modes.extended & (modes.INPUT_MULTILINE | modes.OUTPUT_MULTILINE)),
/**
@@ -255,7 +235,7 @@ const CommandLine = Module("commandline", {
dactyl.triggerObserver("echoLine", str, highlightGroup, forceSingle);
if (!this._commandShown())
if (!this.commandVisible)
commandline.hide();
let field = this.widgets.message.inputField;
@@ -347,9 +327,9 @@ const CommandLine = Module("commandline", {
get quiet() this._quiet,
set quiet(val) {
this._quiet = val;
Array.forEach(document.getElementById("dactyl-commandline").childNodes, function (node) {
Array.forEach(this.widgets.commandline.childNodes, function (node) {
node.style.opacity = this._quiet || this._silent ? "0" : "";
});
}, this);
},
// @param type can be:
@@ -510,8 +490,11 @@ const CommandLine = Module("commandline", {
highlightGroup = highlightGroup || this.HL_NORMAL;
if (flags & this.APPEND_TO_MESSAGES)
this._messageHistory.add({ str: str, highlight: highlightGroup });
if (flags & this.APPEND_TO_MESSAGES) {
let message = isobject(str) ? str : { message: str };
this._messageHistory.add(update({ highlight: highlightGroup }, str));
str = message.message;
}
if ((flags & this.ACTIVE_WINDOW) &&
window != services.get("windowWatcher").activeWindow &&
services.get("windowWatcher").activeWindow.dactyl)
@@ -625,12 +608,12 @@ const CommandLine = Module("commandline", {
if (event.type == "blur") {
// prevent losing focus, there should be a better way, but it just didn't work otherwise
this.setTimeout(function () {
if (this._commandShown() && event.originalTarget == this.widgets.command.inputField)
if (this.commandVisible && event.originalTarget == this.widgets.command.inputField)
this.widgets.command.inputField.focus();
}, 0);
}
else if (event.type == "focus") {
if (!this._commandShown() && event.target == this.widgets.command.inputField) {
if (!this.commandVisible && event.target == this.widgets.command.inputField) {
event.target.blur();
dactyl.beep();
}
@@ -695,7 +678,7 @@ const CommandLine = Module("commandline", {
}
}
catch (e) {
dactyl.reportError(e);
dactyl.reportError(e, true);
}
},
@@ -908,7 +891,7 @@ const CommandLine = Module("commandline", {
// copy text to clipboard
case "<C-y>":
util.copyToClipboard(win.getSelection());
dactyl.clipboardWrite(win.getSelection());
break;
// close the window
@@ -1032,7 +1015,7 @@ const CommandLine = Module("commandline", {
if (/^\s*$/.test(str))
return;
this.store.mutate("filter", function (line) (line.value || line) != str);
this.store.push({ value: str, timestamp: Date.now(), privateData: this.checkPrivate(str) });
this.store.push({ value: str, timestamp: Date.now()*1000, privateData: this.checkPrivate(str) });
this.store.truncate(options["history"], true);
},
/**
@@ -1042,23 +1025,9 @@ const CommandLine = Module("commandline", {
checkPrivate: function (str) {
// Not really the ideal place for this check.
if (this.mode == "command")
return (commands.get(commands.parseCommand(str)[1]) || {}).privateData;
return commands.hasPrivateData(str);
return false;
},
/**
* Removes any private data from this history.
*/
sanitize: function (timespan) {
let range = [0, Number.MAX_VALUE];
if (dactyl.has("sanitizer") && (timespan || options["sanitizetimespan"]))
range = Sanitizer.getClearRange(timespan || options["sanitizetimespan"]);
const self = this;
this.store.mutate("filter", function (item) {
let timestamp = (item.timestamp || Date.now()/1000) * 1000;
return !line.privateData || timestamp < self.range[0] || timestamp > self.range[1];
});
},
/**
* Replace the current input field value.
*
@@ -1443,19 +1412,19 @@ const CommandLine = Module("commandline", {
});
commands.add(["mes[sages]"],
"Display previously given messages",
"Display previously shown messages",
function () {
// TODO: are all messages single line? Some display an aggregation
// of single line messages at least. E.g. :source
if (commandline._messageHistory.length == 1) {
let message = commandline._messageHistory.messages[0];
commandline.echo(message.str, message.highlight, commandline.FORCE_SINGLELINE);
commandline.echo(message.message, message.highlight, commandline.FORCE_SINGLELINE);
}
else if (commandline._messageHistory.length > 1) {
XML.ignoreWhitespace = false;
let list = template.map(commandline._messageHistory.messages, function (message)
<div highlight={message.highlight + " Message"}>{message.str}</div>);
dactyl.echo(list, commandline.FORCE_MULTILINE);
commandline.commandOutput(
template.map(commandline._messageHistory.messages, function (message)
<div highlight={message.highlight + " Message"}>{message.message}</div>));;
}
},
{ argCount: "0" });
@@ -1471,7 +1440,8 @@ const CommandLine = Module("commandline", {
commandline.runSilently(function () dactyl.execute(args[0], null, true));
}, {
completer: function (context) completion.ex(context),
literal: 0
literal: 0,
subCommand: 0
});
},
mappings: function () {
@@ -1532,6 +1502,34 @@ const CommandLine = Module("commandline", {
"Show the current mode in the command line",
"boolean", true);
},
sanitizer: function () {
sanitizer.addItem("commandline", {
description: "Command-line and search history",
action: function (timespan, host) {
if (!host)
storage["history-search"].mutate("filter", function (item) !timespan.contains(item.timestamp));
storage["history-command"].mutate("filter", function (item)
!(timespan.contains(item.timestamp) && (!host || commands.hasDomain(item.value, host))));
}
});
// Delete history-like items from the commandline and messages on history purge
sanitizer.addItem("history", {
action: function (timespan, host) {
storage["history-command"].mutate("filter", function (item)
!(timespan.contains(item.timestamp) && (host ? commands.hasDomain(item.value, host) : item.privateData)));
commandline._messageHistory.filter(function (item) !timespan.contains(item.timestamp * 1000) ||
!item.domains && !item.privateData ||
host && (!item.domains || !item.domains.some(function (d) util.isSubdomain(d, host))));
}
});
sanitizer.addItem("messages", {
description: "Saved :messages",
action: function (timespan, host) {
commandline._messageHistory.filter(function (item) !timespan.contains(item.timestamp * 1000) ||
host && (!item.domains || !item.domains.some(function (d) util.isSubdomain(d, host))));
}
});
},
styles: function () {
let fontSize = util.computedStyle(document.getElementById(config.mainWindowId)).fontSize;
styles.registerSheet("chrome://dactyl/skin/dactyl.css");

View File

@@ -95,11 +95,13 @@ update(CommandOption, {
* bang - see {@link Command#bang}
* completer - see {@link Command#completer}
* count - see {@link Command#count}
* domains - see {@link Command#domains}
* heredoc - see {@link Command#heredoc}
* literal - see {@link Command#literal}
* options - see {@link Command#options}
* serial - see {@link Command#serial}
* privateData - see {@link Command#privateData}
* serialize - see {@link Command#serialize}
* subCommand - see {@link Command#subCommand}
* @optional
* @private
*/
@@ -144,24 +146,22 @@ const Command = Class("Command", {
let self = this;
function exec(command) {
// FIXME: Move to parseCommand?
args = self.parseArgs(command);
if (!args)
return;
args = this.parseArgs(command);
args.count = count;
args.bang = bang;
dactyl.trapErrors(self.action, self, args, modifiers);
this.action(args, modifiers);
}
if (this.hereDoc) {
let matches = args.match(/(.*)<<\s*(\S+)$/);
if (matches && matches[2]) {
commandline.inputMultiline(RegExp("^" + matches[2] + "$", "m"),
function (args) { exec(matches[1] + "\n" + args); });
function (args) { dactyl.trapErrors(exec, self, matches[1] + "\n" + args); });
return;
}
}
exec(args);
dactyl.trapErrors(exec, this, args);
},
/**
@@ -231,11 +231,6 @@ const Command = Class("Command", {
completer: null,
/** @property {boolean} Whether this command accepts a here document. */
hereDoc: false,
/**
* @property {Array} The options this command takes.
* @see Commands@parseArguments
*/
options: [],
/**
* @property {boolean} Whether this command may be called with a bang,
* e.g., :com!
@@ -246,6 +241,13 @@ const Command = Class("Command", {
* e.g., :12bdel
*/
count: false,
/**
* @property {function(args)} A function which should return a list
* of domains referenced in the given args. Used in determing
* whether to purge the command from history when clearing
* private data.
*/
domains: function (args) [],
/**
* @property {boolean} At what index this command's literal arguments
* begin. For instance, with a value of 2, all arguments starting with
@@ -254,6 +256,19 @@ const Command = Class("Command", {
* key mappings or Ex command lines as arguments.
*/
literal: null,
/**
* @property {Array} The options this command takes.
* @see Commands@parseArguments
*/
options: [],
/**
* @property {boolean|function(args)} When true, invocations of this
* command may contain private data which should be purged from
* saved histories when clearing private data. If a function, it
* should return true if an invocation with the given args
* contains private data
*/
privateData: true,
/**
* @property {function} Should return an array of <b>Object</b>s suitable
* to be passed to {@link Commands#commandToString}, one for each past
@@ -262,12 +277,11 @@ const Command = Class("Command", {
*/
serialize: null,
/**
* @property {boolean} When true, invocations of this command
* may contain private data which should be purged from
* saved histories when clearing private data.
* @property {number} If this command takes another ex command as an
* argument, the index of that argument. Used in determining whether to
* purge the command from history when clearing private data.
*/
privateData: false,
subCommand: null,
/**
* @property {boolean} Specifies whether this is a user command. User
* commands may be created by plugins, or directly by users, and,
@@ -441,6 +455,60 @@ const Commands = Module("commands", {
return this._exCommands.filter(function (cmd) cmd.user);
},
_subCommands: function (command) {
while (command) {
// FIXME: This parseCommand/commands.get/parseArgs is duplicated too often.
let [count, cmd, bang, args] = commands.parseCommand(command);
command = commands.get(cmd);
if (command) {
try {
args = command.parseArgs(args, null, { count: count, bang: bang });
yield [command, args];
if (commands.subCommand == null)
break;
command = args[command.subCommand];
}
catch (e) {
break;
}
}
}
},
/**
* Returns true if a command invocation contains a URL referring to the
* domain 'host'.
*
* @param {string} command
* @param {string} host
* @returns {boolean}
*/
hasDomain: function (command, host) {
try {
for (let [cmd, args] in this._subCommands(command))
if (Array.concat(cmd.domains(args)).some(function (domain) util.isSubdomain(domain, host)))
return true;
}
catch (e) {
dactyl.reportError(e);
}
return false;
},
/**
* Returns true if a command invocation contains private data which should
* be cleared when purging private data.
*
* @param {string} command
* @returns {boolean}
*/
hasPrivateData: function (command) {
for (let [cmd, args] in this._subCommands(command))
if (cmd.privateData)
return !callable(cmd.privateData) || cmd.privateData(args);
return false;
},
// TODO: should it handle comments?
// : it might be nice to be able to specify that certain quoting
// should be disabled E.g. backslash without having to resort to
@@ -541,11 +609,11 @@ const Commands = Module("commands", {
args.completeArg = 0;
}
function echoerr(error) {
function fail(error) {
if (complete)
complete.message = error;
else
dactyl.echoerr(error);
dactyl.assert(false, error);
}
outer:
@@ -589,11 +657,12 @@ const Commands = Module("commands", {
else if (!/\s/.test(sep) && sep != undefined) // this isn't really an option as it has trailing characters, parse it as an argument
invalid = true;
if (complete && !/[\s=]/.test(sep))
matchOpts(sub);
let context = null;
if (!complete && quote) {
dactyl.echoerr("Invalid argument for option " + optname);
return null;
}
if (!complete && quote)
fail("Invalid argument for option " + optname);
if (!invalid) {
if (complete && count > 0) {
@@ -602,32 +671,28 @@ const Commands = Module("commands", {
args.completeFilter = arg;
args.quote = Commands.complQuote[quote] || Commands.complQuote[""];
}
if (!complete || arg != null) {
let type = Commands.argTypes[opt.type];
if (type && (!complete || arg != null)) {
if (type) {
let orig = arg;
arg = type.parse(arg);
if (arg == null || (typeof arg == "number" && isNaN(arg))) {
if (!complete || orig != "" || args.completeStart != str.length)
echoerr("Invalid argument for " + type.description + " option: " + optname);
fail("Invalid argument for " + type.description + " option: " + optname);
if (complete)
complete.highlight(args.completeStart, count - 1, "SPELLCHECK");
else
return null;
}
}
// we have a validator function
if (typeof opt.validator == "function") {
if (opt.validator.call(this, arg) == false) {
echoerr("Invalid argument for option: " + optname);
if (complete)
fail("Invalid argument for option: " + optname);
if (complete) // Always true.
complete.highlight(args.completeStart, count - 1, "SPELLCHECK");
else
return null;
}
}
matchOpts(sub);
}
// option allowed multiple times
if (opt.multiple)
@@ -670,14 +735,10 @@ const Commands = Module("commands", {
args.quote = Commands.complQuote[quote] || Commands.complQuote[""];
args.completeFilter = arg || "";
}
else if (count == -1) {
dactyl.echoerr("Error parsing arguments: " + arg);
return null;
}
else if (!onlyArgumentsRemaining && /^-/.test(arg)) {
dactyl.echoerr("Invalid option: " + arg);
return null;
}
else if (count == -1)
fail("Error parsing arguments: " + arg);
else if (!onlyArgumentsRemaining && /^-/.test(arg))
fail("Invalid option: " + arg);
if (arg != null)
args.push(arg);
@@ -700,6 +761,7 @@ const Commands = Module("commands", {
compl = opt.completer || [];
context.title = [opt.names[0]];
context.quote = args.quote;
if (compl)
context.completions = compl;
}
complete.advance(args.completeStart);
@@ -712,16 +774,12 @@ const Commands = Module("commands", {
// check for correct number of arguments
if (args.length == 0 && /^[1+]$/.test(argCount) ||
literal != null && /[1+]/.test(argCount) && !/\S/.test(args.literalArg || "")) {
if (!complete) {
dactyl.echoerr("E471: Argument required");
return null;
}
if (!complete)
fail("E471: Argument required");
}
else if (args.length == 1 && (argCount == "0") ||
args.length > 1 && /^[01?]$/.test(argCount)) {
echoerr("E488: Trailing characters");
return null;
}
args.length > 1 && /^[01?]$/.test(argCount))
fail("E488: Trailing characters");
return args;
},
@@ -980,7 +1038,7 @@ const Commands = Module("commands", {
try {
var completer = dactyl.eval(completeOpt);
if (!(completer instanceof Function))
if (!callable(completer))
throw new TypeError("User-defined custom completer " + completeOpt.quote() + " is not a function");
}
catch (e) {

View File

@@ -137,6 +137,64 @@ const Dactyl = Module("dactyl", {
}
}),
/**
* 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}
*/
clipboardRead: function clipboardRead() {
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;
},
/**
* Copies a string to the system clipboard. If <b>verbose</b> is specified
* the copied string is also echoed to the command line.
*
* @param {string} str
* @param {boolean} verbose
*/
clipboardWrite: function clipboardWrite(str, verbose) {
const clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
clipboardHelper.copyString(str);
if (verbose) {
let message = { message: "Yanked " + str };
try {
message.domains = [util.newURI(str).host];
}
catch (e) {};
dactyl.echomsg(message);
}
},
/**
* Prints a message to the console. If <b>msg</b> is an object it is
* pretty printed.
@@ -906,7 +964,7 @@ const Dactyl = Module("dactyl", {
return func.apply(self || this, Array.slice(arguments, 2));
}
catch (e) {
dactyl.reportError(e);
dactyl.reportError(e, true);
return undefined;
}
},
@@ -917,7 +975,7 @@ const Dactyl = Module("dactyl", {
*
* @param {Object} error The error object.
*/
reportError: function (error) {
reportError: function (error, echo) {
if (error instanceof FailedAssertion) {
if (error.message)
dactyl.echoerr(error.message);
@@ -925,6 +983,8 @@ const Dactyl = Module("dactyl", {
dactyl.beep();
return;
}
if (echo)
dactyl.echoerr(error);
if (Cu.reportError)
Cu.reportError(error);
@@ -1709,7 +1769,8 @@ const Dactyl = Module("dactyl", {
return completion.javascript(context);
},
count: true,
literal: 0
literal: 0,
subCommand: 0
});
commands.add(["verb[ose]"],
@@ -1732,7 +1793,8 @@ const Dactyl = Module("dactyl", {
argCount: "+",
completer: function (context) completion.ex(context),
count: true,
literal: 0
literal: 0,
subCommand: 0
});
commands.add(["ve[rsion]"],

View File

@@ -112,8 +112,8 @@ const Editor = Module("editor", {
// FIXME: #93 (<s-insert> in the bottom of a long textarea bounces up)
let elem = dactyl.focus;
if (elem.setSelectionRange && util.readFromClipboard()) {
// readFromClipboard would return 'undefined' if not checked
if (elem.setSelectionRange && dactyl.clipboardRead()) {
// clipboardRead would return 'undefined' if not checked
// dunno about .setSelectionRange
// This is a hacky fix - but it works.
let curTop = elem.scrollTop;
@@ -122,7 +122,7 @@ const Editor = Module("editor", {
let rangeStart = elem.selectionStart; // caret position
let rangeEnd = elem.selectionEnd;
let tempStr1 = elem.value.substring(0, rangeStart);
let tempStr2 = util.readFromClipboard();
let tempStr2 = dactyl.clipboardRead();
let tempStr3 = elem.value.substring(rangeEnd);
elem.value = tempStr1 + tempStr2 + tempStr3;
elem.selectionStart = rangeStart + tempStr2.length;
@@ -971,7 +971,7 @@ const Editor = Module("editor", {
else {
let sel = window.content.document.getSelection();
dactyl.assert(sel);
util.copyToClipboard(sel, true);
dactyl.clipboardWrite(sel, true);
}
});

View File

@@ -22,7 +22,10 @@ const Events = Module("events", {
this.sessionListeners = [];
this._macros = storage.newMap("macros", true, { privateData: true });
this._macros = storage.newMap("macros", { privateData: true, store: true });
for (let [k, m] in this._macros)
if (isstring(m))
m = { keys: m, timeRecorded: Date.now() };
// NOTE: the order of ["Esc", "Escape"] or ["Escape", "Esc"]
// matters, so use that string as the first item, that you
@@ -85,9 +88,12 @@ const Events = Module("events", {
if (file.exists() && !file.isDirectory() && file.isReadable() &&
/^[\w_-]+(\.vimp)?$/i.test(file.leafName)) {
let name = file.leafName.replace(/\.vimp$/i, "");
this._macros.set(name, file.read().split("\n")[0]);
this._macros.set(name, {
keys: file.read().split("\n")[0],
timeRecorded: file.lastModifiedTime
});
dactyl.log("Macro " + name + " added: " + this._macros.get(name), 5);
dactyl.log("Macro " + name + " added: " + this._macros.get(name).keys, 5);
}
}
}
@@ -146,11 +152,11 @@ const Events = Module("events", {
method.apply(self, arguments);
}
catch (e) {
dactyl.reportError(e);
if (e.message == "Interrupted")
dactyl.echoerr("Interrupted");
else
dactyl.echoerr("Processing " + event.type + " event: " + (e.echoerr || e));
dactyl.reportError(e);
}
};
},
@@ -176,11 +182,11 @@ const Events = Module("events", {
if (/[A-Z]/.test(macro)) { // uppercase (append)
this._currentMacro = macro.toLowerCase();
if (!this._macros.get(this._currentMacro))
this._macros.set(this._currentMacro, ""); // initialize if it does not yet exist
this._macros.set(this._currentMacro, { keys: "", timeRecorded: Date.now() }); // initialize if it does not yet exist
}
else {
this._currentMacro = macro;
this._macros.set(this._currentMacro, "");
this._macros.set(this._currentMacro, { keys: "", timeRecorded: Date.now() });
}
},
@@ -219,7 +225,7 @@ const Events = Module("events", {
buffer.loaded = 1; // even if not a full page load, assume it did load correctly before starting the macro
modes.isReplaying = true;
res = events.feedkeys(this._macros.get(this._lastMacro), { noremap: true });
res = events.feedkeys(this._macros.get(this._lastMacro).keys, { noremap: true });
modes.isReplaying = false;
}
else {
@@ -239,11 +245,8 @@ const Events = Module("events", {
* filter selects all macros.
*/
getMacros: function (filter) {
if (!filter)
return this._macros;
let re = RegExp(filter);
return ([macro, keys] for ([macro, keys] in this._macros) if (re.test(macro)));
let re = RegExp(filter || "");
return ([k, m.keys] for ([k, m] in this._macros) if (re.test(macro)));
},
/**
@@ -253,10 +256,9 @@ const Events = Module("events", {
* filter deletes all macros.
*/
deleteMacros: function (filter) {
let re = RegExp(filter);
let re = RegExp(filter || "");
for (let [item, ] in this._macros) {
if (re.test(item) || !filter)
if (!filter || re.test(item))
this._macros.remove(item);
}
},
@@ -823,13 +825,16 @@ const Events = Module("events", {
if (modes.isRecording) {
if (key == "q" && !modes.mainMode.input) { // TODO: should not be hardcoded
modes.isRecording = false;
dactyl.log("Recorded " + this._currentMacro + ": " + this._macros.get(this._currentMacro), 9);
dactyl.log("Recorded " + this._currentMacro + ": " + this._macros.get(this._currentMacro, {}).keys, 9);
dactyl.echomsg("Recorded macro '" + this._currentMacro + "'");
killEvent();
return;
}
else if (!mappings.hasMap(dactyl.mode, this._input.buffer + key))
this._macros.set(this._currentMacro, this._macros.get(this._currentMacro) + key);
this._macros.set(this._currentMacro, {
keys: this._macros.get(this._currentMacro, {}).keys + key,
timeRecorded: Date.now()
});
}
if (key == "<C-c>")
@@ -1188,11 +1193,22 @@ const Events = Module("events", {
mappings.add([modes.NORMAL, modes.PLAYER, modes.MESSAGE],
["@"], "Play a macro",
function (count, arg) {
if (count < 1) count = 1;
count = Math.max(count, 1);
while (count-- && events.playMacro(arg))
;
},
{ arg: true, count: true });
},
sanitizer: function () {
sanitizer.addItem("macros", {
description: "Saved macros",
action: function (timespan, host) {
if (!host)
for (let [k, m] in events._macros)
if (timespan.contains(m.timeRecorded * 1000))
events._macros.remove(k);
}
});
}
});

View File

@@ -51,8 +51,8 @@ const Hints = Module("hints", {
W: Mode("Generate a ':winopen URL' using hint", function (elem, loc) commandline.open(":", "winopen " + loc, modes.EX)),
v: Mode("View hint source", function (elem, loc) buffer.viewSource(loc, false), extended),
V: Mode("View hint source in external editor", function (elem, loc) buffer.viewSource(loc, true), extended),
y: Mode("Yank hint location", function (elem, loc) util.copyToClipboard(loc, true)),
Y: Mode("Yank hint description", function (elem) util.copyToClipboard(elem.textContent || "", true), extended),
y: Mode("Yank hint location", function (elem, loc) dactyl.clipboardWrite(loc, true)),
Y: Mode("Yank hint description", function (elem) dactyl.clipboardWrite(elem.textContent || "", true), extended),
c: Mode("Open context menu", function (elem) buffer.openContextMenu(elem), extended),
i: Mode("Show image", function (elem) dactyl.open(elem.src), images),
I: Mode("Show image in a new tab", function (elem) dactyl.open(elem.src, dactyl.NEW_TAB), images)

View File

@@ -146,7 +146,8 @@ const History = Module("history", {
context.keys = { text: function (item) (sh.index - item.index) + ": " + item.URI.spec, description: "title", icon: "icon" };
},
count: true,
literal: 0
literal: 0,
privateData: true
});
commands.add(["fo[rward]", "fw"],
@@ -185,7 +186,8 @@ const History = Module("history", {
context.keys = { text: function (item) (item.index - sh.index) + ": " + item.URI.spec, description: "title", icon: "icon" };
},
count: true,
literal: 0
literal: 0,
privateData: true
});
commands.add(["hist[ory]", "hs"],
@@ -194,10 +196,25 @@ const History = Module("history", {
bang: true,
completer: function (context) { context.quote = null; completion.history(context); },
// completer: function (filter) completion.history(filter)
options: [{ names: ["-max", "-m"], description: "The maximum number of items to list", type: CommandOption.INT }]
options: [{ names: ["-max", "-m"], description: "The maximum number of items to list", type: CommandOption.INT }],
privateData: true
});
},
completion: function () {
completion.domain = function (context) {
context.anchored = false;
context.compare = function (a, b) String.localeCompare(a.key, b.key);
context.keys = { text: util.identity, description: util.identity,
key: function (host) host.split(".").reverse().join(".") };
// FIXME: Schema-specific
context.generate = function () [
Array.slice(row.rev_host).reverse().join("").slice(1)
for (row in iter(services.get("history").DBConnection
.createStatement("SELECT DISTINCT rev_host FROM moz_places;")))
].slice(2);
};
completion.history = function _history(context, maxItems) {
context.format = history.format;
context.title = ["History"];

View File

@@ -770,7 +770,7 @@ lookup:
};
completion.addUrlCompleter("f", "Local files", function (context, full) {
if (!/^\.?\//.test(context.filter))
if (/^(\.{0,2}|~)\/|^file:/.test(context.filter))
completion.file(context, full);
});
},

View File

@@ -56,19 +56,6 @@ const JavaScript = Module("javascript", {
return [];
let completions = [k for (k in this.iter(obj, toplevel))];
// Add keys for sorting later.
// Numbers are parsed to ints.
// Constants, which should be unsorted, are found and marked null.
completions.forEach(function (item) {
let key = item[0];
if (!isNaN(key))
key = parseInt(key);
else if (/^[A-Z_][A-Z0-9_]*$/.test(key))
key = "";
item.key = key;
});
return completions;
},
@@ -322,7 +309,7 @@ const JavaScript = Module("javascript", {
let orig = compl;
if (!compl) {
compl = function (context, obj, recurse) {
context.process = [null, function highlight(item, v) template.highlight(v, true)];
context.process = [null, function highlight(item, v) template.highlight(typeof v == "xml" ? new String(v.toXMLString()) : v, true)];
// Sort in a logical fashion for object keys:
// Numbers are sorted as numbers, rather than strings, and appear first.
// Constants are unsorted, and appear before other non-null strings.
@@ -330,10 +317,21 @@ const JavaScript = Module("javascript", {
let compare = context.compare;
function isnan(item) item != '' && isNaN(item);
context.compare = function (a, b) {
if (!isnan(a.item.key) && !isnan(b.item.key))
return a.item.key - b.item.key;
return isnan(b.item.key) - isnan(a.item.key) || compare(a, b);
if (!isnan(a.key) && !isnan(b.key))
return a.key - b.key;
return isnan(b.key) - isnan(a.key) || compare(a, b);
};
context.keys = { text: 0, description: 1,
key: function (item) {
let key = item[0];
if (!isNaN(key))
return parseInt(key);
else if (/^[A-Z_][A-Z0-9_]*$/.test(key))
return ""
return key;
}
};
if (!context.anchored) // We've already listed anchored matches, so don't list them again here.
context.filters.push(function (item) util.compareIgnoreCase(item.text.substr(0, this.filter.length), this.filter));
if (obj == self.cache.evalContext)

View File

@@ -178,7 +178,7 @@ const Mappings = Module("mappings", {
modes = modes.slice();
return (map for ([i, map] in Iterator(stack[modes.shift()].sort(function (m1, m2) String.localeCompare(m1.name, m2.name))))
if (modes.every(function (mode) stack[mode].some(
function (mapping) m.rhs == map.rhs && m.name == map.name))))
function (m) m.rhs == map.rhs && m.name == map.name))))
},
// NOTE: just normal mode for now

View File

@@ -12,8 +12,9 @@
*/
const Marks = Module("marks", {
init: function init() {
this._localMarks = storage.newMap("local-marks", { store: true, privateData: true });
this._urlMarks = storage.newMap("url-marks", { store: true, privateData: true });
function replacer(key, val) val instanceof Node ? null : val;
this._localMarks = storage.newMap("local-marks", { privateData: true, replacer: replacer, store: true });
this._urlMarks = storage.newMap("url-marks", { privateData: true, replacer: replacer, store: true });
this._pendingJumps = [];
},
@@ -65,7 +66,7 @@ const Marks = Module("marks", {
let position = { x: x, y: y };
if (Marks.isURLMark(mark)) {
this._urlMarks.set(mark, { location: win.location.href, position: position, tab: tabs.getTab() });
this._urlMarks.set(mark, { location: win.location.href, position: position, tab: tabs.getTab(), timestamp: Date.now()*1000 });
if (!silent)
dactyl.log("Adding URL mark: " + Marks.markToString(mark, this._urlMarks.get(mark)), 5);
}
@@ -74,7 +75,7 @@ const Marks = Module("marks", {
this._removeLocalMark(mark);
if (!this._localMarks.get(mark))
this._localMarks.set(mark, []);
let vals = { location: win.location.href, position: position };
let vals = { location: win.location.href, position: position, timestamp: Date.now()*1000 };
this._localMarks.get(mark).push(vals);
if (!silent)
dactyl.log("Adding local mark: " + Marks.markToString(mark, vals), 5);
@@ -327,6 +328,20 @@ const Marks = Module("marks", {
context.keys.description = function ([, m]) percent(m.position.y) + "% " + percent(m.position.x) + "% " + m.location;
context.completions = marks.all;
};
},
sanitizer: function () {
sanitizer.addItem("marks", {
description: "Local and URL marks",
action: function (timespan, host) {
function filter(mark) !(timespan.contains(mark.timestamp) && (!host || util.isDomainURL(mark.location, host)));
for (let [k, v] in storage["local-marks"])
storage["local-marks"].set(k, v.filter(filter));
for (let key in (k for ([k, v] in storage["url-marks"]) if (!filter(v))))
storage["url-marks"].remove(key);
}
});
}
});

View File

@@ -20,10 +20,12 @@
* @param {string} defaultValue The default value for this option.
* @param {Object} extraInfo An optional extra configuration hash. The
* following properties are supported.
* completer - see {@link Option#completer}
* domains - see {@link Option#domains}
* getter - see {@link Option#getter}
* privateData - see {@link Option#privateData}
* scope - see {@link Option#scope}
* setter - see {@link Option#setter}
* getter - see {@link Option#getter}
* completer - see {@link Option#completer}
* valdator - see {@link Option#validator}
* @optional
* @private
@@ -61,8 +63,8 @@ const Option = Class("Option", {
},
/** @property {value} The option's global value. @see #scope */
get globalValue() options.store.get(this.name),
set globalValue(val) { options.store.set(this.name, val); },
get globalValue() options.store.get(this.name, {}).value,
set globalValue(val) { options.store.set(this.name, { value: val, time: Date.now() }); },
/**
* Returns <b>value</b> as an array of parsed values if the option type is
@@ -194,6 +196,9 @@ const Option = Class("Option", {
*/
isValidValue: function (values) this.validator(values),
invalidArgument: function (arg, op) "E474: Invalid argument: " +
this.name + (op || "").replace(/=?$/, "=") + arg,
/**
* Resets the option to its default value.
*/
@@ -210,7 +215,7 @@ const Option = Class("Option", {
* {@link #scope}).
* @param {boolean} invert Whether this is an invert boolean operation.
*/
op: function (operator, values, scope, invert) {
op: function (operator, values, scope, invert, str) {
let newValues = this._op(operator, values, scope, invert);
@@ -218,7 +223,7 @@ const Option = Class("Option", {
return "Operator " + operator + " not supported for option type " + this.type;
if (!this.isValidValue(newValues))
return "E474: Invalid argument: " + values;
return this.invalidArgument(str || this.joinValues(values), operator);
this.setValues(newValues, scope);
return null;
@@ -257,6 +262,27 @@ const Option = Class("Option", {
*/
description: "",
/**
* @property {function(CompletionContext, Args)} This option's completer.
* @see CompletionContext
*/
completer: null,
/**
* @property {function(host, values)} A function which should return a list
* of domains referenced in the given values. Used in determing whether
* to purge the command from history when clearing private data.
* @see Command#domains
*/
domains: null,
/**
* @property {function(host, values)} A function which should strip
* references to a given domain from the given values.
*/
filterDomain: function filterDomain(host, values)
Array.filter(values, function (val) !util.isSubdomain(val, host)),
/**
* @property {value} The option's default value. This value will be used
* unless the option is explicitly set either interactively or in an RC
@@ -264,19 +290,25 @@ const Option = Class("Option", {
*/
defaultValue: null,
/**
* @property {function} The function called when the option value is set.
*/
setter: null,
/**
* @property {function} The function called when the option value is read.
*/
getter: null,
/**
* @property {function(CompletionContext, Args)} This option's completer.
* @see CompletionContext
* @property {boolean|function(values)} When true, values of this
* option may contain private data which should be purged from
* saved histories when clearing private data. If a function, it
* should return true if an invocation with the given values
* contains private data
*/
completer: null,
privateData: false,
/**
* @property {function} The function called when the option value is set.
*/
setter: null,
/**
* @property {function} The function called to validate the option's value
* when set.
@@ -294,6 +326,12 @@ const Option = Class("Option", {
*/
hasChanged: false,
/**
* Returns the timestamp when the option's value was last changed.
*/
get lastSet() options.store.get(this.name).time,
set lastSet(val) { options.store.set(this.name, { value: this.globalValue, time: Date.now() }); },
/**
* @property {nsIFile} The script in which this option was last set. null
* implies an interactive command.
@@ -521,10 +559,9 @@ const Options = Module("options", {
},
/** @property {Iterator(Option)} @private */
__iterator__: function () {
let sorted = [o for ([i, o] in Iterator(this._optionHash))].sort(function (a, b) String.localeCompare(a.name, b.name));
return (v for ([k, v] in Iterator(sorted)));
},
__iterator__: function ()
array(values(this._optionHash)).sort(function (a, b) String.localeCompare(a.name, b.name))
.itervalues(),
/** @property {Object} Observes preference value changes. */
prefObserver: {
@@ -612,11 +649,9 @@ const Options = Module("options", {
if (name in this._optionHash)
return (this._optionHash[name].scope & scope) && this._optionHash[name];
for (let opt in Iterator(options)) {
for (let opt in values(this._optionHash))
if (opt.hasName(name))
return (opt.scope & scope) && opt;
}
return null;
},
@@ -1067,7 +1102,7 @@ const Options = Module("options", {
opt.values = !opt.unsetBoolean;
}
try {
var res = opt.option.op(opt.operator || "=", opt.values, opt.scope, opt.invert);
var res = opt.option.op(opt.operator || "=", opt.values, opt.scope, opt.invert, opt.value);
}
catch (e) {
res = e;
@@ -1209,46 +1244,22 @@ const Options = Module("options", {
}
);
commands.add(["setl[ocal]"],
"Set local option",
function (args, modifiers) {
modifiers.scope = Option.SCOPE_LOCAL;
setAction(args, modifiers);
[
{
names: ["setl[ocal]"],
description: "Set local option",
modifiers: { scope: Option.SCOPE_LOCAL }
},
{
bang: true,
count: true,
completer: function (context, args) {
return setCompleter(context, args, { scope: Option.SCOPE_LOCAL });
},
literal: 0
}
);
commands.add(["setg[lobal]"],
"Set global option",
function (args, modifiers) {
modifiers.scope = Option.SCOPE_GLOBAL;
setAction(args, modifiers);
names: ["setg[lobal]"],
description: "Set global option",
modifiers: { scope: Option.SCOPE_GLOBAL }
},
{
bang: true,
count: true,
completer: function (context, args) {
return setCompleter(context, args, { scope: Option.SCOPE_GLOBAL });
},
literal: 0
}
);
commands.add(["se[t]"],
"Set an option",
function (args, modifiers) { setAction(args, modifiers); },
{
bang: true,
completer: function (context, args) {
return setCompleter(context, args);
},
names: ["se[t]"],
description: "Set an option",
modifiers: {},
extra: {
serialize: function () [
{
command: this.name,
@@ -1258,6 +1269,36 @@ const Options = Module("options", {
for (opt in options)
if (!opt.getter && opt.value != opt.defaultValue && (opt.scope & Option.SCOPE_GLOBAL))
]
}
}
].forEach(function (params) {
commands.add(params.names, params.description,
function (args, modifiers) {
setAction(args, update(modifiers, params.modifiers));
},
update({
bang: true,
domains: function (args) array.flatten(args.map(function (spec) {
try {
let opt = options.parseOpt(spec);
if (opt.option && opt.option.domains)
return opt.option.domains(opt.values);
}
catch (e) {
dactyl.reportError(e);
}
return [];
})),
completer: function (context, args) {
return setCompleter(context, args);
},
privateData: function (args) args.some(function (spec) {
let opt = options.parseOpt(spec);
return opt.option && opt.option.privateData &&
(!callable(opt.option.privateData) ||
opt.option.privateData(opt.values))
})
}, params.extra || {}));
});
commands.add(["unl[et]"],
@@ -1332,6 +1373,8 @@ const Options = Module("options", {
context.title = ["Option Value"];
let completions = completer(context);
if (!isarray(completions))
completions = array(completions).__proto__;
if (!completions)
return;
@@ -1362,6 +1405,34 @@ const Options = Module("options", {
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 () options.allPrefs().map(function (pref) [pref, ""])]);
},
sanitizer: function () {
sanitizer.addItem("options", {
description: "Options containing hostname data",
action: function (timespan, host) {
if (host)
for (let opt in values(options._optionHash))
if (timespan.contains(opt.lastSet * 1000) && opt.domains)
try {
opt.values = opt.filterDomain(host, opt.values);
}
catch (e) {
dactyl.reportError(e);
}
},
privateEnter: function () {
for (let opt in values(options._optionHash))
if (opt.privateData && (!callable(opt.privateData) || opt.privateData(opt.values)))
opt.oldValue = opt.value;
},
privateLeave: function () {
for (let opt in values(options._optionHash))
if (opt.oldValue != null) {
opt.value = opt.oldValue;
opt.oldValue = null;
}
}
});
}
});

View File

@@ -1,268 +0,0 @@
// 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?
// - add support for :set sanitizeitems=all like 'eventignore'?
// - integrate with the Clear Private Data dialog?
// FIXME:
// - finish 1.9.0 support if we're going to support sanitizing in Xulmus
const Sanitizer = Module("sanitizer", {
init: function () {
const self = this;
dactyl.loadScript("chrome://browser/content/sanitize.js", Sanitizer);
Sanitizer.getClearRange = Sanitizer.Sanitizer.getClearRange;
this.__proto__.__proto__ = new Sanitizer.Sanitizer; // Good enough.
// TODO: remove this version test
if (/^1.9.1/.test(services.get("xulAppInfo").platformVersion))
self.prefDomain = "privacy.cpd.";
else
self.prefDomain = "privacy.item.";
self.prefDomain2 = "extensions.dactyl.privacy.cpd.";
},
// Largely ripped from from browser/base/content/sanitize.js so we can override
// the pref strategy without stepping on the global prefs namespace.
sanitize: function () {
const prefService = services.get("pref");
let branch = prefService.getBranch(this.prefDomain);
let branch2 = prefService.getBranch(this.prefDomain2);
let errors = null;
function prefSet(name) {
try {
return branch.getBoolPref(name);
}
catch (e) {
return branch2.getBoolPref(name);
}
}
// Cache the range of times to clear
if (this.ignoreTimespan)
var range = null; // If we ignore timespan, clear everything
else
range = this.range || Sanitizer.getClearRange();
for (let itemName in this.items) {
let item = this.items[itemName];
item.range = range;
if ("clear" in item && item.canClear && prefSet(itemName)) {
dactyl.log("Sanitizing " + itemName + " items...");
// Some of these clear() may raise exceptions (see bug #265028)
// to sanitize as much as possible, we catch and store them,
// rather than fail fast.
// Callers should check returned errors and give user feedback
// about items that could not be sanitized
try {
item.clear();
}
catch (e) {
if (!errors)
errors = {};
errors[itemName] = e;
dump("Error sanitizing " + itemName + ": " + e + "\n");
}
}
}
return errors;
},
get prefNames() util.Array.flatten([this.prefDomain, this.prefDomain2].map(options.allPrefs))
}, {
prefArgList: [["commandLine", "commandline"],
["offlineApps", "offlineapps"],
["siteSettings", "sitesettings"]],
prefToArg: function (pref) {
pref = pref.replace(/.*\./, "");
return util.Array.toObject(Sanitizer.prefArgList)[pref] || pref;
},
argToPref: function (arg) [k for ([k, v] in values(Sanitizer.prefArgList)) if (v == arg)][0] || arg
}, {
commands: function () {
commands.add(["sa[nitize]"],
"Clear private data",
function (args) {
dactyl.assert(!options['private'], "Cannot sanitize items in private mode");
let timespan = args["-timespan"] || options["sanitizetimespan"];
sanitizer.range = Sanitizer.getClearRange(timespan);
sanitizer.ignoreTimespan = !sanitizer.range;
if (args.bang) {
dactyl.assert(args.length == 0, "E488: Trailing characters");
dactyl.log("Sanitizing all items in 'sanitizeitems'...");
let errors = sanitizer.sanitize();
if (errors) {
for (let item in errors)
dactyl.echoerr("Error sanitizing " + item + ": " + errors[item]);
}
}
else {
dactyl.assert(args.length > 0, "E471: Argument required");
for (let [, item] in Iterator(args.map(Sanitizer.argToPref))) {
dactyl.log("Sanitizing " + item + " items...");
if (sanitizer.canClearItem(item)) {
try {
sanitizer.items[item].range = sanitizer.range;
sanitizer.clearItem(item);
}
catch (e) {
dactyl.echoerr("Error sanitizing " + item + ": " + e);
}
}
else
dactyl.echomsg("Cannot sanitize " + item);
}
}
},
{
argCount: "*", // FIXME: should be + and 0
bang: true,
completer: function (context) {
context.title = ["Privacy Item", "Description"];
context.completions = options.get("sanitizeitems").completer();
},
options: [
{
names: ["-timespan", "-t"],
description: "Timespan for which to sanitize items",
completer: function () options.get("sanitizetimespan").completer(),
type: CommandOption.INT,
validator: function (arg) /^[0-4]$/.test(arg)
}
]
});
},
options: function () {
const self = this;
// add dactyl-specific private items
[
{
name: "commandLine",
action: function () {
let stores = ["command", "search"];
if (self.range) {
stores.forEach(function (store) {
storage["history-" + store].mutate("filter", function (item) {
let timestamp = item.timestamp * 1000;
return timestamp < self.range[0] || timestamp > self.range[1];
});
});
}
else
stores.forEach(function (store) { storage["history-" + store].truncate(0); });
}
},
{
name: "macros",
action: function () { storage["macros"].clear(); }
},
{
name: "marks",
action: function () {
storage["local-marks"].clear();
storage["url-marks"].clear();
}
}
].forEach(function (item) {
let pref = self.prefDomain2 + item.name;
if (options.getPref(pref) == null)
options.setPref(pref, false);
self.items[item.name] = {
canClear: true,
clear: item.action
};
});
// call Sanitize autocommand
for (let [name, item] in Iterator(self.items)) {
let arg = Sanitizer.prefToArg(name);
if (item.clear) {
let func = item.clear;
item.clear = function () {
autocommands.trigger("Sanitize", { name: arg });
func.call(item);
};
}
}
options.add(["sanitizeitems", "si"],
"The default list of private items to sanitize",
"stringlist", "cache,commandline,cookies,formdata,history,marks,sessions",
{
setter: function (values) {
for (let [, pref] in Iterator(sanitizer.prefNames)) {
options.setPref(pref, false);
for (let [, value] in Iterator(values)) {
if (Sanitizer.prefToArg(pref) == value) {
options.setPref(pref, true);
break;
}
}
}
return values;
},
getter: function () sanitizer.prefNames.filter(function (pref) options.getPref(pref)).map(Sanitizer.prefToArg),
completer: function (value) [
["cache", "Cache"],
["commandline", "Command-line history"],
["cookies", "Cookies"],
["downloads", "Download history"],
["formdata", "Saved form and search history"],
["history", "Browsing history"],
["macros", "Saved macros"],
["marks", "Local and URL marks"],
["offlineapps", "Offline website data"],
["passwords", "Saved passwords"],
["sessions", "Authenticated sessions"],
["sitesettings", "Site preferences"]
]
});
options.add(["sanitizetimespan", "sts"],
"The default sanitizer time span",
"number", 1,
{
setter: function (value) {
options.setPref("privacy.sanitize.timeSpan", value);
return value;
},
getter: function () options.getPref("privacy.sanitize.timeSpan", this.defaultValue),
completer: function (value) [
["0", "Everything"],
["1", "Last hour"],
["2", "Last two hours"],
["3", "Last four hours"],
["4", "Today"]
]
});
}
});
// vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -572,7 +572,8 @@ const Tabs = Module("tabs", {
bang: true,
count: true,
completer: function (context) completion.buffer(context),
literal: 0
literal: 0,
privateData: true
});
commands.add(["keepa[lt]"],
@@ -589,7 +590,8 @@ const Tabs = Module("tabs", {
}, {
argCount: "+",
completer: function (context) completion.ex(context),
literal: 0
literal: 0,
subCommand: 0
});
// TODO: this should open in a new tab positioned directly after the current one, not at the end
@@ -602,7 +604,8 @@ const Tabs = Module("tabs", {
}, {
argCount: "+",
completer: function (context) completion.ex(context),
literal: 0
literal: 0,
subCommand: 0
});
commands.add(["tabd[o]", "bufd[o]"],
@@ -615,7 +618,8 @@ const Tabs = Module("tabs", {
}, {
argCount: "1",
completer: function (context) completion.ex(context),
literal: 0
literal: 0,
subCommand: 0
});
commands.add(["tabl[ast]", "bl[ast]"],
@@ -705,7 +709,8 @@ const Tabs = Module("tabs", {
bang: true,
count: true,
completer: function (context) completion.buffer(context),
literal: 0
literal: 0,
privateData: true
});
commands.add(["buffers", "files", "ls", "tabs"],
@@ -763,6 +768,7 @@ const Tabs = Module("tabs", {
}, {
bang: true,
completer: function (context) completion.url(context),
domains: function (args) commands.get("open").domains(args),
literal: 0,
privateData: true
});
@@ -863,7 +869,8 @@ const Tabs = Module("tabs", {
context.completions = Iterator(tabs.closedTabs);
},
count: true,
literal: 0
literal: 0,
privateData: true
});
commands.add(["undoa[ll]"],

View File

@@ -1023,29 +1023,12 @@
<tags>'si' 'sanitizeitems'</tags>
<spec>'sanitizeitems' 'si'</spec>
<type>stringlist</type>
<default>cache,commandline,cookies,formdata,history,marks,sessions</default>
<default>all</default>
<description>
<p>The default list of private items to sanitize.</p>
<dl>
<dt>cache </dt> <dd>Cache</dd>
<dt>commandline </dt> <dd>Command-line history</dd>
<dt>cookies </dt> <dd>Cookies</dd>
<dt>downloads </dt> <dd>Download history</dd>
<dt>formdata </dt> <dd>Saved form and search history</dd>
<dt>history </dt> <dd>Browsing history</dd>
<dt>marks </dt> <dd>Local and URL marks</dd>
<dt>macros </dt> <dd>Saved macros</dd>
<dt>offlineapps </dt> <dd>Offline website data</dd>
<dt>passwords </dt> <dd>Saved passwords</dd>
<dt>sessions </dt> <dd>Authenticated sessions</dd>
<dt>sitesettings</dt> <dd>Site preferences</dd>
</dl>
<p>
When history items are sanitized <ex>:open</ex>,
<ex>:tabopen</ex> and <ex>:winopen</ex> command-line
history entries are also removed.
The default list of private items to sanitize. See
<ex>:sanitize</ex> for a list and explanation of
possible values.
</p>
</description>
</item>
@@ -1055,21 +1038,20 @@
<tags>'sts' 'sanitizetimespan'</tags>
<spec>'sanitizetimespan' 'sts'</spec>
<type>number</type>
<default>1</default>
<default>all</default>
<description>
<p>
The default sanitizer time span. Only items created within this timespan are
deleted.
deleted. The value must be of the one of the forms,
</p>
<note>This only applies to <em>cookies</em>, <em>history</em>, <em>formdata</em>, and <em>downloads</em>.</note>
<dl>
<dt>0</dt> <dd>Everything</dd>
<dt>1</dt> <dd>Last hour</dd>
<dt>2</dt> <dd>Last two hours</dd>
<dt>3</dt> <dd>Last four hours</dd>
<dt>4</dt> <dd>Today</dd>
<dt>all</dt> <dd>Everything</dd>
<dt>session</dt> <dd>The current session</dd>
<dt><a>n</a>m</dt> <dd>Past <a>n</a> Minutes</dd>
<dt><a>n</a>h</dt> <dd>Past <a>n</a> Hours</dd>
<dt><a>n</a>d</dt> <dd>Past <a>n</a> Days</dd>
<dt><a>n</a>w</dt> <dd>Past <a>n</a> Weeks</dd>
</dl>
</description>
</item>

View File

@@ -94,29 +94,6 @@
</item>
<item>
<tags>:sa :sanitize</tags>
<spec>:sa<oa>nitize</oa> [-timespan=<a>timespan</a>] <a>item</a></spec>
<spec>:sa<oa>nitize</oa>! [-timespan=<a>timespan</a>]</spec>
<description>
<p>
Clear private data items. Where <a>item</a> … is a list of private items to
delete. These may be any of the items valid for <o>sanitizeitems</o>.
</p>
<p>
If <oa>!</oa> is specified then <o>sanitizeitems</o> is used for the list of items to
delete.
</p>
<p>
If <a>timespan</a> is specified then only items within that timespan are deleted,
otherwise the value of <o>sanitizetimespan</o> is used.
</p>
</description>
</item>
<item>
<tags>:sil :silent</tags>
<spec>:sil<oa>ent</oa> <a>command</a></spec>
@@ -153,6 +130,109 @@
</description>
</item>
<h2 tag="privacy">Privacy and sensitive information</h2>
<p>
Part of &dactyl.appname;'s user efficiency comes at the cost of storing a
lot of potentially private data, including command-line history, page
marks, and the like. Because we know that keeping a detailed trail of all
of your activities isn't always welcome, &dactyl.appname; provides
comprehensive facilities for erasing potentially sensitive data.
</p>
<p tag="private-mode porn-mode">
<strut/>
&dactyl.appname; fully supports &dactyl.host;'s private browsing mode.
When in private browsing mode, no other than Bookmarks and QuickMarks are
written to disk. Further, upon exiting private mode, all new data,
including command-line history, local and URL marks, and macros, are
purged. For more information, see <o>private</o>.
</p>
<p tag="sanitizing clearing-data">
<strut/>
In addition to private mode, &dactyl.appname; provides a comprehensive
facility for clearing any potentially sensitive data generated by either
&dactyl.appname; or &dactyl.host;. It directly integrates with
&dactyl.host;'s own sanitization facility, and so automatically clears any
domain data and session history when requested. Further, &dactyl.appname;
provides its own more granular sanitization facility, which allows, e.g.,
clearing the command-line and macro history for the past ten minutes.
</p>
<item>
<tags>:sa :sanitize</tags>
<spec>:sa<oa>nitize</oa> <oa>-host=<a>host</a></oa> <oa>-older</oa> <oa>-timespan=<a>timespan</a></oa> <a>item</a></spec>
<spec>:sa<oa>nitize</oa>! <oa>-host=<a>host</a></oa> <oa>-older</oa> <oa>-timespan=<a>timespan</a></oa></spec>
<description>
<p>
Clear private data items for <a>timespan</a>, where <a>item</a>
is a list of private items to delete. If <oa>!</oa> is specified
then <o>sanitizeitems</o> is used for the list of items to delete.
Items may be any of:
</p>
<dl>
<dt>all </dt> <dd>All items</dd>
<dt>cache </dt> <dd>Cache</dd>
<dt>commandline </dt> <dd>Command-line history</dd>
<dt>cookies </dt> <dd>Cookies</dd>
<dt>downloads </dt> <dd>Download history</dd>
<dt>formdata </dt> <dd>Saved form and search history</dd>
<dt>history </dt> <dd>Browsing history</dd>
<dt>marks </dt> <dd>Local and URL marks</dd>
<dt>macros </dt> <dd>Saved macros</dd>
<dt>messages </dt> <dd>Saved <ex>:messages</ex></dd>
<dt>offlineapps </dt> <dd>Offline website data</dd>
<dt>options </dt> <dd>Options containing hostname data</dd>
<dt>passwords </dt> <dd>Saved passwords</dd>
<dt>sessions </dt> <dd>Authenticated sessions</dd>
<dt>sitesettings</dt> <dd>Site preferences</dd>
</dl>
<p>
When <em>history</em> items are sanitized, all command-line
history items containing URLs or page titles (other than bookmark
commands) are additionally cleared. Invocations of the
<em>:sanitize</em> command are included in this set.
</p>
<p>
If <a>timespan</a> (short name <em>-t</em>) is specified then only
items within that timespan are deleted, otherwise the value of
<o>sanitizetimespan</o> is used. If <oa>-older</oa> (short name
<em>-o</em>) is specified, than only items older than
<a>timespan</a> are deleted.
</p>
<note>
The following items are cleared regardless of <a>timeframe</a>:
<em>cache</em>, <em>offlineapps</em>, <em>passwords</em>,
<em>sessions</em>, <em>sitesettings</em>. Additionally,
<em>options</em> are never cleared.
</note>
<p>
If <a>host</a> (short name <em>-h</em>) is specified, only items
containing a reference to that domain or a subdomain thereof are
cleared. Moreover, if <em>commandline</em> or <em>history</em> is
specified, the invocation of the <em>:sanitize</em> command is
naturally cleared as well.
</p>
<note>
This only applies to <em>commandline</em>, <em>cookies</em>,
<em>history</em>, <em>marks</em>, <em>messages</em>,
<em>options</em>, and <em>sitesettings</em>. All other
domain-specific data is cleared only along with <em>history</em>,
when a request is made to &dactyl.host; to purge all data for
<em>host</em>. Included in this purge are all matchint history
entries, cookies, closed tabs, form data, and location bar
entries.
</note>
</description>
</item>
<h2 tag="online-help">Online help</h2>

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;
}
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];
if (!old)
this.highlight[style.class] = style;
if (old && old.value != old.default)
style.value = old.value;
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,13 +236,12 @@ 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);
if (timers[key])
timers[key].tell();
},
@@ -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.

View File

@@ -215,7 +215,8 @@ const Config = Module("config", ConfigBase, {
{
argCount: "+",
completer: function (context) completion.ex(context),
literal: 0
literal: 0,
subCommand: 0
});
commands.add(["winc[lose]", "wc[lose]"],
@@ -235,6 +236,7 @@ const Config = Module("config", ConfigBase, {
},
{
completer: function (context) completion.url(context),
domains: function (args) commands.get("open").domains(args),
literal: 0,
privateData: true
});