diff --git a/common/components/protocols.js b/common/components/protocols.js index f220023d..adf0880c 100644 --- a/common/components/protocols.js +++ b/common/components/protocols.js @@ -16,19 +16,16 @@ const Ci = Components.interfaces, Cc = Components.classes; Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -const NS_BINDING_ABORTED = 0x804b0002; -const nsIProtocolHandler = Components.interfaces.nsIProtocolHandler; - const ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); const prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService) - .getBranch("extensions.dactyl."); + .getBranch("extensions.dactyl."); const systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].getService(Ci.nsIPrincipal); function dataURL(type, data) "data:" + (type || "application/xml;encoding=UTF-8") + "," + escape(data); function makeChannel(url, orig) { if (url == null) return fakeChannel(); - if (typeof url == "function") + if (typeof url === "function") url = dataURL.apply(null, url()); let uri = ioService.newURI(url, null, null); let channel = ioService.newChannelFromURI(uri); @@ -43,28 +40,30 @@ function redirect(to, orig, time) { return makeChannel(dataURL('text/html', html), ioService.newURI(to, null, null)); } +function Factory(clas) ({ + createInstance: function (outer, iid) { + if (outer != null) + throw Components.results.NS_ERROR_NO_AGGREGATION; + if (!clas.instance) + clas.instance = new clas(); + return clas.instance.QueryInterface(iid); + } +}); + function ChromeData() {} ChromeData.prototype = { contractID: "@mozilla.org/network/protocol;1?name=chrome-data", classID: Components.ID("{c1b67a07-18f7-4e13-b361-2edcc35a5a0d}"), classDescription: "Data URIs with chrome privileges", - QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIProtocolHandler]), - _xpcom_factory: { - createInstance: function (outer, iid) { - if (outer != null) - throw Components.results.NS_ERROR_NO_AGGREGATION; - if (!ChromeData.instance) - ChromeData.instance = new ChromeData(); - return ChromeData.instance.QueryInterface(iid); - } - }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler]), + _xpcom_factory: Factory(ChromeData), scheme: "chrome-data", defaultPort: -1, allowPort: function (port, scheme) false, - protocolFlags: nsIProtocolHandler.URI_NORELATIVE - | nsIProtocolHandler.URI_NOAUTH - | nsIProtocolHandler.URI_IS_UI_RESOURCE, + protocolFlags: Ci.nsIProtocolHandler.URI_NORELATIVE + | Ci.nsIProtocolHandler.URI_NOAUTH + | Ci.nsIProtocolHandler.URI_IS_UI_RESOURCE, newURI: function (spec, charset, baseURI) { var uri = Components.classes["@mozilla.org/network/standard-url;1"] @@ -93,28 +92,16 @@ function Dactyl() { this.addonID = this.name + "@dactyl.googlecode.com"; this.pages = {}; + for each (let pref in ["appName", "fileExt", "host", "hostbin", "idName", "name"]) + this[pref] = prefs.getComplexValue(pref, Ci.nsISupportsString).data; } Dactyl.prototype = { contractID: "@mozilla.org/network/protocol;1?name=dactyl", classID: Components.ID("{9c8f2530-51c8-4d41-b356-319e0b155c44}"), classDescription: "Dactyl utility protocol", - QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIProtocolHandler]), - _xpcom_factory: { - createInstance: function (outer, iid) { - if (outer != null) - throw Components.results.NS_ERROR_NO_AGGREGATION; - if (!Dactyl.instance) - Dactyl.instance = new Dactyl(); - return Dactyl.instance.QueryInterface(iid); - } - }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler]), + _xpcom_factory: Factory(Dactyl), - appName: prefs.getComplexValue("appName", Ci.nsISupportsString).data, - fileExt: prefs.getComplexValue("fileExt", Ci.nsISupportsString).data, - host: prefs.getComplexValue("host", Ci.nsISupportsString).data, - hostbin: prefs.getComplexValue("hostbin", Ci.nsISupportsString).data, - idName: prefs.getComplexValue("idName", Ci.nsISupportsString).data, - name: prefs.getComplexValue("name", Ci.nsISupportsString).data, get version() prefs.getComplexValue("version", Ci.nsISupportsString).data, init: function (obj) { @@ -129,8 +116,8 @@ Dactyl.prototype = { defaultPort: -1, allowPort: function (port, scheme) false, protocolFlags: 0 - | nsIProtocolHandler.URI_IS_UI_RESOURCE - | nsIProtocolHandler.URI_IS_LOCAL_RESOURCE, + | Ci.nsIProtocolHandler.URI_IS_UI_RESOURCE + | Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE, newURI: function (spec, charset, baseURI) { var uri = Cc["@mozilla.org/network/standard-url;1"] @@ -200,9 +187,7 @@ Shim.prototype = { return this; }, getHelperForLanguage: function () null, - getInterfaces: function (count) { - count.value = 0; - } + getInterfaces: function (count) { count.value = 0; } }; if (XPCOMUtils.generateNSGetFactory) diff --git a/common/content/buffer.js b/common/content/buffer.js index a1b9b466..5154d7f6 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -282,18 +282,16 @@ const Buffer = Module("buffer", { statusline.updateProgress(curTotalProgress/maxTotalProgress); }, // happens when the users switches tabs - onLocationChange: function onLocationChange() { + onLocationChange: function onLocationChange(webProgress, request, uri) { onLocationChange.superapply(this, arguments); statusline.updateUrl(); statusline.updateProgress(); for (let frame in values(buffer.allFrames())) frame.dactylFocusAllowed = false; - let browser = config.browser.mCurrentBrowser; - let uri = browser.registeredOpenURI || browser.lastURI; - if (uri.scheme === "dactyl" && !(browser.contentDocument || {}).pageIsFullyLoaded) + if (uri.scheme === "dactyl" && webProgress.isLoadingDocument) // Workaround for bugs 591425 and 606877, dactyl bug #81 - browser.collapsed = true; + config.browser.mCurrentBrowser.collapsed = true; util.timeout(function () { autocommands.trigger("LocationChange", { url: buffer.URL }); diff --git a/common/content/dactyl-overlay.js b/common/content/dactyl-overlay.js index d8a8c3ca..2d8b09eb 100644 --- a/common/content/dactyl-overlay.js +++ b/common/content/dactyl-overlay.js @@ -52,6 +52,7 @@ let prefix = [BASE]; + modules.load("util"); modules.load("services"); prefix.unshift("chrome://" + modules.services["dactyl:"].name + "/content/"); @@ -59,7 +60,6 @@ "modules", "prefs", "storage", - "util", "javascript", "dactyl", "modes", diff --git a/common/content/tabs.js b/common/content/tabs.js index 44fc6287..5963ba63 100644 --- a/common/content/tabs.js +++ b/common/content/tabs.js @@ -432,7 +432,7 @@ const Tabs = Module("tabs", { if (matches) return tabs.select(this.allTabs[parseInt(matches[1], 10) - 1], false); - matches = array.nth(tabs.allTabs, function (t) t.linkedBrowser.lastURI.spec === buffer, 0); + matches = array.nth(tabs.allTabs, function (t) (t.linkedBrowser.lastURI || {}).spec === buffer, 0); if (matches) return tabs.select(matches, false); diff --git a/common/modules/services.jsm b/common/modules/services.jsm index 1e72ccfb..561d30f1 100644 --- a/common/modules/services.jsm +++ b/common/modules/services.jsm @@ -3,7 +3,9 @@ // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. "use strict"; + try { + Components.utils.import("resource://dactyl/base.jsm"); defineModule("services", { exports: ["Services", "services"], @@ -58,7 +60,7 @@ const Services = Module("Services", { this.add("windowWatcher", "@mozilla.org/embedcomp/window-watcher;1", Ci.nsIWindowWatcher); this.addClass("File", "@mozilla.org/file/local;1", Ci.nsILocalFile); - this.addClass("File:", "@mozilla.org/network/protocol;1?name=file", Ci.nsIFileProtocolHandler); + this.addClass("file:", "@mozilla.org/network/protocol;1?name=file", Ci.nsIFileProtocolHandler); this.addClass("Find", "@mozilla.org/embedcomp/rangefind;1", Ci.nsIFind); this.addClass("HtmlConverter","@mozilla.org/widget/htmlformatconverter;1", Ci.nsIFormatConverter); this.addClass("HtmlEncoder", "@mozilla.org/layout/htmlCopyEncoder;1", Ci.nsIDocumentEncoder); @@ -71,21 +73,13 @@ const Services = Module("Services", { _create: function (classes, ifaces, meth) { try { - for (let i = 0; !res && i < 15; i++) // FIXME: Hack. - try { - var res = Cc[classes][meth || "getService"](); - } - catch (e if e.result === Cr.NS_ERROR_XPC_BAD_OP_ON_WN_PROTO) { - util.dump(String(e)); - } - + let res = Cc[classes][meth || "getService"](); if (!ifaces) return res.wrappedJSObject; Array.concat(ifaces).forEach(function (iface) res.QueryInterface(iface)); return res; } catch (e) { - // dactyl.log() is not defined at this time, so just dump any error util.dump("Service creation failed for '" + classes + "': " + e + "\n"); return null; } @@ -103,7 +97,7 @@ const Services = Module("Services", { */ add: function (name, class_, ifaces, meth) { const self = this; - if (name in this && !this.__lookupGetter__(name) && !(this[name] instanceof Ci.nsISupports)) + if (name in this && ifaces && !this.__lookupGetter__(name) && !(this[name] instanceof Ci.nsISupports)) throw TypeError(); this.__defineGetter__(name, function () { delete this[name]; diff --git a/common/modules/storage.jsm b/common/modules/storage.jsm index c7d6f6d9..8f5cca84 100644 --- a/common/modules/storage.jsm +++ b/common/modules/storage.jsm @@ -264,7 +264,7 @@ const File = Class("File", { if (path instanceof Ci.nsIFile) file = path.QueryInterface(Ci.nsIFile).clone(); else if (/file:\/\//.test(path)) - file = services["File:"]().getFileFromURLSpec(path); + file = services["file:"]().getFileFromURLSpec(path); else { try { let expandedPath = File.expandPath(path); diff --git a/common/modules/util.jsm b/common/modules/util.jsm index 91094487..a59897f6 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -20,6 +20,13 @@ const XUL = Namespace("xul", "http://www.mozilla.org/keymaster/gatekeeper/there. const NS = Namespace("dactyl", "http://vimperator.org/namespaces/liberator"); default xml namespace = XHTML; +memoize(this, "Commands", function () { + // FIXME + let obj = {}; + services.subscriptLoader.loadSubScript("chrome://dactyl/content/commands.js", obj); + return obj.Commands; +}); + const FailedAssertion = Class("FailedAssertion", Error, { init: function (message) { this.message = message; @@ -135,6 +142,56 @@ const Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) compareIgnoreCase: function compareIgnoreCase(a, b) String.localeCompare(a.toLowerCase(), b.toLowerCase()), compileFormat: function compileFormat(format) { + let stack = [frame()]; + stack.__defineGetter__("top", function () this[this.length - 1]); + + function frame() update( + function _frame(obj) + _frame === stack.top || _frame.valid(obj) ? + _frame.elements.map(function (e) callable(e) ? e(obj) : e).join("") : "", + { + elements: [], + seen: {}, + valid: function (obj) this.elements.every(function (e) !e.test || e.test(obj)) + }); + + let match, end = 0, re = /(.*?)%(.)/gy; + while (match = re.exec(format)) { + let [, prefix, char] = match; + end += match[0].length; + + if (prefix) + stack.top.elements.push(prefix); + if (char === "%") + stack.top.elements.push("%"); + else if (char === "[") { + let f = frame(); + stack.top.elements.push(f); + stack.push(f); + } + else if (char === "]") { + stack.pop(); + util.assert(stack.length, "Unmatched %] in format"); + } + else { + let quote = function quote(obj, char) obj[char]; + if (char !== char.toLowerCase()) + quote = function quote(obj, char) Commands.quote(obj[char]); + char = char.toLowerCase(); + + stack.top.elements.push(update( + function (obj) obj[char] != null ? quote(obj, char) : "", + { test: function (obj) obj[char] != null })); + } + + for (let elem in array.iterValues(stack)) + elem.seen[char] = true; + } + if (end < format.length) + stack.top.elements.push(format.substr(end)); + + util.assert(stack.length === 1, "Unmatched %[ in format"); + return stack.top; }, /**