Created
August 8, 2012 23:59
-
-
Save kmaglione/3299842 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} </li> | |
<li highlight="CompDesc ErrorMsg">{e} </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, """), | |
'"'); | |
} | |
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} </li> | |
<li highlight="CompDesc">{desc} </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