diff --git a/content/buffer.js b/content/buffer.js index be65cb0d..2e923b76 100644 --- a/content/buffer.js +++ b/content/buffer.js @@ -26,8 +26,6 @@ the provisions above, a recipient may use your version of this file under the terms of any one of the MPL, the GPL or the LGPL. }}} ***** END LICENSE BLOCK *****/ -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); - const Point = new Struct("x", "y"); function Buffer() //{{{ @@ -35,241 +33,6 @@ function Buffer() //{{{ //////////////////////////////////////////////////////////////////////////////// ////////////////////// PRIVATE SECTION ///////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ - - const highlightClasses = ["Boolean", "ErrorMsg", "Filter", "Function", "InfoMsg", "Keyword", - "LineNr", "ModeMsg", "MoreMsg", "Normal", "Null", "Number", "Object", "Question", - "StatusLine", "StatusLineBroken", "StatusLineSecure", "String", "TabClose", "TabIcon", - "TabIconNumber", "TabNumber", "TabText", "Tag", "Title", "URL", "WarningMsg", - ["Hint", ".liberator-hint", "*"], - ["Search", ".__liberator-search", "*"], - ["Bell", "#liberator-visualbell"], - ]; - const highlightDocs = "chrome://liberator/content/buffer.xhtml,chrome://browser/content/browser.xul"; - - var highlight = storage.newMap("highlight", false); - - const util = modules.util; - const arrayIter = util.Array.iterator; - - function Styles(name, store, serial) - { - /* Can't reference liberator or Components inside Styles -- - * they're members of the window object, which disappear - * with this window. - */ - const sleep = liberator.sleep; - const storage = modules.storage; - const consoleService = Components.classes["@mozilla.org/consoleservice;1"] - .getService(Components.interfaces.nsIConsoleService); - const ios = Components.classes["@mozilla.org/network/io-service;1"] - .getService(Components.interfaces.nsIIOService); - const sss = Components.classes["@mozilla.org/content/style-sheet-service;1"] - .getService(Components.interfaces.nsIStyleSheetService); - const XHTML = "http://www.w3.org/1999/xhtml"; - const namespace = "@namespace html url(" + XHTML + ");\n" + - "@namespace xul url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);\n"; - const Sheet = new Struct("name", "sites", "css", "ref"); - - let cssUri = function (css) "chrome-data:text/css," + encodeURI(css); - - let userSheets = []; - let systemSheets = []; - let userNames = {}; - let systemNames = {}; - - this.__iterator__ = function () Iterator(userSheets.concat(systemSheets)); - this.__defineGetter__("systemSheets", function () Iterator(systemSheets)); - this.__defineGetter__("userSheets", function () Iterator(userSheets)); - this.__defineGetter__("systemNames", function () Iterator(systemNames)); - this.__defineGetter__("userNames", function () Iterator(userNames)); - - this.addSheet = function (name, filter, css, system, force) - { - let sheets = system ? systemSheets : userSheets; - let names = system ? systemNames : userNames; - if (name && name in names) - this.removeSheet(name, null, null, null, system); - - let sheet = sheets.filter(function (s) s.sites.join(",") == filter && s.css == css)[0]; - if (!sheet) - sheet = new Sheet(name, filter.split(","), css, null); - - if (sheet.ref == null) // Not registered yet - { - sheet.ref = []; - try - { - this.registerSheet(cssUri(wrapCSS(sheet)), !force); - } - catch (e) - { - return e.echoerr || e; - } - sheets.push(sheet); - } - if (name) - { - sheet.ref.push(name); - names[name] = sheet; - } - return null; - } - - this.findSheets = function (name, filter, css, index, system) - { - let sheets = system ? systemSheets : userSheets; - let names = system ? systemNames : userNames; - - // Grossly inefficient. - let matches = [k for ([k, v] in sheets)]; - if (index) - matches = String(index).split(",").filter(function (i) i in sheets); - if (name) - matches = matches.filter(function (i) sheets[i] == names[name]); - if (css) - matches = matches.filter(function (i) sheets[i].css == css); - if (filter) - matches = matches.filter(function (i) sheets[i].sites.indexOf(filter) >= 0); - return matches; - }, - - this.removeSheet = function (name, filter, css, index, system) - { - let self = this; - let sheets = system ? systemSheets : userSheets; - let names = system ? systemNames : userNames; - - if (filter && filter.indexOf(",") > -1) - return filter.split(",").reduce( - function (n, f) n + self.removeSheet(name, f, index, system), 0); - - if (filter == undefined) - filter = ""; - - let matches = this.findSheets(name, filter, css, index, system); - if (matches.length == 0) - return; - - for (let [,i] in Iterator(matches.reverse())) - { - let sheet = sheets[i]; - if (name) - { - sheet.ref.splice(sheet.ref.indexOf(name)); - delete names[name]; - } - if (!sheet.ref.length) - { - sheets.splice(i); - this.unregisterSheet(cssUri(wrapCSS(sheet))); - } - // Filter out the given site, and re-add if there are any left - if (filter) - { - let sites = sheet.sites.filter(function (f) f != filter); - if (sites.length) - this.addSheet(name, sites.join(","), css, system, true); - } - } - return matches.length; - } - - this.registerSheet = function (uri, doCheckSyntax, reload) - { - if (doCheckSyntax) - checkSyntax(uri); - if (reload) - this.unregisterSheet(uri); - uri = ios.newURI(uri, null, null); - if (reload || !sss.sheetRegistered(uri, sss.USER_SHEET)) - sss.loadAndRegisterSheet(uri, sss.USER_SHEET); - } - - this.unregisterSheet = function (uri) - { - uri = ios.newURI(uri, null, null); - if (sss.sheetRegistered(uri, sss.USER_SHEET)) - sss.unregisterSheet(uri, sss.USER_SHEET); - } - - function wrapCSS(sheet) - { - let filter = sheet.sites; - let css = sheet.css; - if (filter[0] == "*") - return namespace + css; - let selectors = filter.map(function (part) (/[*]$/.test(part) ? "url-prefix" : - /[\/:]/.test(part) ? "url" - : "domain") - + '("' + part.replace(/"/g, "%22").replace(/[*]$/, "") + '")') - .join(", "); - return namespace + "@-moz-document " + selectors + "{\n" + css + "\n}\n"; - } - - let queryinterface = XPCOMUtils.generateQI([Components.interfaces.nsIConsoleListener]); - /* What happens if more than one thread tries to use this? */ - let testDoc = document.implementation.createDocument(XHTML, "doc", null); - function checkSyntax(uri) - { - let errors = []; - let listener = { - QueryInterface: queryinterface, - observe: function (message) - { - try - { - message = message.QueryInterface(Components.interfaces.nsIScriptError); - if (message.sourceName == uri) - errors.push(message); - } - catch (e) {} - } - }; - - try - { - consoleService.registerListener(listener); - if (testDoc.documentElement.firstChild) - testDoc.documentElement.removeChild(testDoc.documentElement.firstChild); - testDoc.documentElement.appendChild(util.xmlToDom( - , testDoc)); - - while (true) - { - try - { - // Throws NS_ERROR_DOM_INVALID_ACCESS_ERR if not finished loading - testDoc.styleSheets[0].cssRules.length; - break; - } - catch (e) - { - if (e.name != "NS_ERROR_DOM_INVALID_ACCESS_ERR") - return [e.toString()]; - sleep(10); - } - } - } - finally - { - consoleService.unregisterListener(listener); - } - if (errors.length) - { - let err = new Error("", errors[0].sourceName.replace(/^(chrome-data:text\/css,).*/, "$1..."), errors[0].lineNumber); - err.name = "CSSError" - err.message = errors.reduce(function (msg, e) msg + "; " + e.lineNumber + ": " + e.errorMessage, errors.shift().errorMessage); - err.echoerr = err.fileName + ":" + err.lineNumber + ": " + err.message; - throw err; - } - } - } - Styles.prototype = { - get sites() util.Array.uniq(util.Array.flatten([v.sites for ([k, v] in this.userSheets)])) - }; - - let styles = storage.newObject("styles", Styles, false); - /* FIXME: This doesn't belong here. */ let mainWindowID = config.mainWindowID || "main-window"; let fontSize = util.computedStyle(document.getElementById(mainWindowID))["font-size"]; @@ -813,114 +576,6 @@ function Buffer() //{{{ function () { BrowserStop(); }, { argCount: "0" }); - commands.add(["sty[le]"], - "Add or list user styles", - function (args, special) - { - let [filter] = args.arguments; - let name = args["-name"]; - let css = args.literalArg; - - if (!css) - { - let list = Array.concat([i for (i in styles.userNames)], - [i for (i in styles.userSheets) if (!i[1].ref.length)]); - let str = template.tabular(["", "Filter", "CSS"], - ["padding: 0 1em 0 1ex; vertical-align: top", "padding: 0 1em 0 0; vertical-align: top"], - ([k, v[1].join(","), v[2]] - for ([i, [k, v]] in Iterator(list)) - if ((!filter || v[1].indexOf(filter) >= 0) && (!name || v[0] == name)))); - commandline.echo(str, commandline.HL_NORMAL, commandline.FORCE_MULTILINE); - } - else - { - let err = styles.addSheet(name, filter, css, false, special); - if (err) - liberator.echoerr(err); - } - }, - { - completer: function (filter) { - let compl = []; - try - { - compl.push([content.location.host, "Current Host"]); - compl.push([content.location.href, "Current URL"]); - } - catch (e) {} - comp = compl.concat([[s, ""] for each (s in styles.sites)]) - return [0, completion.filter(compl, filter)]; - }, - argCount: 1, - bang: true, - hereDoc: true, - literal: true, - options: [[["-name", "-n"], commands.OPTION_STRING]], - serial: function () [ - { - command: this.name, - bang: true, - options: sty.name ? {"-name": sty.name} : {}, - arguments: [sty.sites.join(",")], - literalArg: sty.css - } for ([k, sty] in styles.userSheets) - ] - }); - - commands.add(["dels[tyle]"], - "Remove a user stylesheet", - function (args) { - styles.removeSheet(args["-name"], args.arguments[0], args.literalArg, args["-index"], false); - }, - { - argCount: 1, - completer: function (filter) [0, completion.filter( - [[i, <>{s.sites.join(",")}: {s.css.replace("\n", "\\n")}] - for ([i, s] in styles.userSheets) - ] - .concat([[s, ""] for each (s in styles.sites)]) - , filter)], - literal: true, - options: [[["-index", "-i"], commands.OPTION_INT], - [["-name", "-n"], commands.OPTION_STRING]] - }); - - commands.add(["hi[ghlight]"], - "Set the style of certain display elements", - function (args, special) - { - let key = args.arguments[0]; - let css = args.literalArg; - if (!css && !(key && special)) - { - let str = template.tabular(["Key", "CSS"], - ["padding: 0 1em 0 0; vertical-align: top"], - (h for (h in highlight) if (!key || h[0].indexOf(key) > -1))); - commandline.echo(str, commandline.HL_NORMAL, commandline.FORCE_MULTILINE); - return; - } - buffer.highlight(key, css, special); - }, - { - // TODO: add this as a standard highlight completion function? - // I agree. It could (should) be much more sophisticated. --Kris - completer: function (filter) [0, - completion.filter([[v instanceof Array ? v[0] : v, ""] for ([k, v] in Iterator(highlightClasses))], filter) - ], - argCount: 1, - bang: true, - hereDoc: true, - literal: true, - serial: function () [ - { - command: this.name, - arguments: [k], - literalArg: v - } - for ([k, v] in Iterator(highlight)) - ] - }); - commands.add(["vie[wsource]"], "View source code of current document", function (args, special) { buffer.viewSource(args.arguments[0], special); }, @@ -1030,7 +685,7 @@ function Buffer() //{{{ // put feeds rss into pageFeeds[] let nFeed = 0; var linkNodes = doc.getElementsByTagName("link"); - for (link in arrayIter(linkNodes)) + for (link in util.arrayIter(linkNodes)) { if (!link.href) return; @@ -1065,7 +720,7 @@ function Buffer() //{{{ .getService(nsICacheService); let cacheKey = doc.location.toString().replace(/#.*$/, ""); - for (let proto in arrayIter(["HTTP", "FTP"])) + for (let proto in util.arrayIter(["HTTP", "FTP"])) { try { @@ -1206,39 +861,6 @@ function Buffer() //{{{ addPageInfoSection: addPageInfoSection, - highlight: function (key, style, force) - { - let [, class, selectors] = key.match(/^([a-zA-Z_-]+)(.*)/); - - class = highlightClasses.filter(function (i) i == class || i[0] == class)[0]; - if (!class) - { - liberator.echoerr("Unknown highlight keyword"); - return; - } - if (!(class instanceof Array)) - class = [class]; - - styles.removeSheet("hl-" + key, null, null, null, true); - highlight.remove(key); - - if (/^\s*$/.test(style)) - return; - - let cssClass = class[1] || ".hl-" + class[0]; - let scope = class[2] || highlightDocs; - - let css = style.replace(/(?:!\s*important\s*)?(?:;?\s*$|;)/g, "!important;") - .replace(";!important;", ";", "g") // Seeming Spidermonkey bug - css = cssClass + selectors + " { " + css + " }"; - - let error = styles.addSheet("hl-" + key, scope, css, true, force); - if (error) - liberator.echoerr(error); - else - highlight.set(key, style); - }, - // returns an XPathResult object evaluateXPath: function (expression, doc, elem, asIterator) { diff --git a/content/find.js b/content/find.js index 03edecae..b7acd8a7 100644 --- a/content/find.js +++ b/content/find.js @@ -284,7 +284,7 @@ function Search() //{{{ if (!aWord) { - let elems = doc.getElementsByClassName("__liberator-search"); + let elems = doc.getElementsByClassName("liberator-search"); for (let i = elems.length; --i >= 0;) { let elem = elems[i]; @@ -303,7 +303,7 @@ function Search() //{{{ return; } - var baseNode = + var baseNode = baseNode = util.xmlToDom(baseNode, window.content.document); var body = doc.body; @@ -551,7 +551,7 @@ function Search() //{{{ highlight: function (text) { // already highlighted? - if (window.content.document.getElementsByClassName("__liberator-search").length > 0) + if (window.content.document.getElementsByClassName("liberator-search").length > 0) return; if (!text) diff --git a/content/liberator-overlay.js b/content/liberator-overlay.js index 66903973..2ff583aa 100644 --- a/content/liberator-overlay.js +++ b/content/liberator-overlay.js @@ -25,6 +25,7 @@ ["liberator.js", "util.js", + "style.js", "config.js", "buffer.js", "commands.js", diff --git a/content/liberator.js b/content/liberator.js index 7b202381..af9be989 100644 --- a/content/liberator.js +++ b/content/liberator.js @@ -37,6 +37,10 @@ const liberator = (function () //{{{ var callbacks = []; var observers = []; + function registerObserver(type, callback) + { + observers.push([type, callback]); + } function loadModule(name, func) { @@ -46,6 +50,7 @@ const liberator = (function () //{{{ liberator.log(message, 0); liberator.dump(message); modules[name] = func(); + liberator.triggerObserver("load_" + name, name); } catch (e) { @@ -57,7 +62,7 @@ const liberator = (function () //{{{ } // Only general options are added here, which are valid for all vimperator like extensions - function addOptions() + registerObserver("load_options", function () { options.add(["errorbells", "eb"], "Ring the bell when an error message is displayed", @@ -140,9 +145,9 @@ const liberator = (function () //{{{ return value; } }); - } + }) - function addMappings() + registerObserver("load_mappings", function () { mappings.add(modes.all, [""], "Open help window", @@ -158,9 +163,9 @@ const liberator = (function () //{{{ mappings.add([modes.NORMAL], ["ZZ"], "Quit and save the session", function () { liberator.quit(true); }); - } + }) - function addCommands() + registerObserver("load_commands", function () { commands.add(["addo[ns]"], "Manage available Extensions and Themes", @@ -529,7 +534,7 @@ const liberator = (function () //{{{ argCount: "0", bang: true }); - } + }) // initially hide all GUI, it is later restored unless the user has :set go= or something // similar in his config @@ -603,10 +608,7 @@ const liberator = (function () //{{{ return false; }, - registerObserver: function (type, callback) - { - observers.push([type, callback]); - }, + registerObserver: registerObserver, triggerObserver: function (type, data) { @@ -1145,9 +1147,9 @@ const liberator = (function () //{{{ config.features.push(navigator.platform); // commands must always be the first module to be initialized - loadModule("commands", Commands); addCommands(); - loadModule("options", Options); addOptions(); - loadModule("mappings", Mappings); addMappings(); + loadModule("commands", Commands); + loadModule("options", Options); + loadModule("mappings", Mappings); loadModule("buffer", Buffer); loadModule("events", Events); loadModule("commandline", CommandLine); diff --git a/content/style.js b/content/style.js new file mode 100644 index 00000000..0b5cd46d --- /dev/null +++ b/content/style.js @@ -0,0 +1,473 @@ +/***** BEGIN LICENSE BLOCK ***** {{{ + ©2008 Kris Maglione + Distributable under the terms of the MIT license, which allows + for sublicensing under any compatible license, including the MPL, + GPL, and MPL. Anyone who changes this file is welcome to relicense + it under any or all of those licenseses. +}}} ***** END LICENSE BLOCK *****/ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function Highlights(name, store, serial) +{ + const highlightCSS = .toString(); + var self = this; + var highlight = {}; + var styles = storage.styles; + + const Highlight = Struct("class", "selector", "filter", "default", "value"); + Highlight.defaultValue("filter", function () "chrome://liberator/content/buffer.xhtml,chrome://browser/content/browser.xul"); + Highlight.defaultValue("selector", function () ".hl-" + this.class); + Highlight.defaultValue("value", function () this.default); + Highlight.prototype.toString = function () [k + ": " + util.escapeString(v || "undefined") for ([k, v] in this)].join(", "); + + this.__iterator__ = function () (v for ([k,v] in Iterator(highlight))); + + this.get = function (k) highlight[k]; + this.set = function (key, newStyle, force) + { + let [, class, selectors] = key.match(/^([a-zA-Z_-]+)(.*)/); + + if (!(class in highlight)) + return "Unknown highlight keyword"; + + if (/^\s*$/.test(newStyle)) + newStyle = null; + + let style = highlight[key] || new Highlight(key); + styles.removeSheet(style.selector, null, null, true); + + if (newStyle == null) + { + if (style.default == null) + { + delete highlight[style.class]; + return null; + } + newStyle = style.default; + force = true; + } + + let css = newStyle.replace(/(?:!\s*important\s*)?(?:;?\s*$|;)/g, "!important;") + .replace(";!important;", ";", "g"); // Seeming Spidermonkey bug + css = style.selector + " { " + css + " }"; + + let error = styles.addSheet(style.selector, style.filter, css, true, force); + if (!error) + style.value = newStyle; + return error; + } + + highlightCSS.replace(/\{((?:.|\n)*?)\}/g, function (_, _1) _1.replace(/\n\s*/g, " ")) + .split("\n").filter(function (s) /\S/.test(s)) + .forEach(function (style) + { + let style = Highlight.apply(Highlight, Array.slice(style.match(/^\s*([^,\s]+)(?:,([^,\s]+))?(?:,([^,\s]+))?(?:\s+(.*))?/), 1)); + highlight[style.class] = style; + self.set(style.class); + }); +} + +function Styles(name, store, serial) +{ + /* Can't reference liberator or Components inside Styles -- + * they're members of the window object, which disappear + * with this window. + */ + const util = modules.util; + const sleep = liberator.sleep; + const storage = modules.storage; + const consoleService = Components.classes["@mozilla.org/consoleservice;1"] + .getService(Components.interfaces.nsIConsoleService); + const ios = Components.classes["@mozilla.org/network/io-service;1"] + .getService(Components.interfaces.nsIIOService); + const sss = Components.classes["@mozilla.org/content/style-sheet-service;1"] + .getService(Components.interfaces.nsIStyleSheetService); + const XHTML = "http://www.w3.org/1999/xhtml"; + const namespace = "@namespace html url(" + XHTML + ");\n" + + "@namespace xul url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);\n"; + const Sheet = new Struct("name", "sites", "css", "ref"); + + let cssUri = function (css) "chrome-data:text/css," + encodeURI(css); + + let userSheets = []; + let systemSheets = []; + let userNames = {}; + let systemNames = {}; + + this.__iterator__ = function () Iterator(userSheets.concat(systemSheets)); + this.__defineGetter__("systemSheets", function () Iterator(systemSheets)); + this.__defineGetter__("userSheets", function () Iterator(userSheets)); + this.__defineGetter__("systemNames", function () Iterator(systemNames)); + this.__defineGetter__("userNames", function () Iterator(userNames)); + + this.addSheet = function (name, filter, css, system, force) + { + let sheets = system ? systemSheets : userSheets; + let names = system ? systemNames : userNames; + if (name && name in names) + this.removeSheet(name, null, null, null, system); + + let sheet = sheets.filter(function (s) s.sites.join(",") == filter && s.css == css)[0]; + if (!sheet) + sheet = new Sheet(name, filter.split(","), css, null); + + if (sheet.ref == null) // Not registered yet + { + sheet.ref = []; + try + { + this.registerSheet(cssUri(wrapCSS(sheet)), !force); + } + catch (e) + { + return e.echoerr || e; + } + sheets.push(sheet); + } + if (name) + { + sheet.ref.push(name); + names[name] = sheet; + } + return null; + } + + this.findSheets = function (name, filter, css, index, system) + { + let sheets = system ? systemSheets : userSheets; + let names = system ? systemNames : userNames; + + // Grossly inefficient. + let matches = [k for ([k, v] in sheets)]; + if (index) + matches = String(index).split(",").filter(function (i) i in sheets); + if (name) + matches = matches.filter(function (i) sheets[i] == names[name]); + if (css) + matches = matches.filter(function (i) sheets[i].css == css); + if (filter) + matches = matches.filter(function (i) sheets[i].sites.indexOf(filter) >= 0); + return matches; + }, + + this.removeSheet = function (name, filter, css, index, system) + { + let self = this; + let sheets = system ? systemSheets : userSheets; + let names = system ? systemNames : userNames; + + if (filter && filter.indexOf(",") > -1) + return filter.split(",").reduce( + function (n, f) n + self.removeSheet(name, f, index, system), 0); + + if (filter == undefined) + filter = ""; + + let matches = this.findSheets(name, filter, css, index, system); + if (matches.length == 0) + return; + + for (let [,i] in Iterator(matches.reverse())) + { + let sheet = sheets[i]; + if (name) + { + sheet.ref.splice(sheet.ref.indexOf(name)); + delete names[name]; + } + if (!sheet.ref.length) + { + sheets.splice(i); + this.unregisterSheet(cssUri(wrapCSS(sheet))); + } + // Filter out the given site, and re-add if there are any left + if (filter) + { + let sites = sheet.sites.filter(function (f) f != filter); + if (sites.length) + this.addSheet(name, sites.join(","), css, system, true); + } + } + return matches.length; + } + + this.registerSheet = function (uri, doCheckSyntax, reload) + { + if (doCheckSyntax) + checkSyntax(uri); + if (reload) + this.unregisterSheet(uri); + uri = ios.newURI(uri, null, null); + if (reload || !sss.sheetRegistered(uri, sss.USER_SHEET)) + sss.loadAndRegisterSheet(uri, sss.USER_SHEET); + } + + this.unregisterSheet = function (uri) + { + uri = ios.newURI(uri, null, null); + if (sss.sheetRegistered(uri, sss.USER_SHEET)) + sss.unregisterSheet(uri, sss.USER_SHEET); + } + + function wrapCSS(sheet) + { + let filter = sheet.sites; + let css = sheet.css; + if (filter[0] == "*") + return namespace + css; + let selectors = filter.map(function (part) (/[*]$/.test(part) ? "url-prefix" : + /[\/:]/.test(part) ? "url" + : "domain") + + '("' + part.replace(/"/g, "%22").replace(/[*]$/, "") + '")') + .join(", "); + return namespace + "@-moz-document " + selectors + "{\n" + css + "\n}\n"; + } + + let queryinterface = XPCOMUtils.generateQI([Components.interfaces.nsIConsoleListener]); + /* What happens if more than one thread tries to use this? */ + let testDoc = document.implementation.createDocument(XHTML, "doc", null); + function checkSyntax(uri) + { + let errors = []; + let listener = { + QueryInterface: queryinterface, + observe: function (message) + { + try + { + message = message.QueryInterface(Components.interfaces.nsIScriptError); + if (message.sourceName == uri) + errors.push(message); + } + catch (e) {} + } + }; + + try + { + consoleService.registerListener(listener); + if (testDoc.documentElement.firstChild) + testDoc.documentElement.removeChild(testDoc.documentElement.firstChild); + testDoc.documentElement.appendChild(util.xmlToDom( + , testDoc)); + + while (true) + { + try + { + // Throws NS_ERROR_DOM_INVALID_ACCESS_ERR if not finished loading + testDoc.styleSheets[0].cssRules.length; + break; + } + catch (e) + { + if (e.name != "NS_ERROR_DOM_INVALID_ACCESS_ERR") + return [e.toString()]; + sleep(10); + } + } + } + finally + { + consoleService.unregisterListener(listener); + } + if (errors.length) + { + let err = new Error("", errors[0].sourceName.replace(/^(chrome-data:text\/css,).*/, "$1..."), errors[0].lineNumber); + err.name = "CSSError" + err.message = errors.reduce(function (msg, e) msg + "; " + e.lineNumber + ": " + e.errorMessage, + errors.shift().errorMessage); + err.echoerr = err.fileName + ":" + err.lineNumber + ": " + err.message; + throw err; + } + } +} +Styles.prototype = { + get sites() util.Array.uniq(util.Array.flatten([v.sites for ([k, v] in this.userSheets)])) +}; + +const styles = storage.newObject("styles", Styles, false); +const highlight = storage.newObject("highlight", Highlights, false); + +liberator.registerObserver("load_commands", function () +{ + commands.add(["sty[le]"], + "Add or list user styles", + function (args, special) + { + let [filter] = args.arguments; + let name = args["-name"]; + let css = args.literalArg; + + if (!css) + { + let list = Array.concat([i for (i in styles.userNames)], + [i for (i in styles.userSheets) if (!i[1].ref.length)]); + let str = template.tabular(["", "Filter", "CSS"], + ["padding: 0 1em 0 1ex; vertical-align: top", "padding: 0 1em 0 0; vertical-align: top"], + ([k, v[1].join(","), v[2]] + for ([i, [k, v]] in Iterator(list)) + if ((!filter || v[1].indexOf(filter) >= 0) && (!name || v[0] == name)))); + commandline.echo(str, commandline.HL_NORMAL, commandline.FORCE_MULTILINE); + } + else + { + let err = styles.addSheet(name, filter, css, false, special); + if (err) + liberator.echoerr(err); + } + }, + { + completer: function (filter) { + let compl = []; + try + { + compl.push([content.location.host, "Current Host"]); + compl.push([content.location.href, "Current URL"]); + } + catch (e) {} + comp = compl.concat([[s, ""] for each (s in styles.sites)]) + return [0, completion.filter(compl, filter)]; + }, + argCount: 1, + bang: true, + hereDoc: true, + literal: true, + options: [[["-name", "-n"], commands.OPTION_STRING]], + serial: function () [ + { + command: this.name, + bang: true, + options: sty.name ? {"-name": sty.name} : {}, + arguments: [sty.sites.join(",")], + literalArg: sty.css + } for ([k, sty] in styles.userSheets) + ] + }); + + commands.add(["dels[tyle]"], + "Remove a user stylesheet", + function (args) { + styles.removeSheet(args["-name"], args.arguments[0], args.literalArg, args["-index"], false); + }, + { + argCount: 1, + completer: function (filter) [0, completion.filter( + [[i, <>{s.sites.join(",")}: {s.css.replace("\n", "\\n")}] + for ([i, s] in styles.userSheets) + ] + .concat([[s, ""] for each (s in styles.sites)]) + , filter)], + literal: true, + options: [[["-index", "-i"], commands.OPTION_INT], + [["-name", "-n"], commands.OPTION_STRING]] + }); + + commands.add(["hi[ghlight]"], + "Set the style of certain display elements", + function (args, special) + { + let key = args.arguments[0]; + let css = args.literalArg; + if (!css && !(key && special)) + { + let str = template.tabular(["Key", "CSS"], + ["padding: 0 1em 0 0; vertical-align: top"], + ([h.class, h.value] + for (h in highlight) + if (!key || h.class.indexOf(key) > -1))); + commandline.echo(str, commandline.HL_NORMAL, commandline.FORCE_MULTILINE); + return; + } + let error = highlight.set(key, css, special); + if (error) + liberator.echoerr(error); + }, + { + // TODO: add this as a standard highlight completion function? + // I agree. It could (should) be much more sophisticated. --Kris + completer: function (filter) [0, + completion.filter([[v.class, ""] for (v in highlight)], filter) + ], + argCount: 1, + bang: true, + hereDoc: true, + literal: true, + serial: function () [ + { + command: this.name, + arguments: [k], + literalArg: v + } + for ([k, v] in Iterator(highlight)) + if (v.value != v.default) + ] + }); +}); + diff --git a/content/ui.js b/content/ui.js index 6f949c3a..6241fbec 100644 --- a/content/ui.js +++ b/content/ui.js @@ -214,8 +214,10 @@ function CommandLine() //{{{ .selection.getRangeAt(0) .startContainer.parentNode .scrollWidth > commandWidget.inputField.scrollWidth) - // Yeah, the min-width is stupid. Somehow, it doesn't work otherwise. - setMultiline(

