Skip to content

Instantly share code, notes, and snippets.

@kmaglione
Created August 8, 2012 23:59
Show Gist options
  • Save kmaglione/3299842 to your computer and use it in GitHub Desktop.
Save kmaglione/3299842 to your computer and use it in GitHub Desktop.
diff --git a/common/bootstrap.js b/common/bootstrap.js
--- a/common/bootstrap.js
+++ b/common/bootstrap.js
@@ -190,27 +190,25 @@ function init() {
case "component":
components[fields[1]] = new FactoryProxy(getURI(fields[2]).spec, fields[1]);
break;
case "contract":
components[fields[2]].contractID = fields[1];
break;
case "resource":
- var hardSuffix = /^[^\/]*/.exec(fields[2])[0];
-
resources.push(fields[1], fields[1] + suffix);
resourceProto.setSubstitution(fields[1], getURI(fields[2]));
resourceProto.setSubstitution(fields[1] + suffix, getURI(fields[2]));
}
}
// Flush the cache if necessary, just to be paranoid
let pref = "extensions.dactyl.cacheFlushCheck";
- let val = addon.version + "-" + hardSuffix;
+ let val = addon.version;
if (!Services.prefs.prefHasUserValue(pref) || Services.prefs.getCharPref(pref) != val) {
var cacheFlush = true;
Services.obs.notifyObservers(null, "startupcache-invalidate", "");
Services.prefs.setCharPref(pref, val);
}
try {
module(DISABLE_ACR).init(addon.id);
diff --git a/common/content/bookmarks.js b/common/content/bookmarks.js
--- a/common/content/bookmarks.js
+++ b/common/content/bookmarks.js
@@ -10,23 +10,23 @@
var Bookmarks = Module("bookmarks", {
init: function () {
this.timer = Timer(0, 100, function () {
this.checkBookmarked(buffer.uri);
}, this);
storage.addObserver("bookmark-cache", function (key, event, arg) {
if (["add", "change", "remove"].indexOf(event) >= 0)
- autocommands.trigger("Bookmark" + event[0].toUpperCase() + event.substr(1),
+ autocommands.trigger("Bookmark" + util.capitalize(event),
iter({
bookmark: {
toString: function () "bookmarkcache.bookmarks[" + arg.id + "]",
valueOf: function () arg
}
- }, arg));
+ }, arg).toObject());
bookmarks.timer.tell();
}, window);
},
signals: {
"browser.locationChange": function (webProgress, request, uri) {
statusline.bookmarked = false;
this.checkBookmarked(uri);
@@ -340,21 +340,22 @@ var Bookmarks = Module("bookmarks", {
[, url, charset] = matches;
else
try {
charset = services.history.getCharsetForURI(util.newURI(url));
}
catch (e) {}
if (charset)
- var encodedParam = escape(window.convertFromUnicode(charset, param));
+ var encodedParam = escape(window.convertFromUnicode(charset, param)).replace(/\+/g, encodeURIComponent);
else
- encodedParam = bookmarkcache.keywords[keyword].encodeURIComponent(param);
+ encodedParam = bookmarkcache.keywords[keyword.toLowerCase()].encodeURIComponent(param);
- url = url.replace(/%s/g, encodedParam).replace(/%S/g, param);
+ url = url.replace(/%s/g, function () encodedParam)
+ .replace(/%S/g, function () param);
if (/%s/i.test(data))
postData = window.getPostDataStream(data, param, encodedParam, "application/x-www-form-urlencoded");
}
else if (param)
postData = null;
if (postData)
return [url, postData];
@@ -467,17 +468,17 @@ var Bookmarks = Module("bookmarks", {
tags: args["-tags"] || [],
title: args["-title"] || (args.length === 0 ? buffer.title : null),
url: args.length === 0 ? buffer.uri.spec : args[0]
};
let updated = bookmarks.add(opts);
let action = updated ? "updated" : "added";
- let extra = (opts.title == opts.url) ? "" : " (" + opts.title + ")";
+ let extra = (opts.title && opts.title != opts.url) ? " (" + opts.title + ")" : "";
dactyl.echomsg({ domains: [util.getHost(opts.url)], message: _("bookmark." + action, opts.url + extra) },
1, commandline.FORCE_SINGLELINE);
}, {
argCount: "?",
bang: true,
completer: function (context, args) {
if (!args.bang) {
@@ -702,16 +703,19 @@ var Bookmarks = Module("bookmarks", {
return;
let ctxt = context.fork(name, 0);
ctxt.title = [/*L*/desc + " Suggestions"];
ctxt.keys = { text: util.identity, description: function () "" };
ctxt.compare = CompletionContext.Sort.unsorted;
ctxt.filterFunc = null;
+ if (ctxt.waitingForTab)
+ return;
+
let words = ctxt.filter.toLowerCase().split(/\s+/g);
ctxt.completions = ctxt.completions.filter(function (i) words.every(function (w) i.toLowerCase().indexOf(w) >= 0));
ctxt.hasItems = ctxt.completions.length;
ctxt.incomplete = true;
ctxt.cache.request = bookmarks.getSuggestions(name, ctxt.filter, function (compl) {
ctxt.incomplete = false;
ctxt.completions = array.uniq(ctxt.completions.filter(function (c) compl.indexOf(c) >= 0)
diff --git a/common/content/browser.js b/common/content/browser.js
--- a/common/content/browser.js
+++ b/common/content/browser.js
@@ -175,17 +175,17 @@ var Browser = Module("browser", XPCOM(Ci
// called at the very end of a page load
asyncUpdateUI: util.wrapCallback(function asyncUpdateUI() {
asyncUpdateUI.superapply(this, arguments);
util.timeout(function () { statusline.updateStatus(); }, 100);
}),
setOverLink: util.wrapCallback(function setOverLink(link, b) {
setOverLink.superapply(this, arguments);
dactyl.triggerObserver("browser.overLink", link);
- }),
+ })
}
}, {
}, {
events: function initEvents(dactyl, modules, window) {
events.listen(config.browser, browser, "events", true);
},
commands: function initCommands(dactyl, modules, window) {
commands.add(["o[pen]"],
@@ -199,16 +199,17 @@ var Browser = Module("browser", XPCOM(Ci
privateData: true
});
commands.add(["redr[aw]"],
"Redraw the screen",
function () {
window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils)
.redraw();
+ statusline.overLink = null;
statusline.updateStatus();
commandline.clear();
},
{ argCount: "0" });
},
mappings: function initMappings(dactyl, modules, window) {
let openModes = array.toObject([
[dactyl.CURRENT_TAB, ""],
diff --git a/common/content/commandline.js b/common/content/commandline.js
--- a/common/content/commandline.js
+++ b/common/content/commandline.js
@@ -277,19 +277,22 @@ var CommandWidgets = Class("CommandWidge
yield elem;
},
completionContainer: Class.Memoize(function () this.completionList.parentNode),
contextMenu: Class.Memoize(function () {
["copy", "copylink", "selectall"].forEach(function (tail) {
// some host apps use "hostPrefixContext-copy" ids
- let xpath = "//xul:menuitem[contains(@id, '" + "ontext-" + tail + "') and not(starts-with(@id, 'dactyl-'))]";
- document.getElementById("dactyl-context-" + tail).style.listStyleImage =
- DOM(DOM.XPath(xpath, document).snapshotItem(0)).style.listStyleImage;
+ let css = "menuitem[id$='ontext-" + tail + "']:not([id^=dactyl-])";
+ let style = DOM(css, document).style;
+ DOM("#dactyl-context-" + tail, document).css({
+ listStyleImage: style.listStyleImage,
+ MozImageRegion: style.MozImageRegion
+ });
});
return document.getElementById("dactyl-contextmenu");
}),
multilineOutput: Class.Memoize(function () this._whenReady("dactyl-multiline-output", function (elem) {
highlight.highlightNode(elem.contentDocument.body, "MOW");
}), true),
@@ -748,19 +751,17 @@ var CommandLine = Module("commandline",
let message = isObject(data) ? data : { message: data };
// Make sure the memoized message property is an instance property.
message.message;
this._messageHistory.add(update({ highlight: highlightGroup }, message));
data = message.message;
}
- if ((flags & this.ACTIVE_WINDOW) &&
- window != services.windowWatcher.activeWindow &&
- services.windowWatcher.activeWindow.dactyl)
+ if ((flags & this.ACTIVE_WINDOW) && window != overlay.activeWindow)
return;
if ((flags & this.DISALLOW_MULTILINE) && !this.widgets.mowContainer.collapsed)
return;
let single = flags & (this.FORCE_SINGLELINE | this.DISALLOW_MULTILINE);
let action = this._echoLine;
@@ -1212,16 +1213,17 @@ var CommandLine = Module("commandline",
* @param {boolean} show Passed to {@link #reset}.
* @param {boolean} tabPressed Should be set to true if, and
* only if, this function is being called in response
* to a <Tab> press.
*/
complete: function complete(show, tabPressed) {
this.session.ignoredCount = 0;
+ this.waiting = null;
this.context.reset();
this.context.tabPressed = tabPressed;
this.session.complete(this.context);
if (!this.session.active)
return;
this.reset(show, tabPressed);
@@ -1482,18 +1484,22 @@ var CommandLine = Module("commandline",
default:
break;
}
if (!fromTab)
this.wildIndex = this.wildtypes.length - 1;
if (idx && idx[1] >= idx[0].items.length) {
+ if (!idx[0].incomplete)
+ this.waiting = null;
+ else {
this.waiting = idx;
statusline.progress = _("completion.waitingForResults");
+ }
return;
}
this.waiting = null;
this.itemList.select(idx, null, position);
this.selected = idx;
@@ -1664,16 +1670,17 @@ var CommandLine = Module("commandline",
bases: [modes.COMMAND_LINE]
});
modes.addMode("PROMPT", {
description: "Active when a prompt is open in the command line",
bases: [modes.COMMAND_LINE]
});
modes.addMode("INPUT_MULTILINE", {
+ description: "Active when the command line's multiline input buffer is open",
bases: [modes.INSERT]
});
},
mappings: function init_mappings() {
mappings.add([modes.COMMAND],
[":"], "Enter Command Line mode",
function () { CommandExMode().open(""); });
@@ -1940,16 +1947,17 @@ var ItemList = Class("ItemList", {
start %= this.itemCount || 1;
if (start < 0 && (!noWrap || arguments[1] === null))
start += this.itemCount;
if (noWrap && offset > 0) {
// Check if we've passed any incomplete contexts
let i = groups.indexOf(group);
+ util.assert(i >= 0, undefined, false);
for (; i < groups.length; i++) {
let end = groups[i].offsets.start + groups[i].itemCount;
if (start >= end && groups[i].context.incomplete)
return [groups[i].context, start - groups[i].offsets.start];
if (start >= end);
break;
}
diff --git a/common/content/dactyl.js b/common/content/dactyl.js
--- a/common/content/dactyl.js
+++ b/common/content/dactyl.js
@@ -882,21 +882,25 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
{ file: /*L*/"[Command Line]", line: 1 });
}
},
/**
* Opens one or more URLs. Returns true when load was initiated, or
* false on error.
*
- * @param {string|object|Array} urls A representation of the URLs to open. May be
- * either a string, which will be passed to
- * {@link Dactyl#parseURLs}, an array in the same format as
- * would be returned by the same, or an object as returned by
- * {@link DOM#formData}.
+ * @param {string|Array} urls A representation of the URLs to open.
+ * A string will be passed to {@link Dactyl#parseURLs}. An array may
+ * contain elements of the following forms:
+ *
+ * • {string} A URL to open.
+ * • {[string, {string|Array}]} Pair of a URL and POST data.
+ * • {object} Object compatible with those returned
+ * by {@link DOM#formData}.
+ *
* @param {object} params A set of parameters specifying how to open the
* URLs. The following properties are recognized:
*
* • background If true, new tabs are opened in the background.
*
* • from The designation of the opener, as appears in
* 'activate' and 'newtab' options. If present,
* the newtab option provides the default 'where'
@@ -947,16 +951,28 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
let browser = config.tabbrowser;
function open(loc, where) {
try {
if (isArray(loc))
loc = { url: loc[0], postData: loc[1] };
else if (isString(loc))
loc = { url: loc };
+ else
+ loc = Object.create(loc);
+
+ if (isString(loc.postData))
+ loc.postData = ["application/x-www-form-urlencoded", loc.postData];
+
+ if (isArray(loc.postData)) {
+ let stream = services.MIMEStream(services.StringStream(loc.postData[1]));
+ stream.addHeader("Content-Type", loc.postData[0]);
+ stream.addContentLength = true;
+ loc.postData = stream;
+ }
// decide where to load the first url
switch (where) {
case dactyl.NEW_TAB:
if (!dactyl.has("tabs"))
return open(loc, dactyl.NEW_WINDOW);
@@ -1934,17 +1950,17 @@ var Dactyl = Module("dactyl", XPCOM(Ci.n
if (localRCFile && !localRCFile.equals(rcFile))
io.source(localRCFile.path, { group: contexts.user });
}
}
if (dactyl.commandLineOptions.rcFile == "NONE" || dactyl.commandLineOptions.noPlugins)
options["loadplugins"] = [];
- if (options["loadplugins"])
+ if (options["loadplugins"].length)
dactyl.loadPlugins();
}
catch (e) {
dactyl.reportError(e, true);
}
// after sourcing the initialization files, this function will set
// all gui options to their default values, if they have not been
diff --git a/common/content/editor.js b/common/content/editor.js
--- a/common/content/editor.js
+++ b/common/content/editor.js
@@ -275,17 +275,17 @@ var Editor = Module("editor", XPCOM(Ci.n
idx = Math.constrain(idx + delta, 0, node.textContent.length);
this.selection.collapse(node, idx);
}
finally {
editor.endPlaceHolderTransaction();
}
},
- findChar: function findNumber(key, count, backward, offset) {
+ findChar: function findChar(key, count, backward, offset) {
count = count || 1; // XXX ?
offset = (offset || 0) - !!backward;
// Grab the charcode of the key spec. Using the key name
// directly will break keys like <
let code = DOM.Event.parse(key)[0].charCode;
let char = String.fromCharCode(code);
util.assert(code);
@@ -450,17 +450,17 @@ var Editor = Module("editor", XPCOM(Ci.n
if (force !== true && tmpfile.lastModifiedTime <= lastUpdate)
return;
lastUpdate = Date.now();
let val = tmpfile.read();
if (textBox) {
textBox.value = val;
- if (false) {
+ if (true) {
let elem = DOM(textBox);
elem.attrNS(NS, "modifiable", true)
.style.MozUserInput;
elem.input().attrNS(NS, "modifiable", null);
}
}
else {
while (editor_.rootElement.firstChild)
@@ -778,23 +778,25 @@ var Editor = Module("editor", XPCOM(Ci.n
};
},
mappings: function init_mappings() {
Map.types["editor"] = {
preExecute: function preExecute(args) {
if (editor.editor && !this.editor) {
this.editor = editor.editor;
+ if (!this.noTransaction)
this.editor.beginTransaction();
}
editor.inEditMap = true;
},
postExecute: function preExecute(args) {
editor.inEditMap = false;
if (this.editor) {
+ if (!this.noTransaction)
this.editor.endTransaction();
this.editor = null;
}
},
};
Map.types["operator"] = {
preExecute: function preExecute(args) {
editor.inEditMap = true;
@@ -1153,27 +1155,27 @@ var Editor = Module("editor", XPCOM(Ci.n
bind(["<C-x>"], "Decrement the next number",
function ({ count }) { editor.modifyNumber(-(count || 1)) },
{ count: true });
// text edit mode
bind(["u"], "Undo changes",
function (args) {
- editor.executeCommand("cmd_undo", Math.max(args.count, 1));
+ editor.editor.undo(Math.max(args.count, 1));
editor.deselect();
},
- { count: true });
+ { count: true, noTransaction: true });
bind(["<C-r>"], "Redo undone changes",
function (args) {
- editor.executeCommand("cmd_redo", Math.max(args.count, 1));
+ editor.editor.redo(Math.max(args.count, 1));
editor.deselect();
},
- { count: true });
+ { count: true, noTransaction: true });
bind(["D"], "Delete characters from the cursor to the end of the line",
function () { editor.executeCommand("cmd_deleteToEndOfLine"); });
bind(["o"], "Open line below current",
function () {
editor.executeCommand("cmd_endLine", 1);
modes.push(modes.INSERT);
diff --git a/common/content/events.js b/common/content/events.js
--- a/common/content/events.js
+++ b/common/content/events.js
@@ -111,17 +111,17 @@ var Events = Module("events", {
oncommandupdate="dactyl.modules.events.onFocusChange(event);"/>
<commandset id="dactyl-onselect" commandupdater="true" events="select"
oncommandupdate="dactyl.modules.events.onSelectionChange(event);"/>
</window>
</e4x>.elements()
});
this._fullscreen = window.fullScreen;
- this._lastFocus = null;
+ this._lastFocus = { get: function () null };
this._macroKeys = [];
this._lastMacro = "";
this._macros = storage.newMap("registers", { privateData: true, store: true });
if (storage.exists("macros")) {
for (let [k, m] in storage.newMap("macros", { store: true }))
this._macros.set(k, { text: m.keys, timestamp: m.timeRecorded * 1000 });
storage.remove("macros");
@@ -138,17 +138,17 @@ var Events = Module("events", {
|| elem.localName == "tooltip"
|| !elem.popupBoxObject)
return;
if (!~this.active.indexOf(elem))
this.active.push(elem);
}
- this.active = this.active.filter(function (e) e.popupBoxObject.popupState != "closed");
+ this.active = this.active.filter(function (e) e.popupBoxObject && e.popupBoxObject.popupState != "closed");
if (!this.active.length && !this.activeMenubar)
modes.remove(modes.MENU, true);
else if (modes.main != modes.MENU)
modes.push(modes.MENU);
},
events: {
@@ -367,18 +367,16 @@ var Events = Module("events", {
var wasFeeding = this.feedingKeys;
this.feedingKeys = true;
var wasQuiet = commandline.quiet;
if (quiet)
commandline.quiet = quiet;
- keys = mappings.expandLeader(keys);
-
for (let [, evt_obj] in Iterator(DOM.Event.parse(keys))) {
let now = Date.now();
let key = DOM.Event.stringify(evt_obj);
for (let type in values(["keydown", "keypress", "keyup"])) {
let evt = update({}, evt_obj, { type: type });
if (type !== "keypress" && !evt.keyCode)
evt.keyCode = evt._keyCode || 0;
@@ -620,17 +618,17 @@ var Events = Module("events", {
if (elem.frameElement)
dactyl.focusContent(true);
else if (!(elem instanceof Window) || Editor.getEditor(elem))
dactyl.focus(window);
}
if (elem instanceof Element)
- elem.dactylFocusAllowed = undefined;
+ delete overlay.getData(elem)["focus-allowed"];
},
/*
onFocus: function onFocus(event) {
let elem = event.originalTarget;
if (!(elem instanceof Element))
return;
let win = elem.ownerDocument.defaultView;
@@ -790,19 +788,20 @@ var Events = Module("events", {
modes.main == modes.PASS_THROUGH ||
modes.main == modes.QUOTE
&& modes.getStack(1).main !== modes.PASS_THROUGH
&& !this.shouldPass(event) ||
!modes.passThrough && this.shouldPass(event) ||
!this.processor && event.type === "keydown"
&& options.get("passunknown").getKey(modes.main.allBases)
&& let (key = DOM.Event.stringify(event))
- !modes.main.allBases.some(
+ !(modes.main.count && /^\d$/.test(key) ||
+ modes.main.allBases.some(
function (mode) mappings.hives.some(
- function (hive) hive.get(mode, key) || hive.getCandidates(mode, key)));
+ function (hive) hive.get(mode, key) || hive.getCandidates(mode, key))));
events.dbg("ON " + event.type.toUpperCase() + " " + DOM.Event.stringify(event) +
" passing: " + this.passing + " " +
" pass: " + pass +
" replay: " + event.isReplay +
" macro: " + event.isMacro);
if (event.type === "keydown")
@@ -897,30 +896,32 @@ var Events = Module("events", {
}
if (config.focusChange) {
config.focusChange(win);
return;
}
let urlbar = document.getElementById("urlbar");
- if (elem == null && urlbar && urlbar.inputField == this._lastFocus)
+ if (elem == null && urlbar && urlbar.inputField == this._lastFocus.get())
util.threadYield(true); // Why? --Kris
while (modes.main.ownsFocus
- && modes.topOfStack.params.ownsFocus != elem
- && modes.topOfStack.params.ownsFocus != win
+ && let ({ ownsFocus } = modes.topOfStack.params)
+ (!ownsFocus ||
+ ownsFocus.get() != elem &&
+ ownsFocus.get() != win)
&& !modes.topOfStack.params.holdFocus)
modes.pop(null, { fromFocus: true });
}
finally {
- this._lastFocus = elem;
+ this._lastFocus = util.weakReference(elem);
if (modes.main.ownsFocus)
- modes.topOfStack.params.ownsFocus = elem;
+ modes.topOfStack.params.ownsFocus = util.weakReference(elem);
}
}),
onSelectionChange: function onSelectionChange(event) {
// Ignore selection events caused by editor commands.
if (editor.inEditMap || modes.main == modes.OPERATOR)
return;
diff --git a/common/content/help.xsl b/common/content/help.xsl
--- a/common/content/help.xsl
+++ b/common/content/help.xsl
@@ -292,18 +292,19 @@
</xsl:template>
<!-- Tag Links {{{1 -->
<xsl:template name="linkify-tag">
<xsl:param name="contents" select="text()"/>
<xsl:variable name="tag" select="$contents"/>
<xsl:variable name="tag-url" select="
- regexp:replace(regexp:replace($tag, '%', 'g', '%25'),
- '#', 'g', '%23')"/>
+ regexp:replace(regexp:replace(regexp:replace($tag, '%', 'g', '%25'),
+ '#', 'g', '%23'),
+ ';', 'g', '%3B')"/>
<a style="color: inherit;">
<xsl:if test="not(@link) or @link != 'false'">
<xsl:choose>
<xsl:when test="@link and @link != 'false'">
<xsl:attribute name="href">dactyl://help-tag/<xsl:value-of select="@link"/></xsl:attribute>
</xsl:when>
<xsl:when test="contains(ancestor::*/@document-tags, concat(' ', $tag, ' '))">
diff --git a/common/content/hints.js b/common/content/hints.js
--- a/common/content/hints.js
+++ b/common/content/hints.js
@@ -296,16 +296,17 @@ var HintSession = Class("HintSession", C
let rect = elem.getBoundingClientRect();
if (!rect ||
rect.top > offsets.bottom || rect.bottom < offsets.top ||
rect.left > offsets.right || rect.right < offsets.left)
return false;
if (!rect.width || !rect.height)
if (!Array.some(elem.childNodes, function (elem) elem instanceof Element && DOM(elem).style.float != "none" && isVisible(elem)))
+ if (elem.textContent || !elem.name)
return false;
let computedStyle = doc.defaultView.getComputedStyle(elem, null);
if (computedStyle.visibility != "visible" || computedStyle.display == "none")
return false;
return true;
}
@@ -527,16 +528,17 @@ var HintSession = Class("HintSession", C
*
* Lingers on the active hint briefly to confirm the selection to the user.
*
* @param {number} timeout The number of milliseconds before the active
* hint disappears.
*/
removeHints: function _removeHints(timeout) {
for (let { doc, start, end } in values(this.docs)) {
+ DOM(doc.documentElement).highlight.remove("Hinting");
// Goddamn stupid fucking Gecko 1.x security manager bullshit.
try { delete doc.dactylLabels; } catch (e) { doc.dactylLabels = undefined; }
for (let elem in DOM.XPath("//*[@dactyl:highlight='hints']", doc))
elem.parentNode.removeChild(elem);
for (let i in util.range(start, end + 1)) {
this.pageHints[i].ambiguous = false;
this.pageHints[i].valid = false;
@@ -569,39 +571,40 @@ var HintSession = Class("HintSession", C
*/
show: function _show() {
let hintnum = 1;
let validHint = hints.hintMatcher(this.hintString.toLowerCase());
let activeHint = this.hintNumber || 1;
this.validHints = [];
for (let { doc, start, end } in values(this.docs)) {
+ DOM(doc.documentElement).highlight.add("Hinting");
let [offsetX, offsetY] = this.getContainerOffsets(doc);
inner:
for (let i in (util.interruptibleRange(start, end + 1, 500))) {
let hint = this.pageHints[i];
hint.valid = validHint(hint.text);
if (!hint.valid)
continue inner;
if (hint.text == "" && hint.elem.firstChild && hint.elem.firstChild instanceof HTMLImageElement) {
if (!hint.imgSpan) {
let rect = hint.elem.firstChild.getBoundingClientRect();
if (!rect)
continue;
- hint.imgSpan = util.xmlToDom(<span highlight="Hint" dactyl:hl="HintImage" xmlns:dactyl={NS}/>, doc);
- hint.imgSpan.style.display = "none";
- hint.imgSpan.style.left = (rect.left + offsetX) + "px";
- hint.imgSpan.style.top = (rect.top + offsetY) + "px";
- hint.imgSpan.style.width = (rect.right - rect.left) + "px";
- hint.imgSpan.style.height = (rect.bottom - rect.top) + "px";
- hint.span.parentNode.appendChild(hint.imgSpan);
+ hint.imgSpan = DOM(<span highlight="Hint" dactyl:hl="HintImage" xmlns:dactyl={NS}/>, doc).css({
+ display: "none",
+ left: (rect.left + offsetX) + "px",
+ top: (rect.top + offsetY) + "px",
+ width: (rect.right - rect.left) + "px",
+ height: (rect.bottom - rect.top) + "px"
+ }).appendTo(hint.span.parentNode)[0];
}
}
let str = this.getHintString(hintnum);
let text = [];
if (hint.elem instanceof HTMLInputElement)
if (hint.elem.type === "radio")
text.push(UTF8(hint.elem.checked ? "⊙" : "○"));
@@ -715,17 +718,17 @@ var HintSession = Class("HintSession", C
this.show();
this.process(followFirst);
},
/**
* Display the current status to the user.
*/
updateStatusline: function _updateStatusline() {
- statusline.inputBuffer = (this.escapeNumbers ? options["mapleader"] : "") +
+ statusline.inputBuffer = (this.escapeNumbers ? "\\" : "") +
(this.hintNumber ? this.getHintString(this.hintNumber) : "");
},
});
var Hints = Module("hints", {
init: function init() {
this.resizeTimer = Timer(100, 500, function () {
if (isinstance(modes.main, modes.HINTS))
@@ -754,16 +757,21 @@ var Hints = Module("hints", {
this.addMode("T", "Generate a ‘:tabopen URL’ prompt", function (elem, loc) CommandExMode().open("tabopen " + loc));
this.addMode("W", "Generate a ‘:winopen URL’ prompt", function (elem, loc) CommandExMode().open("winopen " + loc));
this.addMode("a", "Add a bookmark", function (elem) bookmarks.addSearchKeyword(elem));
this.addMode("S", "Add a search keyword", function (elem) bookmarks.addSearchKeyword(elem));
this.addMode("v", "View hint source", function (elem, loc) buffer.viewSource(loc, false));
this.addMode("V", "View hint source in external editor", function (elem, loc) buffer.viewSource(loc, true));
this.addMode("y", "Yank hint location", function (elem, loc) editor.setRegister(null, loc, true));
this.addMode("Y", "Yank hint description", function (elem) editor.setRegister(null, elem.textContent || "", true));
+ this.addMode("A", "Yank hint anchor url", function (elem) {
+ let uri = elem.ownerDocument.documentURIObject.clone();
+ uri.ref = elem.id || elem.name;
+ dactyl.clipboardWrite(uri.spec, true);
+ });
this.addMode("c", "Open context menu", function (elem) DOM(elem).contextmenu());
this.addMode("i", "Show image", function (elem) dactyl.open(elem.src));
this.addMode("I", "Show image in a new tab", function (elem) dactyl.open(elem.src, dactyl.NEW_TAB));
function isScrollable(elem) isinstance(elem, [HTMLFrameElement, HTMLIFrameElement]) ||
Buffer.isScrollable(elem, 0, true) || Buffer.isScrollable(elem, 0, false);
},
@@ -1015,17 +1023,17 @@ var Hints = Module("hints", {
return charsAtBeginningOfWords(hintStrings[0], words, allowWordOverleaping);
else
return stringsAtBeginningOfWords(hintStrings, words, allowWordOverleaping);
};
} //}}}
let indexOf = String.indexOf;
if (options.get("hintmatching").has("transliterated"))
- indexOf = Hints.indexOf;
+ indexOf = Hints.closure.indexOf;
switch (options["hintmatching"][0]) {
case "contains" : return containsMatcher(hintString);
case "wordstartswith": return wordStartsWithMatcher(hintString, true);
case "firstletters" : return wordStartsWithMatcher(hintString, false);
case "custom" : return dactyl.plugins.customHintMatcher(hintString);
default : dactyl.echoerr(_("hints.noMatcher", hintMatching));
}
@@ -1164,17 +1172,17 @@ var Hints = Module("hints", {
[0x1ea0, 0x1eb7, ["A", "a"]], [0x1eb8, 0x1ec7, ["E", "e"]],
[0x1ec8, 0x1ecb, ["I", "i"]], [0x1ecc, 0x1ee3, ["O", "o"]],
[0x1ee4, 0x1ef1, ["U", "u"]], [0x1ef2, 0x1ef9, ["Y", "y"]],
[0x2071, 0x2071, ["i"]], [0x207f, 0x207f, ["n"]],
[0x249c, 0x24b5, "a"], [0x24b6, 0x24cf, "A"],
[0x24d0, 0x24e9, "a"],
[0xfb00, 0xfb06, ["ff", "fi", "fl", "ffi", "ffl", "st", "st"]],
[0xff21, 0xff3a, "A"], [0xff41, 0xff5a, "a"]
- ].forEach(function (start, stop, val) {
+ ].forEach(function ([start, stop, val]) {
if (typeof val != "string")
for (let i = start; i <= stop; i++)
table[String.fromCharCode(i)] = val[(i - start) % val.length];
else {
let n = val.charCodeAt(0);
for (let i = start; i <= stop; i++)
table[String.fromCharCode(i)] = String.fromCharCode(n + i - start);
}
@@ -1182,17 +1190,17 @@ var Hints = Module("hints", {
return table;
}),
indexOf: function indexOf(dest, src) {
let table = this.translitTable;
var end = dest.length - src.length;
if (src.length == 0)
return 0;
outer:
- for (var i = 0; i < end; i++) {
+ for (var i = 0; i <= end; i++) {
var j = i;
for (var k = 0; k < src.length;) {
var s = dest[j++];
s = table[s] || s;
for (var l = 0; l < s.length; l++, k++) {
if (s[l] != src[k])
continue outer;
if (k == src.length - 1)
@@ -1254,45 +1262,50 @@ var Hints = Module("hints", {
bind(["<S-Tab>"],
"Focus the previous matching hint",
function ({ self }) { self.tab(true); });
bind(["<BS>", "<C-h>"],
"Delete the previous character",
function ({ self }) self.backspace());
- bind(["<Leader>"],
+ bind(["\\"],
"Toggle hint filtering",
function ({ self }) { self.escapeNumbers = !self.escapeNumbers; });
},
options: function () {
options.add(["extendedhinttags", "eht"],
"XPath or CSS selector strings of hintable elements for extended hint modes",
"regexpmap", {
+ // Make sure to update the docs when you change this.
"[iI]": "img",
"[asOTvVWy]": [":-moz-any-link", "area[href]", "img[src]", "iframe[src]"],
+ "[A]": ["[id]", "a[name]"],
"[f]": "body",
"[F]": ["body", "code", "div", "html", "p", "pre", "span"],
"[S]": ["input:not([type=hidden])", "textarea", "button", "select"]
},
{
keepQuotes: true,
getKey: function (val, default_)
let (res = array.nth(this.value, function (re) let (match = re.exec(val)) match && match[0] == val, 0))
res ? res.matcher : default_,
- setter: function (vals) {
+ parse: function parse(val) {
+ let vals = parse.supercall(this, val);
for (let value in values(vals))
value.matcher = DOM.compileMatcher(Option.splitList(value.result));
return vals;
},
+ testValues: function testValues(vals, validator) vals.every(function (re) Option.splitList(re).every(validator)),
validator: DOM.validateMatcher
});
options.add(["hinttags", "ht"],
"XPath or CSS selector strings of hintable elements for Hints mode",
+ // Make sure to update the docs when you change this.
"stringlist", ":-moz-any-link,area,button,iframe,input:not([type=hidden]),select,textarea," +
"[onclick],[onmouseover],[onmousedown],[onmouseup],[oncommand]," +
"[tabindex],[role=link],[role=button],[contenteditable=true]",
{
setter: function (values) {
this.matcher = DOM.compileMatcher(values);
return values;
},
diff --git a/common/content/mappings.js b/common/content/mappings.js
--- a/common/content/mappings.js
+++ b/common/content/mappings.js
@@ -350,28 +350,26 @@ var Mappings = Module("mappings", {
},
repeat: Modes.boundProperty(),
get allHives() contexts.allGroups.mappings,
get userHives() this.allHives.filter(function (h) h !== this.builtin, this),
- expandLeader: function expandLeader(keyString) keyString.replace(/<Leader>/i, function () options["mapleader"]),
+ expandLeader: deprecated("your brain", function expandLeader(keyString) keyString),
prefixes: Class.Memoize(function () {
let list = Array.map("CASM", function (s) s + "-");
return iter(util.range(0, 1 << list.length)).map(function (mask)
list.filter(function (p, i) mask & (1 << i)).join("")).toArray().concat("*-");
}),
expand: function expand(keys) {
- keys = keys.replace(/<leader>/i, options["mapleader"]);
-
if (!/<\*-/.test(keys))
var res = keys;
else
res = util.debrace(DOM.Event.iterKeys(keys).map(function (key) {
if (/^<\*-/.test(key))
return ["<", this.prefixes, key.slice(3)];
return key;
}, this).flatten().array).map(function (k) DOM.Event.canonicalKeys(k));
@@ -536,17 +534,17 @@ var Mappings = Module("mappings", {
&& mapmodes.every(function (m) m.count))
args[1] = "<count>" + args[1];
let [lhs, rhs] = args;
if (noremap)
args["-builtin"] = true;
if (!rhs) // list the mapping
- mappings.list(mapmodes, mappings.expandLeader(lhs), hives);
+ mappings.list(mapmodes, lhs, hives);
else {
util.assert(args["-group"].modifiable,
_("map.builtinImmutable"));
args["-group"].add(mapmodes, [lhs],
args["-description"],
contexts.bindMacro(args, "-keys", function (params) params),
{
@@ -654,17 +652,17 @@ var Mappings = Module("mappings", {
commands.add([ch ? ch + "m[ap]" : "map"],
"Map a key sequence" + modeDescription,
function (args) { map(args, false); },
update({}, opts));
commands.add([ch + "no[remap]"],
"Map a key sequence without remapping keys" + modeDescription,
function (args) { map(args, true); },
- update({}, opts));
+ update({ deprecated: ":" + ch + "map -builtin" }, opts));
commands.add([ch + "unm[ap]"],
"Remove a mapping" + modeDescription,
function (args) {
util.assert(args["-group"].modifiable, _("map.builtinImmutable"));
util.assert(args.bang ^ !!args[0], _("error.argumentOrBang"));
@@ -823,24 +821,16 @@ var Mappings = Module("mappings", {
},
javascript: function initJavascript(dactyl, modules, window) {
JavaScript.setCompleter([Mappings.prototype.get, MapHive.prototype.get],
[
null,
function (context, obj, args) [[m.names, m.description] for (m in this.iterate(args[0]))]
]);
},
- options: function initOptions(dactyl, modules, window) {
- options.add(["mapleader", "ml"],
- "Define the replacement keys for the <Leader> pseudo-key",
- "string", "\\", {
- setter: function (value) {
- if (this.hasChanged)
- for (let hive in values(mappings.allHives))
- for (let stack in values(hive.stacks))
- delete stack.states;
- return value;
- }
- });
+ mappings: function initMappings(dactyl, modules, window) {
+ mappings.add([modes.COMMAND],
+ ["\\"], "Emits <Leader> pseudo-key",
+ function () { events.feedkeys("<Leader>") });
}
});
// vim: set fdm=marker sw=4 ts=4 et:
diff --git a/common/content/modes.js b/common/content/modes.js
--- a/common/content/modes.js
+++ b/common/content/modes.js
@@ -114,17 +114,17 @@ var Modes = Module("modes", {
: "PASS THROUGH") + " (next)"
}, {
// Fix me.
preExecute: function (map) { if (modes.main == modes.QUOTE && map.name !== "<C-v>") modes.pop(); },
postExecute: function (map) { if (modes.main == modes.QUOTE && map.name === "<C-v>") modes.pop(); },
onKeyPress: function (events) { if (modes.main == modes.QUOTE) modes.pop(); }
});
this.addMode("IGNORE", { hidden: true }, {
- onKeyPress: function (events) Events.KILL,
+ onKeyPress: function (events) false,
bases: [],
passthrough: true
});
this.addMode("MENU", {
description: "Active when a menu or other pop-up is open",
input: true,
passthrough: true,
@@ -294,17 +294,17 @@ var Modes = Module("modes", {
set: function set(mainMode, extendedMode, params, stack) {
var delayed, oldExtended, oldMain, prev, push;
if (this.inSet) {
dactyl.reportError(Error(_("mode.recursiveSet")), true);
return;
}
- params = params || this.getMode(mainMode || this.main).params;
+ params = params || Object.create(this.getMode(mainMode || this.main).params);
if (!stack && mainMode != null && this._modeStack.length > 1)
this.reset();
this.withSavedValues(["inSet"], function set() {
this.inSet = true;
oldMain = this._main, oldExtended = this._extended;
diff --git a/common/content/statusline.js b/common/content/statusline.js
--- a/common/content/statusline.js
+++ b/common/content/statusline.js
@@ -105,19 +105,21 @@ var StatusLine = Module("statusline", {
this.timeout(function () {
this.updateBufferPosition();
this.updateZoomLevel();
}, 500);
},
"browser.overLink": function (link) {
switch (options["showstatuslinks"]) {
case "status":
+ this.overLink = link ? _("status.link", link) : null;
this.status = link ? _("status.link", link) : buffer.uri;
break;
case "command":
+ this.overLink = null;
if (link)
dactyl.echo(_("status.link", link), commandline.FORCE_SINGLELINE);
else
commandline.clear();
break;
}
},
"browser.progressChange": function onProgressChange(webProgress, request, curSelfProgress, maxSelfProgress, curTotalProgress, maxTotalProgress) {
@@ -245,17 +247,17 @@ var StatusLine = Module("statusline", {
set bookmarked(val) {
this._bookmarked = val;
if (this.status)
this.status = this.status;
},
updateStatus: function updateStatus() {
this.timeout(function () {
- this.status = buffer.uri;
+ this.status = this.overLink || buffer.uri;
});
},
updateUrl: deprecated("statusline.status", function updateUrl(url) { this.status = url || buffer.uri }),
/**
* Set the contents of the status line's input buffer to the given
* string. Used primarily when a key press requires further input
diff --git a/common/modules/addons.jsm b/common/modules/addons.jsm
--- a/common/modules/addons.jsm
+++ b/common/modules/addons.jsm
@@ -78,52 +78,52 @@ var actions = {
description: "Uninstall an extension",
action: callResult("uninstall"),
perm: "uninstall"
},
enable: {
name: "exte[nable]",
description: "Enable an extension",
action: function (addon) { addon.userDisabled = false; },
- filter: function ({ item }) item.userDisabled,
+ filter: function (addon) addon.userDisabled,
perm: "enable"
},
disable: {
name: "extd[isable]",
description: "Disable an extension",
action: function (addon) { addon.userDisabled = true; },
- filter: function ({ item }) !item.userDisabled,
+ filter: function (addon) !addon.userDisabled,
perm: "disable"
},
options: {
name: ["exto[ptions]", "extp[references]"],
description: "Open an extension's preference dialog",
bang: true,
action: function (addon, bang) {
if (bang)
this.window.openDialog(addon.optionsURL, "_blank", "chrome");
else
this.dactyl.open(addon.optionsURL, { from: "extoptions" });
},
- filter: function ({ item }) item.isActive && item.optionsURL
+ filter: function (addon) addon.isActive && addon.optionsURL
},
rehash: {
name: "extr[ehash]",
description: "Reload an extension",
action: function (addon) {
util.assert(config.haveGecko("2b"), _("command.notUseful", config.host));
util.flushCache();
util.timeout(function () {
addon.userDisabled = true;
addon.userDisabled = false;
});
},
get filter() {
- return function ({ item }) !item.userDisabled &&
- !(item.operationsRequiringRestart & (AddonManager.OP_NEEDS_RESTART_ENABLE | AddonManager.OP_NEEDS_RESTART_DISABLE))
+ return function (addon) !addon.userDisabled &&
+ !(addon.operationsRequiringRestart & (AddonManager.OP_NEEDS_RESTART_ENABLE | AddonManager.OP_NEEDS_RESTART_DISABLE))
},
perm: "disable"
},
toggle: {
name: "extt[oggle]",
description: "Toggle an extension's enabled status",
action: function (addon) { addon.userDisabled = !addon.userDisabled; }
},
@@ -165,17 +165,17 @@ var Addon = Class("Addon", {
},
commandAllowed: function commandAllowed(cmd) {
util.assert(Set.has(actions, cmd), _("addon.unknownCommand"));
let action = actions[cmd];
if ("perm" in action && !(this.permissions & AddonManager["PERM_CAN_" + action.perm.toUpperCase()]))
return false;
- if ("filter" in action && !action.filter({ item: this }))
+ if ("filter" in action && !action.filter(this))
return false;
return true;
},
command: function command(cmd) {
util.assert(this.commandAllowed(cmd), _("addon.commandNotAllowed"));
let action = actions[cmd];
@@ -351,17 +351,17 @@ var Addons = Module("addons", {
errors: Class.Memoize(function ()
array(["ERROR_NETWORK_FAILURE", "ERROR_INCORRECT_HASH",
"ERROR_CORRUPT_FILE", "ERROR_FILE_ACCESS"])
.map(function (e) [AddonManager[e], _("AddonManager." + e)])
.toObject())
}, {
}, {
commands: function (dactyl, modules, window) {
- const { CommandOption, commands, completion } = modules;
+ const { CommandOption, commands, completion, io } = modules;
commands.add(["addo[ns]", "ao"],
"List installed extensions",
function (args) {
let addons = AddonList(modules, args["-types"], args[0]);
modules.commandline.echo(addons);
if (modules.commandline.savingOutput)
@@ -407,17 +407,17 @@ var Addons = Module("addons", {
completion.file(context);
},
literal: 0
});
// TODO: handle extension dependencies
values(actions).forEach(function (command) {
let perm = command.perm && AddonManager["PERM_CAN_" + command.perm.toUpperCase()];
- function ok(addon) !perm || addon.permissions & perm;
+ function ok(addon) (!perm || addon.permissions & perm) && (!command.filter || command.filter(addon));
commands.add(Array.concat(command.name),
command.description,
function (args) {
let name = args[0];
if (args.bang && !command.bang)
dactyl.assert(!name, _("error.trailingCharacters"));
else
@@ -425,29 +425,28 @@ var Addons = Module("addons", {
AddonManager.getAddonsByTypes(args["-types"], dactyl.wrapCallback(function (list) {
if (!args.bang || command.bang) {
list = list.filter(function (addon) addon.id == name || addon.name == name);
dactyl.assert(list.length, _("error.invalidArgument", name));
dactyl.assert(list.some(ok), _("error.invalidOperation"));
list = list.filter(ok);
}
+ dactyl.assert(list.every(ok));
if (command.actions)
command.actions(list, this.modules);
else
list.forEach(function (addon) command.action.call(this.modules, addon, args.bang), this);
}));
}, {
argCount: "?", // FIXME: should be "1"
bang: true,
completer: function (context, args) {
completion.addon(context, args["-types"]);
context.filters.push(function ({ item }) ok(item));
- if (command.filter)
- context.filters.push(command.filter);
},
literal: 0,
options: [
{
names: ["-types", "-type", "-t"],
description: "The add-on types to operate on",
default: ["extension"],
completer: function (context, args) completion.addonType(context),
diff --git a/common/modules/base.jsm b/common/modules/base.jsm
--- a/common/modules/base.jsm
+++ b/common/modules/base.jsm
@@ -154,17 +154,17 @@ function defineModule(name, params, modu
currentModule = module;
module.startTime = Date.now();
}
defineModule.loadLog = [];
Object.defineProperty(defineModule.loadLog, "push", {
value: function (val) {
val = defineModule.prefix + val;
- if (false)
+ if (true)
defineModule.dump(val + "\n");
this[this.length] = Date.now() + " " + val;
}
});
defineModule.prefix = "";
defineModule.dump = function dump_() {
let msg = Array.map(arguments, function (msg) {
if (loaded.util && typeof msg == "object")
@@ -284,17 +284,17 @@ function properties(obj, prototypes, deb
if (key in obj && !Set.add(seen, key))
yield key;
}
catch (e) {}
for (; obj; obj = prototypes && prototype(obj)) {
try {
if (sandbox.Object.getOwnPropertyNames || !debugger_ || !services.debugger.isOn)
- var iter = values(Object.getOwnPropertyNames(obj));
+ var iter = (v for each (v in Object.getOwnPropertyNames(obj)));
}
catch (e) {}
if (!iter)
iter = (prop.name.stringValue for (prop in values(debuggerProperties(obj))));
for (let key in iter)
if (!prototypes || !Set.add(seen, key) && obj != orig)
yield key;
@@ -358,17 +358,17 @@ function keys(obj) iter(function keys()
/**
* Iterates over all of the top-level, iterable property values of an
* object.
*
* @param {object} obj The object to inspect.
* @returns {Generator}
*/
function values(obj) iter(function values() {
- if (isinstance(obj, ["Generator", "Iterator"]))
+ if (isinstance(obj, ["Generator", "Iterator", Iter]))
for (let k in obj)
yield k;
else
for (var k in obj)
if (hasOwnProperty.call(obj, k))
yield obj[k];
}());
@@ -1447,27 +1447,17 @@ function iter(obj, iface) {
yield obj.getNext();
})();
}
else if ("enumerator" in obj) {
if (callable(obj.enumerator))
return iter(obj.enumerator());
return iter(obj.enumerator);
}
- res.__noSuchMethod__ = function __noSuchMethod__(meth, args) {
- if (meth in iter)
- var res = iter[meth].apply(iter, [this].concat(args));
- else
- res = let (ary = array(this))
- ary[meth] ? ary[meth].apply(ary, args) : ary.__noSuchMethod__(meth, args);
- if (isinstance(res, ["Iterator", "Generator"]))
- return iter(res);
- return res;
- };
- return res;
+ return Iter(res);
}
update(iter, {
toArray: function toArray(iter) array(iter).array,
// See array.prototype for API docs.
toObject: function toObject(iter) {
let obj = {};
for (let [k, v] in iter)
@@ -1571,16 +1561,33 @@ update(iter, {
zip: function zip(iter1, iter2) {
try {
yield [iter1.next(), iter2.next()];
}
catch (e if e instanceof StopIteration) {}
}
});
+const Iter = Class("Iter", {
+ init: function init(iter) {
+ this.iter = iter;
+ if ("__iterator__" in iter)
+ this.iter = iter.__iterator__();
+
+ if (this.iter.finalize)
+ this.finalize = function finalize() this.iter.finalize.apply(this.iter, arguments);
+ },
+
+ next: function next() this.iter.next(),
+
+ send: function send() this.iter.send.apply(this.iter, arguments),
+
+ __iterator__: function () this.iter
+});
+
/**
* Array utility methods.
*/
var array = Class("array", Array, {
init: function (ary) {
if (isinstance(ary, ["Iterator", "Generator"]) || "__iterator__" in ary)
ary = [k for (k in ary)];
else if (ary.length)
@@ -1726,13 +1733,47 @@ var array = Class("array", Array, {
zip: function zip(ary1, ary2) {
let res = [];
for (let [i, item] in Iterator(ary1))
res.push([item, i in ary2 ? ary2[i] : ""]);
return res;
}
});
+/* Make Minefield not explode, because Minefield exploding is not fun. */
+let iterProto = Iter.prototype;
+Object.keys(iter).forEach(function (k) {
+ iterProto[k] = function () {
+ let res = iter[k].apply(iter, [this].concat(Array.slice(arguments)));
+ if (isinstance(res, ["Iterator", "Generator"]))
+ return Iter(res);
+ return res;
+ };
+});
+
+Object.keys(array).forEach(function (k) {
+ if (!(k in iterProto))
+ iterProto[k] = function () {
+ let res = array[k].apply(array, [this.toArray()].concat(Array.slice(arguments)));
+ if (isinstance(res, ["Iterator", "Generator"]))
+ return Iter(res);
+ if (isArray(res))
+ return array(res);
+ return res;
+ };
+});
+
+Object.getOwnPropertyNames(Array.prototype).forEach(function (k) {
+ if (!(k in iterProto) && callable(Array.prototype[k]))
+ iterProto[k] = function () {
+ let ary = iter(this).toArray();
+ let res = ary[k].apply(ary, arguments);
+ if (isArray(res))
+ return array(res);
+ return res;
+ };
+});
+
endModule();
// catch(e){dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack);}
// vim: set fdm=marker sw=4 ts=4 et ft=javascript:
diff --git a/common/modules/bookmarkcache.jsm b/common/modules/bookmarkcache.jsm
--- a/common/modules/bookmarkcache.jsm
+++ b/common/modules/bookmarkcache.jsm
@@ -37,17 +37,29 @@ update(Bookmark.prototype, {
services.bookmarks.changeBookmarkURI(this.id, uri);
this.tags = tags;
},
encodeURIComponent: function _encodeURIComponent(str) {
if (!this.charset || this.charset === "UTF-8")
return encodeURIComponent(str);
let conv = services.CharsetConv(this.charset);
- return escape(conv.ConvertFromUnicode(str) + conv.Finish());
+ return escape(conv.ConvertFromUnicode(str) + conv.Finish()).replace(/\+/g, encodeURIComponent);
+ },
+
+ get folder() {
+ let res = [];
+ res.toString = function () this.join("/");
+
+ let id = this.id, parent, title;
+ while ((id = services.bookmarks.getFolderIdForItem(id)) &&
+ (title = services.bookmarks.getItemTitle(id)))
+ res.push(title);
+
+ return res.reverse();
}
})
Bookmark.prototype.members.uri = Bookmark.prototype.members.url;
Bookmark.setter = function (key, func) this.prototype.__defineSetter__(key, func);
Bookmark.setter("url", function (val) { this.uri = isString(val) ? newURI(val) : val; });
Bookmark.setter("title", function (val) { services.bookmarks.setItemTitle(this.id, val); });
Bookmark.setter("post", function (val) { bookmarkcache.annotate(this.id, bookmarkcache.POST, val); });
Bookmark.setter("charset", function (val) { bookmarkcache.annotate(this.id, bookmarkcache.CHARSET, val); });
@@ -89,17 +101,17 @@ var BookmarkCache = Module("BookmarkCach
_loadBookmark: function loadBookmark(node) {
if (node.uri == null) // How does this happen?
return false;
let uri = newURI(node.uri);
let keyword = services.bookmarks.getKeywordForBookmark(node.itemId);
- let tags = tags in node ? (node.tags ? node.tags.split(/, /g) : [])
+ let tags = "tags" in node ? (node.tags ? node.tags.split(/, /g) : [])
: services.tagging.getTagsForURI(uri, {}) || [];
let post = BookmarkCache.getAnnotation(node.itemId, this.POST);
let charset = BookmarkCache.getAnnotation(node.itemId, this.CHARSET);
return Bookmark(node.uri, node.title, node.icon && node.icon.spec, post, keyword, tags, charset, node.itemId);
},
annotate: function (id, key, val, timespan) {
@@ -167,17 +179,21 @@ var BookmarkCache = Module("BookmarkCach
},
load: function load() {
let bookmarks = {};
let query = services.history.getNewQuery();
let options = services.history.getNewQueryOptions();
options.queryType = options.QUERY_TYPE_BOOKMARKS;
+ try {
+ // https://bugzil.la/702639
options.excludeItemIfParentHasAnnotation = "livemark/feedURI";
+ }
+ catch (e) {}
let { root } = services.history.executeQuery(query, options);
root.containerOpen = true;
try {
// iterate over the immediate children of this folder
for (let i = 0; i < root.childCount; i++) {
let node = root.getChild(i);
if (node.type == node.RESULT_TYPE_URI) // bookmark
diff --git a/common/modules/bootstrap.jsm b/common/modules/bootstrap.jsm
--- a/common/modules/bootstrap.jsm
+++ b/common/modules/bootstrap.jsm
@@ -118,18 +118,19 @@ else
loadSubScript: function loadSubScript(script) {
let now = Date.now();
this.loader.loadSubScript.apply(this.loader, arguments);
this.times.add("loadSubScript", script, Date.now() - now);
},
cleanup: function unregister() {
- for each (let factory in this.factories.splice(0))
+ for each (let factory in this.factories)
this.manager.unregisterFactory(factory.classID, factory);
+ this.factories = {};
},
purge: function purge() {
dump("dactyl: JSMLoader: purge\n");
this.bootstrap = null;
if (Cu.unload) {
@@ -180,19 +181,23 @@ else
catch (e) {
Cu.reportError(e);
throw e;
}
}
}),
registerFactory: function registerFactory(factory) {
+ if (Set.has(this.factories, factory.contractID))
+ this.manager.unregisterFactory(this.factories[factory.contractID].classID,
+ this.factories[factory.contractID]);
+
this.manager.registerFactory(factory.classID,
String(factory.classID),
factory.contractID,
factory);
- this.factories.push(factory);
+ this.factories[factory.contractID] = factory;
}
};
}catch(e){ dump(e + "\n" + (e.stack || Error().stack)); Components.utils.reportError(e) }
// vim: set fdm=marker sw=4 sts=4 et ft=javascript:
diff --git a/common/modules/buffer.jsm b/common/modules/buffer.jsm
--- a/common/modules/buffer.jsm
+++ b/common/modules/buffer.jsm
@@ -8,18 +8,19 @@ try {"use strict";
Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("buffer", {
exports: ["Buffer", "buffer"],
require: ["prefs", "services", "util"]
}, this);
this.lazyRequire("finder", ["RangeFind"]);
+this.lazyRequire("io", ["io"]);
this.lazyRequire("overlay", ["overlay"]);
-this.lazyRequire("storage", ["storage"]);
+this.lazyRequire("storage", ["File", "storage"]);
this.lazyRequire("template", ["template"]);
/**
* A class to manage the primary web content buffer. The name comes
* from Vim's term, 'buffer', which signifies instances of open
* files.
* @instance buffer
*/
@@ -67,17 +68,17 @@ var Buffer = Module("Buffer", {
climbUrlPath: function climbUrlPath(count) {
let { dactyl } = this.modules;
let url = this.documentURI.clone();
dactyl.assert(url instanceof Ci.nsIURL);
while (count-- && url.path != "/")
- url.path = url.path.replace(/[^\/]+\/*$/, "");
+ url.path = url.path.replace(/[^\/]*\/*$/, "");
dactyl.assert(!url.equals(this.documentURI));
dactyl.open(url.spec);
},
incrementURL: function incrementURL(count) {
let { dactyl } = this.modules;
@@ -389,17 +390,17 @@ var Buffer = Module("Buffer", {
yield elem;
elems = frame.document.getElementsByTagName("a");
for (let elem in iter(elems))
yield elem;
function a(regexp, elem) regexp.test(elem.textContent) === regexp.result ||
Array.some(elem.childNodes, function (child) regexp.test(child.alt) === regexp.result);
- function b(regexp, elem) regexp.test(elem.title);
+ function b(regexp, elem) regexp.test(elem.title) === regexp.result;
let res = Array.filter(frame.document.querySelectorAll(selector), Hints.isVisible);
for (let test in values([a, b]))
for (let regexp in values(regexps))
for (let i in util.range(res.length, 0, -1))
if (test(regexp, res[i]))
yield res[i];
}
@@ -451,36 +452,37 @@ var Buffer = Module("Buffer", {
else if (elem instanceof Ci.nsIDOMHTMLInputElement && elem.type == "file") {
Buffer.openUploadPrompt(elem);
return;
}
let { dactyl } = this.modules;
let ctrlKey = false, shiftKey = false;
+ let button = 0;
switch (dactyl.forceTarget || where) {
case dactyl.NEW_TAB:
case dactyl.NEW_BACKGROUND_TAB:
- ctrlKey = true;
+ button = 1;
shiftKey = dactyl.forceBackground != null ? dactyl.forceBackground
: where != dactyl.NEW_BACKGROUND_TAB;
break;
case dactyl.NEW_WINDOW:
shiftKey = true;
break;
case dactyl.CURRENT_TAB:
break;
}
this.focusElement(elem);
prefs.withContext(function () {
prefs.set("browser.tabs.loadInBackground", true);
let params = {
- screenX: offsetX, screenY: offsetY,
+ button: button, screenX: offsetX, screenY: offsetY,
ctrlKey: ctrlKey, shiftKey: shiftKey, metaKey: ctrlKey
};
DOM(elem).mousedown(params).mouseup(params);
if (!config.haveGecko("2b"))
DOM(elem).click(params);
let sel = util.selectionController(win);
@@ -628,28 +630,34 @@ var Buffer = Module("Buffer", {
* @param {nsIFile} file The file into which to write the result.
*/
saveURI: function saveURI(uri, file, callback, self) {
var persist = services.Persist();
persist.persistFlags = persist.PERSIST_FLAGS_FROM_CACHE
| persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
let window = this.topWindow;
+ if (!file.exists())
+ file.create(Ci.nsIFile.NORMAL_FILE_TYPE, octal(666));
+
let downloadListener = new window.DownloadListener(window,
services.Transfer(uri, File(file).URI, "",
null, null, null, persist));
+ if (callback)
persist.progressListener = update(Object.create(downloadListener), {
onStateChange: util.wrapCallback(function onStateChange(progress, request, flags, status) {
if (callback && (flags & Ci.nsIWebProgressListener.STATE_STOP) && status == 0)
util.trapErrors(callback, self, uri, file, progress, request, flags, status);
return onStateChange.superapply(this, arguments);
})
});
+ else
+ persist.progressListener = downloadListener;
persist.saveURI(uri, null, null, null, null, file);
},
/**
* Scrolls the currently active element horizontally. See
* {@link Buffer.scrollHorizontal} for parameters.
*/
@@ -1576,47 +1584,50 @@ var Buffer = Module("Buffer", {
function (args) {
let arg = args[0];
// FIXME: arg handling is a bit of a mess, check for filename
dactyl.assert(!arg || arg[0] == ">" && !config.OS.isWindows,
_("error.trailingCharacters"));
const PRINTER = "PostScript/default";
- const BRANCH = "print.printer_" + PRINTER + ".";
+ const BRANCH = "printer_" + PRINTER + ".";
+ const BRANCHES = ["print.", BRANCH, "print." + BRANCH];
+ function set(pref, value) {
+ BRANCHES.forEach(function (branch) { prefs.set(branch + pref, value) });
+ }
prefs.withContext(function () {
if (arg) {
prefs.set("print.print_printer", PRINTER);
- prefs.set( "print.print_to_file", true);
- prefs.set(BRANCH + "print_to_file", true);
-
- prefs.set( "print.print_to_filename", io.File(arg.substr(1)).path);
- prefs.set(BRANCH + "print_to_filename", io.File(arg.substr(1)).path);
+ set("print_to_file", true);
+ set("print_to_filename", io.File(arg.substr(1)).path);
dactyl.echomsg(_("print.toFile", arg.substr(1)));
}
else
dactyl.echomsg(_("print.sending"));
prefs.set("print.always_print_silent", args.bang);
+ if (false)
prefs.set("print.show_print_progress", !args.bang);
config.browser.contentWindow.print();
});
- if (arg)
- dactyl.echomsg(_("print.printed", arg.substr(1)));
- else
dactyl.echomsg(_("print.sent"));
},
{
argCount: "?",
bang: true,
+ completer: function (context, args) {
+ if (args.bang && /^>/.test(context.filter))
+ context.fork("file", 1, modules.completion, "file");
+ },
literal: 0
});
commands.add(["pa[geinfo]"],
"Show various page information",
function (args) {
let arg = args[0];
let opt = options.get("pageinfo");
@@ -1801,17 +1812,18 @@ var Buffer = Module("Buffer", {
this.incomplete = true;
this.completions = Buffer.getDefaultNames(node);
util.httpGet(node.href || node.src || node.documentURI, {
method: "HEAD",
callback: function callback(xhr) {
context.incomplete = false;
try {
if (/filename="(.*?)"/.test(xhr.getResponseHeader("Content-Disposition")))
- context.completions.push([decodeURIComponent(RegExp.$1), _("buffer.save.suggested")]);
+ context.completions.push([decodeURIComponent(RegExp.$1),
+ _("buffer.save.suggested")]);
}
finally {
context.completions = context.completions.slice();
}
},
notificationCallbacks: Class(XPCOM([Ci.nsIChannelEventSink, Ci.nsIInterfaceRequestor]), {
getInterface: function getInterface(iid) this.QueryInterface(iid),
@@ -2015,17 +2027,17 @@ var Buffer = Module("Buffer", {
function (args) {
buffer.findLink("next", options["nextpattern"], (args.count || 1) - 1, true);
},
{ count: true });
mappings.add([modes.NORMAL], ["[[", "<previous-page>"],
"Follow the link labeled 'prev', 'previous' or '<' if it exists",
function (args) {
- buffer.findLink("previous", options["previouspattern"], (args.count || 1) - 1, true);
+ buffer.findLink("prev", options["previouspattern"], (args.count || 1) - 1, true);
},
{ count: true });
mappings.add([modes.NORMAL], ["gf", "<view-source>"],
"Toggle between rendered and source view",
function () { buffer.viewSource(null, false); });
mappings.add([modes.NORMAL], ["gF", "<view-source-externally>"],
@@ -2225,21 +2237,24 @@ var Buffer = Module("Buffer", {
},
validator: function (value) DOM.validateMatcher.call(this, value)
&& Object.keys(value).every(function (v) v.length == 1)
});
options.add(["linenumbers", "ln"],
"Patterns used to determine line numbers used by G",
"sitemap", {
+ // Make sure to update the docs when you change this.
+ "view-source:*": 'body,[id^=line]',
"code.google.com": '#nums [id^="nums_table"] a[href^="#"]',
"github.com": '.line_numbers>*',
"mxr.mozilla.org": 'a.l',
"pastebin.com": '#code_frame>div>ol>li',
"addons.mozilla.org": '.gutter>.line>a',
+ "bugzilla.mozilla.org": ".bz_comment:not(.bz_first_comment):not(.ih_history)",
"*": '/* Hgweb/Gitweb */ .completecodeline a.codeline, a.linenr'
},
{
getLine: function getLine(doc, line) {
let uri = util.newURI(doc.documentURI);
for (let filter in values(this.value))
if (filter(uri, doc)) {
if (/^func:/.test(filter.result))
@@ -2272,22 +2287,22 @@ var Buffer = Module("Buffer", {
else
return DOM.testMatcher(Option.dequote(value));
});
}
});
options.add(["nextpattern"],
"Patterns to use when guessing the next page in a document sequence",
- "regexplist", UTF8(/'\bnext\b',^>$,^(>>|»)$,^(>|»),(>|»)$,'\bmore\b'/.source),
+ "regexplist", UTF8(/'^Next [>»]','^Next »','\bnext\b',^>$,^(>>|»)$,^(>|»),(>|»)$,'\bmore\b'/.source),
{ regexpFlags: "i" });
options.add(["previouspattern"],
"Patterns to use when guessing the previous page in a document sequence",
- "regexplist", UTF8(/'\bprev|previous\b',^<$,^(<<|«)$,^(<|«),(<|«)$/.source),
+ "regexplist", UTF8(/'[<«] Prev$','« Prev$','\bprev(ious)?\b',^<$,^(<<|«)$,^(<|«),(<|«)$/.source),
{ regexpFlags: "i" });
options.add(["pageinfo", "pa"],
"Define which sections are shown by the :pageinfo command",
"charlist", "gesfm",
{ get values() values(Buffer.pageInfo).toObject() });
options.add(["scroll", "scr"],
diff --git a/common/modules/completion.jsm b/common/modules/completion.jsm
--- a/common/modules/completion.jsm
+++ b/common/modules/completion.jsm
@@ -405,17 +405,17 @@ var CompletionContext = Class("Completio
* longer valid.
*/
generateCompletions: function generateCompletions() {
if (this.offset != this._cache.offset || this.lastActivated != this.top.runCount) {
this.itemCache = {};
this._cache.offset = this.offset;
this.lastActivated = this.top.runCount;
}
- if (!this.itemCache[this.key]) {
+ if (!this.itemCache[this.key] && !this.waitingForTab) {
try {
let res = this._generate();
if (res != null)
this.itemCache[this.key] = res;
}
catch (e) {
util.reportError(e);
this.message = _("error.error", e);
@@ -652,16 +652,17 @@ var CompletionContext = Class("Completio
if (cache) {
if (idx in this.items && !(idx in this.cache.rows))
try {
cache[idx] = util.xmlToDom(this.createRow(this.items[idx]),
doc || this.doc);
}
catch (e) {
util.reportError(e);
+ XML.ignoreWhitespace = XML.prettyPrinting = false;
cache[idx] = util.xmlToDom(
<div highlight="CompItem" style="white-space: nowrap">
<li highlight="CompResult">{this.text}&#xa0;</li>
<li highlight="CompDesc ErrorMsg">{e}&#xa0;</li>
</div>, doc || this.doc);
}
return cache[idx];
}
@@ -1009,17 +1010,16 @@ var Completion = Module("completion", {
context.anchored = false;
context.compare = CompletionContext.Sort.unsorted;
context.filterFunc = null;
let words = context.filter.toLowerCase().split(/\s+/g);
context.hasItems = true;
context.completions = context.completions.filter(function ({ url, title })
words.every(function (w) (url + " " + title).toLowerCase().indexOf(w) >= 0))
- context.incomplete = true;
context.format = this.modules.bookmarks.format;
context.keys.extra = function (item) {
try {
return bookmarkcache.get(item.url).extra;
}
catch (e) {}
return null;
@@ -1028,30 +1028,34 @@ var Completion = Module("completion", {
context.cancel = function () {
this.incomplete = false;
if (running[provider])
service.stopSearch();
running[provider] = false;
};
+ if (!context.waitingForTab) {
+ context.incomplete = true;
+
service.startSearch(context.filter, "", context.result, {
onSearchResult: util.wrapCallback(function onSearchResult(search, result) {
if (result.searchResult <= result.RESULT_SUCCESS)
running[provider] = null;
context.incomplete = result.searchResult >= result.RESULT_NOMATCH_ONGOING;
context.completions = [
{ url: result.getValueAt(i), title: result.getCommentAt(i), icon: result.getImageAt(i) }
for (i in util.range(0, result.matchCount))
];
}),
get onUpdateSearchResult() this.onSearchResult
});
running[provider] = true;
+ }
}),
urls: function (context, tags) {
let compare = String.localeCompare;
let contains = String.indexOf;
if (context.ignoreCase) {
compare = util.compareIgnoreCase;
contains = function (a, b) a && a.toLowerCase().indexOf(b.toLowerCase()) > -1;
diff --git a/common/modules/config.jsm b/common/modules/config.jsm
--- a/common/modules/config.jsm
+++ b/common/modules/config.jsm
@@ -14,16 +14,17 @@ defineModule("config", {
}, this);
this.lazyRequire("addons", ["AddonManager"]);
this.lazyRequire("cache", ["cache"]);
this.lazyRequire("highlight", ["highlight"]);
this.lazyRequire("messages", ["_"]);
this.lazyRequire("prefs", ["localPrefs", "prefs"]);
this.lazyRequire("storage", ["storage", "File"]);
+this.lazyRequire("styles", ["Styles"]);
function AboutHandler() {}
AboutHandler.prototype = {
get classDescription() "About " + config.appName + " Page",
classID: Components.ID("81495d80-89ee-4c36-a88d-ea7c4e5ac63f"),
get contractID() services.ABOUT + config.name,
@@ -210,27 +211,28 @@ var ConfigBase = Class("ConfigBase", {
locales: Class.Memoize(function () {
// TODO: Merge with completion.file code.
function getDir(str) str.match(/^(?:.*[\/\\])?/)[0];
let uri = "resource://dactyl-locale/";
let jar = io.isJarURL(uri);
if (jar) {
let prefix = getDir(jar.JAREntry);
- var res = iter(s.slice(prefix.length).replace(/\/.*/, "") for (s in io.listJar(jar.JARFile, prefix)))
+ var res = iter(s.slice(prefix.length).replace(/\/.*/, "")
+ for (s in io.listJar(jar.JARFile, prefix)))
.toArray();
}
else {
res = array(f.leafName
// Fails on FF3: for (f in util.getFile(uri).iterDirectory())
for (f in values(util.getFile(uri).readDirectory()))
if (f.isDirectory())).array;
}
- function exists(pkg) services["resource:"].hasSubstitution("dactyl-locale-" + pkg);
+ let exists = function exists(pkg) services["resource:"].hasSubstitution("dactyl-locale-" + pkg);
return array.uniq([this.appLocale, this.appLocale.replace(/-.*/, "")]
.filter(exists)
.concat(res));
}),
/**
* Returns the best locale match to the current locale from a list
@@ -397,21 +399,21 @@ var ConfigBase = Class("ConfigBase", {
dtd: Class.Memoize(function ()
iter(this.dtdExtra,
(["dactyl." + k, v] for ([k, v] in iter(config.dtdDactyl))),
(["dactyl." + s, config[s]] for each (s in config.dtdStrings)))
.toObject()),
dtdDactyl: memoize({
get name() config.name,
- get home() "http://dactyl.sourceforge.net/",
+ get home() "http://5digits.org/",
get apphome() this.home + this.name,
code: "http://code.google.com/p/dactyl/",
get issues() this.home + "bug/" + this.name,
- get plugins() "http://dactyl.sf.net/" + this.name + "/plugins",
+ get plugins() "http://5digits.org/" + this.name + "/plugins",
get faq() this.home + this.name + "/faq",
"list.mailto": Class.Memoize(function () config.name + "@googlegroups.com"),
"list.href": Class.Memoize(function () "http://groups.google.com/group/" + config.name),
"hg.latest": Class.Memoize(function () this.code + "source/browse/"), // XXX
"irc": "irc://irc.oftc.net/#pentadactyl",
}),
diff --git a/common/modules/contexts.jsm b/common/modules/contexts.jsm
--- a/common/modules/contexts.jsm
+++ b/common/modules/contexts.jsm
@@ -217,17 +217,20 @@ var Contexts = Module("contexts", {
if (!self && isPlugin && false)
self = Set.has(plugins, id) && plugins[id];
if (self) {
if (Set.has(self, "onUnload"))
util.trapErrors("onUnload", self);
}
else {
- self = args && !isArray(args) ? args : newContext.apply(null, args || [userContext]);
+ let params = Array.slice(args || [userContext]);
+ params[2] = params[2] || File(file).URI.spec;
+
+ self = args && !isArray(args) ? args : newContext.apply(null, params);
update(self, {
NAME: Const(id),
PATH: Const(file.path),
CONTEXT: Const(self),
set isGlobalModule(val) {
diff --git a/common/modules/dom.jsm b/common/modules/dom.jsm
--- a/common/modules/dom.jsm
+++ b/common/modules/dom.jsm
@@ -5,16 +5,19 @@
// given in the LICENSE.txt file included with this file.
/* use strict */
Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("dom", {
exports: ["$", "DOM", "NS", "XBL", "XHTML", "XUL"]
}, this);
+this.lazyRequire("highlight", ["highlight"]);
+this.lazyRequire("template", ["template"]);
+
var XBL = Namespace("xbl", "http://www.mozilla.org/xbl");
var XHTML = Namespace("html", "http://www.w3.org/1999/xhtml");
var XUL = Namespace("xul", "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
var NS = Namespace("dactyl", "http://vimperator.org/namespaces/liberator");
default xml namespace = XHTML;
function BooleanAttribute(attr) ({
get: function (elem) elem.getAttribute(attr) == "true",
@@ -50,22 +53,22 @@ var DOM = Class("DOM", {
val = context.querySelectorAll(val);
if (val == null)
;
else if (typeof val == "xml" && context instanceof Ci.nsIDOMDocument)
this[length++] = DOM.fromXML(val, context, this.nodes);
else if (val instanceof Ci.nsIDOMNode || val instanceof Ci.nsIDOMWindow)
this[length++] = val;
+ else if ("__iterator__" in val || isinstance(val, ["Iterator", "Generator"]))
+ for (let elem in val)
+ this[length++] = elem;
else if ("length" in val)
for (let i = 0; i < val.length; i++)
this[length++] = val[i];
- else if ("__iterator__" in val || isinstance(val, ["Iterator", "Generator"]))
- for (let elem in val)
- this[length++] = elem;
else
this[length++] = val;
this.length = length;
return self || this;
},
__iterator__: function __iterator__() {
@@ -186,21 +189,21 @@ var DOM = Class("DOM", {
},
all: function all(fn, self) {
let res = this.Empty();
this.each(function (elem) {
while(true) {
elem = fn.call(this, elem)
- if (elem instanceof Ci.nsIDOMElement)
+ if (elem instanceof Ci.nsIDOMNode)
res[res.length++] = elem;
else if (elem && "length" in elem)
- for (let i = 0; i < tmp.length; i++)
- res[res.length++] = tmp[j];
+ for (let i = 0; i < elem.length; i++)
+ res[res.length++] = elem[j];
else
break;
}
}, self || this);
return res;
},
map: function map(fn, self) {
@@ -251,16 +254,19 @@ var DOM = Class("DOM", {
get siblings() this.map(function (elem) Array.filter(elem.parentNode.childNodes,
function (e) e != elem && e instanceof Ci.nsIDOMElement),
this),
get siblingsBefore() this.all(function (elem) elem.previousElementSibling),
get siblingsAfter() this.all(function (elem) elem.nextElementSibling),
+ get allSiblingsBefore() this.all(function (elem) elem.previousSibling),
+ get allSiblingsAfter() this.all(function (elem) elem.nextSibling),
+
get class() let (self = this) ({
toString: function () self[0].className,
get list() Array.slice(self[0].classList),
set list(val) self.attr("class", val.join(" ")),
each: function each(meth, arg) {
return self.each(function (elem) {
@@ -313,32 +319,36 @@ var DOM = Class("DOM", {
height: this[0].scrollMaxY + this[0].innerHeight,
get right() this.width + this.left,
get bottom() this.height + this.top,
top: -this[0].scrollY,
left: -this[0].scrollX } :
this[0] ? this[0].getBoundingClientRect() : {},
get viewport() {
- if (this[0] instanceof Ci.nsIDOMWindow)
+ let node = this[0];
+ if (node instanceof Ci.nsIDOMDocument)
+ node = node.defaultView;
+
+ if (node instanceof Ci.nsIDOMWindow)
return {
get width() this.right - this.left,
get height() this.bottom - this.top,
- bottom: this[0].innerHeight,
- right: this[0].innerWidth,
+ bottom: node.innerHeight,
+ right: node.innerWidth,
top: 0, left: 0
};
let r = this.rect;
return {
- width: this[0].clientWidth,
- height: this[0].clientHeight,
- top: r.top + this[0].clientTop,
+ width: node.clientWidth,
+ height: node.clientHeight,
+ top: r.top + node.clientTop,
get bottom() this.top + this.height,
- left: r.left + this[0].clientLeft,
+ left: r.left + node.clientLeft,
get right() this.left + this.width
}
},
scrollPos: function scrollPos(left, top) {
if (arguments.length == 0) {
if (this[0] instanceof Ci.nsIDOMElement)
return { top: this[0].scrollTop, left: this[0].scrollLeft,
@@ -401,17 +411,17 @@ var DOM = Class("DOM", {
}
catch (e) {}
editor instanceof Ci.nsIPlaintextEditor;
editor instanceof Ci.nsIHTMLEditor;
return editor;
},
- get isEditable() !!this.editor,
+ get isEditable() !!this.editor || this[0] instanceof Ci.nsIDOMElement && this.style.MozUserModify == "read-write",
get isInput() isinstance(this[0], [Ci.nsIDOMHTMLInputElement,
Ci.nsIDOMHTMLTextAreaElement,
Ci.nsIDOMXULTextBoxElement])
&& this.isEditable,
/**
* Returns an object representing a Node's computed CSS style.
@@ -779,35 +789,54 @@ var DOM = Class("DOM", {
},
listen: function listen(event, listener, capture) {
if (isObject(event))
capture = listener;
else
event = array.toObject([[event, listener]]);
- for (let [k, v] in Iterator(event))
- event[k] = util.wrapCallback(v, true);
+ for (let [evt, callback] in Iterator(event))
+ event[evt] = util.wrapCallback(callback, true);
return this.each(function (elem) {
- for (let [k, v] in Iterator(event))
- elem.addEventListener(k, v, capture);
+ for (let [evt, callback] in Iterator(event))
+ elem.addEventListener(evt, callback, capture);
});
},
unlisten: function unlisten(event, listener, capture) {
if (isObject(event))
capture = listener;
else
- event = array.toObject([[key, val]]);
+ event = array.toObject([[event, listener]]);
return this.each(function (elem) {
for (let [k, v] in Iterator(event))
elem.removeEventListener(k, v.wrapper || v, capture);
});
},
+ once: function once(event, listener, capture) {
+ if (isObject(event))
+ capture = listener;
+ else
+ event = array.toObject([[event, listener]]);
+
+ for (let pair in Iterator(event)) {
+ let [evt, callback] = pair;
+ event[evt] = util.wrapCallback(function wrapper(event) {
+ this.removeEventListener(evt, wrapper.wrapper, capture);
+ return callback.apply(this, arguments);
+ }, true);
+ }
+
+ return this.each(function (elem) {
+ for (let [k, v] in Iterator(event))
+ elem.addEventListener(k, v, capture);
+ });
+ },
dispatch: function dispatch(event, params, extraProps) {
this.canceled = false;
return this.each(function (elem) {
let evt = DOM.Event(this.document, event, params, elem);
if (!DOM.Event.dispatch(elem, evt, extraProps))
this.canceled = true;
}, this);
@@ -940,31 +969,40 @@ var DOM = Class("DOM", {
}
}, {
init: function init() {
// NOTE: the order of ["Esc", "Escape"] or ["Escape", "Esc"]
// matters, so use that string as the first item, that you
// want to refer to within dactyl's source code for
// comparisons like if (key == "<Esc>") { ... }
this.keyTable = {
- add: ["Plus", "Add"],
+ add: ["+", "Plus", "Add"],
+ back_quote: ["`"],
+ back_slash: ["\\"],
back_space: ["BS"],
+ comma: [","],
count: ["count"],
+ close_bracket: ["]"],
delete: ["Del"],
+ equals: ["="],
escape: ["Esc", "Escape"],
insert: ["Insert", "Ins"],
leader: ["Leader"],
left_shift: ["LT", "<"],
nop: ["Nop"],
+ open_bracket: ["["],
pass: ["Pass"],
+ period: ["."],
+ quote: ["'"],
return: ["Return", "CR", "Enter"],
right_shift: [">"],
+ semicolon: [";"],
slash: ["/"],
space: ["Space", " "],
- subtract: ["Minus", "Subtract"]
+ subtract: ["-", "Minus", "Subtract"]
};
this.key_key = {};
this.code_key = {};
this.key_code = {};
this.code_nativeKey = {};
for (let list in values(this.keyTable))
@@ -1171,17 +1209,19 @@ var DOM = Class("DOM", {
modifier += "M-";
if (/^key/.test(event.type)) {
let charCode = event.type == "keyup" ? 0 : event.charCode; // Why? --Kris
if (charCode == 0) {
if (event.keyCode in this.code_key) {
key = this.code_key[event.keyCode];
- if (event.shiftKey && (key.length > 1 || event.ctrlKey || event.altKey || event.metaKey) || event.dactylShift)
+ if (event.shiftKey && (key.length > 1 || key.toUpperCase() == key.toLowerCase()
+ || event.ctrlKey || event.altKey || event.metaKey)
+ || event.dactylShift)
modifier += "S-";
else if (!modifier && key.length === 1)
if (event.shiftKey)
key = key.toUpperCase();
else
key = key.toLowerCase();
if (!modifier && key.length == 1)
@@ -1525,22 +1565,26 @@ var DOM = Class("DOM", {
}
let result = doc.evaluate(expression, elem,
resolver,
asIterator ? Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE : Ci.nsIDOMXPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
- return Object.create(result, {
- __iterator__: {
- value: asIterator ? function () { let elem; while ((elem = this.iterateNext())) yield elem; }
+ let res = {
+ iterateNext: function () result.iterateNext(),
+ get resultType() result.resultType,
+ get snapshotLength() result.snapshotLength,
+ snapshotItem: function (i) result.snapshotItem(i),
+ __iterator__:
+ asIterator ? function () { let elem; while ((elem = this.iterateNext())) yield elem; }
: function () { for (let i = 0; i < this.snapshotLength; i++) yield this.snapshotItem(i); }
- }
- });
+ };
+ return res;
}
catch (e) {
throw e.stack ? e : Error(e);
}
},
{
resolver: function lookupNamespaceURI(prefix) (DOM.namespaces[prefix] || null)
}),
diff --git a/common/modules/downloads.jsm b/common/modules/downloads.jsm
--- a/common/modules/downloads.jsm
+++ b/common/modules/downloads.jsm
@@ -4,16 +4,18 @@
// given in the LICENSE.txt file included with this file.
/* use strict */
Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("downloads", {
exports: ["Download", "Downloads", "downloads"]
}, this);
+this.lazyRequire("overlay", ["overlay"]);
+
Cu.import("resource://gre/modules/DownloadUtils.jsm", this);
let prefix = "DOWNLOAD_";
var states = iter([v, k.slice(prefix.length).toLowerCase()]
for ([k, v] in Iterator(Ci.nsIDownloadManager))
if (k.indexOf(prefix) == 0))
.toObject();
@@ -21,16 +23,18 @@ var Download = Class("Download", {
init: function init(id, list) {
let self = this;
this.download = services.downloadManager.getDownload(id);
this.list = list;
this.nodes = {
commandTarget: self
};
+ XML.ignoreWhitespace = true;
+ XML.prettyPrinting = false;
util.xmlToDom(
<tr highlight="Download" key="row" xmlns:dactyl={NS} xmlns={XHTML}>
<td highlight="DownloadTitle">
<span highlight="Link">
<a key="launch"
href={self.target.spec} path={self.targetFile.path}>{self.displayName}</a>
<span highlight="LinkInfo">{self.targetFile.path}</span>
</span>
@@ -217,18 +221,20 @@ var DownloadList = Class("DownloadList",
cleanup: function cleanup() {
this.observe.unregister();
services.downloadManager.removeListener(this);
},
message: Class.Memoize(function () {
+ XML.ignoreWhitespace = true;
+ XML.prettyPrinting = false;
util.xmlToDom(<table highlight="Downloads" key="list" xmlns={XHTML}>
- <tr highlight="DownloadHead">
+ <tr highlight="DownloadHead" key="head">
<span>{_("title.Title")}</span>
<span>{_("title.Status")}</span>
<span/>
<span>{_("title.Progress")}</span>
<span/>
<span>{_("title.Speed")}</span>
<span>{_("title.Time remaining")}</span>
<span>{_("title.Source")}</span>
@@ -246,16 +252,19 @@ var DownloadList = Class("DownloadList",
</td>
<td highlight="DownloadPercent" key="percent"/>
<td highlight="DownloadSpeed" key="speed"/>
<td highlight="DownloadTime" key="time"/>
<td/>
</tr>
</table>, this.document, this.nodes);
+ this.index = Array.indexOf(this.nodes.list.childNodes,
+ this.nodes.head);
+
for (let row in iter(services.downloadManager.DBConnection
.createStatement("SELECT id FROM moz_downloads")))
this.addDownload(row.id);
this.update();
util.addObserver(this);
services.downloadManager.addListener(this);
return this.nodes.list;
@@ -267,17 +276,17 @@ var DownloadList = Class("DownloadList",
if (this.filter && download.displayName.indexOf(this.filter) === -1)
return;
this.downloads[id] = download;
let index = values(this.downloads).sort(function (a, b) a.compare(b))
.indexOf(download);
this.nodes.list.insertBefore(download.nodes.row,
- this.nodes.list.childNodes[index + 1]);
+ this.nodes.list.childNodes[index + this.index + 1]);
}
},
removeDownload: function removeDownload(id) {
if (id in this.downloads) {
this.nodes.list.removeChild(this.downloads[id].nodes.row);
delete this.downloads[id];
}
},
@@ -383,17 +392,40 @@ var DownloadList = Class("DownloadList",
this.updateProgress();
}
catch (e) {
util.reportError(e);
}
}
});
-var Downloads = Module("downloads", {
+var Downloads = Module("downloads", XPCOM(Ci.nsIDownloadProgressListener), {
+ init: function () {
+ services.downloadManager.addListener(this);
+ },
+
+ destroy: function destroy() {
+ services.downloadManager.removeListener(this);
+ },
+
+ onDownloadStateChange: function (state, download) {
+ if (download.state == services.downloadManager.DOWNLOAD_FINISHED) {
+ let url = download.source.spec;
+ let title = download.displayName;
+ let file = download.targetFile.path;
+ let size = download.size;
+
+
+ overlay.modules.forEach(function (modules) {
+ modules.dactyl.echomsg({ domains: [util.getHost(url)], message: _("io.downloadFinished", title, file) },
+ 1, modules.commandline.ACTIVE_WINDOW);
+ modules.autocommands.trigger("DownloadPost", { url: url, title: title, file: file, size: size });
+ });
+ }
+ }
}, {
}, {
commands: function initCommands(dactyl, modules, window) {
const { commands, CommandOption } = modules;
commands.add(["downl[oads]", "dl"],
"Display the downloads list",
function (args) {
diff --git a/common/modules/finder.jsm b/common/modules/finder.jsm
--- a/common/modules/finder.jsm
+++ b/common/modules/finder.jsm
@@ -39,17 +39,17 @@ var RangeFinder = Module("rangefinder",
return find;
},
set rangeFind(val) overlay.setData(this.content.document,
"range-find", val)
}),
init: function init() {
prefs.safeSet("accessibility.typeaheadfind.autostart", false);
- // The above should be sufficient, but: http://dactyl.sf.net/bmo/348187
+ // The above should be sufficient, but: http://bugzil.la/348187
prefs.safeSet("accessibility.typeaheadfind", false);
},
cleanup: function cleanup() {
for (let doc in util.iterDocuments()) {
let find = overlay.getData(doc, "range-find", null);
if (find)
find.highlight(true);
diff --git a/common/modules/help.jsm b/common/modules/help.jsm
--- a/common/modules/help.jsm
+++ b/common/modules/help.jsm
@@ -121,17 +121,17 @@ var Help = Module("Help", {
(?:[^-•*+\s] | [-•*+]\S)
.*\n
)+
)
| (?: ^ [^\S\n]* \n) +
]]>), "gmxy");
- let betas = util.regexp(/\[(b\d)\]/, "gx");
+ let betas = util.regexp(/\[((?:b|rc)\d)\]/, "gx");
let beta = array(betas.iterate(NEWS))
.map(function (m) m[1]).uniq().slice(-1)[0];
default xml namespace = NS;
function rec(text, level, li) {
XML.ignoreWhitespace = XML.prettyPrinting = false;
@@ -312,71 +312,79 @@ var Help = Module("Help", {
addDataEntry = function addDataEntry(file, data) // Unideal to an extreme.
addURIEntry(file, "data:text/plain;charset=UTF-8," + encodeURI(data));
}
let empty = Set("area base basefont br col frame hr img input isindex link meta param"
.split(" "));
function fix(node) {
switch(node.nodeType) {
- case Node.ELEMENT_NODE:
- if (isinstance(node, [HTMLBaseElement]))
+ case Ci.nsIDOMNode.ELEMENT_NODE:
+ if (isinstance(node, [Ci.nsIDOMHTMLBaseElement]))
return;
data.push("<"); data.push(node.localName);
- if (node instanceof HTMLHtmlElement)
+ if (node instanceof Ci.nsIDOMHTMLHtmlElement)
data.push(" xmlns=" + XHTML.uri.quote(),
" xmlns:dactyl=" + NS.uri.quote());
for (let { name, value } in array.iterValues(node.attributes)) {
if (name == "dactyl:highlight") {
Set.add(styles, value);
name = "class";
value = "hl-" + value;
}
if (name == "href") {
value = node.href || value;
if (value.indexOf("dactyl://help-tag/") == 0) {
+ try {
let uri = services.io.newChannel(value, null, null).originalURI;
value = uri.spec == value ? "javascript:;" : uri.path.substr(1);
}
+ catch (e) {
+ util.dump("Magical tag thingy failure for: " + value);
+ dactyl.reportError(e);
+ }
+ }
if (!/^#|[\/](#|$)|^[a-z]+:/.test(value))
value = value.replace(/(#|$)/, ".xhtml$1");
}
if (name == "src" && value.indexOf(":") > 0) {
chromeFiles[value] = value.replace(/.*\//, "");
value = value.replace(/.*\//, "");
}
data.push(" ", name, '="',
<>{value}</>.toXMLString().replace(/"/g, "&quot;"),
'"');
}
if (node.localName in empty)
data.push(" />");
else {
data.push(">");
- if (node instanceof HTMLHeadElement)
+ if (node instanceof Ci.nsIDOMHTMLHeadElement)
data.push(<link rel="stylesheet" type="text/css" href="help.css"/>.toXMLString());
Array.map(node.childNodes, fix);
data.push("</", node.localName, ">");
}
break;
- case Node.TEXT_NODE:
+ case Ci.nsIDOMNode.TEXT_NODE:
data.push(<>{node.textContent}</>.toXMLString());
}
}
+ let { buffer, content, events } = modules;
let chromeFiles = {};
let styles = {};
+
for (let [file, ] in Iterator(help.files)) {
let url = "dactyl://help/" + file;
dactyl.open(url);
util.waitFor(function () content.location.href == url && buffer.loaded
- && content.document.documentElement instanceof HTMLHtmlElement,
+ && content.document.documentElement instanceof Ci.nsIDOMHTMLHtmlElement,
15000);
events.waitForPageLoad();
var data = [
'<?xml version="1.0" encoding="UTF-8"?>\n',
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"\n',
' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
];
fix(content.document.documentElement);
diff --git a/common/modules/io.jsm b/common/modules/io.jsm
--- a/common/modules/io.jsm
+++ b/common/modules/io.jsm
@@ -36,36 +36,16 @@ var IO = Module("io", {
init: function init() {
this.config = modules.config;
this._processDir = services.directory.get("CurWorkD", Ci.nsIFile);
this._cwd = this._processDir.path;
this._oldcwd = null;
this._lastRunCommand = ""; // updated whenever the users runs a command with :!
this._scriptNames = [];
-
- this.downloadListener = {
- onDownloadStateChange: function (state, download) {
- if (download.state == services.downloadManager.DOWNLOAD_FINISHED) {
- let url = download.source.spec;
- let title = download.displayName;
- let file = download.targetFile.path;
- let size = download.size;
-
- dactyl.echomsg({ domains: [util.getHost(url)], message: _("io.downloadFinished", title, file) },
- 1, modules.commandline.ACTIVE_WINDOW);
- modules.autocommands.trigger("DownloadPost", { url: url, title: title, file: file, size: size });
- }
- },
- onStateChange: function () {},
- onProgressChange: function () {},
- onSecurityChange: function () {}
- };
-
- services.downloadManager.addListener(this.downloadListener);
},
CommandFileMode: Class("CommandFileMode", modules.CommandMode, {
init: function init(prompt, params) {
init.supercall(this);
this.prompt = isArray(prompt) ? prompt : ["Question", prompt];
update(this, params);
},
@@ -79,20 +59,16 @@ var IO = Module("io", {
this.completer(context);
context = context.fork("files", 0);
modules.completion.file(context);
context.filters = context.filters.concat(this.filters || []);
}
}),
- destroy: function destroy() {
- services.downloadManager.removeListener(this.downloadListener);
- },
-
/**
* Returns all directories named *name* in 'runtimepath'.
*
* @param {string} name
* @returns {nsIFile[])
*/
getRuntimeDirectories: function getRuntimeDirectories(name) {
return modules.options.get("runtimepath").files
@@ -326,24 +302,29 @@ var IO = Module("io", {
return null;
},
/**
* Creates a temporary file.
*
* @returns {File}
*/
- createTempFile: function createTempFile(name) {
- if (name instanceof Ci.nsIFile)
+ createTempFile: function createTempFile(name, type) {
+ if (name instanceof Ci.nsIFile) {
var file = name.clone();
+ if (!type || type == "file")
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, octal(666));
+ else
+ file.createUnique(Ci.nsIFile.DIRECTORY_TYPE, octal(777));
+ }
else {
file = services.directory.get("TmpD", Ci.nsIFile);
file.append(this.config.tempFile + (name ? "." + name : ""));
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, octal(666));
}
- file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, octal(600));
services.externalApp.deleteTemporaryFileOnExit(file);
return File(file);
},
/**
* Determines whether the given URL string resolves to a JAR URL and
@@ -409,17 +390,18 @@ var IO = Module("io", {
*
* @param {string} bin The name of the executable to find.
* @returns {File|null}
*/
pathSearch: function pathSearch(bin) {
if (bin instanceof File || File.isAbsolutePath(bin))
return this.File(bin);
- let dirs = services.environment.get("PATH").split(config.OS.isWindows ? ";" : ":");
+ let dirs = services.environment.get("PATH")
+ .split(config.OS.pathListSep);
// Windows tries the CWD first TODO: desirable?
if (config.OS.isWindows)
dirs = [io.cwd].concat(dirs);
for (let [, dir] in Iterator(dirs))
try {
dir = this.File(dir, true);
@@ -482,20 +464,22 @@ var IO = Module("io", {
}
return process.exitValue;
},
// TODO: when https://bugzilla.mozilla.org/show_bug.cgi?id=68702 is
// fixed use that instead of a tmpfile
/**
- * Runs *command* in a subshell and returns the output in a string. The
- * shell used is that specified by the 'shell' option.
+ * Runs *command* in a subshell and returns the output. The shell used is
+ * that specified by the 'shell' option.
*
- * @param {string} command The command to run.
+ * @param {string|[string]} command The command to run. This can be a shell
+ * command string or an array of strings (a command and arguments)
+ * which will be escaped and concatenated.
* @param {string} input Any input to be provided to the command on stdin.
* @param {function(object)} callback A callback to be called when
* the command completes. @optional
* @returns {object|null}
*/
system: function system(command, input, callback) {
util.dactyl.echomsg(_("io.callingShell", command), 4);
@@ -551,17 +535,18 @@ var IO = Module("io", {
* *func* returns.
*
* @param {function} func The function to execute.
* @param {Object} self The 'this' object used when executing func.
* @returns {boolean} false if temp files couldn't be created,
* otherwise, the return value of *func*.
*/
withTempFiles: function withTempFiles(func, self, checked, ext) {
- let args = array(util.range(0, func.length)).map(bind("createTempFile", this, ext)).array;
+ let args = array(util.range(0, func.length))
+ .map(bind("createTempFile", this, ext)).array;
try {
if (!args.every(util.identity))
return false;
var res = func.apply(self || this, args);
}
finally {
if (!checked || res !== true)
args.forEach(function (f) f.remove(false));
diff --git a/common/modules/javascript.jsm b/common/modules/javascript.jsm
--- a/common/modules/javascript.jsm
+++ b/common/modules/javascript.jsm
@@ -5,17 +5,18 @@
/* use strict */
let { getOwnPropertyNames } = Object;
try {
Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("javascript", {
- exports: ["JavaScript", "javascript"]
+ exports: ["JavaScript", "javascript"],
+ require: ["util"]
}, this);
let isPrototypeOf = Object.prototype.isPrototypeOf;
// TODO: Clean this up.
var JavaScript = Module("javascript", {
init: function () {
@@ -48,19 +49,19 @@ var JavaScript = Module("javascript", {
[this.modules, "modules"],
[this.window, "window"]
]),
toplevel: Class.Memoize(function () this.modules.jsmodules),
lazyInit: true,
- newContext: function () this.modules.newContext(this.modules.userContext, true),
+ newContext: function () this.modules.newContext(this.modules.userContext, true, "Dactyl JS Temp Context"),
- get completers() JavaScript.completers, // For backward compatibility
+ completers: Class.Memoize(function () Object.create(JavaScript.completers)),
// Some object members are only accessible as function calls
getKey: function (obj, key) {
try {
return obj[key];
}
catch (e) {}
return undefined;
@@ -545,17 +546,17 @@ var JavaScript = Module("javascript", {
obj = obj.slice(0, 1);
try {
let func = obj[0][0][funcName];
var completer = func.dactylCompleter;
}
catch (e) {}
if (!completer)
- completer = JavaScript.completers[funcName];
+ completer = this.completers[funcName];
if (!completer)
return null;
// Split up the arguments
let prev = this._get(-2).offset;
let args = [];
for (let [i, idx] in Iterator(this._get(-2).comma)) {
let arg = this._str.substring(prev + 1, idx);
@@ -760,20 +761,20 @@ var JavaScript = Module("javascript", {
modules.CommandREPLMode = Class("CommandREPLMode", modules.CommandMode, {
init: function init(context) {
init.supercall(this);
let self = this;
let sandbox = true || isinstance(context, ["Sandbox"]);
- this.context = modules.newContext(context, !sandbox);
+ this.context = modules.newContext(context, !sandbox, "Dactyl REPL Context");
this.js = modules.JavaScript();
this.js.replContext = this.context;
- this.js.newContext = function newContext() modules.newContext(self.context, !sandbox);
+ this.js.newContext = function newContext() modules.newContext(self.context, !sandbox, "Dactyl REPL Temp Context");
this.js.globals = [
[this.context, /*L*/"REPL Variables"],
[context, /*L*/"REPL Global"]
].concat(this.js.globals.filter(function ([global]) isPrototypeOf.call(global, context)));
if (!isPrototypeOf.call(modules.jsmodules, context))
this.js.toplevel = context;
diff --git a/common/modules/main.jsm b/common/modules/main.jsm
--- a/common/modules/main.jsm
+++ b/common/modules/main.jsm
@@ -23,16 +23,18 @@ var ModuleBase = Class("ModuleBase", {
* @property {[string]} A list of module prerequisites which
* must be initialized before this module is loaded.
*/
requires: [],
toString: function () "[module " + this.constructor.className + "]"
});
+var _id = 0;
+
var Modules = function Modules(window) {
/**
* @constructor Module
*
* Constructs a new ModuleBase class and makes arrangements for its
* initialization. Arguments marked as optional must be either
* entirely elided, or they must have the exact type specified.
* Loading semantics are as follows:
@@ -131,24 +133,25 @@ var Modules = function Modules(window) {
require(jsmodules, script);
}
catch (e) {
util.dump("Loading script " + script + ":");
util.reportError(e);
}
},
- newContext: function newContext(proto, normal) {
+ newContext: function newContext(proto, normal, name) {
if (normal)
return create(proto);
if (services.has("dactyl") && services.dactyl.createGlobal)
var sandbox = services.dactyl.createGlobal();
else
sandbox = Components.utils.Sandbox(window, { sandboxPrototype: proto || modules,
+ sandboxName: name || ("Dactyl Sandbox " + ++_id),
wantXrays: false });
// Hack:
sandbox.Object = jsmodules.Object;
sandbox.File = jsmodules.File;
sandbox.Math = jsmodules.Math;
sandbox.__proto__ = proto || modules;
return sandbox;
@@ -192,23 +195,16 @@ overlay.overlayWindow(Object.keys(config
delete window.dactyl;
this.startTime = Date.now();
this.deferredInit = { load: {} };
this.seen = {};
this.loaded = {};
modules.loaded = this.loaded;
- defineModule.modules.forEach(function defModule(mod) {
- let names = Set(Object.keys(mod.INIT));
- if ("init" in mod.INIT)
- Set.add(names, "init");
-
- keys(names).forEach(function (name) { self.deferInit(name, mod.INIT, mod); });
- });
this.modules = modules;
this.scanModules();
this.initDependencies("init");
modules.config.scripts.forEach(modules.load);
this.scanModules();
@@ -303,38 +299,48 @@ overlay.overlayWindow(Object.keys(config
deferInit: function deferInit(name, INIT, mod) {
let { modules } = this.modules;
let init = this.deferredInit[name] || {};
this.deferredInit[name] = init;
let className = mod.className || mod.constructor.className;
+ if (!Set.has(init, className)) {
init[className] = function callee() {
function finish() {
this.currentDependency = className;
defineModule.time(className, name, INIT[name], mod,
modules.dactyl, modules, window);
}
if (!callee.frobbed) {
callee.frobbed = true;
if (modules[name] instanceof Class)
modules[name].withSavedValues(["currentDependency"], finish);
else
finish.call({});
}
};
INIT[name].require = function (name) { init[name](); };
+ }
},
scanModules: function scanModules() {
let self = this;
let { Module, modules } = this.modules;
+ defineModule.modules.forEach(function defModule(mod) {
+ let names = Set(Object.keys(mod.INIT));
+ if ("init" in mod.INIT)
+ Set.add(names, "init");
+
+ keys(names).forEach(function (name) { self.deferInit(name, mod.INIT, mod); });
+ });
+
Module.list.forEach(function frobModule(mod) {
if (!mod.frobbed) {
modules.__defineGetter__(mod.className, function () {
delete modules[mod.className];
return self.loadModule(mod.className, null, Components.stack.caller);
});
Object.keys(mod.prototype.INIT)
.forEach(function (name) { self.deferInit(name, mod.prototype.INIT, mod); });
diff --git a/common/modules/messages.jsm b/common/modules/messages.jsm
--- a/common/modules/messages.jsm
+++ b/common/modules/messages.jsm
@@ -40,17 +40,18 @@ var Messages = Module("messages", {
cleanup: function cleanup() {
services.stringBundle.flushBundles();
},
bundles: Class.Memoize(function ()
array.uniq([JSMLoader.getTarget("dactyl://locale/" + this.name + ".properties"),
JSMLoader.getTarget("dactyl://locale-local/" + this.name + ".properties"),
"resource://dactyl-locale/en-US/" + this.name + ".properties",
- "resource://dactyl-locale-local/en-US/" + this.name + ".properties"])
+ "resource://dactyl-locale-local/en-US/" + this.name + ".properties"],
+ true)
.map(services.stringBundle.createBundle)
.filter(function (bundle) { try { bundle.getSimpleEnumeration(); return true; } catch (e) { return false; } })),
iterate: function () {
let seen = {};
for (let bundle in values(this.bundles))
for (let { key, value } in iter(bundle.getSimpleEnumeration(), Ci.nsIPropertyElement))
if (!Set.add(seen, key))
diff --git a/common/modules/options.jsm b/common/modules/options.jsm
--- a/common/modules/options.jsm
+++ b/common/modules/options.jsm
@@ -334,17 +334,19 @@ var Option = Class("Option", {
return null;
if (this.type === "string")
defaultValue = Commands.quote(defaultValue);
if (isArray(defaultValue))
defaultValue = defaultValue.map(Option.quote).join(",");
else if (isObject(defaultValue))
- defaultValue = iter(defaultValue).map(function (val) val.map(Option.quote).join(":")).join(",");
+ defaultValue = iter(defaultValue).map(function (val) val.map(function (v) Option.quote(v, /:/))
+ .join(":"))
+ .join(",");
if (isArray(defaultValue))
defaultValue = defaultValue.map(Option.quote).join(",");
return this.parse(defaultValue);
}),
/**
@@ -441,26 +443,26 @@ var Option = Class("Option", {
re.bang = bang;
re.result = result !== undefined ? result : !bang;
re.key = re.bang + Option.quote(util.regexp.getSource(re), /^!|:/);
re.toString = function () Option.unparseRegexp(this, keepQuotes);
return re;
},
unparseRegexp: function unparseRegexp(re, quoted) re.bang + Option.quote(util.regexp.getSource(re), /^!|:/) +
- (typeof re.result === "boolean" ? "" : ":" + (quoted ? re.result : Option.quote(re.result))),
+ (typeof re.result === "boolean" ? "" : ":" + (quoted ? re.result : Option.quote(re.result, /:/))),
parseSite: function parseSite(pattern, result, rest) {
if (isArray(rest)) // Called by Array.map
result = undefined;
let [, bang, filter] = /^(!?)(.*)/.exec(pattern);
- filter = Option.dequote(filter);
+ filter = Option.dequote(filter).trim();
- let quote = this.keepQuotes ? util.identity : Option.quote;
+ let quote = this.keepQuotes ? util.identity : function (v) Option.quote(v, /:/);
return update(Styles.matchFilter(filter), {
bang: bang,
filter: filter,
result: result !== undefined ? result : !bang,
toString: function toString() this.bang + Option.quote(this.filter, /:/) +
(typeof this.result === "boolean" ? "" : ":" + quote(this.result)),
});
@@ -486,17 +488,17 @@ var Option = Class("Option", {
get sitemap() this.sitelist
},
stringify: {
charlist: function (vals) Commands.quote(vals.join("")),
stringlist: function (vals) vals.map(Option.quote).join(","),
- stringmap: function (vals) [Option.quote(k, /:/) + ":" + Option.quote(v) for ([k, v] in Iterator(vals))].join(","),
+ stringmap: function (vals) [Option.quote(k, /:/) + ":" + Option.quote(v, /:/) for ([k, v] in Iterator(vals))].join(","),
regexplist: function (vals) vals.join(","),
get regexpmap() this.regexplist,
get sitelist() this.regexplist,
get sitemap() this.regexplist
},
parse: {
diff --git a/common/modules/prefs.jsm b/common/modules/prefs.jsm
--- a/common/modules/prefs.jsm
+++ b/common/modules/prefs.jsm
@@ -21,16 +21,17 @@ var Prefs = Module("prefs", XPCOM([Ci.ns
RESTORE: "extensions.dactyl.restore.",
SAVED: "extensions.dactyl.saved.",
INIT: {},
init: function init(branch, defaults) {
this._prefContexts = [];
this.branch = services.pref[defaults ? "getDefaultBranch" : "getBranch"](branch || "");
+ if ("nsIPrefBranch2" in Ci)
this.branch instanceof Ci.nsIPrefBranch2;
this.defaults = defaults ? this : this.constructor(branch, true);
this.branches = memoize({
__proto__: this,
get original() this.constructor(this.ORIGINAL + this.root),
get restore() this.constructor(this.RESTORE + this.root),
@@ -64,28 +65,288 @@ var Prefs = Module("prefs", XPCOM([Ci.ns
}
if (reason == "uninstall")
localPrefs.resetBranch();
}
},
/**
+ * Returns a new Prefs instance for the sub-branch *branch* of this
+ * branch.
+ *
+ * @param {string} branch The sub-branch to branch to.
+ * @returns {Prefs}
+ */
+ Branch: function Branch(branch) Prefs(this.root + branch),
+
+ /**
+ * Clears the entire branch.
+ *
+ * @param {string} name The name of the preference branch to delete.
+ */
+ clear: function clear(branch) {
+ this.branch.deleteBranch(branch || "");
+ },
+
+ /**
* Returns the full name of this object's preference branch.
*/
get root() this.branch.root,
/**
- * Returns a new Prefs instance for the sub-branch *branch* of this
- * branch.
+ * Returns the value of the preference *name*, or *defaultValue* if
+ * the preference does not exist.
*
- * @param {string} branch The branch to branch to.
- * @returns {Prefs}
+ * @param {string} name The name of the preference to return.
+ * @param {*} defaultValue The value to return if the preference has no value.
+ * @optional
*/
- Branch: function Branch(branch) Prefs(this.root + branch),
+ get: function get(name, defaultValue) {
+ if (defaultValue == null)
+ defaultValue = null;
+ if (isArray(name))
+ name = name.join(".");
+
+ let type = this.branch.getPrefType(name);
+ try {
+ switch (type) {
+ case Ci.nsIPrefBranch.PREF_STRING:
+ let value = this.branch.getComplexValue(name, Ci.nsISupportsString).data;
+ // try in case it's a localized string (will throw an exception if not)
+ if (!this.branch.prefIsLocked(name) && !this.branch.prefHasUserValue(name) &&
+ RegExp("chrome://.+/locale/.+\\.properties").test(value))
+ value = this.branch.getComplexValue(name, Ci.nsIPrefLocalizedString).data;
+ return value;
+ case Ci.nsIPrefBranch.PREF_INT:
+ return this.branch.getIntPref(name);
+ case Ci.nsIPrefBranch.PREF_BOOL:
+ return this.branch.getBoolPref(name);
+ default:
+ return defaultValue;
+ }
+ }
+ catch (e) {
+ return defaultValue;
+ }
+ },
+
+ getDefault: deprecated("Prefs#defaults.get", function getDefault(name, defaultValue) this.defaults.get(name, defaultValue)),
+
+ /**
+ * Returns an array of all preference names in this branch or the
+ * given sub-branch.
+ *
+ * @param {string} branch The sub-branch for which to return preferences.
+ * @optional
+ */
+ getNames: function getNames(branch) this.branch.getChildList(branch || "", { value: 0 }),
+
+ /**
+ * Returns true if the given preference exists in this branch.
+ *
+ * @param {string} name The name of the preference to check.
+ */
+ has: function has(name) this.branch.getPrefType(name) !== 0,
+
+ /**
+ * Returns true if the given preference is set to its default value.
+ *
+ * @param {string} name The name of the preference to check.
+ */
+ isDefault: function isDefault(name) !this.branch.prefHasUserValue(name),
+
+ _checkSafe: function _checkSafe(name, message, value) {
+ let curval = this.get(name, null);
+
+ if (this.branches.original.get(name) == null && !this.branches.saved.has(name))
+ this.branches.original.set(name, curval, true);
+
+ if (arguments.length > 2 && curval === value)
+ return;
+
+ let defval = this.defaults.get(name, null);
+ let saved = this.branches.saved.get(name);
+
+ if (saved == null && curval != defval || saved != null && curval != saved) {
+ let msg = _("pref.safeSet.warnChanged", name);
+ if (message)
+ msg = template.linkifyHelp(msg + " " + message);
+ util.dactyl.warn(msg);
+ }
+ },
+
+ /**
+ * Resets the preference *name* to *value* but warns the user if the value
+ * is changed from its default.
+ *
+ * @param {string} name The preference name.
+ * @param {value} value The new preference value.
+ * @param {boolean} silent Ignore errors.
+ */
+ safeReset: function safeReset(name, message, silent) {
+ this._checkSafe(name, message);
+ this.set(name, this.branches.original.get(name), silent);
+ this.branches.original.reset(name);
+ this.branches.saved.reset(name);
+ },
+
+ /**
+ * Sets the preference *name* to *value* but warns the user if the value is
+ * changed from its default.
+ *
+ * @param {string} name The preference name.
+ * @param {value} value The new preference value.
+ */
+ safeSet: function safeSet(name, value, message, skipSave) {
+ this._checkSafe(name, message, value);
+ this.set(name, value);
+ this.branches.saved[skipSave ? "reset" : "set"](name, value);
+ },
+
+ /**
+ * Sets the preference *name* to *value*. If the preference already
+ * exists, it must have the same type as the given value.
+ *
+ * @param {name} name The name of the preference to change.
+ * @param {string|number|boolean} value The value to set.
+ * @param {boolean} silent Ignore errors.
+ */
+ set: function set(name, value, silent) {
+ if (this._prefContexts.length)
+ this._prefContexts[this._prefContexts.length - 1][name] = this.get(name, null);
+
+ function assertType(needType)
+ util.assert(type === Ci.nsIPrefBranch.PREF_INVALID || type === needType,
+ type === Ci.nsIPrefBranch.PREF_INT
+ ? /*L*/"E521: Number required after =: " + name + "=" + value
+ : /*L*/"E474: Invalid argument: " + name + "=" + value);
+
+ let type = this.branch.getPrefType(name);
+ try {
+ switch (typeof value) {
+ case "string":
+ assertType(Ci.nsIPrefBranch.PREF_STRING);
+
+ this.branch.setComplexValue(name, Ci.nsISupportsString, services.String(value));
+ break;
+ case "number":
+ assertType(Ci.nsIPrefBranch.PREF_INT);
+
+ this.branch.setIntPref(name, value);
+ break;
+ case "boolean":
+ assertType(Ci.nsIPrefBranch.PREF_BOOL);
+
+ this.branch.setBoolPref(name, value);
+ break;
+ default:
+ if (value == null && this != this.defaults)
+ this.reset(name);
+ else
+ throw FailedAssertion("Unknown preference type: " + typeof value + " (" + name + "=" + value + ")");
+ }
+ }
+ catch (e if silent) {}
+ return value;
+ },
+
+ /**
+ * Saves the current value of a preference to be restored at next
+ * startup.
+ *
+ * @param {string} name The preference to save.
+ */
+ save: function save(name) {
+ let val = this.get(name);
+ this.set(this.RESTORE + name, val);
+ this.safeSet(name, val);
+ },
+
+ /**
+ * Restores saved preferences in the given branch.
+ *
+ * @param {string} branch The branch from which to restore
+ * preferences. @optional
+ */
+ restore: function restore(branch) {
+ this.getNames(this.RESTORE + (branch || "")).forEach(function (pref) {
+ this.safeSet(pref.substr(this.RESTORE.length), this.get(pref), null, true);
+ this.reset(pref);
+ }, this);
+ },
+
+ /**
+ * Resets the preference *name* to its default value.
+ *
+ * @param {string} name The name of the preference to reset.
+ */
+ reset: function reset(name) {
+ if (this.branch.prefHasUserValue(name))
+ this.branch.clearUserPref(name);
+ },
+
+ /**
+ * Resets the preference branch *branch* to its default value.
+ *
+ * @param {string} branch The preference name. @optional
+ */
+ resetBranch: function resetBranch(branch) {
+ this.getNames(branch).forEach(this.closure.reset);
+ },
+
+ /**
+ * Toggles the value of the boolean preference *name*.
+ *
+ * @param {string} name The preference name.
+ */
+ toggle: function toggle(name) {
+ util.assert(this.branch.getPrefType(name) === Ci.nsIPrefBranch.PREF_BOOL,
+ _("error.trailingCharacters", name + "!"));
+ this.set(name, !this.get(name));
+ },
+
+ /**
+ * Pushes a new preference context onto the context stack.
+ *
+ * @see #withContext
+ */
+ pushContext: function pushContext() {
+ this._prefContexts.push({});
+ },
+
+ /**
+ * Pops the top preference context from the stack.
+ *
+ * @see #withContext
+ */
+ popContext: function popContext() {
+ for (let [k, v] in Iterator(this._prefContexts.pop()))
+ this.set(k, v);
+ },
+
+ /**
+ * Executes *func* with a new preference context. When *func* returns, the
+ * context is popped and any preferences set via {@link #setPref} or
+ * {@link #invertPref} are restored to their previous values.
+ *
+ * @param {function} func The function to call.
+ * @param {Object} func The 'this' object with which to call *func*
+ * @see #pushContext
+ * @see #popContext
+ */
+ withContext: function withContext(func, self) {
+ try {
+ this.pushContext();
+ return func.call(self);
+ }
+ finally {
+ this.popContext();
+ }
+ },
observe: null,
observers: {
"nsPref:changed": function (subject, data) {
let observers = this._observers[data];
if (observers) {
let value = this.get(data, false);
this._observers[data] = observers.filter(function (callback) {
@@ -149,258 +410,16 @@ var Prefs = Module("prefs", XPCOM([Ci.ns
};
yield option;
}
};
return template.options(_("pref.hostPreferences", config.host), prefs.call(this));
},
-
- /**
- * Returns the value of a preference.
- *
- * @param {string} name The preference name.
- * @param {value} defaultValue The value to return if the preference
- * is unset.
- */
- get: function get(name, defaultValue) {
- if (defaultValue == null)
- defaultValue = null;
-
- let type = this.branch.getPrefType(name);
- try {
- switch (type) {
- case Ci.nsIPrefBranch.PREF_STRING:
- let value = this.branch.getComplexValue(name, Ci.nsISupportsString).data;
- // try in case it's a localized string (will throw an exception if not)
- if (!this.branch.prefIsLocked(name) && !this.branch.prefHasUserValue(name) &&
- RegExp("chrome://.+/locale/.+\\.properties").test(value))
- value = this.branch.getComplexValue(name, Ci.nsIPrefLocalizedString).data;
- return value;
- case Ci.nsIPrefBranch.PREF_INT:
- return this.branch.getIntPref(name);
- case Ci.nsIPrefBranch.PREF_BOOL:
- return this.branch.getBoolPref(name);
- default:
- return defaultValue;
- }
- }
- catch (e) {
- return defaultValue;
- }
- },
-
- getDefault: deprecated("Prefs#defaults.get", function getDefault(name, defaultValue) this.defaults.get(name, defaultValue)),
-
- /**
- * Returns the names of all preferences.
- *
- * @param {string} branch The branch in which to search preferences.
- * @default ""
- */
- getNames: function getNames(branch) this.branch.getChildList(branch || "", { value: 0 }),
-
- /**
- * Returns true if the current branch has the given preference.
- *
- * @param {string} name The preference name.
- * @returns {boolean}
- */
- has: function get(name) this.branch.getPrefType(name) != 0,
-
- _checkSafe: function _checkSafe(name, message, value) {
- let curval = this.get(name, null);
-
- if (this.branches.original.get(name) == null && !this.branches.saved.has(name))
- this.branches.original.set(name, curval, true);
-
- if (arguments.length > 2 && curval === value)
- return;
-
- let defval = this.defaults.get(name, null);
- let saved = this.branches.saved.get(name);
-
- if (saved == null && curval != defval || saved != null && curval != saved) {
- let msg = _("pref.safeSet.warnChanged", name);
- if (message)
- msg = template.linkifyHelp(msg + " " + message);
- util.dactyl.warn(msg);
- }
- },
-
- /**
- * Resets the preference *name* to *value* but warns the user if the value
- * is changed from its default.
- *
- * @param {string} name The preference name.
- * @param {value} value The new preference value.
- * @param {boolean} silent Ignore errors.
- */
- safeReset: function safeReset(name, message, silent) {
- this._checkSafe(name, message);
- this.set(name, this.branches.original.get(name), silent);
- this.branches.original.reset(name);
- this.branches.saved.reset(name);
- },
-
- /**
- * Sets the preference *name* to *value* but warns the user if the value is
- * changed from its default.
- *
- * @param {string} name The preference name.
- * @param {value} value The new preference value.
- */
- safeSet: function safeSet(name, value, message, skipSave) {
- this._checkSafe(name, message, value);
- this.set(name, value);
- this.branches.saved[skipSave ? "reset" : "set"](name, value);
- },
-
- /**
- * Sets the preference *name* to *value*.
- *
- * @param {string} name The preference name.
- * @param {value} value The new preference value.
- * @param {boolean} silent Ignore errors.
- */
- set: function set(name, value, silent) {
- if (this._prefContexts.length)
- this._prefContexts[this._prefContexts.length - 1][name] = this.get(name, null);
-
- function assertType(needType)
- util.assert(type === Ci.nsIPrefBranch.PREF_INVALID || type === needType,
- type === Ci.nsIPrefBranch.PREF_INT
- ? /*L*/"E521: Number required after =: " + name + "=" + value
- : /*L*/"E474: Invalid argument: " + name + "=" + value);
-
- let type = this.branch.getPrefType(name);
- try {
- switch (typeof value) {
- case "string":
- assertType(Ci.nsIPrefBranch.PREF_STRING);
-
- this.branch.setComplexValue(name, Ci.nsISupportsString, services.String(value));
- break;
- case "number":
- assertType(Ci.nsIPrefBranch.PREF_INT);
-
- this.branch.setIntPref(name, value);
- break;
- case "boolean":
- assertType(Ci.nsIPrefBranch.PREF_BOOL);
-
- this.branch.setBoolPref(name, value);
- break;
- default:
- if (value == null && this != this.defaults)
- this.reset(name);
- else
- throw FailedAssertion("Unknown preference type: " + typeof value + " (" + name + "=" + value + ")");
- }
- }
- catch (e if silent) {}
- return value;
- },
-
- /**
- * Saves the current value of a preference to be restored at next
- * startup.
- *
- * @param {string} name The preference to save.
- */
- save: function save(name) {
- let val = this.get(name);
- this.set(this.RESTORE + name, val);
- this.safeSet(name, val);
- },
-
- /**
- * Restores saved preferences in the given branch.
- *
- * @param {string} branch The branch from which to restore
- * preferences. @optional
- */
- restore: function restore(branch) {
- this.getNames(this.RESTORE + (branch || "")).forEach(function (pref) {
- this.safeSet(pref.substr(this.RESTORE.length), this.get(pref), null, true);
- this.reset(pref);
- }, this);
- },
-
- /**
- * Resets the preference *name* to its default value.
- *
- * @param {string} name The preference name.
- */
- reset: function reset(name) {
- try {
- this.branch.clearUserPref(name);
- }
- catch (e) {} // ignore - thrown if not a user set value
- },
-
- /**
- * Resets the preference branch *branch* to its default value.
- *
- * @param {string} branch The preference name. @optional
- */
- resetBranch: function resetBranch(branch) {
- this.getNames(branch).forEach(this.closure.reset);
- },
-
- /**
- * Toggles the value of the boolean preference *name*.
- *
- * @param {string} name The preference name.
- */
- toggle: function toggle(name) {
- util.assert(this.branch.getPrefType(name) === Ci.nsIPrefBranch.PREF_BOOL,
- _("error.trailingCharacters", name + "!"));
- this.set(name, !this.get(name));
- },
-
- /**
- * Pushes a new preference context onto the context stack.
- *
- * @see #withContext
- */
- pushContext: function pushContext() {
- this._prefContexts.push({});
- },
-
- /**
- * Pops the top preference context from the stack.
- *
- * @see #withContext
- */
- popContext: function popContext() {
- for (let [k, v] in Iterator(this._prefContexts.pop()))
- this.set(k, v);
- },
-
- /**
- * Executes *func* with a new preference context. When *func* returns, the
- * context is popped and any preferences set via {@link #setPref} or
- * {@link #invertPref} are restored to their previous values.
- *
- * @param {function} func The function to call.
- * @param {Object} func The 'this' object with which to call *func*
- * @see #pushContext
- * @see #popContext
- */
- withContext: function withContext(func, self) {
- try {
- this.pushContext();
- return func.call(self);
- }
- finally {
- this.popContext();
- }
- }
}, {
}, {
completion: function init_completion(dactyl, modules) {
modules.completion.preference = function preference(context) {
context.anchored = false;
context.title = [config.host + " Preference", "Value"];
context.keys = { text: function (item) item, description: function (item) prefs.get(item) };
context.completions = prefs.getNames();
diff --git a/common/modules/protocol.jsm b/common/modules/protocol.jsm
--- a/common/modules/protocol.jsm
+++ b/common/modules/protocol.jsm
@@ -137,17 +137,17 @@ ProtocolBase.prototype = {
throw e;
}
}
};
function LocaleChannel(pkg, locale, path, orig) {
for each (let locale in [locale, "en-US"])
for each (let sep in "-/") {
- var channel = Channel(["resource:/", pkg + sep + locale, path].join("/"), orig, true);
+ var channel = Channel(["resource:/", pkg + sep + locale, path].join("/"), orig, true, true);
if (channel)
return channel;
}
return NetError(orig);
}
function StringChannel(data, contentType, uri) {
@@ -179,16 +179,18 @@ function XMLChannel(uri, contentType, no
this.channel = services.StreamChannel(uri);
this.channel.contentStream = this.pipe.inputStream;
this.channel.contentType = contentType || channel.contentType;
this.channel.contentCharset = "UTF-8";
if (!unprivileged)
this.channel.owner = systemPrincipal;
+ let type = this.channel.contentType;
+ if (/^text\/|[\/+]xml$/.test(type)) {
let stream = services.InputStream(channelStream);
let [, pre, doctype, url, extra, open, post] = util.regexp(<![CDATA[
^ ([^]*?)
(?:
(<!DOCTYPE \s+ \S+ \s+) (?:SYSTEM \s+ "([^"]*)" | ((?:[^[>\s]|\s[^[])*))
(\s+ \[)?
([^]*)
)?
@@ -204,16 +206,17 @@ function XMLChannel(uri, contentType, no
this.writes.push("\n]");
for (let [, pre, url] in util.regexp.iterate(/([^]*?)(?:%include\s+"([^"]*)";|$)/gy, post)) {
this.writes.push(pre);
if (url)
this.addChannel(url);
}
}
+ }
this.writes.push(channelStream);
this.writeNext();
}
XMLChannel.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver]),
addChannel: function addChannel(url) {
diff --git a/common/modules/sanitizer.jsm b/common/modules/sanitizer.jsm
--- a/common/modules/sanitizer.jsm
+++ b/common/modules/sanitizer.jsm
@@ -14,20 +14,21 @@
Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("sanitizer", {
exports: ["Range", "Sanitizer", "sanitizer"],
require: ["config", "prefs", "services", "util"]
}, this);
this.lazyRequire("messages", ["_"]);
+this.lazyRequire("overlay", ["overlay"]);
this.lazyRequire("storage", ["storage"]);
this.lazyRequire("template", ["teplate"]);
-let tmp = {};
+let tmp = Object.create(this);
JSMLoader.loadSubScript("chrome://browser/content/sanitize.js", tmp);
tmp.Sanitizer.prototype.__proto__ = Class.prototype;
var Range = Struct("min", "max");
update(Range.prototype, {
contains: function (date) date == null ||
(this.min == null || date >= this.min) && (this.max == null || date <= this.max),
@@ -175,18 +176,20 @@ var Sanitizer = Module("sanitizer", XPCO
init: function init(win) {
let pane = win.document.getElementById("SanitizeDialogPane");
for (let [, pref] in iter(pane.preferences))
pref.updateElements();
init.superapply(this, arguments);
}
});
+ util.timeout(function () { // Load order issue...
+
let (branch = Item.PREFIX + Item.SHUTDOWN_BRANCH) {
- util.overlayWindow("chrome://browser/content/preferences/sanitize.xul",
+ overlay.overlayWindow("chrome://browser/content/preferences/sanitize.xul",
function (win) prefOverlay(branch, true, {
append: {
SanitizeDialogPane:
<groupbox orient="horizontal" xmlns={XUL}>
<caption label={config.appName + /*L*/" (see :help privacy)"}/>
<grid flex="1">
<columns><column flex="1"/><column flex="1"/></columns>
<rows>{
@@ -198,17 +201,17 @@ var Sanitizer = Module("sanitizer", XPCO
}</row>)
}</rows>
</grid>
</groupbox>
}
}));
}
let (branch = Item.PREFIX + Item.BRANCH) {
- util.overlayWindow("chrome://browser/content/sanitize.xul",
+ overlay.overlayWindow("chrome://browser/content/sanitize.xul",
function (win) prefOverlay(branch, false, {
append: {
itemList: <>
<listitem xmlns={XUL} label={/*L*/"See :help privacy for the following:"} disabled="true" style="font-style: italic; font-weight: bold;"/>
{
template.map(ourItems(), function ([item, desc])
<listitem xmlns={XUL} type="checkbox"
label={config.appName + " " + desc}
@@ -229,16 +232,17 @@ var Sanitizer = Module("sanitizer", XPCO
if (item.shouldSanitize(false))],
Range.fromArray(this.range || []));
}, this);
}
});
}
}));
}
+ });
},
firstRun: 0,
addItem: function addItem(name, params) {
let item = this.itemMap[name] || Item(name, params);
this.itemMap[name] = item;
diff --git a/common/modules/services.jsm b/common/modules/services.jsm
--- a/common/modules/services.jsm
+++ b/common/modules/services.jsm
@@ -87,30 +87,31 @@ var Services = Module("Services", {
this.addClass("FileInStream", "@mozilla.org/network/file-input-stream;1", "nsIFileInputStream", "init", false);
this.addClass("FileOutStream","@mozilla.org/network/file-output-stream;1", "nsIFileOutputStream", "init", false);
this.addClass("Find", "@mozilla.org/embedcomp/rangefind;1", "nsIFind");
this.addClass("FormData", "@mozilla.org/files/formdata;1", "nsIDOMFormData");
this.addClass("HtmlConverter","@mozilla.org/widget/htmlformatconverter;1", "nsIFormatConverter");
this.addClass("HtmlEncoder", "@mozilla.org/layout/htmlCopyEncoder;1", "nsIDocumentEncoder");
this.addClass("InterfacePointer", "@mozilla.org/supports-interface-pointer;1", "nsISupportsInterfacePointer", "data");
this.addClass("InputStream", "@mozilla.org/scriptableinputstream;1", "nsIScriptableInputStream", "init");
+ this.addClass("MIMEStream", "@mozilla.org/network/mime-input-stream;1", "nsIMIMEInputStream", "setData");
this.addClass("Persist", "@mozilla.org/embedding/browser/nsWebBrowserPersist;1", "nsIWebBrowserPersist");
this.addClass("Pipe", "@mozilla.org/pipe;1", "nsIPipe", "init");
this.addClass("Process", "@mozilla.org/process/util;1", "nsIProcess", "init");
this.addClass("Pump", "@mozilla.org/network/input-stream-pump;1", "nsIInputStreamPump", "init")
this.addClass("StreamChannel","@mozilla.org/network/input-stream-channel;1",
["nsIInputStreamChannel", "nsIChannel"], "setURI");
this.addClass("StreamCopier", "@mozilla.org/network/async-stream-copier;1","nsIAsyncStreamCopier", "init");
this.addClass("String", "@mozilla.org/supports-string;1", "nsISupportsString", "data");
this.addClass("StringStream", "@mozilla.org/io/string-input-stream;1", "nsIStringInputStream", "data");
this.addClass("Transfer", "@mozilla.org/transfer;1", "nsITransfer", "init");
this.addClass("Transferable", "@mozilla.org/widget/transferable;1", "nsITransferable");
this.addClass("Timer", "@mozilla.org/timer;1", "nsITimer", "initWithCallback");
this.addClass("URL", "@mozilla.org/network/standard-url;1", ["nsIStandardURL", "nsIURL"], "init");
- this.addClass("Xmlhttp", "@mozilla.org/xmlextras/xmlhttprequest;1", "nsIXMLHttpRequest", "open");
+ this.addClass("Xmlhttp", "@mozilla.org/xmlextras/xmlhttprequest;1", [], "open");
this.addClass("XPathEvaluator", "@mozilla.org/dom/xpath-evaluator;1", "nsIDOMXPathEvaluator");
this.addClass("XMLDocument", "@mozilla.org/xml/xml-document;1", ["nsIDOMXMLDocument", "nsIDOMNodeSelector"]);
this.addClass("ZipReader", "@mozilla.org/libjar/zip-reader;1", "nsIZipReader", "open", false);
this.addClass("ZipWriter", "@mozilla.org/zipwriter;1", "nsIZipWriter", "open", false);
},
reinit: function () {},
_create: function (name, args) {
diff --git a/common/modules/styles.jsm b/common/modules/styles.jsm
--- a/common/modules/styles.jsm
+++ b/common/modules/styles.jsm
@@ -415,16 +415,18 @@ var Styles = Module("Styles", {
* given two arguments, returns true if the second argument matches
* the given filter.
*
* @param {string} filter The URI filter to match against.
* @param {nsIURI} uri The location to test.
* @returns {nsIURI -> boolean}
*/
matchFilter: function (filter) {
+ filter = filter.trim();
+
if (filter === "*")
var test = function test(uri) true;
else if (!/^(?:[a-z-]+:|[a-z-.]+$)/.test(filter)) {
let re = util.regexp(filter);
test = function test(uri) re.test(uri.spec);
}
else if (/[*]$/.test(filter)) {
let re = RegExp("^" + util.regexp.escape(filter.substr(0, filter.length - 1)));
diff --git a/common/modules/template.jsm b/common/modules/template.jsm
--- a/common/modules/template.jsm
+++ b/common/modules/template.jsm
@@ -77,23 +77,32 @@ var Binding = Class("Binding", {
desc[k] = this.bind(desc[k]);
res[prop] = desc;
}
}
return res;
})
});
+["appendChild", "getAttribute", "insertBefore", "setAttribute"].forEach(function (key) {
+ Object.defineProperty(Binding.prototype, key, {
+ configurable: true,
+ enumerable: false,
+ value: function () this.node[key].apply(this.node, arguments),
+ writable: true
+ });
+});
+
var Template = Module("Template", {
add: function add(a, b) a + b,
join: function join(c) function (a, b) a + c + b,
map: function map(iter, func, sep, interruptable) {
- XML.ignoreWhitespace = false; XML.prettyPrinting = false;
- if (iter.length) // FIXME: Kludge?
+ XML.ignoreWhitespace = XML.prettyPrinting = false;
+ if (typeof iter.length == "number") // FIXME: Kludge?
iter = array.iterValues(iter);
let res = <></>;
let n = 0;
for each (let i in Iterator(iter)) {
let val = func(i, n);
if (val == undefined)
continue;
if (n++ && sep)
@@ -187,17 +196,17 @@ var Template = Module("Template", {
var text = item[0] || "";
var desc = item[1] || "";
}
else {
var text = this.processor[0].call(this, item, item.result);
var desc = this.processor[1].call(this, item, item.description);
}
- XML.ignoreWhitespace = false; XML.prettyPrinting = false;
+ XML.ignoreWhitespace = XML.prettyPrinting = false;
// <e4x>
return <div highlight={highlightGroup || "CompItem"} style="white-space: nowrap">
<!-- The non-breaking spaces prevent empty elements
- from pushing the baseline down and enlarging
- the row.
-->
<li highlight={"CompResult " + item.highlight}>{text}&#xa0;</li>
<li highlight="CompDesc">{desc}&#xa0;</li>
@@ -213,17 +222,17 @@ var Template = Module("Template", {
if (/^\[.*\]$/.test(topic))
topic = topic.slice(1, -1);
else if (/^n_/.test(topic))
topic = topic.slice(2);
if (help.initialized && !Set.has(help.tags, topic))
return <span highlight={type || ""}>{text || token}</span>;
- XML.ignoreWhitespace = false; XML.prettyPrinting = false;
+ XML.ignoreWhitespace = XML.prettyPrinting = false;
type = type || (/^'.*'$/.test(token) ? "HelpOpt" :
/^\[.*\]$|^E\d{3}$/.test(token) ? "HelpTopic" :
/^:\w/.test(token) ? "HelpEx" : "HelpKey");
return <a highlight={"InlineHelpLink " + type} tag={topic} href={"dactyl://help-tag/" + topic} dactyl:command="dactyl.help" xmlns:dactyl={NS}>{text || topic}</a>;
},
HelpLink: function (token) {
if (!help.initialized)
@@ -233,28 +242,28 @@ var Template = Module("Template", {
if (/^\[.*\]$/.test(topic))
topic = topic.slice(1, -1);
else if (/^n_/.test(topic))
topic = topic.slice(2);
if (help.initialized && !Set.has(help.tags, topic))
return <>{token}</>;
- XML.ignoreWhitespace = false; XML.prettyPrinting = false;
+ XML.ignoreWhitespace = XML.prettyPrinting = false;
let tag = (/^'.*'$/.test(token) ? "o" :
/^\[.*\]$|^E\d{3}$/.test(token) ? "t" :
/^:\w/.test(token) ? "ex" : "k");
topic = topic.replace(/^'(.*)'$/, "$1");
return <{tag} xmlns={NS}>{topic}</{tag}>;
},
linkifyHelp: function linkifyHelp(str, help) {
let re = util.regexp(<![CDATA[
(?P<pre> [/\s]|^)
- (?P<tag> '[\w-]+' | :(?:[\w-]+!?|!) | (?:._)?<[\w-]+>\w* | \b[a-zA-Z]_(?:\w+|.) | \[[\w-]+\] | E\d{3} )
+ (?P<tag> '[\w-]+' | :(?:[\w-]+!?|!) | (?:._)?<[\w-]+>\w* | \b[a-zA-Z]_(?:[\w[\]]+|.) | \[[\w-;]+\] | E\d{3} )
(?= [[\)!,:;./\s]|$)
]]>, "gx");
return this.highlightSubstrings(str, (function () {
for (let res in re.iterate(str))
yield [res.index + res.pre.length, res.tag.length];
})(), template[help ? "HelpLink" : "helpLink"]);
},
@@ -273,17 +282,17 @@ var Template = Module("Template", {
}
},
_sandbox: Class.Memoize(function () Cu.Sandbox(global, { wantXrays: false })),
// if "processStrings" is true, any passed strings will be surrounded by " and
// any line breaks are displayed as \n
highlight: function highlight(arg, processStrings, clip, bw) {
- XML.ignoreWhitespace = false; XML.prettyPrinting = false;
+ XML.ignoreWhitespace = XML.prettyPrinting = false;
// some objects like window.JSON or getBrowsers()._browsers need the try/catch
try {
let str = this.stringify(arg);
if (clip)
str = util.clip(str, clip);
switch (arg == null ? "undefined" : typeof arg) {
case "number":
return <span highlight="Number">{str}</span>;
@@ -329,16 +338,18 @@ var Template = Module("Template", {
highlightFilter: function highlightFilter(str, filter, highlight, isURI) {
if (isURI)
str = util.losslessDecodeURI(str);
return this.highlightSubstrings(str, (function () {
if (filter.length == 0)
return;
+
+ XML.ignoreWhitespace = XML.prettyPrinting = false;
let lcstr = String.toLowerCase(str);
let lcfilter = filter.toLowerCase();
let start = 0;
while ((start = lcstr.indexOf(lcfilter, start)) > -1) {
yield [start, filter.length];
start += filter.length;
}
})(), highlight || template.filter);
@@ -382,17 +393,17 @@ var Template = Module("Template", {
return str;
},
icon: function (item, text) <>
<span highlight="CompIcon">{item.icon ? <img src={item.icon}/> : <></>}</span><span class="td-strut"/>{text}
</>,
jumps: function jumps(index, elems) {
- XML.ignoreWhitespace = false; XML.prettyPrinting = false;
+ XML.ignoreWhitespace = XML.prettyPrinting = false;
// <e4x>
return <table>
<tr style="text-align: left;" highlight="Title">
<th colspan="2">{_("title.Jump")}</th>
<th>{_("title.HPos")}</th>
<th>{_("title.VPos")}</th>
<th>{_("title.Title")}</th>
<th>{_("title.URI")}</th>
@@ -408,17 +419,17 @@ var Template = Module("Template", {
<td><a href={val.URI.spec} highlight="URL jump-list">{util.losslessDecodeURI(val.URI.spec)}</a></td>
</tr>)
}
</table>;
// </e4x>
},
options: function options(title, opts, verbose) {
- XML.ignoreWhitespace = false; XML.prettyPrinting = false;
+ XML.ignoreWhitespace = XML.prettyPrinting = false;
// <e4x>
return <table>
<tr highlight="Title" align="left">
<th>--- {title} ---</th>
</tr>
{
this.map(opts, function (opt)
<tr>
@@ -435,26 +446,26 @@ var Template = Module("Template", {
</table>;
// </e4x>
},
sourceLink: function (frame) {
let url = util.fixURI(frame.filename || "unknown");
let path = util.urlPath(url);
- XML.ignoreWhitespace = false; XML.prettyPrinting = false;
+ XML.ignoreWhitespace = XML.prettyPrinting = false;
return <a xmlns:dactyl={NS} dactyl:command="buffer.viewSource"
href={url} path={path} line={frame.lineNumber}
highlight="URL">{
path + ":" + frame.lineNumber
}</a>;
},
table: function table(title, data, indent) {
- XML.ignoreWhitespace = false; XML.prettyPrinting = false;
+ XML.ignoreWhitespace = XML.prettyPrinting = false;
let table = // <e4x>
<table>
<tr highlight="Title" align="left">
<th colspan="2">{title}</th>
</tr>
{
this.map(data, function (datum)
<tr>
@@ -465,17 +476,17 @@ var Template = Module("Template", {
</table>;
// </e4x>
if (table.tr.length() > 1)
return table;
},
tabular: function tabular(headings, style, iter) {
// TODO: This might be mind-bogglingly slow. We'll see.
- XML.ignoreWhitespace = false; XML.prettyPrinting = false;
+ XML.ignoreWhitespace = XML.prettyPrinting = false;
// <e4x>
return <table>
<tr highlight="Title" align="left">
{
this.map(headings, function (h)
<th>{h}</th>)
}
</tr>
@@ -488,17 +499,17 @@ var Template = Module("Template", {
}
</tr>)
}
</table>;
// </e4x>
},
usage: function usage(iter, format) {
- XML.ignoreWhitespace = false; XML.prettyPrinting = false;
+ XML.ignoreWhitespace = XML.prettyPrinting = false;
format = format || {};
let desc = format.description || function (item) template.linkifyHelp(item.description);
let help = format.help || function (item) item.name;
function sourceLink(frame) {
let source = template.sourceLink(frame);
source.@NS::hint = source.text();
return source;
}
diff --git a/common/modules/util.jsm b/common/modules/util.jsm
--- a/common/modules/util.jsm
+++ b/common/modules/util.jsm
@@ -622,20 +622,20 @@ var Util = Module("Util", XPCOM([Ci.nsIO
/**
* Converts *seconds* into a human readable time string.
*
* @param {number} seconds
* @returns {string}
*/
formatSeconds: function formatSeconds(seconds) {
function pad(n, val) ("0000000" + val).substr(-Math.max(n, String(val).length));
- function div(num, denom) [Math.round(num / denom), Math.round(num % denom)];
+ function div(num, denom) [Math.floor(num / denom), Math.round(num % denom)];
let days, hours, minutes;
- [minutes, seconds] = div(seconds, 60);
+ [minutes, seconds] = div(Math.round(seconds), 60);
[hours, minutes] = div(minutes, 60);
[days, hours] = div(hours, 24);
if (days)
return /*L*/days + " days " + hours + " hours"
if (hours)
return /*L*/hours + "h " + minutes + "m";
if (minutes)
return /*L*/minutes + ":" + pad(2, seconds);
@@ -705,16 +705,18 @@ var Util = Module("Util", XPCOM([Ci.nsIO
* background: {boolean} Whether to perform the request in the
* background. @default true
*
* mimeType: {string} Override the response mime type with the
* given value.
* responseType: {string} Override the type of the "response"
* property.
*
+ * headers: {objects} Extra request headers.
+ *
* user: {string} The user name to send via HTTP Authentication.
* pass: {string} The password to send via HTTP Authentication.
*
* quiet: {boolean} If true, don't report errors.
*
* @returns {XMLHttpRequest}
*/
httpGet: function httpGet(url, callback, self) {
@@ -744,22 +746,28 @@ var Util = Module("Util", XPCOM([Ci.nsIO
if (isObject(params.data) && !(params.data instanceof Ci.nsISupports)) {
let data = services.FormData();
for (let [k, v] in iter(params.data))
data.append(k, v);
params.data = data;
}
-
if (params.mimeType)
xmlhttp.overrideMimeType(params.mimeType);
- xmlhttp.open(params.method || "GET", url, async,
- params.user, params.pass);
+ let args = [params.method || "GET", url, async];
+ if (params.user != null || params.pass != null)
+ args.push(params.user);
+ if (params.pass != null)
+ args.push(prams.pass);
+ xmlhttp.open.apply(xmlhttp, args);
+
+ for (let [header, val] in Iterator(params.headers || {}))
+ xmlhttp.setRequestHeader(header, val);
if (params.responseType)
xmlhttp.responseType = params.responseType;
if (params.notificationCallbacks)
xmlhttp.channel.notificationCallbacks = params.notificationCallbacks;
xmlhttp.send(params.data);
@@ -1303,17 +1311,17 @@ var Util = Module("Util", XPCOM([Ci.nsIO
win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsISelectionDisplay)
.QueryInterface(Ci.nsISelectionController),
/**
* Escapes a string against shell meta-characters and argument
* separators.
*/
- shellEscape: function shellEscape(str) '"' + String.replace(str, /[\\"$]/g, "\\$&") + '"',
+ shellEscape: function shellEscape(str) '"' + String.replace(str, /[\\"$`]/g, "\\$&") + '"',
/**
* Suspend execution for at least *delay* milliseconds. Functions by
* yielding execution to the next item in the main event queue, and
* so may lead to unexpected call graphs, and long delays if another
* handler yields execution while waiting.
*
* @param {number} delay The time period for which to sleep in milliseconds.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment