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:
@@ -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(",");
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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]"],
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"];
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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:
|
||||
@@ -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]"],
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
330
common/modules/sanitizer.jsm
Normal file
330
common/modules/sanitizer.jsm
Normal 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:
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user