{str}

, highlightGroup); + { + setCommand(""); + setMultiline({str}, highlightGroup); + } } // TODO: extract CSS @@ -233,7 +235,7 @@ function CommandLine() //{{{ * after interpolated data. */ XML.ignoreWhitespace = typeof str == "xml"; - var output =
{template.maybeXML(str)}
; + var output =
{template.maybeXML(str)}
; XML.ignoreWhiteSpace = true; lastMowOutput = output; @@ -267,7 +269,7 @@ function CommandLine() //{{{ if (win.scrollY >= win.scrollMaxY) setLine("Press ENTER or type command to continue", commandline.HL_QUESTION, true); else - setLine("-- More --", commandline.HL_QUESTION, true); + setLine("-- More --", commandline.HL_MOREMSG, true); } else { @@ -278,7 +280,7 @@ function CommandLine() //{{{ win.focus(); startHints = false; - modes.push(modes.COMMAND_LINE, modes.OUTPUT_MULTILINE); + modes.set(modes.COMMAND_LINE, modes.OUTPUT_MULTILINE); } function autosizeMultilineInputWidget() @@ -532,7 +534,7 @@ function CommandLine() //{{{ let list = <>; for (let [,message] in Iterator(messageHistory.messages)) - list +=
{message.str}
; + list +=
{message.str}
; liberator.echo(list, commandline.FORCE_MULTILINE); } @@ -591,7 +593,7 @@ function CommandLine() //{{{ historyIndex = UNINITIALIZED; completionIndex = UNINITIALIZED; - modes.push(modes.COMMAND_LINE, currentExtendedMode); + modes.set(modes.COMMAND_LINE, currentExtendedMode); setHighlightGroup(this.HL_NORMAL); setPrompt(currentPrompt); setCommand(currentCommand); diff --git a/content/util.js b/content/util.js index e3a862a1..5410d827 100644 --- a/content/util.js +++ b/content/util.js @@ -484,10 +484,23 @@ function Struct() { let self = this instanceof arguments.callee ? this : new arguments.callee(); for (let [k, v] in Iterator(Array.slice(arguments))) - self[k] = v; + { + if (v != undefined) + self[k] = v; + } return self; } ConStructor.prototype = self; + ConStructor.defaultValue = function (key, val) + { + let i = args.indexOf(key); + let _i = "_" + i; + ConStructor.prototype.__defineGetter__(i, val); + ConStructor.prototype.__defineSetter__(i, function (val) { + this.__defineGetter__(i, function () this[_i]); + this[_i] = val; + }); + }; return self.constructor = ConStructor; } @@ -497,7 +510,11 @@ Struct.prototype = { return this.constructor.apply(null, this.slice()); }, // Iterator over our named members - __iterator__: function () ([v, this[v]] for ([k, v] in this.members)) + __iterator__: function () + { + let self = this; + return ([v, self[v]] for ([k, v] in Iterator(self.members))) + } } // Add no-sideeffect array methods. Can't set new Array() as the prototype or