1 // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
2 // Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
3 // Copyright (c) 2008-2012 Kris Maglione <maglione.k@gmail.com>
5 // This work is licensed for reuse under an MIT license. Details are
6 // given in the LICENSE.txt file included with this file.
11 var EVAL_ERROR = "__dactyl_eval_error";
12 var EVAL_RESULT = "__dactyl_eval_result";
13 var EVAL_STRING = "__dactyl_eval_string";
15 var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
18 // cheap attempt at compatibility
19 let prop = { get: deprecated("dactyl", function liberator() dactyl),
21 Object.defineProperty(window, "liberator", prop);
22 Object.defineProperty(modules, "liberator", prop);
25 this.modules = modules;
27 util.addObserver(this);
29 this.commands["dactyl.restart"] = function (event) {
33 styles.registerSheet("resource://dactyl-skin/dactyl.css");
36 this.cleanups.push(overlay.overlayObject(window, {
37 focusAndSelectUrlBar: function focusAndSelectUrlBar() {
38 switch (options.get("strictfocus").getKey(document.documentURIObject || util.newURI(document.documentURI), "moderate")) {
40 if (!Events.isHidden(window.gURLBar, true))
41 return focusAndSelectUrlBar.superapply(this, arguments);
49 cleanup: function () {
50 for (let cleanup in values(this.cleanups))
54 delete window.liberator;
56 // Prevents box ordering bugs after our stylesheet is removed.
57 styles.system.add("cleanup-sheet", config.styleableChrome, literal(/*
58 #TabsToolbar tab { display: none; }
60 styles.unregisterSheet("resource://dactyl-skin/dactyl.css");
61 DOM('#TabsToolbar tab', document).style.display;
64 destroy: function () {
65 this.observe.unregister();
66 autocommands.trigger("LeavePre", {});
67 dactyl.triggerObserver("shutdown", null);
68 util.dump("All dactyl modules destroyed\n");
69 autocommands.trigger("Leave", {});
72 // initially hide all GUI elements, they are later restored unless the user
73 // has :set go= or something similar in his config
74 hideGUI: function () {
75 let guioptions = config.guioptions;
76 for (let option in guioptions) {
77 guioptions[option].forEach(function (elem) {
79 document.getElementById(elem).collapsed = true;
88 "dactyl-cleanup": function dactyl_cleanup(subject, reason) {
89 let modules = dactyl.modules;
91 for (let mod in values(modules.moduleList.reverse())) {
94 this.trapErrors("cleanup", mod, reason);
96 this.trapErrors("destroy", mod, reason);
99 modules.moduleManager.initDependencies("cleanup");
101 for (let name in values(Object.getOwnPropertyNames(modules).reverse()))
103 delete modules[name];
106 modules.__proto__ = {};
111 "io.source": function ioSource(context, file, modTime) {
112 if (contexts.getDocs(context))
113 help.flush("help/plugins.xml", modTime);
117 profileName: deprecated("config.profileName", { get: function profileName() config.profileName }),
120 * @property {Modes.Mode} The current main mode.
121 * @see modes#mainModes
123 mode: deprecated("modes.main", {
124 get: function mode() modes.main,
125 set: function mode(val) modes.main = val
128 getMenuItems: function getMenuItems(targetPath) {
129 function addChildren(node, parent) {
130 DOM(node).createContents();
132 if (~["menu", "menupopup"].indexOf(node.localName) && node.children.length)
133 DOM(node).popupshowing({ bubbles: false });
135 for (let [, item] in Iterator(node.childNodes)) {
136 if (item.childNodes.length == 0 && item.localName == "menuitem"
138 && !/rdf:http:/.test(item.getAttribute("label"))) { // FIXME
139 item.dactylPath = parent + item.getAttribute("label");
140 if (!targetPath || targetPath.indexOf(item.dactylPath) == 0)
145 if (item.localName == "menu")
146 path += item.getAttribute("label") + ".";
147 if (!targetPath || targetPath.indexOf(path) == 0)
148 addChildren(item, path);
154 addChildren(document.getElementById(config.guioptions["m"][1]), "");
158 get menuItems() this.getMenuItems(),
163 NEW_BACKGROUND_TAB: "background-tab",
164 NEW_WINDOW: "window",
166 forceBackground: null,
170 get forceOpen() ({ background: this.forceBackground,
171 target: this.forceTarget }),
173 for (let [k, v] in Iterator({ background: "forceBackground", target: "forceTarget" }))
178 version: deprecated("config.version", { get: function version() config.version }),
181 * @property {Object} The map of command-line options. These are
182 * specified in the argument to the host application's -{config.name}
183 * option. E.g. $ firefox -pentadactyl '+u=/tmp/rcfile ++noplugin'
185 * +u RCFILE Use RCFILE instead of .pentadactylrc.
186 * ++noplugin Don't load plugins.
187 * These two can be specified multiple times:
188 * ++cmd CMD Execute an Ex command before initialization.
189 * +c CMD Execute an Ex command after initialization.
191 commandLineOptions: {
192 /** @property Whether plugin loading should be prevented. */
194 /** @property An RC file to use rather than the default. */
196 /** @property An Ex command to run before any initialization is performed. */
198 /** @property An Ex command to run after all initialization has been performed. */
202 registerObserver: function registerObserver(type, callback, weak) {
203 if (!(type in this._observers))
204 this._observers[type] = [];
205 this._observers[type].push(weak ? util.weakReference(callback) : { get: function () callback });
208 registerObservers: function registerObservers(obj, prop) {
209 for (let [signal, func] in Iterator(obj[prop || "signals"]))
210 this.registerObserver(signal, obj.closure(func), false);
213 unregisterObserver: function unregisterObserver(type, callback) {
214 if (type in this._observers)
215 this._observers[type] = this._observers[type].filter(function (c) c.get() != callback);
218 applyTriggerObserver: function triggerObserver(type, args) {
219 if (type in this._observers)
220 this._observers[type] = this._observers[type].filter(function (callback) {
221 if (callback.get()) {
224 callback.get().apply(null, args);
226 catch (e if e.message == "can't wrap XML objects") {
228 callback.get().apply(null, [String(args[0])].concat(args.slice(1)));
232 dactyl.reportError(e);
239 triggerObserver: function triggerObserver(type) {
240 return this.applyTriggerObserver(type, Array.slice(arguments, 1));
243 addUsageCommand: function (params) {
244 function keys(item) (item.names || [item.name]).concat(item.description, item.columns || []);
246 let name = commands.add(params.name, params.description,
248 let results = array(params.iterate(args))
249 .sort(function (a, b) String.localeCompare(a.name, b.name));
251 let filters = args.map(function (arg) let (re = util.regexp.escape(arg))
252 util.regexp("\\b" + re + "\\b|(?:^|[()\\s])" + re + "(?:$|[()\\s])", "i"));
254 results = results.filter(function (item) filters.every(function (re) keys(item).some(re.closure.test)));
256 commandline.commandOutput(
257 template.usage(results, params.format));
261 completer: function (context, args) {
262 context.keys.text = util.identity;
263 context.keys.description = function () seen[this.text] + /*L*/" matching items";
264 context.ignoreCase = true;
266 context.completions = array(keys(item).join(" ").toLowerCase().split(/[()\s]+/)
267 for (item in params.iterate(args)))
270 seen[k] = (seen[k] || 0) + 1;
274 options: params.options || []
278 this.indices[params.index] = function () {
279 let results = array((params.iterateIndex || params.iterate).call(params, commands.get(name).newArgs()))
280 .array.sort(function (a, b) String.localeCompare(a.name, b.name));
282 let haveTag = Set.has(help.tags);
283 for (let obj in values(results)) {
284 let res = dactyl.generateHelp(obj, null, null, true);
285 if (!haveTag(obj.helpTag))
286 res[0][1].tag = obj.helpTag;
294 * Triggers the application bell to notify the user of an error. The
295 * bell may be either audible or visual depending on the value of the
296 * 'visualbell' option.
299 this.triggerObserver("beep");
300 if (options["visualbell"]) {
302 bell: document.getElementById("dactyl-bell"),
303 strut: document.getElementById("dactyl-bell-strut")
306 overlay.overlayWindow(window, {
309 ["window", { id: document.documentElement.id, xmlns: "xul" },
310 ["hbox", { style: "display: none", highlight: "Bell", id: "dactyl-bell", key: "bell" }]]],
312 ["window", { id: document.documentElement.id, xmlns: "xul" },
313 ["hbox", { style: "display: none", highlight: "Bell", id: "dactyl-bell-strut", key: "strut" }]]]
316 elems.bell.style.height = window.innerHeight + "px";
317 elems.strut.style.marginBottom = -window.innerHeight + "px";
318 elems.strut.style.display = elems.bell.style.display = "";
320 util.timeout(function () { elems.strut.style.display = elems.bell.style.display = "none"; }, 20);
323 let soundService = Cc["@mozilla.org/sound;1"].getService(Ci.nsISound);
329 * Reads a string from the system clipboard.
331 * This is same as Firefox's readFromClipboard function, but is needed for
332 * apps like Thunderbird which do not provide it.
334 * @param {string} which Which clipboard to write to. Either
335 * "global" or "selection". If not provided, both clipboards are
340 clipboardRead: function clipboardRead(which) {
342 const { clipboard } = services;
344 let transferable = services.Transferable();
345 transferable.addDataFlavor("text/unicode");
347 let source = clipboard[which == "global" || !clipboard.supportsSelectionClipboard() ?
348 "kGlobalClipboard" : "kSelectionClipboard"];
349 clipboard.getData(transferable, source);
351 let str = {}, len = {};
352 transferable.getTransferData("text/unicode", str, len);
355 return str.value.QueryInterface(Ci.nsISupportsString)
356 .data.substr(0, len.value / 2);
363 * Copies a string to the system clipboard. If *verbose* is specified the
364 * copied string is also echoed to the command line.
366 * @param {string} str The string to write.
367 * @param {boolean} verbose If true, the user is notified of the copied data.
368 * @param {string} which Which clipboard to write to. Either
369 * "global" or "selection". If not provided, both clipboards are
373 clipboardWrite: function clipboardWrite(str, verbose, which) {
374 if (which == null || which == "selection" && !services.clipboard.supportsSelectionClipboard())
375 services.clipboardHelper.copyString(str);
377 services.clipboardHelper.copyStringToClipboard(str,
378 services.clipboard["k" + util.capitalize(which) + "Clipboard"]);
381 let message = { message: _("dactyl.yank", str) };
383 message.domains = [util.newURI(str).host];
386 dactyl.echomsg(message);
390 dump: deprecated("util.dump",
391 { get: function dump() util.closure.dump }),
392 dumpStack: deprecated("util.dumpStack",
393 { get: function dumpStack() util.closure.dumpStack }),
396 * Outputs a plain message to the command line.
398 * @param {string} str The message to output.
399 * @param {number} flags These control the multi-line message behavior.
400 * See {@link CommandLine#echo}.
402 echo: function echo(str, flags) {
403 commandline.echo(str, commandline.HL_NORMAL, flags);
407 * Outputs an error message to the command line.
409 * @param {string} str The message to output.
410 * @param {number} flags These control the multi-line message behavior.
411 * See {@link CommandLine#echo}.
413 echoerr: function echoerr(str, flags) {
414 flags |= commandline.APPEND_TO_MESSAGES;
416 if (isinstance(str, ["DOMException", "Error", "Exception", ErrorBase])
417 || isinstance(str, ["XPCWrappedNative_NoHelper"]) && /^\[Exception/.test(str))
418 dactyl.reportError(str);
420 if (isObject(str) && "echoerr" in str)
422 else if (isinstance(str, ["Error", FailedAssertion]) && str.fileName)
423 str = [str.fileName.replace(/^.* -> /, ""), ": ", str.lineNumber, ": ", str].join("");
425 if (options["errorbells"])
428 commandline.echo(str, commandline.HL_ERRORMSG, flags);
432 * Outputs a warning message to the command line.
434 * @param {string} str The message to output.
435 * @param {number} flags These control the multi-line message behavior.
436 * See {@link CommandLine#echo}.
438 warn: function warn(str, flags) {
439 commandline.echo(str, "WarningMsg", flags | commandline.APPEND_TO_MESSAGES);
442 // TODO: add proper level constants
444 * Outputs an information message to the command line.
446 * @param {string} str The message to output.
447 * @param {number} verbosity The messages log level (0 - 15). Only
448 * messages with verbosity less than or equal to the value of the
449 * *verbosity* option will be output.
450 * @param {number} flags These control the multi-line message behavior.
451 * See {@link CommandLine#echo}.
453 echomsg: function echomsg(str, verbosity, flags) {
454 if (verbosity == null)
455 verbosity = 0; // verbosity level is exclusionary
457 if (options["verbose"] >= verbosity)
458 commandline.echo(str, commandline.HL_INFOMSG,
459 flags | commandline.APPEND_TO_MESSAGES);
463 * Loads and executes the script referenced by *uri* in the scope of the
466 * @param {string} uri The URI of the script to load. Should be a local
467 * chrome:, file:, or resource: URL.
468 * @param {Object} context The context object into which the script
471 loadScript: function loadScript(uri, context) {
472 JSMLoader.loadSubScript(uri, context, File.defaultEncoding);
475 userEval: function userEval(str, context, fileName, lineNumber) {
477 if (jsmodules.__proto__ != window && jsmodules.__proto__ != XPCNativeWrapper(window) &&
478 jsmodules.isPrototypeOf(context))
479 str = "with (window) { with (modules) { (this.eval || eval)(" + str.quote() + ") } }";
481 let info = contexts.context;
482 if (fileName == null)
484 ({ file: fileName, line: lineNumber, context: ctxt }) = info;
486 if (fileName && fileName[0] == "[")
487 fileName = "dactyl://command-line/";
489 context = ctxt || _userContext;
491 if (isinstance(context, ["Sandbox"]))
492 return Cu.evalInSandbox(str, context, "1.8", fileName, lineNumber);
495 context = userContext || ctxt;
497 if (services.has("dactyl") && services.dactyl.evalInContext)
498 return services.dactyl.evalInContext(str, context, fileName, lineNumber);
501 context[EVAL_ERROR] = null;
502 context[EVAL_STRING] = str;
503 context[EVAL_RESULT] = null;
505 this.loadScript("resource://dactyl-content/eval.js", context);
506 if (context[EVAL_ERROR]) {
508 context[EVAL_ERROR].fileName = info.file;
509 context[EVAL_ERROR].lineNumber += info.line;
512 throw context[EVAL_ERROR];
514 return context[EVAL_RESULT];
517 delete context[EVAL_ERROR];
518 delete context[EVAL_RESULT];
519 delete context[EVAL_STRING];
524 * Acts like the Function builtin, but the code executes in the
525 * userContext global.
527 userFunc: function userFunc() {
528 return this.userEval(
529 "(function userFunction(" + Array.slice(arguments, 0, -1).join(", ") + ")" +
530 " { " + arguments[arguments.length - 1] + " })");
534 * Execute an Ex command string. E.g. ":zoom 300".
536 * @param {string} str The command to execute.
537 * @param {Object} modifiers Any modifiers to be passed to
538 * {@link Command#action}.
539 * @param {boolean} silent Whether the command should be echoed on the
542 execute: function execute(str, modifiers, silent) {
543 // skip comments and blank lines
544 if (/^\s*("|$)/.test(str))
547 modifiers = modifiers || {};
550 commands.lastCommand = str.replace(/^\s*:\s*/, "");
552 for (let [command, args] in commands.parseCommands(str.replace(/^'(.*)'$/, "$1"))) {
553 if (command === null)
554 throw FailedAssertion(_("dactyl.notCommand", config.appName, args.commandString));
556 res = res && command.execute(args, modifiers);
561 focus: function focus(elem, flags) {
562 DOM(elem).focus(flags);
566 * Focuses the content window.
568 * @param {boolean} clearFocusedElement Remove focus from any focused
571 focusContent: function focusContent(clearFocusedElement) {
572 if (window != services.focus.activeWindow)
575 let win = document.commandDispatcher.focusedWindow;
576 let elem = config.mainWidget || content;
578 // TODO: make more generic
580 if (this.has("mail") && !config.isComposeWindow) {
581 let i = gDBView.selection.currentIndex;
582 if (i == -1 && gDBView.rowCount >= 0)
584 gDBView.selection.select(i);
587 let frame = buffer.focusedFrame;
588 if (frame && frame.top == content && !Editor.getEditor(frame))
594 if (clearFocusedElement) {
595 if (dactyl.focusedElement)
596 dactyl.focusedElement.blur();
597 if (win && Editor.getEditor(win)) {
598 this.withSavedValues(["ignoreFocus"], function _focusContent() {
599 this.ignoreFocus = true;
600 if (win.frameElement)
601 win.frameElement.blur();
603 if (content.document.activeElement instanceof HTMLIFrameElement)
604 content.document.activeElement.blur();
609 if (elem instanceof Window && Editor.getEditor(elem))
612 if (elem && elem != dactyl.focusedElement)
616 /** @property {Element} The currently focused element. */
617 get focusedElement() services.focus.getFocusedElementForWindow(window, true, {}),
618 set focusedElement(elem) dactyl.focus(elem),
621 * Returns whether this Dactyl extension supports *feature*.
623 * @param {string} feature The feature name.
626 has: function has(feature) Set.has(config.features, feature),
631 initDocument: function initDocument(doc) {
633 if (doc.location.protocol === "dactyl:") {
643 help: deprecated("help.help", { get: function help() modules.help.closure.help }),
644 findHelp: deprecated("help.findHelp", { get: function findHelp() help.closure.findHelp }),
648 * Initialize the help system.
650 initHelp: function initHelp() {
651 if ("noscriptOverlay" in window)
652 window.noscriptOverlay.safeAllow("dactyl:", true, false);
658 * Generates a help entry and returns it as a string.
660 * @param {Command|Map|Option} obj A dactyl *Command*, *Map* or *Option*
662 * @param {XMLList} extraHelp Extra help text beyond the description.
665 generateHelp: function generateHelp(obj, extraHelp, str, specOnly) {
667 link = tag = spec = util.identity;
670 if (obj instanceof Command) {
671 link = function (cmd) ["ex", {}, cmd];
672 args = obj.parseArgs("", CompletionContext(str || ""));
673 tag = function (cmd) DOM.DOMString(":" + cmd);
674 spec = function (cmd) [
675 obj.count ? ["oa", {}, "count"] : [],
677 obj.bang ? ["oa", {}, "!"] : []
680 else if (obj instanceof Map) {
681 spec = function (map) obj.count ? [["oa", {}, "count"], map] : DOM.DOMString(map);
682 tag = function (map) [
683 let (c = obj.modes[0].char) c ? c + "_" : "",
686 link = function (map) {
687 let [, mode, name, extra] = /^(?:(.)_)?(?:<([^>]+)>)?(.*)$/.exec(map);
688 let k = ["k", {}, extra];
696 else if (obj instanceof Option) {
697 spec = function () template.map(obj.names, tag, " ");
698 tag = function (name) DOM.DOMString("'" + name + "'");
699 link = function (opt, name) ["o", {}, name];
700 args = { value: "", values: [] };
704 ["dt", {}, link(obj.helpTag || tag(obj.name), obj.name)],
706 template.linkifyHelp(obj.description ? obj.description.replace(/\.$/, "") : "", true)]];
710 let description = ["description", {},
711 obj.description ? ["p", {}, template.linkifyHelp(obj.description.replace(/\.?$/, "."), true)] : "",
712 extraHelp ? extraHelp : "",
713 !(extraHelp || obj.description) ? ["p", {}, /*L*/ "Sorry, no help available."] : ""]
717 ["tags", {}, template.map(obj.names.slice().reverse(),
721 let (name = (obj.specs || obj.names)[0])
722 spec(template.highlightRegexp(tag(name),
724 function (m, n0) ["oa", {}, n0]),
727 ["type", {}, obj.type],
728 ["default", {}, obj.stringDefaultValue]],
733 ["dl", {}, template.map(ary,
734 function ([a, b]) [["dt", {}, a], " ",
738 if (obj.completer && false)
739 add(completion._runCompleter(obj.closure.completer, "", null, args).items
740 .map(function (i) [i.text, i.description]));
742 if (obj.options && obj.options.some(function (o) o.description) && false)
743 add(obj.options.filter(function (o) o.description)
747 o.names.length == 1 ? "" :
748 ["", " (short name: ",
749 template.map(o.names.slice(1), function (n) ["em", {}, n], ", "),
753 return DOM.toPrettyXML(res, true, null, { "": String(NS) });
757 * The map of global variables.
759 * These are set and accessed with the "g:" prefix.
761 _globalVariables: {},
762 globalVariables: deprecated(_("deprecated.for.theOptionsSystem"), {
763 get: function globalVariables() this._globalVariables
766 loadPlugins: function loadPlugins(args, force) {
767 function sourceDirectory(dir) {
768 dactyl.assert(dir.isReadable(), _("io.notReadable", dir.path));
770 dactyl.log(_("dactyl.sourcingPlugins", dir.path), 3);
772 let loadplugins = options.get("loadplugins");
774 loadplugins = { __proto__: loadplugins, value: args.map(Option.parseRegexp) };
776 dir.readDirectory(true).forEach(function (file) {
777 if (file.leafName[0] == ".")
779 else if (file.isFile() && loadplugins.getKey(file.path)
780 && !(!force && file.path in dactyl.pluginFiles && dactyl.pluginFiles[file.path] >= file.lastModifiedTime)) {
782 io.source(file.path);
783 dactyl.pluginFiles[file.path] = file.lastModifiedTime;
786 dactyl.reportError(e);
789 else if (file.isDirectory())
790 sourceDirectory(file);
794 let dirs = io.getRuntimeDirectories("plugins");
796 if (dirs.length == 0) {
797 dactyl.log(_("dactyl.noPluginDir"), 3);
802 _("plugin.searchingForIn",
803 ("plugins/**/*.{js," + config.fileExtension + "}").quote(),
804 [dir.path.replace(/.plugins$/, "") for ([, dir] in Iterator(dirs))]
808 dirs.forEach(function (dir) {
809 dactyl.echomsg(_("plugin.searchingFor", (dir.path + "/**/*.{js," + config.fileExtension + "}").quote()), 3);
810 sourceDirectory(dir);
814 // TODO: add proper level constants
816 * Logs a message to the JavaScript error console. Each message has an
817 * associated log level. Only messages with a log level less than or equal
818 * to *level* will be printed. If *msg* is an object, it is pretty printed.
820 * @param {string|Object} msg The message to print.
821 * @param {number} level The logging level 0 - 15.
823 log: function log(msg, level) {
824 let verbose = config.prefs.get("loglevel", 0);
826 if (!level || level <= verbose) {
827 if (isObject(msg) && !isinstance(msg, _))
828 msg = util.objectToString(msg, false);
830 services.console.logStringMessage(config.name + ": " + msg);
835 click: function onClick(event) {
836 let elem = event.originalTarget;
838 if (elem instanceof Element && services.security.isSystemPrincipal(elem.nodePrincipal)) {
839 let command = elem.getAttributeNS(NS, "command");
840 if (command && event.button == 0) {
841 event.preventDefault();
843 if (dactyl.commands[command])
844 dactyl.withSavedValues(["forceTarget"], function () {
845 if (event.ctrlKey || event.shiftKey || event.button == 1)
846 dactyl.forceTarget = dactyl.NEW_TAB;
847 dactyl.commands[command](event);
853 "dactyl.execute": function onExecute(event) {
854 let cmd = event.originalTarget.getAttribute("dactyl-execute");
855 commands.execute(cmd, null, false, null,
856 { file: /*L*/"[Command Line]", line: 1 });
861 * Opens one or more URLs. Returns true when load was initiated, or
864 * @param {string|Array} urls A representation of the URLs to open.
865 * A string will be passed to {@link Dactyl#parseURLs}. An array may
866 * contain elements of the following forms:
868 * • {string} A URL to open.
869 * • {[string, {string|Array}]} Pair of a URL and POST data.
870 * • {object} Object compatible with those returned
871 * by {@link DOM#formData}.
873 * @param {object} params A set of parameters specifying how to open the
874 * URLs. The following properties are recognized:
876 * • background If true, new tabs are opened in the background.
878 * • from The designation of the opener, as appears in
879 * 'activate' and 'newtab' options. If present,
880 * the newtab option provides the default 'where'
881 * parameter, and the value of the 'activate'
882 * parameter is inverted if 'background' is true.
884 * • where One of CURRENT_TAB, NEW_TAB, or NEW_WINDOW
886 * As a deprecated special case, the where parameter may be provided
887 * by itself, in which case it is transformed into { where: params }.
889 * @param {boolean} force Don't prompt whether to open more than 20
893 open: function open(urls, params, force) {
894 if (typeof urls == "string")
895 urls = dactyl.parseURLs(urls);
897 if (urls.length > prefs.get("browser.tabs.maxOpenBeforeWarn", 20) && !force)
898 return commandline.input(_("dactyl.prompt.openMany", urls.length) + " ",
900 if (resp && resp.match(/^y(es)?$/i))
901 dactyl.open(urls, params, true);
904 params = params || {};
905 if (isString(params))
906 params = { where: params };
909 for (let [opt, flag] in Iterator({ replace: "REPLACE_HISTORY", hide: "BYPASS_HISTORY" }))
910 flags |= params[opt] && Ci.nsIWebNavigation["LOAD_FLAGS_" + flag];
912 let where = params.where || dactyl.CURRENT_TAB;
913 let background = dactyl.forceBackground != null ? dactyl.forceBackground :
914 ("background" in params) ? params.background
915 : params.where == dactyl.NEW_BACKGROUND_TAB;
917 if (params.from && dactyl.has("tabs")) {
918 if (!params.where && options.get("newtab").has(params.from))
919 where = dactyl.NEW_TAB;
920 background ^= !options.get("activate").has(params.from);
923 if (urls.length == 0)
926 let browser = config.tabbrowser;
927 function open(loc, where) {
930 loc = { url: loc[0], postData: loc[1] };
931 else if (isString(loc))
934 loc = Object.create(loc);
936 if (isString(loc.postData))
937 loc.postData = ["application/x-www-form-urlencoded", loc.postData];
939 if (isArray(loc.postData)) {
940 let stream = services.MIMEStream(services.StringStream(loc.postData[1]));
941 stream.addHeader("Content-Type", loc.postData[0]);
942 stream.addContentLength = true;
943 loc.postData = stream;
946 // decide where to load the first url
950 if (!dactyl.has("tabs"))
951 return open(loc, dactyl.NEW_WINDOW);
953 return prefs.withContext(function () {
954 prefs.set("browser.tabs.loadInBackground", true);
955 return browser.loadOneTab(loc.url, null, null, loc.postData, background).linkedBrowser.contentDocument;
958 case dactyl.NEW_WINDOW:
959 let options = ["chrome", "all", "dialog=no"];
960 if (dactyl.forcePrivate)
961 options.push("private");
963 let win = window.openDialog(document.documentURI, "_blank", options.join(","));
964 util.waitFor(function () win.document.readyState === "complete");
965 browser = win.dactyl && win.dactyl.modules.config.tabbrowser || win.getBrowser();
967 case dactyl.CURRENT_TAB:
968 browser.loadURIWithFlags(loc.url, flags, null, null, loc.postData);
969 return browser.contentWindow;
973 // Unfortunately, failed page loads throw exceptions and
974 // cause a lot of unwanted noise. This solution means that
975 // any genuine errors go unreported.
978 if (dactyl.forceTarget)
979 where = dactyl.forceTarget;
981 where = dactyl.CURRENT_TAB;
983 return urls.map(function (url) {
984 let res = open(url, where);
985 where = dactyl.NEW_TAB;
992 * Returns an array of URLs parsed from *str*.
994 * Given a string like 'google bla, www.osnews.com' return an array
995 * ['www.google.com/search?q=bla', 'www.osnews.com']
997 * @param {string} str
998 * @returns {[string]}
1000 parseURLs: function parseURLs(str) {
1003 if (options["urlseparator"])
1004 urls = util.splitLiteral(str, util.regexp("\\s*" + options["urlseparator"] + "\\s*"));
1008 return urls.map(function (url) {
1011 if (/^(\.{0,2}|~)(\/|$)/.test(url) || config.OS.isWindows && /^[a-z]:/i.test(url)) {
1013 // Try to find a matching file.
1014 let file = io.File(url);
1015 if (file.exists() && file.isReadable())
1016 return file.URI.spec;
1021 // If it starts with a valid protocol, pass it through.
1022 let proto = /^([-\w]+):/.exec(url);
1023 if (proto && services.PROTOCOL + proto[1] in Cc)
1026 // Check for a matching search keyword.
1027 let searchURL = this.has("bookmarks") && bookmarks.getSearchURL(url, false);
1031 // If it looks like URL-ish (foo.com/bar), let Gecko figure it out.
1032 if (this.urlish.test(url) || !this.has("bookmarks"))
1033 return util.createURI(url).spec;
1035 // Pass it off to the default search engine or, failing
1036 // that, let Gecko deal with it as is.
1037 return bookmarks.getSearchURL(url, true) || util.createURI(url).spec;
1040 stringToURLArray: deprecated("dactyl.parseURLs", "parseURLs"),
1041 urlish: Class.Memoize(function () util.regexp(literal(/*
1043 <domain>+ (:\d+)? (/ .*) |
1045 <domain>+ \. [a-z0-9]+ |
1049 domain: util.regexp(String.replace(literal(/*
1051 U0000-U002c // U002d-U002e --.
1054 U003a-U0040 // U0041-U005a a-z
1055 U005b-U0060 // U0061-U007a A-Z
1058 */), /U/g, "\\u"), "x")
1063 get plugins() plugins,
1065 setNodeVisible: function setNodeVisible(node, visible) {
1066 if (window.setToolbarVisibility && node.localName == "toolbar")
1067 window.setToolbarVisibility(node, visible);
1069 node.collapsed = !visible;
1072 confirmQuit: function confirmQuit()
1073 prefs.withContext(function () {
1074 prefs.set("browser.warnOnQuit", false);
1075 return window.canQuitApplication();
1079 * Quit the host application, no matter how many tabs/windows are open.
1081 * @param {boolean} saveSession If true the current session will be
1082 * saved and restored when the host application is restarted.
1083 * @param {boolean} force Forcibly quit irrespective of whether all
1084 * windows could be closed individually.
1086 quit: function quit(saveSession, force) {
1087 if (!force && !this.confirmQuit())
1090 let pref = "browser.startup.page";
1093 prefs.safeSet(pref, 3);
1094 if (!saveSession && prefs.get(pref) >= 2)
1095 prefs.safeSet(pref, 1);
1097 services.appStartup.quit(Ci.nsIAppStartup[force ? "eForceQuit" : "eAttemptQuit"]);
1101 * Restart the host application.
1103 restart: function restart(args) {
1104 if (!this.confirmQuit())
1107 config.prefs.set("commandline-args", args);
1109 services.appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
1112 get assert() util.assert,
1115 * Traps errors in the called function, possibly reporting them.
1117 * @param {function} func The function to call
1118 * @param {object} self The 'this' object for the function.
1120 trapErrors: function trapErrors(func, self) {
1124 return func.apply(self || this, Array.slice(arguments, 2));
1128 dactyl.reportError(e, true);
1131 util.reportError(e);
1138 * Reports an error to both the console and the host application's
1141 * @param {Object} error The error object.
1143 reportError: function reportError(error, echo) {
1144 if (error instanceof FailedAssertion && error.noTrace || error.message === "Interrupted") {
1145 let context = contexts.context;
1146 let prefix = context ? context.file + ":" + context.line + ": " : "";
1147 if (error.message && error.message.indexOf(prefix) !== 0 &&
1148 prefix != "[Command Line]:1: ")
1149 error.message = prefix + error.message;
1152 dactyl.echoerr(template.linkifyHelp(error.message));
1157 util.reportError(error);
1161 if (error.result == Cr.NS_BINDING_ABORTED)
1165 dactyl.echoerr(error, commandline.FORCE_SINGLELINE);
1167 util.reportError(error);
1171 * Parses a Dactyl command-line string i.e. the value of the
1172 * -dactyl command-line option.
1174 * @param {string} cmdline The string to parse for command-line
1177 * @see Commands#parseArgs
1179 parseCommandLine: function parseCommandLine(cmdline) {
1181 return commands.get("rehash").parseArgs(cmdline);
1184 dactyl.reportError(e, true);
1188 wrapCallback: function wrapCallback(callback, self) {
1189 self = self || this;
1190 let save = ["forceOpen"];
1191 let saved = save.map(function (p) dactyl[p]);
1192 return function wrappedCallback() {
1193 let args = arguments;
1194 return dactyl.withSavedValues(save, function () {
1195 saved.forEach(function (p, i) dactyl[save[i]] = p);
1197 return callback.apply(self, args);
1200 dactyl.reportError(e, true);
1207 * @property {[Window]} Returns an array of all the host application's
1210 get windows() [win for (win in iter(services.windowMediator.getEnumerator("navigator:browser"))) if (win.dactyl)],
1213 toolbarHidden: function hidden(elem) (elem.getAttribute("autohide") || elem.getAttribute("collapsed")) == "true"
1215 cache: function initCache() {
1216 cache.register("help/plugins.xml", function () {
1217 // Process plugin help entries.
1220 for (let [, context] in Iterator(plugins.contexts))
1222 let info = contexts.getDocs(context);
1223 if (DOM.isJSONXML(info)) {
1224 let langs = info.slice(2).filter(function (e) isArray(e) && isObject(e[1]) && e[1].lang);
1226 let lang = config.bestLocale(l[1].lang for each (l in langs));
1228 info = info.slice(0, 2).concat(
1229 info.slice(2).filter(function (e) !isArray(e) || !isObject(e[1])
1230 || e[1].lang == lang));
1232 for each (let elem in info.slice(2).filter(function (e) isArray(e) && e[0] == "info" && isObject(e[1])))
1233 for (let attr in values(["name", "summary", "href"]))
1234 if (attr in elem[1])
1235 info[attr] = elem[1][attr];
1237 body.push(["h2", { xmlns: "dactyl", tag: info[1].name + '-plugin' },
1238 String(info[1].summary)]);
1243 util.reportError(e);
1246 return '<?xml version="1.0"?>\n' +
1247 '<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>\n' +
1249 ["document", { xmlns: "dactyl", name: "plugins",
1250 title: config.appName + ", Plugins" },
1251 ["h1", { tag: "using-plugins" }, _("help.title.Using Plugins")],
1252 ["toc", { start: "2" }],
1257 cache.register("help/index.xml", function () {
1258 return '<?xml version="1.0"?>\n' +
1259 DOM.toXML(["overlay", { xmlns: "dactyl" },
1260 template.map(dactyl.indices, function ([name, iter])
1261 ["dl", { insertafter: name + "-index" },
1262 template.map(iter(), util.identity)],
1266 cache.register("help/gui.xml", function () {
1267 return '<?xml version="1.0"?>\n' +
1268 DOM.toXML(["overlay", { xmlns: "dactyl" },
1269 ["dl", { insertafter: "dialog-list" },
1270 template.map(config.dialogs, function ([name, val])
1271 (!val[2] || val[2]())
1272 ? [["dt", {}, name],
1278 cache.register("help/privacy.xml", function () {
1279 return '<?xml version="1.0"?>\n' +
1280 DOM.toXML(["overlay", { xmlns: "dactyl" },
1281 ["dl", { insertafter: "sanitize-items" },
1282 template.map(options.get("sanitizeitems").values
1283 .sort(function (a, b) String.localeCompare(a.name,
1285 function ({ name, description })
1287 ["dd", {}, template.linkifyHelp(description, true)]],
1291 events: function initEvents() {
1292 events.listen(window, dactyl, "events", true);
1294 // Only general options are added here, which are valid for all Dactyl extensions
1295 options: function initOptions() {
1296 options.add(["errorbells", "eb"],
1297 "Ring the bell when an error message is displayed",
1300 options.add(["exrc", "ex"],
1301 "Enable automatic sourcing of an RC file in the current directory at startup",
1304 options.add(["fullscreen", "fs"],
1305 "Show the current window fullscreen",
1307 setter: function (value) window.fullScreen = value,
1308 getter: function () window.fullScreen
1314 c: ["Always show the command line, even when empty"],
1315 C: ["Always show the command line outside of the status line"],
1316 M: ["Always show messages outside of the status line"]
1318 setter: function (opts) {
1319 if (loaded.commandline || ~opts.indexOf("c"))
1320 commandline.widgets.updateVisibility();
1325 s: ["Status bar", [statusline.statusBar.id]]
1326 }, config.guioptions),
1327 setter: function (opts) {
1328 for (let [opt, [, ids]] in Iterator(this.opts)) {
1329 ids.map(function (id) document.getElementById(id))
1330 .forEach(function (elem) {
1332 dactyl.setNodeVisible(elem, opts.indexOf(opt) >= 0);
1339 r: ["Right Scrollbar", "vertical"],
1340 l: ["Left Scrollbar", "vertical"],
1341 b: ["Bottom Scrollbar", "horizontal"]
1343 setter: function (opts) {
1344 let dir = ["horizontal", "vertical"].filter(
1345 function (dir) !Array.some(opts,
1346 function (o) this.opts[o] && this.opts[o][1] == dir, this),
1348 let class_ = dir.map(function (dir) "html|html > xul|scrollbar[orient=" + dir + "]");
1350 styles.system.add("scrollbar", "*",
1351 class_.length ? class_.join(", ") + " { visibility: collapse !important; }" : "",
1354 prefs.safeSet("layout.scrollbar.side", opts.indexOf("l") >= 0 ? 3 : 2,
1355 _("option.guioptions.safeSet"));
1357 validator: function (opts) Option.validIf(!(opts.indexOf("l") >= 0 && opts.indexOf("r") >= 0),
1358 UTF8("Only one of ‘l’ or ‘r’ allowed"))
1363 n: ["Tab number", highlight.selector("TabNumber")],
1364 N: ["Tab number over icon", highlight.selector("TabIconNumber")]
1366 setter: function (opts) {
1367 let classes = [v[1] for ([k, v] in Iterator(this.opts)) if (opts.indexOf(k) < 0)];
1369 styles.system.add("taboptions", "chrome://*",
1370 classes.length ? classes.join(",") + "{ display: none; }" : "");
1372 if (!dactyl.has("Gecko2")) {
1373 tabs.tabBinding.enabled = Array.some(opts, function (k) k in this.opts, this);
1374 tabs.updateTabCount();
1376 if (config.tabbrowser.tabContainer._positionPinnedTabs)
1377 config.tabbrowser.tabContainer._positionPinnedTabs();
1380 validator: function (opts) dactyl.has("Gecko2") ||
1381 Option.validIf(!/[nN]/.test(opts), "Tab numbering not available in this " + config.host + " version")
1384 ].filter(function (group) !group.feature || dactyl.has(group.feature));
1386 options.add(["guioptions", "go"],
1387 "Show or hide certain GUI elements like the menu or toolbar",
1391 cleanupValue: config.cleanups.guioptions ||
1392 "rb" + [k for ([k, v] in iter(groups[1].opts))
1393 if (!Dactyl.toolbarHidden(document.getElementById(v[1][0])))].join(""),
1395 values: array(groups).map(function (g) [[k, v[0]] for ([k, v] in Iterator(g.opts))]).flatten(),
1397 setter: function (value) {
1398 for (let group in values(groups))
1399 group.setter(value);
1400 events.checkFocus();
1403 validator: function (val) Option.validateCompleter.call(this, val) &&
1404 groups.every(function (g) !g.validator || g.validator(val))
1407 options.add(["helpfile", "hf"],
1408 "Name of the main help file",
1411 options.add(["loadplugins", "lpl"],
1412 "A regexp list that defines which plugins are loaded at startup and via :loadplugins",
1413 "regexplist", "'\\.(js|" + config.fileExtension + ")$'");
1415 options.add(["titlestring"],
1416 "The string shown at the end of the window title",
1417 "string", config.host,
1419 setter: function (value) {
1420 let win = document.documentElement;
1421 function updateTitle(old, current) {
1422 if (config.browser.updateTitlebar)
1423 config.browser.updateTitlebar();
1425 document.title = document.title.replace(RegExp("(.*)" + util.regexp.escape(old)), "$1" + current);
1428 if (win.hasAttribute("titlemodifier_privatebrowsing")) {
1429 let oldValue = win.getAttribute("titlemodifier_normal");
1430 let suffix = win.getAttribute("titlemodifier_privatebrowsing").substr(oldValue.length);
1432 win.setAttribute("titlemodifier_normal", value);
1433 win.setAttribute("titlemodifier_privatebrowsing", value + suffix);
1435 if (storage.privateMode) {
1436 updateTitle(oldValue + suffix, value + suffix);
1437 win.setAttribute("titlemodifier", value + suffix);
1442 updateTitle(win.getAttribute("titlemodifier"), value);
1443 win.setAttribute("titlemodifier", value);
1449 options.add(["urlseparator", "urlsep", "us"],
1450 "The regular expression used to separate multiple URLs in :open and friends",
1452 { validator: function (value) RegExp(value) });
1454 options.add(["verbose", "vbs"],
1455 "Define which info messages are displayed",
1457 { validator: function (value) Option.validIf(value >= 0 && value <= 15, "Value must be between 0 and 15") });
1459 options.add(["visualbell", "vb"],
1460 "Use visual bell instead of beeping on errors",
1463 setter: function (value) {
1464 prefs.safeSet("accessibility.typeaheadfind.enablesound", !value,
1465 _("option.safeSet", "visualbell"));
1471 mappings: function initMappings() {
1472 if (dactyl.has("session"))
1473 mappings.add([modes.NORMAL], ["ZQ"],
1474 "Quit and don't save the session",
1475 function () { dactyl.quit(false); });
1477 mappings.add([modes.NORMAL], ["ZZ"],
1478 "Quit and save the session",
1479 function () { dactyl.quit(true); });
1482 commands: function initCommands() {
1483 commands.add(["dia[log]"],
1484 "Open a " + config.appName + " dialog",
1486 let dialog = args[0];
1488 dactyl.assert(dialog in config.dialogs,
1489 _("error.invalidArgument", dialog));
1490 dactyl.assert(!config.dialogs[dialog][2] || config.dialogs[dialog][2](),
1491 _("dialog.notAvailable", dialog));
1493 config.dialogs[dialog][1]();
1496 dactyl.echoerr(_("error.cantOpen", dialog.quote(), e.message || e));
1500 completer: function (context) {
1501 context.ignoreCase = true;
1502 completion.dialog(context);
1506 commands.add(["em[enu]"],
1507 "Execute the specified menu item from the command line",
1509 let arg = args[0] || "";
1510 let items = dactyl.getMenuItems(arg);
1512 dactyl.assert(items.some(function (i) i.dactylPath == arg),
1513 _("emenu.notFound", arg));
1515 for (let [, item] in Iterator(items)) {
1516 if (item.dactylPath == arg) {
1517 dactyl.assert(!item.disabled, _("error.disabled", item.dactylPath));
1523 completer: function (context) completion.menuItem(context),
1527 commands.add(["exe[cute]"],
1528 "Execute the argument as an Ex command",
1531 let cmd = dactyl.userEval(args[0] || "");
1532 dactyl.execute(cmd || "", null, true);
1538 completer: function (context) completion.javascript(context),
1542 commands.add(["loadplugins", "lpl"],
1543 "Load all or matching plugins",
1545 dactyl.loadPlugins(args.length ? args : null, args.bang);
1552 serialize: function () [
1555 literalArg: options["loadplugins"].join(" ")
1560 commands.add(["norm[al]"],
1561 "Execute Normal mode commands",
1562 function (args) { events.feedkeys(args[0], args.bang, false, modes.NORMAL); },
1569 commands.add(["pr[ivate]", "pr0n", "porn"],
1570 "Enable privacy features of a command, when applicable, and do not save the invocation in command history",
1572 dactyl.withSavedValues(["forcePrivate"], function () {
1573 this.forcePrivate = true;
1574 dactyl.execute(args[0], null, true);
1578 completer: function (context) completion.ex(context),
1580 privateData: "never-save",
1584 commands.add(["exit", "x"],
1585 "Quit " + config.appName,
1587 dactyl.quit(false, args.bang);
1593 commands.add(["q[uit]"],
1594 dactyl.has("tabs") ? "Quit current tab" : "Quit application",
1596 if (dactyl.has("tabs") && tabs.remove(tabs.getTab(), 1, false))
1598 else if (dactyl.windows.length > 1)
1601 dactyl.quit(false, args.bang);
1607 let startupOptions = [
1610 description: "The initialization file to execute at startup",
1611 type: CommandOption.STRING
1614 names: ["++noplugin"],
1615 description: "Do not automatically load plugins"
1619 description: "Ex commands to execute prior to initialization",
1620 type: CommandOption.STRING,
1625 description: "Ex commands to execute after initialization",
1626 type: CommandOption.STRING,
1630 names: ["+purgecaches"],
1631 description: "Purge " + config.appName + " caches at startup",
1632 type: CommandOption.NOARG
1636 commands.add(["reh[ash]"],
1637 "Reload the " + config.appName + " add-on",
1640 storage.storeForSession("rehashCmd", args.trailing); // Hack.
1643 if (args["+purgecaches"])
1646 util.delay(function () { util.rehash(args) });
1649 argCount: "0", // FIXME
1650 options: startupOptions
1653 commands.add(["res[tart]"],
1654 "Force " + config.host + " to restart",
1656 if (args["+purgecaches"])
1659 dactyl.restart(args.string);
1663 options: startupOptions
1666 function findToolbar(name) DOM.XPath(
1667 "//*[@toolbarname=" + util.escapeString(name, "'") + " or " +
1668 "@toolbarname=" + util.escapeString(name.trim(), "'") + "]",
1669 document).snapshotItem(0);
1671 var toolbox = document.getElementById("navigator-toolbox");
1673 let toolbarCommand = function (names, desc, action, filter) {
1674 commands.add(names, desc,
1676 let toolbar = findToolbar(args[0] || "");
1677 dactyl.assert(toolbar, _("error.invalidArgument"));
1679 events.checkFocus();
1682 completer: function (context) {
1683 completion.toolbar(context);
1685 context.filters.push(filter);
1691 toolbarCommand(["toolbars[how]", "tbs[how]"], "Show the named toolbar",
1692 function (toolbar) dactyl.setNodeVisible(toolbar, true),
1693 function ({ item }) Dactyl.toolbarHidden(item));
1694 toolbarCommand(["toolbarh[ide]", "tbh[ide]"], "Hide the named toolbar",
1695 function (toolbar) dactyl.setNodeVisible(toolbar, false),
1696 function ({ item }) !Dactyl.toolbarHidden(item));
1697 toolbarCommand(["toolbart[oggle]", "tbt[oggle]"], "Toggle the named toolbar",
1698 function (toolbar) dactyl.setNodeVisible(toolbar, Dactyl.toolbarHidden(toolbar)));
1701 commands.add(["time"],
1702 "Profile a piece of code or run a command multiple times",
1704 let count = args.count;
1705 let special = args.bang;
1706 args = args[0] || "";
1709 var func = function () commands.execute(args, null, false);
1711 func = dactyl.userFunc(args);
1715 let each, eachUnits, totalUnits;
1718 for (let i in util.interruptibleRange(0, count, 500)) {
1719 let now = Date.now();
1721 total += Date.now() - now;
1727 if (total / count >= 100) {
1728 each = total / 1000.0 / count;
1732 each = total / count;
1737 total = total / 1000.0;
1741 totalUnits = "msec";
1743 commandline.commandOutput(
1745 ["tr", { highlight: "Title", align: "left" },
1746 ["th", { colspan: "3" }, _("title.Code execution summary")]],
1748 ["td", {}, _("title.Executed"), ":"],
1749 ["td", { align: "right" },
1750 ["span", { class: "times-executed" }, count]],
1751 ["td", {}, /*L*/"times"]],
1753 ["td", {}, _("title.Average time"), ":"],
1754 ["td", { align: "right" },
1755 ["span", { class: "time-average" }, each.toFixed(2)]],
1756 ["td", {}, eachUnits]],
1758 ["td", {}, _("title.Total time"), ":"],
1759 ["td", { align: "right" },
1760 ["span", { class: "time-total" }, total.toFixed(2)]],
1761 ["td", {}, totalUnits]]]);
1764 let beforeTime = Date.now();
1770 let afterTime = Date.now();
1772 if (afterTime - beforeTime >= 100)
1773 dactyl.echo(_("time.total", ((afterTime - beforeTime) / 1000.0).toFixed(2) + " sec"));
1775 dactyl.echo(_("time.total", (afterTime - beforeTime) + " msec"));
1784 completer: function (context) {
1785 if (/^:/.test(context.filter))
1786 return completion.ex(context);
1788 return completion.javascript(context);
1796 commands.add(["verb[ose]"],
1797 "Execute a command with 'verbose' set",
1799 let vbs = options.get("verbose");
1800 let value = vbs.value;
1801 let setFrom = vbs.setFrom;
1804 vbs.set(args.count || 1);
1806 dactyl.execute(args[0] || "", null, true);
1810 vbs.setFrom = setFrom;
1814 completer: function (context) completion.ex(context),
1820 commands.add(["ve[rsion]"],
1821 "Show version information",
1824 dactyl.open("about:");
1826 let date = config.buildDate;
1827 date = date ? " (" + date + ")" : "";
1829 commandline.commandOutput([
1830 ["div", {}, [config.appName, " ", config.version, date, " running on: "].join("")],
1831 ["div", {}, [window.navigator.userAgent].join("")]
1841 completion: function initCompletion() {
1842 completion.dialog = function dialog(context) {
1843 context.title = ["Dialog"];
1844 context.filters.push(function ({ item }) !item[2] || item[2]());
1845 context.completions = [[k, v[0], v[2]] for ([k, v] in Iterator(config.dialogs))];
1848 completion.menuItem = function menuItem(context) {
1849 context.title = ["Menu Path", "Label"];
1850 context.anchored = false;
1853 description: function (item) item.getAttribute("label"),
1854 highlight: function (item) item.disabled ? "Disabled" : ""
1856 context.generate = function () dactyl.menuItems;
1859 var toolbox = document.getElementById("navigator-toolbox");
1860 completion.toolbar = function toolbar(context) {
1861 context.title = ["Toolbar"];
1862 context.keys = { text: function (item) item.getAttribute("toolbarname"), description: function () "" };
1863 context.completions = DOM.XPath("//*[@toolbarname]", document);
1866 completion.window = function window(context) {
1867 context.title = ["Window", "Title"];
1868 context.keys = { text: function (win) dactyl.windows.indexOf(win) + 1, description: function (win) win.document.title };
1869 context.completions = dactyl.windows;
1872 load: function initLoad() {
1873 dactyl.triggerObserver("load");
1875 dactyl.log(_("dactyl.modulesLoaded"), 3);
1877 userContext.DOM = Class("DOM", DOM, { init: function DOM_(sel, ctxt) DOM(sel, ctxt || buffer.focusedFrame.document) });
1878 userContext.$ = modules.userContext.DOM;
1880 // Hack: disable disabling of Personas in private windows.
1881 let root = document.documentElement;
1883 if (PrivateBrowsingUtils && PrivateBrowsingUtils.isWindowPrivate(window)
1884 && root._lightweightTheme
1885 && root._lightweightTheme._lastScreenWidth == null) {
1887 dactyl.withSavedValues.call(PrivateBrowsingUtils,
1888 ["isWindowPrivate"], function () {
1889 PrivateBrowsingUtils.isWindowPrivate = function () false;
1891 Cu.import("resource://gre/modules/LightweightThemeConsumer.jsm", {})
1892 .LightweightThemeConsumer.call(root._lightweightTheme, document);
1896 dactyl.timeout(function () {
1898 var args = config.prefs.get("commandline-args")
1899 || storage.session.commandlineArgs
1900 || services.commandLineHandler.optionValue;
1902 config.prefs.reset("commandline-args");
1905 args = dactyl.parseCommandLine(args);
1908 dactyl.commandLineOptions.rcFile = args["+u"];
1909 dactyl.commandLineOptions.noPlugins = "++noplugin" in args;
1910 dactyl.commandLineOptions.postCommands = args["+c"];
1911 dactyl.commandLineOptions.preCommands = args["++cmd"];
1912 util.dump("Processing command-line option: " + args.string);
1916 dactyl.echoerr(_("dactyl.parsingCommandLine", e));
1919 dactyl.log(_("dactyl.commandlineOpts", util.objectToString(dactyl.commandLineOptions)), 3);
1921 if (config.prefs.get("first-run", true))
1922 dactyl.timeout(function () {
1923 config.prefs.set("first-run", false);
1924 this.withSavedValues(["forceTarget"], function () {
1925 this.forceTarget = dactyl.NEW_TAB;
1930 // TODO: we should have some class where all this guioptions stuff fits well
1931 // dactyl.hideGUI();
1933 if (dactyl.userEval("typeof document", null, "test.js") === "undefined")
1934 jsmodules.__proto__ = XPCSafeJSObjectWrapper(window);
1936 if (dactyl.commandLineOptions.preCommands)
1937 dactyl.commandLineOptions.preCommands.forEach(function (cmd) {
1938 dactyl.execute(cmd);
1941 // finally, read the RC file and source plugins
1942 let init = services.environment.get(config.idName + "_INIT");
1943 let rcFile = io.getRCFile("~");
1946 if (dactyl.commandLineOptions.rcFile) {
1947 let filename = dactyl.commandLineOptions.rcFile;
1948 if (!/^(NONE|NORC)$/.test(filename))
1949 io.source(io.File(filename).path, { group: contexts.user });
1953 dactyl.execute(init);
1956 io.source(rcFile.path, { group: contexts.user });
1957 services.environment.set("MY_" + config.idName + "RC", rcFile.path);
1960 dactyl.log(_("dactyl.noRCFile"), 3);
1963 if (options["exrc"] && !dactyl.commandLineOptions.rcFile) {
1964 let localRCFile = io.getRCFile(io.cwd);
1965 if (localRCFile && !localRCFile.equals(rcFile))
1966 io.source(localRCFile.path, { group: contexts.user });
1970 if (dactyl.commandLineOptions.rcFile == "NONE" || dactyl.commandLineOptions.noPlugins)
1971 options["loadplugins"] = [];
1973 if (options["loadplugins"].length)
1974 dactyl.loadPlugins();
1977 dactyl.reportError(e, true);
1980 // after sourcing the initialization files, this function will set
1981 // all gui options to their default values, if they have not been
1982 // set before by any RC file
1983 for (let option in values(options.needInit))
1986 if (dactyl.commandLineOptions.postCommands)
1987 dactyl.commandLineOptions.postCommands.forEach(function (cmd) {
1988 dactyl.execute(cmd);
1991 if (storage.session.rehashCmd)
1992 dactyl.execute(storage.session.rehashCmd);
1993 storage.session.rehashCmd = null;
1995 dactyl.fullyInitialized = true;
1996 dactyl.triggerObserver("enter", null);
1997 autocommands.trigger("Enter", {});
2000 statusline.update();
2001 dactyl.log(_("dactyl.initialized", config.appName), 0);
2002 dactyl.initialized = true;
2006 // vim: set fdm=marker sw=4 ts=4 et: