diff --git a/common/content/commandline.js b/common/content/commandline.js index 0ec215b6..1bfbcfb9 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -1571,8 +1571,6 @@ const CommandLine = Module("commandline", { if (typeof arg === "object") arg = util.objectToString(arg, useColor); - else if (typeof arg === "string" && /\n/.test(arg)) - arg = {arg}; else arg = String(arg); @@ -1983,7 +1981,8 @@ const ItemList = Class("ItemList", { this._fill(newOffset); if (index >= 0) { this._getCompletion(index).setAttribute("selected", "true"); - util.scrollIntoView(this._getCompletion(index)); + if (this._container.height != 0) + util.scrollIntoView(this._getCompletion(index)); } //if (index == 0) diff --git a/common/content/completion.js b/common/content/completion.js index 1e0d436d..5aca30e3 100644 --- a/common/content/completion.js +++ b/common/content/completion.js @@ -733,8 +733,11 @@ const Completion = Module("completion", { let context = CompletionContext(filter); context.maxItems = maxItems; let res = context.fork.apply(context, ["run", 0, this, name].concat(Array.slice(arguments, 3))); - if (res) // FIXME - return { items: res.map(function (i) ({ item: i })) }; + if (res) { + if (Components.stack.caller.name === "runCompleter") // FIXME + return { items: res.map(function (i) ({ item: i })) }; + context.contexts["/run"].completions = res; + } context.wait(true); return context.allItems; }, diff --git a/common/content/dactyl.js b/common/content/dactyl.js index b9e9df39..02b2053a 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -603,16 +603,27 @@ const Dactyl = Module("dactyl", { * @param {XMLList} extraHelp Extra help text beyond the description. * @returns {string} */ - generateHelp: function generateHelp(obj, extraHelp) { + generateHelp: function generateHelp(obj, extraHelp, str) { default xml namespace = ""; - let spec = util.identity; - let tag = util.identity; - if (obj instanceof Command) + + let link, tag, spec; + link = tag = spec = util.identity; + let args = null; + + if (obj instanceof Command) { tag = spec = function (cmd) <>:{cmd}; - else if (obj instanceof Map && obj.count) + link = function (cmd) :{cmd} + args = obj.parseArgs("", CompletionContext(str || "")); + } + else if (obj instanceof Map && obj.count) { spec = function (map) <>count{map}; - else if (obj instanceof Option) + link = function (map) let (res = /^<(.*?)>(.*?)/.exec(map)) + res ? {res[2]} : {map}; + } + else if (obj instanceof Option) { tag = spec = function (opt) <>'{opt}'; + link = function (opt) {opt}; + } XML.prettyPrinting = false; XML.ignoreWhitespace = false; @@ -621,7 +632,17 @@ const Dactyl = Module("dactyl", { let br = <> ; + if (obj.completer) + var completions = let (br = br + <> ) <> +
{ br + + template.map( + completion._runCompleter(obj.completer, "", null, args).items, + function (i) <>
{i.text}
{i.description}
, + br) } +
; + return <> +
{link(obj.name)}
{obj.description ? obj.description.replace(/\.$/, "") : ""}
{template.map(obj.names, tag, " ")} {spec((obj.specs || obj.names)[0])}{ @@ -631,7 +652,8 @@ const Dactyl = Module("dactyl", { { obj.description ? br+

{obj.description.replace(/\.?$/, ".")}

: "" }{ extraHelp ? br+extraHelp : "" }{ - !(extraHelp || obj.description) ? br+

Sorry, no help available.

: "" } + !(extraHelp || obj.description) ? br+

Sorry, no help available.

: "" }{ + completions ? br + completions : "" }
.toXMLString().replace(/^ {12}/gm, ""); }, diff --git a/common/locale/en-US/index.xml b/common/locale/en-US/index.xml index 4850c567..fa87d917 100644 --- a/common/locale/en-US/index.xml +++ b/common/locale/en-US/index.xml @@ -226,6 +226,7 @@ This file contains a list of all available commands, mappings and options.
:comclear
Delete all user-defined commands
:command
List and define commands
:contexts
List the completion contexts used during completion of an Ex command
+
:cookies
Change cookie permissions for sites
:cunabbrev
Remove an abbreviation in Command-line mode
:cunmap
Remove a mapping in Command-line mode
:delbmarks
Delete a bookmark
@@ -364,6 +365,9 @@ This file contains a list of all available commands, mappings and options.
banghist
Replace occurrences of ! with the previous command when executing external commands
cdpath
List of directories searched when executing :cd
complete
Items which are completed at the :open prompts
+
cookieaccept
When to accept cookies
+
cookielifetime
The lifetime for which to accept cookies
+
cookies
The default mode for newly added cookie permissions
defsearch
Set the default search engine
editor
Set the external text editor
encoding
Changes the character encoding of the current buffer
diff --git a/common/locale/en-US/options.xml b/common/locale/en-US/options.xml index fd5d3f50..c494ae30 100644 --- a/common/locale/en-US/options.xml +++ b/common/locale/en-US/options.xml @@ -422,6 +422,52 @@ + + 'cookieaccept' 'ca' + 'cookieaccept' + string + all + +

When to accept cookies.

+ +
+
all
Accept all cookies
+
none
Accept no cookies
+
samesite
Accept all non-third-party cookies
+
+
+
+ + + 'cl' 'cookielifetime' + 'cookielifetime' + string + default + +

+ The lifetime for which to accept cookies. The available + options are: +

+
+
default
The lifetime requested by the setter
+
prompt
Always prompt for a lifetime
+
session
The current session
+
days
When a number is given, it is + interpreted as the number of days for which to keep + cookies
+
+
+
+ + + 'ck' 'cookies' + 'cookies' 'ck' + stringlist session + +

The default action for the :cookies command.

+
+
+ 'cpt' 'complete' 'complete' 'cpt' diff --git a/common/locale/en-US/various.xml b/common/locale/en-US/various.xml index a093cc42..41d8c9d2 100644 --- a/common/locale/en-US/various.xml +++ b/common/locale/en-US/various.xml @@ -221,6 +221,38 @@ +

Cookie Settings

+ + :cookies :ck + :cookies host action + +

+ Manage cookies for host. Additionally, the completion + list will show you information about the cookies and + permissions for the current page. +

+ +

Available actions:

+ +
+
unset
Unset special permissions for host
+
allow
Allow cookies from host
+
deny
Deny cookies from host
+
session
Allow cookies from host for the current session
+
list
List all cookies for host
+
clear
Clear all cookies for host
+
clear-persistent
Clear all persistent cookies for host
+
clear-session
Clear all session cookies for host
+
+ +

+ If no action is given, the value of cookies is used. +

+ + :map -b c :cookies +
+
+

Online help

diff --git a/common/modules/base.jsm b/common/modules/base.jsm index 20c3b538..9495f089 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -760,28 +760,6 @@ Class.extend = function extend(subclass, superclass, overrides) { superclass.prototype.constructor = superclass; } -/** - * A base class generator for classes which impliment XPCOM interfaces. - * - * @param {nsIIID|[nsIJSIID]} interfaces The interfaces which the class - * implements. - * @param {Class} superClass A super class. @optional - * @returns {Class} - */ -function XPCOM(interfaces, superClass) { - interfaces = Array.concat(interfaces); - - let shim = interfaces.reduce(function (shim, iface) shim.QueryInterface(iface), - Cc["@dactyl.googlecode.com/base/xpc-interface-shim"].createInstance()); - - let res = Class("XPCOM(" + interfaces + ")", superClass || Class, update( - array([k, v === undefined || callable(v) ? function stub() null : v] - for ([k, v] in Iterator(shim))).toObject(), - { QueryInterface: XPCOMUtils.generateQI(interfaces) })); - shim = interfaces = null; - return res; -} - /** * Memoizes the value of a class property to the falue returned by * the passed function the first time the property is accessed. @@ -843,6 +821,28 @@ Class.prototype = { } }; +/** + * A base class generator for classes which impliment XPCOM interfaces. + * + * @param {nsIIID|[nsIJSIID]} interfaces The interfaces which the class + * implements. + * @param {Class} superClass A super class. @optional + * @returns {Class} + */ +function XPCOM(interfaces, superClass) { + interfaces = Array.concat(interfaces); + + let shim = interfaces.reduce(function (shim, iface) shim.QueryInterface(iface), + Cc["@dactyl.googlecode.com/base/xpc-interface-shim"].createInstance()); + + let res = Class("XPCOM(" + interfaces + ")", superClass || Class, update( + array([k, v === undefined || callable(v) ? function stub() null : v] + for ([k, v] in Iterator(shim))).toObject(), + { QueryInterface: XPCOMUtils.generateQI(interfaces) })); + shim = interfaces = null; + return res; +} + /** * Constructs a mew Module class and instantiates an instance into the current * module global object. diff --git a/common/modules/highlight.jsm b/common/modules/highlight.jsm index d93f4e5b..6202bdb9 100644 --- a/common/modules/highlight.jsm +++ b/common/modules/highlight.jsm @@ -32,7 +32,7 @@ Highlight.defaultValue("baseClass", function () /^(\w*)/.exec(this.class)[0]); Highlight.defaultValue("selector", function () highlight.selector(this.class)); Highlight.defaultValue("sites", function () this.base ? this.base.sites - : ["chrome://dactyl/*", "dactyl:*", "file://*"].concat( + : ["chrome://dactyl/*", "dactyl:*", "file://*", "resource://gre-resources/hiddenWindow.html"].concat( highlight.styleableChrome)); Highlight.defaultValue("style", function () styles.addSheet(true, "highlight:" + this.class, this.sites, this.css, this.agent, true)); diff --git a/common/modules/sanitizer.jsm b/common/modules/sanitizer.jsm index a32eccdc..64e93225 100644 --- a/common/modules/sanitizer.jsm +++ b/common/modules/sanitizer.jsm @@ -8,8 +8,6 @@ // 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 Melodactyl @@ -304,6 +302,24 @@ const Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakR return errors; }) }, { + PERMS: { + unset: 0, + allow: 1, + deny: 2, + session: 8, + }, + UNPERMS: Class.memoize(function () array.toObject([[v, k] for ([k, v] in Iterator(this.PERMS))])), + COMMANDS: { + unset: "Unset", + allow: "Allowed", + deny: "Denied", + session: "Allowed for the current session", + list: "List all cookies for domain", + clear: "Clear all cookies for domain", + "clear-persistent": "Clear all persistent cookies for domain", + "clear-session": "Clear all session cookies for domain", + }, + argPrefMap: { offlineapps: "offlineApps", sitesettings: "siteSettings", @@ -414,6 +430,93 @@ const Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakR ], privateData: true }); + + + function getPerms(host) { + let uri = util.createURI(host); + if (uri) + return Sanitizer.UNPERMS[services.get("permissions").testPermission(uri, "cookie")]; + return "unset"; + } + function setPerms(host, perm) { + let uri = util.createURI(host); + services.get("permissions").remove(uri, "cookie"); + services.get("permissions").add(uri, "cookie", Sanitizer.PERMS[perm]); + } + commands.addUserCommand(["cookies", "ck"], + "Change cookie permissions for sites.", + function (args) { + let host = args.shift(); + let session = true; + if (!args.length) + args = modules.options["cookies"]; + + for (let [,cmd] in Iterator(args)) + switch (cmd) { + case "clear": + for (let c in Sanitizer.iterCookies(host)) + services.get("cookies").remove(c.host, c.name, c.path, false); + break; + case "clear-persistent": + session = false; + case "clear-session": + for (let c in Sanitizer.iterCookies(host)) + if (c.isSession == session) + services.get("cookies").remove(c.host, c.name, c.path, false); + return; + + case "list": + modules.commandLine.commandOutput(template.tabular( + ["Host", "Session", "Path", "Value"], ["padding-right: 1em", "padding-right: 1em", "padding-right: 1em"], + ([c.host, + {c.isSession ? "session" : "persistent"}, + c.path, + c.value] + for (c in Sanitizer.iterCookies(host))))); + return; + default: + util.assert(cmd in Sanitizer.PERMS, "Invalid argument"); + setPerms(host, cmd); + } + }, { + argCount: "+", + completer: function (context, args) { + switch (args.completeArg) { + case 0: + modules.completion.visibleHosts(context); + context.title[1] = "Current Permissions"; + context.keys.description = function desc(host) { + let count = [0, 0]; + for (let c in Sanitizer.iterCookies(host)) + count[c.isSession + 0]++; + return <>{Sanitizer.COMMANDS[getPerms(host)]} (session: {count[1]} persistent: {count[0]}); + } + break; + case 1: + context.completions = Sanitizer.COMMANDS; + break; + } + }, + }, true); + }, + completion: function (dactyl, modules, window) { + modules.completion.visibleHosts = function completeHosts(context) { + let res = [], seen = {}; + (function rec(frame) { + try { + res = res.concat(util.subdomains(frame.location.host)); + } catch (e) {} + Array.forEach(frame.frames, rec); + })(window.content); + if (context.filter && !res.some(function (host) host.indexOf(context.filter) >= 0)) + res.push(context.filter); + + context.title = ["Domain"]; + context.anchored = false; + context.compare = modules.CompletionContext.Sort.unsorted; + context.keys = { text: util.identity, description: util.identity }; + context.completions = res.filter(function (h) !set.add(seen, h)); + }; }, options: function (dactyl, modules) { const options = modules.options; @@ -427,7 +530,8 @@ const Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakR setter: function (value) { if (services.get("privateBrowsing").privateBrowsingEnabled != value) services.get("privateBrowsing").privateBrowsingEnabled = value - } + }, + persist: false }); options.add(["sanitizeitems", "si"], @@ -481,6 +585,55 @@ const Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakR }, validator: function (value) /^(a(ll)?|s(ession)|\d+[mhdw])$/.test(value) }); + + + options.add(["cookies", "ck"], + "The default mode for newly added cookie permissions", + "stringlist", "session", + { completer: function (context) iter(Sanitizer.COMMANDS) }); + options.add(["cookieaccept", "ca"], + "When to accept cookies", + "string", "all", + { + PREF: "network.cookie.cookieBehavior", + completer: function (context) [ + ["all", "Accept all cookies"], + ["samesite", "Accept all non-third-party cookies"], + ["none", "Accept no cookies"] + ], + getter: function () (this.completer()[prefs.get(this.PREF)] || ["all"])[0], + setter: function (val) { + prefs.set(this.PREF, this.completer().map(function (i) i[0]).indexOf(val)); + return val; + }, + initialValue: true, + persist: false + }); + options.add(["cookielifetime", "cl"], + "The lifetime for which to accept cookies", + "string", "default", { + PREF: "network.cookie.lifetimePolicy", + PREF_DAYS: "network.cookie.lifetime.days", + completer: function (context) [ + ["default", "The lifetime requested by the setter"], + ["prompt", "Always prompt for a lifetime"], + ["session", "The current session"] + ], + getter: function () (this.completer()[prefs.get(this.PREF)] + || [prefs.get(this.PREF_DAYS)])[0], + setter: function (value) { + let val = this.completer().map(function (i) i[0]).indexOf(value); + if (val > -1) + prefs.set(this.PREF, val); + else { + prefs.set(this.PREF, 3); + prefs.set(this.PREF_DAYS, parseInt(value)); + } + }, + initialValue: true, + persist: false, + validator: function (val) parseInt(val) == val || modules.Option.validateCompleter.call(this, val) + }); } }); diff --git a/common/modules/util.jsm b/common/modules/util.jsm index 9e95d967..d8306819 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -268,10 +268,14 @@ const Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) domToString: function (node) { this._div.appendChild(this._div.ownerDocument.importNode(node, true)); - let sel = this._div.ownerDocument.defaultView.getSelection(); - sel.removeAllRanges(); - sel.selectAllChildren(this._div); - let res = sel.toString(); + if (/^pre(-wrap)?$/.test(this.computedStyle(this._div.lastChild).whiteSpace)) + var res = this._div.textContent; + else { + let sel = this._div.ownerDocument.defaultView.getSelection(); + sel.removeAllRanges(); + sel.selectAllChildren(this._div); + var res = sel.toString(); + } while (this._div.firstChild) this._div.removeChild(this._div.firstChild); return res; @@ -730,6 +734,12 @@ const Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) } }, + overlayWindow: function (url, fn) { + Array.concat(url).forEach(function (url) { + this.overlays[url] = fn; + }, this); + }, + /** * Parses the fields of a form and returns a URL/POST-data pair * that is the equivalent of submitting the form. @@ -797,6 +807,26 @@ const Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) } }, + /** + * An interruptible generator that returns all values between start + * and end. The thread yields every time milliseconds. + * + * @param {number} start The interval's start value. + * @param {number} end The interval's end value. + * @param {number} time The time in milliseconds between thread yields. + * @returns {Iterator(Object)} + */ + interruptibleRange: function interruptibleRange(start, end, time) { + let endTime = Date.now() + time; + while (start < end) { + if (Date.now() > endTime) { + util.threadYield(true, true); + endTime = Date.now() + time; + } + yield start++; + } + }, + maxErrors: 15, errors: Class.memoize(function () []), reportError: function (error) { @@ -823,29 +853,26 @@ const Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) }, /** - * An interruptible generator that returns all values between start - * and end. The thread yields every time milliseconds. + * Given a domain, returns an array of all non-toplevel subdomains + * of that domain. * - * @param {number} start The interval's start value. - * @param {number} end The interval's end value. - * @param {number} time The time in milliseconds between thread yields. - * @returns {Iterator(Object)} + * @param {string} host The host for which to find subdomains. + * @returns {[string]} */ - interruptibleRange: function interruptibleRange(start, end, time) { - let endTime = Date.now() + time; - while (start < end) { - if (Date.now() > endTime) { - util.threadYield(true, true); - endTime = Date.now() + time; - } - yield start++; - } - }, + subdomains: function subdomains(host) { + if (/(^|\.)\d+$|:.*:/.test(host)) + // IP address or similar + return [host]; - overlayWindow: function (url, fn) { - Array.concat(url).forEach(function (url) { - this.overlays[url] = fn; - }, this); + let base = host.replace(/.*\.(.+?\..+?)$/, "$1"); + try { + base = services.get("tld").getBaseDomainFromHost(host); + } + catch (e) {} + + let ary = host.split("."); + ary = [ary.slice(i).join(".") for (i in util.range(ary.length - 1, 0, -1))]; + return ary.filter(function (h) h.length >= base.length); }, /**