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-2011 by 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 default xml namespace = XHTML;
12 XML.ignoreWhitespace = false;
13 XML.prettyPrinting = false;
15 var EVAL_ERROR = "__dactyl_eval_error";
16 var EVAL_RESULT = "__dactyl_eval_result";
17 var EVAL_STRING = "__dactyl_eval_string";
19 var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
22 // cheap attempt at compatibility
23 let prop = { get: deprecated("dactyl", function liberator() dactyl) };
24 Object.defineProperty(window, "liberator", prop);
25 Object.defineProperty(modules, "liberator", prop);
28 this.modules = modules;
30 util.addObserver(this);
32 this.commands["dactyl.restart"] = function (event) {
36 styles.registerSheet("resource://dactyl-skin/dactyl.css");
39 this.cleanups.push(overlay.overlayObject(window, {
40 focusAndSelectUrlBar: function focusAndSelectUrlBar() {
41 switch (options.get("strictfocus").getKey(document.documentURIObject || util.newURI(document.documentURI), "moderate")) {
43 if (!Events.isHidden(window.gURLBar, true))
44 return focusAndSelectUrlBar.superapply(this, arguments);
52 cleanup: function () {
53 for (let cleanup in values(this.cleanups))
57 delete window.liberator;
59 // Prevents box ordering bugs after our stylesheet is removed.
60 styles.system.add("cleanup-sheet", config.styleableChrome, <![CDATA[
61 #TabsToolbar tab { display: none; }
63 styles.unregisterSheet("resource://dactyl-skin/dactyl.css");
64 DOM('#TabsToolbar tab', document).style.display;
67 destroy: function () {
68 this.observe.unregister();
69 autocommands.trigger("LeavePre", {});
70 dactyl.triggerObserver("shutdown", null);
71 util.dump("All dactyl modules destroyed\n");
72 autocommands.trigger("Leave", {});
75 // initially hide all GUI elements, they are later restored unless the user
76 // has :set go= or something similar in his config
77 hideGUI: function () {
78 let guioptions = config.guioptions;
79 for (let option in guioptions) {
80 guioptions[option].forEach(function (elem) {
82 document.getElementById(elem).collapsed = true;
91 "dactyl-cleanup": function dactyl_cleanup(subject, reason) {
92 let modules = dactyl.modules;
94 for (let mod in values(modules.moduleList.reverse())) {
97 this.trapErrors("cleanup", mod, reason);
99 this.trapErrors("destroy", mod, reason);
102 modules.moduleManager.initDependencies("cleanup");
104 for (let name in values(Object.getOwnPropertyNames(modules).reverse()))
106 delete modules[name];
109 modules.__proto__ = {};
114 "io.source": function ioSource(context, file, modTime) {
116 help.flush("help/plugins.xml", modTime);
120 profileName: deprecated("config.profileName", { get: function profileName() config.profileName }),
123 * @property {Modes.Mode} The current main mode.
124 * @see modes#mainModes
126 mode: deprecated("modes.main", {
127 get: function mode() modes.main,
128 set: function mode(val) modes.main = val
131 getMenuItems: function getMenuItems(targetPath) {
132 function addChildren(node, parent) {
133 DOM(node).createContents();
135 if (~["menu", "menupopup"].indexOf(node.localName) && node.children.length)
136 DOM(node).popupshowing({ bubbles: false });
138 for (let [, item] in Iterator(node.childNodes)) {
139 if (item.childNodes.length == 0 && item.localName == "menuitem"
141 && !/rdf:http:/.test(item.getAttribute("label"))) { // FIXME
142 item.dactylPath = parent + item.getAttribute("label");
143 if (!targetPath || targetPath.indexOf(item.dactylPath) == 0)
148 if (item.localName == "menu")
149 path += item.getAttribute("label") + ".";
150 if (!targetPath || targetPath.indexOf(path) == 0)
151 addChildren(item, path);
157 addChildren(document.getElementById(config.guioptions["m"][1]), "");
161 get menuItems() this.getMenuItems(),
166 NEW_BACKGROUND_TAB: "background-tab",
167 NEW_WINDOW: "window",
169 forceBackground: null,
172 get forceOpen() ({ background: this.forceBackground,
173 target: this.forceTarget }),
175 for (let [k, v] in Iterator({ background: "forceBackground", target: "forceTarget" }))
180 version: deprecated("config.version", { get: function version() config.version }),
183 * @property {Object} The map of command-line options. These are
184 * specified in the argument to the host application's -{config.name}
185 * option. E.g. $ firefox -pentadactyl '+u=/tmp/rcfile ++noplugin'
187 * +u RCFILE Use RCFILE instead of .pentadactylrc.
188 * ++noplugin Don't load plugins.
189 * These two can be specified multiple times:
190 * ++cmd CMD Execute an Ex command before initialization.
191 * +c CMD Execute an Ex command after initialization.
193 commandLineOptions: {
194 /** @property Whether plugin loading should be prevented. */
196 /** @property An RC file to use rather than the default. */
198 /** @property An Ex command to run before any initialization is performed. */
200 /** @property An Ex command to run after all initialization has been performed. */
204 registerObserver: function registerObserver(type, callback, weak) {
205 if (!(type in this._observers))
206 this._observers[type] = [];
207 this._observers[type].push(weak ? util.weakReference(callback) : { get: function () callback });
210 registerObservers: function registerObservers(obj, prop) {
211 for (let [signal, func] in Iterator(obj[prop || "signals"]))
212 this.registerObserver(signal, obj.closure(func), false);
215 unregisterObserver: function unregisterObserver(type, callback) {
216 if (type in this._observers)
217 this._observers[type] = this._observers[type].filter(function (c) c.get() != callback);
220 applyTriggerObserver: function triggerObserver(type, args) {
221 if (type in this._observers)
222 this._observers[type] = this._observers[type].filter(function (callback) {
223 if (callback.get()) {
226 callback.get().apply(null, args);
228 catch (e if e.message == "can't wrap XML objects") {
230 callback.get().apply(null, [String(args[0])].concat(args.slice(1)));
234 dactyl.reportError(e);
241 triggerObserver: function triggerObserver(type) {
242 return this.applyTriggerObserver(type, Array.slice(arguments, 1));
245 addUsageCommand: function (params) {
246 function keys(item) (item.names || [item.name]).concat(item.description, item.columns || []);
248 let name = commands.add(params.name, params.description,
250 let results = array(params.iterate(args))
251 .sort(function (a, b) String.localeCompare(a.name, b.name));
253 let filters = args.map(function (arg) let (re = util.regexp.escape(arg))
254 util.regexp("\\b" + re + "\\b|(?:^|[()\\s])" + re + "(?:$|[()\\s])", "i"));
256 results = results.filter(function (item) filters.every(function (re) keys(item).some(re.closure.test)));
258 commandline.commandOutput(
259 template.usage(results, params.format));
263 completer: function (context, args) {
264 context.keys.text = util.identity;
265 context.keys.description = function () seen[this.text] + /*L*/" matching items";
266 context.ignoreCase = true;
268 context.completions = array(keys(item).join(" ").toLowerCase().split(/[()\s]+/)
269 for (item in params.iterate(args)))
272 seen[k] = (seen[k] || 0) + 1;
276 options: params.options || []
280 this.indices[params.index] = function () {
281 let results = array((params.iterateIndex || params.iterate).call(params, commands.get(name).newArgs()))
282 .array.sort(function (a, b) String.localeCompare(a.name, b.name));
284 let haveTag = Set.has(help.tags);
285 for (let obj in values(results)) {
286 let res = dactyl.generateHelp(obj, null, null, true);
287 if (!haveTag(obj.helpTag))
288 res[1].@tag = obj.helpTag;
296 * Triggers the application bell to notify the user of an error. The
297 * bell may be either audible or visual depending on the value of the
298 * 'visualbell' option.
301 this.triggerObserver("beep");
302 if (options["visualbell"]) {
304 bell: document.getElementById("dactyl-bell"),
305 strut: document.getElementById("dactyl-bell-strut")
307 XML.ignoreWhitespace = true;
309 overlay.overlayWindow(window, {
312 <window id={document.documentElement.id} xmlns={XUL}>
313 <hbox style="display: none" highlight="Bell" id="dactyl-bell" key="bell"/>
317 <window id={document.documentElement.id} xmlns={XUL}>
318 <hbox style="display: none" highlight="Bell" id="dactyl-bell-strut" key="strut"/>
323 elems.bell.style.height = window.innerHeight + "px";
324 elems.strut.style.marginBottom = -window.innerHeight + "px";
325 elems.strut.style.display = elems.bell.style.display = "";
327 util.timeout(function () { elems.strut.style.display = elems.bell.style.display = "none"; }, 20);
330 let soundService = Cc["@mozilla.org/sound;1"].getService(Ci.nsISound);
336 * Reads a string from the system clipboard.
338 * This is same as Firefox's readFromClipboard function, but is needed for
339 * apps like Thunderbird which do not provide it.
341 * @param {string} which Which clipboard to write to. Either
342 * "global" or "selection". If not provided, both clipboards are
347 clipboardRead: function clipboardRead(which) {
349 const { clipboard } = services;
351 let transferable = services.Transferable();
352 transferable.addDataFlavor("text/unicode");
354 let source = clipboard[which == "global" || !clipboard.supportsSelectionClipboard() ?
355 "kGlobalClipboard" : "kSelectionClipboard"];
356 clipboard.getData(transferable, source);
358 let str = {}, len = {};
359 transferable.getTransferData("text/unicode", str, len);
362 return str.value.QueryInterface(Ci.nsISupportsString)
363 .data.substr(0, len.value / 2);
370 * Copies a string to the system clipboard. If *verbose* is specified the
371 * copied string is also echoed to the command line.
373 * @param {string} str The string to write.
374 * @param {boolean} verbose If true, the user is notified of the copied data.
375 * @param {string} which Which clipboard to write to. Either
376 * "global" or "selection". If not provided, both clipboards are
380 clipboardWrite: function clipboardWrite(str, verbose, which) {
381 if (which == null || which == "selection" && !services.clipboard.supportsSelectionClipboard())
382 services.clipboardHelper.copyString(str);
384 services.clipboardHelper.copyStringToClipboard(str,
385 services.clipboard["k" + util.capitalize(which) + "Clipboard"]);
388 let message = { message: _("dactyl.yank", str) };
390 message.domains = [util.newURI(str).host];
393 dactyl.echomsg(message);
397 dump: deprecated("util.dump",
398 { get: function dump() util.closure.dump }),
399 dumpStack: deprecated("util.dumpStack",
400 { get: function dumpStack() util.closure.dumpStack }),
403 * Outputs a plain message to the command line.
405 * @param {string} str The message to output.
406 * @param {number} flags These control the multi-line message behavior.
407 * See {@link CommandLine#echo}.
409 echo: function echo(str, flags) {
410 commandline.echo(str, commandline.HL_NORMAL, flags);
414 * Outputs an error message to the command line.
416 * @param {string} str The message to output.
417 * @param {number} flags These control the multi-line message behavior.
418 * See {@link CommandLine#echo}.
420 echoerr: function echoerr(str, flags) {
421 flags |= commandline.APPEND_TO_MESSAGES;
423 if (isinstance(str, ["DOMException", "Error", "Exception", ErrorBase])
424 || isinstance(str, ["XPCWrappedNative_NoHelper"]) && /^\[Exception/.test(str))
425 dactyl.reportError(str);
427 if (isObject(str) && "echoerr" in str)
429 else if (isinstance(str, ["Error", FailedAssertion]) && str.fileName)
430 str = <>{str.fileName.replace(/^.* -> /, "")}: {str.lineNumber}: {str}</>;
432 if (options["errorbells"])
435 commandline.echo(str, commandline.HL_ERRORMSG, flags);
439 * Outputs a warning message to the command line.
441 * @param {string} str The message to output.
442 * @param {number} flags These control the multi-line message behavior.
443 * See {@link CommandLine#echo}.
445 warn: function warn(str, flags) {
446 commandline.echo(str, "WarningMsg", flags | commandline.APPEND_TO_MESSAGES);
449 // TODO: add proper level constants
451 * Outputs an information message to the command line.
453 * @param {string} str The message to output.
454 * @param {number} verbosity The messages log level (0 - 15). Only
455 * messages with verbosity less than or equal to the value of the
456 * *verbosity* option will be output.
457 * @param {number} flags These control the multi-line message behavior.
458 * See {@link CommandLine#echo}.
460 echomsg: function echomsg(str, verbosity, flags) {
461 if (verbosity == null)
462 verbosity = 0; // verbosity level is exclusionary
464 if (options["verbose"] >= verbosity)
465 commandline.echo(str, commandline.HL_INFOMSG,
466 flags | commandline.APPEND_TO_MESSAGES);
470 * Loads and executes the script referenced by *uri* in the scope of the
473 * @param {string} uri The URI of the script to load. Should be a local
474 * chrome:, file:, or resource: URL.
475 * @param {Object} context The context object into which the script
478 loadScript: function (uri, context) {
479 JSMLoader.loadSubScript(uri, context, File.defaultEncoding);
482 userEval: function (str, context, fileName, lineNumber) {
484 if (jsmodules.__proto__ != window && jsmodules.__proto__ != XPCNativeWrapper(window) &&
485 jsmodules.isPrototypeOf(context))
486 str = "with (window) { with (modules) { (this.eval || eval)(" + str.quote() + ") } }";
488 let info = contexts.context;
489 if (fileName == null)
491 ({ file: fileName, line: lineNumber, context: ctxt }) = info;
493 if (fileName && fileName[0] == "[")
494 fileName = "dactyl://command-line/";
496 context = ctxt || _userContext;
498 if (isinstance(context, ["Sandbox"]))
499 return Cu.evalInSandbox(str, context, "1.8", fileName, lineNumber);
502 context = userContext || ctxt;
504 if (services.has("dactyl") && services.dactyl.evalInContext)
505 return services.dactyl.evalInContext(str, context, fileName, lineNumber);
508 context[EVAL_ERROR] = null;
509 context[EVAL_STRING] = str;
510 context[EVAL_RESULT] = null;
512 this.loadScript("resource://dactyl-content/eval.js", context);
513 if (context[EVAL_ERROR]) {
515 context[EVAL_ERROR].fileName = info.file;
516 context[EVAL_ERROR].lineNumber += info.line;
519 throw context[EVAL_ERROR];
521 return context[EVAL_RESULT];
524 delete context[EVAL_ERROR];
525 delete context[EVAL_RESULT];
526 delete context[EVAL_STRING];
531 * Acts like the Function builtin, but the code executes in the
532 * userContext global.
534 userFunc: function () {
535 return this.userEval(
536 "(function userFunction(" + Array.slice(arguments, 0, -1).join(", ") + ")" +
537 " { " + arguments[arguments.length - 1] + " })");
541 * Execute an Ex command string. E.g. ":zoom 300".
543 * @param {string} str The command to execute.
544 * @param {Object} modifiers Any modifiers to be passed to
545 * {@link Command#action}.
546 * @param {boolean} silent Whether the command should be echoed on the
549 execute: function (str, modifiers, silent) {
550 // skip comments and blank lines
551 if (/^\s*("|$)/.test(str))
554 modifiers = modifiers || {};
557 commands.lastCommand = str.replace(/^\s*:\s*/, "");
559 for (let [command, args] in commands.parseCommands(str.replace(/^'(.*)'$/, "$1"))) {
560 if (command === null)
561 throw FailedAssertion(_("dactyl.notCommand", config.appName, args.commandString));
563 res = res && command.execute(args, modifiers);
568 focus: function focus(elem, flags) {
569 DOM(elem).focus(flags);
573 * Focuses the content window.
575 * @param {boolean} clearFocusedElement Remove focus from any focused
578 focusContent: function focusContent(clearFocusedElement) {
579 if (window != services.focus.activeWindow)
582 let win = document.commandDispatcher.focusedWindow;
583 let elem = config.mainWidget || content;
585 // TODO: make more generic
587 if (this.has("mail") && !config.isComposeWindow) {
588 let i = gDBView.selection.currentIndex;
589 if (i == -1 && gDBView.rowCount >= 0)
591 gDBView.selection.select(i);
594 let frame = buffer.focusedFrame;
595 if (frame && frame.top == content && !Editor.getEditor(frame))
601 if (clearFocusedElement) {
602 if (dactyl.focusedElement)
603 dactyl.focusedElement.blur();
604 if (win && Editor.getEditor(win)) {
605 this.withSavedValues(["ignoreFocus"], function _focusContent() {
606 this.ignoreFocus = true;
607 if (win.frameElement)
608 win.frameElement.blur();
610 if (content.document.activeElement instanceof HTMLIFrameElement)
611 content.document.activeElement.blur();
616 if (elem instanceof Window && Editor.getEditor(elem))
619 if (elem && elem != dactyl.focusedElement)
623 /** @property {Element} The currently focused element. */
624 get focusedElement() services.focus.getFocusedElementForWindow(window, true, {}),
625 set focusedElement(elem) dactyl.focus(elem),
628 * Returns whether this Dactyl extension supports *feature*.
630 * @param {string} feature The feature name.
633 has: function (feature) Set.has(config.features, feature),
638 initDocument: function initDocument(doc) {
640 if (doc.location.protocol === "dactyl:") {
650 help: deprecated("help.help", { get: function help() modules.help.closure.help }),
651 findHelp: deprecated("help.findHelp", { get: function findHelp() help.closure.findHelp }),
655 * Initialize the help system.
657 initHelp: function initHelp() {
658 if ("noscriptOverlay" in window)
659 noscriptOverlay.safeAllow("dactyl:", true, false);
664 stringifyXML: function (xml) {
665 XML.prettyPrinting = false;
666 XML.ignoreWhitespace = false;
667 return UTF8(xml.toXMLString());
671 * Generates a help entry and returns it as a string.
673 * @param {Command|Map|Option} obj A dactyl *Command*, *Map* or *Option*
675 * @param {XMLList} extraHelp Extra help text beyond the description.
678 generateHelp: function generateHelp(obj, extraHelp, str, specOnly) {
679 default xml namespace = "";
682 link = tag = spec = util.identity;
685 if (obj instanceof Command) {
686 link = function (cmd) <ex>{cmd}</ex>;
687 args = obj.parseArgs("", CompletionContext(str || ""));
688 tag = function (cmd) <>:{cmd}</>;
689 spec = function (cmd) <>{
690 obj.count ? <oa>count</oa> : <></>
694 obj.bang ? <oa>!</oa> : <></>
697 else if (obj instanceof Map) {
698 spec = function (map) obj.count ? <><oa>count</oa>{map}</> : <>{map}</>;
699 tag = function (map) <>{
700 let (c = obj.modes[0].char) c ? c + "_" : ""
702 link = function (map) {
703 let [, mode, name, extra] = /^(?:(.)_)?(?:<([^>]+)>)?(.*)$/.exec(map);
704 let k = <k>{extra}</k>;
712 else if (obj instanceof Option) {
713 spec = function () template.map(obj.names, tag, " ");
714 tag = function (name) <>'{name}'</>;
715 link = function (opt, name) <o>{name}</o>;
716 args = { value: "", values: [] };
719 XML.prettyPrinting = false;
720 XML.ignoreWhitespace = false;
721 default xml namespace = NS;
723 // E4X has its warts.
728 <dt>{link(obj.helpTag || tag(obj.name), obj.name)}</dt> <dd>{
729 template.linkifyHelp(obj.description ? obj.description.replace(/\.$/, "") : "", true)
732 return res.elements();
736 <tags>{template.map(obj.names.slice().reverse(), tag, " ")}</tags>
737 <spec>{let (name = (obj.specs || obj.names)[0])
738 spec(template.highlightRegexp(tag(name),
740 function (m, n0) <oa>{n0}</oa>),
744 <type>{obj.type}</type>
745 <default>{obj.stringDefaultValue}</default></>}
747 obj.description ? br + <p>{template.linkifyHelp(obj.description.replace(/\.?$/, "."), true)}</p> : "" }{
748 extraHelp ? br + extraHelp : "" }{
749 !(extraHelp || obj.description) ? br + <p><!--L-->Sorry, no help available.</p> : "" }
754 res.item.description.* += br +
755 let (br = br + <> </>)
756 <><dl>{ br + template.map(ary, function ([a, b]) <><dt>{a}</dt> <dd>{b}</dd></>, br) }
762 add(completion._runCompleter(obj.closure.completer, "", null, args).items
763 .map(function (i) [i.text, i.description]));
765 if (obj.options && obj.options.some(function (o) o.description))
766 add(obj.options.filter(function (o) o.description)
770 o.names.length == 1 ? "" :
772 template.map(o.names.slice(1), function (n) <em>{n}</em>, <>, </>)
776 return res.*.toXMLString()
777 .replace(' xmlns="' + NS + '"', "", "g")
778 .replace(/^ {12}|[ \t]+$/gm, "")
779 .replace(/^\s*\n|\n\s*$/g, "") + "\n";
783 * The map of global variables.
785 * These are set and accessed with the "g:" prefix.
787 _globalVariables: {},
788 globalVariables: deprecated(_("deprecated.for.theOptionsSystem"), {
789 get: function globalVariables() this._globalVariables
792 loadPlugins: function (args, force) {
793 function sourceDirectory(dir) {
794 dactyl.assert(dir.isReadable(), _("io.notReadable", dir.path));
796 dactyl.log(_("dactyl.sourcingPlugins", dir.path), 3);
798 let loadplugins = options.get("loadplugins");
800 loadplugins = { __proto__: loadplugins, value: args.map(Option.parseRegexp) };
802 dir.readDirectory(true).forEach(function (file) {
803 if (file.leafName[0] == ".")
805 else if (file.isFile() && loadplugins.getKey(file.path)
806 && !(!force && file.path in dactyl.pluginFiles && dactyl.pluginFiles[file.path] >= file.lastModifiedTime)) {
808 io.source(file.path);
809 dactyl.pluginFiles[file.path] = file.lastModifiedTime;
812 dactyl.reportError(e);
815 else if (file.isDirectory())
816 sourceDirectory(file);
820 let dirs = io.getRuntimeDirectories("plugins");
822 if (dirs.length == 0) {
823 dactyl.log(_("dactyl.noPluginDir"), 3);
828 _("plugin.searchingForIn",
829 ("plugins/**/*.{js," + config.fileExtension + "}").quote(),
830 [dir.path.replace(/.plugins$/, "") for ([, dir] in Iterator(dirs))]
834 dirs.forEach(function (dir) {
835 dactyl.echomsg(_("plugin.searchingFor", (dir.path + "/**/*.{js," + config.fileExtension + "}").quote()), 3);
836 sourceDirectory(dir);
840 // TODO: add proper level constants
842 * Logs a message to the JavaScript error console. Each message has an
843 * associated log level. Only messages with a log level less than or equal
844 * to *level* will be printed. If *msg* is an object, it is pretty printed.
846 * @param {string|Object} msg The message to print.
847 * @param {number} level The logging level 0 - 15.
849 log: function (msg, level) {
850 let verbose = config.prefs.get("loglevel", 0);
852 if (!level || level <= verbose) {
853 if (isObject(msg) && !isinstance(msg, _))
854 msg = util.objectToString(msg, false);
856 services.console.logStringMessage(config.name + ": " + msg);
861 click: function onClick(event) {
862 let elem = event.originalTarget;
864 if (elem instanceof Element && services.security.isSystemPrincipal(elem.nodePrincipal)) {
865 let command = elem.getAttributeNS(NS, "command");
866 if (command && event.button == 0) {
867 event.preventDefault();
869 if (dactyl.commands[command])
870 dactyl.withSavedValues(["forceTarget"], function () {
871 if (event.ctrlKey || event.shiftKey || event.button == 1)
872 dactyl.forceTarget = dactyl.NEW_TAB;
873 dactyl.commands[command](event);
879 "dactyl.execute": function onExecute(event) {
880 let cmd = event.originalTarget.getAttribute("dactyl-execute");
881 commands.execute(cmd, null, false, null,
882 { file: /*L*/"[Command Line]", line: 1 });
887 * Opens one or more URLs. Returns true when load was initiated, or
890 * @param {string|object|Array} urls A representation of the URLs to open. May be
891 * either a string, which will be passed to
892 * {@link Dactyl#parseURLs}, an array in the same format as
893 * would be returned by the same, or an object as returned by
894 * {@link DOM#formData}.
895 * @param {object} params A set of parameters specifying how to open the
896 * URLs. The following properties are recognized:
898 * • background If true, new tabs are opened in the background.
900 * • from The designation of the opener, as appears in
901 * 'activate' and 'newtab' options. If present,
902 * the newtab option provides the default 'where'
903 * parameter, and the value of the 'activate'
904 * parameter is inverted if 'background' is true.
906 * • where One of CURRENT_TAB, NEW_TAB, or NEW_WINDOW
908 * As a deprecated special case, the where parameter may be provided
909 * by itself, in which case it is transformed into { where: params }.
911 * @param {boolean} force Don't prompt whether to open more than 20
915 open: function (urls, params, force) {
916 if (typeof urls == "string")
917 urls = dactyl.parseURLs(urls);
919 if (urls.length > prefs.get("browser.tabs.maxOpenBeforeWarn", 20) && !force)
920 return commandline.input(_("dactyl.prompt.openMany", urls.length) + " ",
922 if (resp && resp.match(/^y(es)?$/i))
923 dactyl.open(urls, params, true);
926 params = params || {};
927 if (isString(params))
928 params = { where: params };
931 for (let [opt, flag] in Iterator({ replace: "REPLACE_HISTORY", hide: "BYPASS_HISTORY" }))
932 flags |= params[opt] && Ci.nsIWebNavigation["LOAD_FLAGS_" + flag];
934 let where = params.where || dactyl.CURRENT_TAB;
935 let background = dactyl.forceBackground != null ? dactyl.forceBackground :
936 ("background" in params) ? params.background
937 : params.where == dactyl.NEW_BACKGROUND_TAB;
939 if (params.from && dactyl.has("tabs")) {
940 if (!params.where && options.get("newtab").has(params.from))
941 where = dactyl.NEW_TAB;
942 background ^= !options.get("activate").has(params.from);
945 if (urls.length == 0)
948 let browser = config.tabbrowser;
949 function open(loc, where) {
952 loc = { url: loc[0], postData: loc[1] };
953 else if (isString(loc))
956 // decide where to load the first url
960 if (!dactyl.has("tabs"))
961 return open(loc, dactyl.NEW_WINDOW);
963 return prefs.withContext(function () {
964 prefs.set("browser.tabs.loadInBackground", true);
965 return browser.loadOneTab(loc.url, null, null, loc.postData, background).linkedBrowser.contentDocument;
968 case dactyl.NEW_WINDOW:
969 let win = window.openDialog(document.documentURI, "_blank", "chrome,all,dialog=no");
970 util.waitFor(function () win.document.readyState === "complete");
971 browser = win.dactyl && win.dactyl.modules.config.tabbrowser || win.getBrowser();
973 case dactyl.CURRENT_TAB:
974 browser.loadURIWithFlags(loc.url, flags, null, null, loc.postData);
975 return browser.contentWindow;
979 // Unfortunately, failed page loads throw exceptions and
980 // cause a lot of unwanted noise. This solution means that
981 // any genuine errors go unreported.
984 if (dactyl.forceTarget)
985 where = dactyl.forceTarget;
987 where = dactyl.CURRENT_TAB;
989 return urls.map(function (url) {
990 let res = open(url, where);
991 where = dactyl.NEW_TAB;
998 * Returns an array of URLs parsed from *str*.
1000 * Given a string like 'google bla, www.osnews.com' return an array
1001 * ['www.google.com/search?q=bla', 'www.osnews.com']
1003 * @param {string} str
1004 * @returns {[string]}
1006 parseURLs: function parseURLs(str) {
1009 if (options["urlseparator"])
1010 urls = util.splitLiteral(str, util.regexp("\\s*" + options["urlseparator"] + "\\s*"));
1014 return urls.map(function (url) {
1017 if (/^(\.{0,2}|~)(\/|$)/.test(url) || config.OS.isWindows && /^[a-z]:/i.test(url)) {
1019 // Try to find a matching file.
1020 let file = io.File(url);
1021 if (file.exists() && file.isReadable())
1022 return services.io.newFileURI(file).spec;
1027 // If it starts with a valid protocol, pass it through.
1028 let proto = /^([-\w]+):/.exec(url);
1029 if (proto && services.PROTOCOL + proto[1] in Cc)
1032 // Check for a matching search keyword.
1033 let searchURL = this.has("bookmarks") && bookmarks.getSearchURL(url, false);
1037 // If it looks like URL-ish (foo.com/bar), let Gecko figure it out.
1038 if (this.urlish.test(url) || !this.has("bookmarks"))
1039 return util.createURI(url).spec;
1041 // Pass it off to the default search engine or, failing
1042 // that, let Gecko deal with it as is.
1043 return bookmarks.getSearchURL(url, true) || util.createURI(url).spec;
1046 stringToURLArray: deprecated("dactyl.parseURLs", "parseURLs"),
1047 urlish: Class.Memoize(function () util.regexp(<![CDATA[
1049 <domain>+ (:\d+)? (/ .*) |
1051 <domain>+ \. [a-z0-9]+ |
1055 domain: util.regexp(String.replace(<![CDATA[
1057 U0000-U002c // U002d-U002e --.
1060 U003a-U0040 // U0041-U005a a-z
1061 U005b-U0060 // U0061-U007a A-Z
1064 ]]>, /U/g, "\\u"), "x")
1069 get plugins() plugins,
1071 setNodeVisible: function setNodeVisible(node, visible) {
1072 if (window.setToolbarVisibility && node.localName == "toolbar")
1073 window.setToolbarVisibility(node, visible);
1075 node.collapsed = !visible;
1078 confirmQuit: function confirmQuit()
1079 prefs.withContext(function () {
1080 prefs.set("browser.warnOnQuit", false);
1081 return window.canQuitApplication();
1085 * Quit the host application, no matter how many tabs/windows are open.
1087 * @param {boolean} saveSession If true the current session will be
1088 * saved and restored when the host application is restarted.
1089 * @param {boolean} force Forcibly quit irrespective of whether all
1090 * windows could be closed individually.
1092 quit: function (saveSession, force) {
1093 if (!force && !this.confirmQuit())
1096 let pref = "browser.startup.page";
1099 prefs.safeSet(pref, 3);
1100 if (!saveSession && prefs.get(pref) >= 2)
1101 prefs.safeSet(pref, 1);
1103 services.appStartup.quit(Ci.nsIAppStartup[force ? "eForceQuit" : "eAttemptQuit"]);
1107 * Restart the host application.
1109 restart: function (args) {
1110 if (!this.confirmQuit())
1113 config.prefs.set("commandline-args", args);
1115 services.appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
1118 get assert() util.assert,
1121 * Traps errors in the called function, possibly reporting them.
1123 * @param {function} func The function to call
1124 * @param {object} self The 'this' object for the function.
1126 trapErrors: function trapErrors(func, self) {
1130 return func.apply(self || this, Array.slice(arguments, 2));
1134 dactyl.reportError(e, true);
1137 util.reportError(e);
1144 * Reports an error to both the console and the host application's
1147 * @param {Object} error The error object.
1149 reportError: function reportError(error, echo) {
1150 if (error instanceof FailedAssertion && error.noTrace || error.message === "Interrupted") {
1151 let context = contexts.context;
1152 let prefix = context ? context.file + ":" + context.line + ": " : "";
1153 if (error.message && error.message.indexOf(prefix) !== 0 &&
1154 prefix != "[Command Line]:1: ")
1155 error.message = prefix + error.message;
1158 dactyl.echoerr(template.linkifyHelp(error.message));
1163 util.reportError(error);
1167 if (error.result == Cr.NS_BINDING_ABORTED)
1171 dactyl.echoerr(error, commandline.FORCE_SINGLELINE);
1173 util.reportError(error);
1177 * Parses a Dactyl command-line string i.e. the value of the
1178 * -dactyl command-line option.
1180 * @param {string} cmdline The string to parse for command-line
1183 * @see Commands#parseArgs
1185 parseCommandLine: function (cmdline) {
1187 return commands.get("rehash").parseArgs(cmdline);
1190 dactyl.reportError(e, true);
1194 wrapCallback: function (callback, self) {
1195 self = self || this;
1196 let save = ["forceOpen"];
1197 let saved = save.map(function (p) dactyl[p]);
1198 return function wrappedCallback() {
1199 let args = arguments;
1200 return dactyl.withSavedValues(save, function () {
1201 saved.forEach(function (p, i) dactyl[save[i]] = p);
1203 return callback.apply(self, args);
1206 dactyl.reportError(e, true);
1213 * @property {[Window]} Returns an array of all the host application's
1216 get windows() [win for (win in iter(services.windowMediator.getEnumerator("navigator:browser"))) if (win.dactyl)],
1219 toolbarHidden: function hidden(elem) (elem.getAttribute("autohide") || elem.getAttribute("collapsed")) == "true"
1221 cache: function () {
1222 cache.register("help/plugins.xml", function () {
1223 // Process plugin help entries.
1224 XML.ignoreWhiteSpace = XML.prettyPrinting = false;
1227 for (let [, context] in Iterator(plugins.contexts))
1229 let info = contexts.getDocs(context);
1230 if (info instanceof XML) {
1231 if (info.*.@lang.length()) {
1232 let lang = config.bestLocale(String(a) for each (a in info.*.@lang));
1234 info.* = info.*.(function::attribute("lang").length() == 0 || @lang == lang);
1236 for each (let elem in info.NS::info)
1237 for (let attr in values(["@name", "@summary", "@href"]))
1238 if (elem[attr].length())
1239 info[attr] = elem[attr];
1241 body += <h2 xmlns={NS.uri} tag={info.@name + '-plugin'}>{info.@summary}</h2> +
1246 util.reportError(e);
1249 return '<?xml version="1.0"?>\n' +
1250 '<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>\n' +
1251 '<!DOCTYPE document SYSTEM "resource://dactyl-content/dactyl.dtd">\n' +
1252 <document xmlns={NS}
1253 name="plugins" title={config.appName + " Plugins"}>
1254 <h1 tag="using-plugins">{_("help.title.Using Plugins")}</h1>
1258 </document>.toXMLString();
1261 cache.register("help/index.xml", function () {
1262 default xml namespace = NS;
1264 return '<?xml version="1.0"?>\n' +
1265 <overlay xmlns={NS}>{
1266 template.map(dactyl.indices, function ([name, iter])
1267 <dl insertafter={name + "-index"}>{
1268 template.map(iter(), util.identity)
1269 }</dl>, <>{"\n\n"}</>)
1273 cache.register("help/gui.xml", function () {
1274 default xml namespace = NS;
1276 return '<?xml version="1.0"?>\n' +
1277 <overlay xmlns={NS}>
1278 <dl insertafter="dialog-list">{
1279 template.map(config.dialogs, function ([name, val])
1280 (!val[2] || val[2]())
1281 ? <><dt>{name}</dt><dd>{val[0]}</dd></>
1288 cache.register("help/privacy.xml", function () {
1289 default xml namespace = NS;
1291 return '<?xml version="1.0"?>\n' +
1292 <overlay xmlns={NS}>
1293 <dl insertafter="sanitize-items">{
1294 template.map(options.get("sanitizeitems").values
1295 .sort(function (a, b) String.localeCompare(a.name, b.name)),
1296 function ({ name, description })
1297 <><dt>{name}</dt><dd>{template.linkifyHelp(description, true)}</dd></>,
1303 events: function () {
1304 events.listen(window, dactyl, "events", true);
1306 // Only general options are added here, which are valid for all Dactyl extensions
1307 options: function () {
1308 options.add(["errorbells", "eb"],
1309 "Ring the bell when an error message is displayed",
1312 options.add(["exrc", "ex"],
1313 "Enable automatic sourcing of an RC file in the current directory at startup",
1316 options.add(["fullscreen", "fs"],
1317 "Show the current window fullscreen",
1319 setter: function (value) window.fullScreen = value,
1320 getter: function () window.fullScreen
1326 c: ["Always show the command line, even when empty"],
1327 C: ["Always show the command line outside of the status line"],
1328 M: ["Always show messages outside of the status line"]
1330 setter: function (opts) {
1331 if (loaded.commandline || ~opts.indexOf("c"))
1332 commandline.widgets.updateVisibility();
1337 s: ["Status bar", [statusline.statusBar.id]]
1338 }, config.guioptions),
1339 setter: function (opts) {
1340 for (let [opt, [, ids]] in Iterator(this.opts)) {
1341 ids.map(function (id) document.getElementById(id))
1342 .forEach(function (elem) {
1344 dactyl.setNodeVisible(elem, opts.indexOf(opt) >= 0);
1351 r: ["Right Scrollbar", "vertical"],
1352 l: ["Left Scrollbar", "vertical"],
1353 b: ["Bottom Scrollbar", "horizontal"]
1355 setter: function (opts) {
1356 let dir = ["horizontal", "vertical"].filter(
1357 function (dir) !Array.some(opts,
1358 function (o) this.opts[o] && this.opts[o][1] == dir, this),
1360 let class_ = dir.map(function (dir) "html|html > xul|scrollbar[orient=" + dir + "]");
1362 styles.system.add("scrollbar", "*",
1363 class_.length ? class_.join(", ") + " { visibility: collapse !important; }" : "",
1366 prefs.safeSet("layout.scrollbar.side", opts.indexOf("l") >= 0 ? 3 : 2,
1367 _("option.guioptions.safeSet"));
1369 validator: function (opts) Option.validIf(!(opts.indexOf("l") >= 0 && opts.indexOf("r") >= 0),
1370 UTF8("Only one of ‘l’ or ‘r’ allowed"))
1375 n: ["Tab number", highlight.selector("TabNumber")],
1376 N: ["Tab number over icon", highlight.selector("TabIconNumber")]
1378 setter: function (opts) {
1379 let classes = [v[1] for ([k, v] in Iterator(this.opts)) if (opts.indexOf(k) < 0)];
1381 styles.system.add("taboptions", "chrome://*",
1382 classes.length ? classes.join(",") + "{ display: none; }" : "");
1384 if (!dactyl.has("Gecko2")) {
1385 tabs.tabBinding.enabled = Array.some(opts, function (k) k in this.opts, this);
1386 tabs.updateTabCount();
1388 if (config.tabbrowser.tabContainer._positionPinnedTabs)
1389 config.tabbrowser.tabContainer._positionPinnedTabs();
1392 validator: function (opts) dactyl.has("Gecko2") ||
1393 Option.validIf(!/[nN]/.test(opts), "Tab numbering not available in this " + config.host + " version")
1396 ].filter(function (group) !group.feature || dactyl.has(group.feature));
1398 options.add(["guioptions", "go"],
1399 "Show or hide certain GUI elements like the menu or toolbar",
1403 cleanupValue: config.cleanups.guioptions ||
1404 "rb" + [k for ([k, v] in iter(groups[1].opts))
1405 if (!Dactyl.toolbarHidden(document.getElementById(v[1][0])))].join(""),
1407 values: array(groups).map(function (g) [[k, v[0]] for ([k, v] in Iterator(g.opts))]).flatten(),
1409 setter: function (value) {
1410 for (let group in values(groups))
1411 group.setter(value);
1412 events.checkFocus();
1415 validator: function (val) Option.validateCompleter.call(this, val) &&
1416 groups.every(function (g) !g.validator || g.validator(val))
1419 options.add(["helpfile", "hf"],
1420 "Name of the main help file",
1423 options.add(["loadplugins", "lpl"],
1424 "A regexp list that defines which plugins are loaded at startup and via :loadplugins",
1425 "regexplist", "'\\.(js|" + config.fileExtension + ")$'");
1427 options.add(["titlestring"],
1428 "The string shown at the end of the window title",
1429 "string", config.host,
1431 setter: function (value) {
1432 let win = document.documentElement;
1433 function updateTitle(old, current) {
1434 if (config.browser.updateTitlebar)
1435 config.browser.updateTitlebar();
1437 document.title = document.title.replace(RegExp("(.*)" + util.regexp.escape(old)), "$1" + current);
1440 if (services.has("privateBrowsing")) {
1441 let oldValue = win.getAttribute("titlemodifier_normal");
1442 let suffix = win.getAttribute("titlemodifier_privatebrowsing").substr(oldValue.length);
1444 win.setAttribute("titlemodifier_normal", value);
1445 win.setAttribute("titlemodifier_privatebrowsing", value + suffix);
1447 if (services.privateBrowsing.privateBrowsingEnabled) {
1448 updateTitle(oldValue + suffix, value + suffix);
1453 updateTitle(win.getAttribute("titlemodifier"), value);
1454 win.setAttribute("titlemodifier", value);
1460 options.add(["urlseparator", "urlsep", "us"],
1461 "The regular expression used to separate multiple URLs in :open and friends",
1463 { validator: function (value) RegExp(value) });
1465 options.add(["verbose", "vbs"],
1466 "Define which info messages are displayed",
1468 { validator: function (value) Option.validIf(value >= 0 && value <= 15, "Value must be between 0 and 15") });
1470 options.add(["visualbell", "vb"],
1471 "Use visual bell instead of beeping on errors",
1474 setter: function (value) {
1475 prefs.safeSet("accessibility.typeaheadfind.enablesound", !value,
1476 _("option.safeSet", "visualbell"));
1482 mappings: function () {
1483 if (dactyl.has("session"))
1484 mappings.add([modes.NORMAL], ["ZQ"],
1485 "Quit and don't save the session",
1486 function () { dactyl.quit(false); });
1488 mappings.add([modes.NORMAL], ["ZZ"],
1489 "Quit and save the session",
1490 function () { dactyl.quit(true); });
1493 commands: function () {
1494 commands.add(["dia[log]"],
1495 "Open a " + config.appName + " dialog",
1497 let dialog = args[0];
1499 dactyl.assert(dialog in config.dialogs,
1500 _("error.invalidArgument", dialog));
1501 dactyl.assert(!config.dialogs[dialog][2] || config.dialogs[dialog][2](),
1502 _("dialog.notAvailable", dialog));
1504 config.dialogs[dialog][1]();
1507 dactyl.echoerr(_("error.cantOpen", dialog.quote(), e.message || e));
1511 completer: function (context) {
1512 context.ignoreCase = true;
1513 completion.dialog(context);
1517 commands.add(["em[enu]"],
1518 "Execute the specified menu item from the command line",
1520 let arg = args[0] || "";
1521 let items = dactyl.getMenuItems(arg);
1523 dactyl.assert(items.some(function (i) i.dactylPath == arg),
1524 _("emenu.notFound", arg));
1526 for (let [, item] in Iterator(items)) {
1527 if (item.dactylPath == arg) {
1528 dactyl.assert(!item.disabled, _("error.disabled", item.dactylPath));
1534 completer: function (context) completion.menuItem(context),
1538 commands.add(["exe[cute]"],
1539 "Execute the argument as an Ex command",
1542 let cmd = dactyl.userEval(args[0] || "");
1543 dactyl.execute(cmd || "", null, true);
1549 completer: function (context) completion.javascript(context),
1553 commands.add(["loadplugins", "lpl"],
1554 "Load all or matching plugins",
1556 dactyl.loadPlugins(args.length ? args : null, args.bang);
1563 serialize: function () [
1566 literalArg: options["loadplugins"].join(" ")
1571 commands.add(["norm[al]"],
1572 "Execute Normal mode commands",
1573 function (args) { events.feedkeys(args[0], args.bang, false, modes.NORMAL); },
1580 commands.add(["exit", "x"],
1581 "Quit " + config.appName,
1583 dactyl.quit(false, args.bang);
1589 commands.add(["q[uit]"],
1590 dactyl.has("tabs") ? "Quit current tab" : "Quit application",
1592 if (dactyl.has("tabs") && tabs.remove(tabs.getTab(), 1, false))
1594 else if (dactyl.windows.length > 1)
1597 dactyl.quit(false, args.bang);
1603 let startupOptions = [
1606 description: "The initialization file to execute at startup",
1607 type: CommandOption.STRING
1610 names: ["++noplugin"],
1611 description: "Do not automatically load plugins"
1615 description: "Ex commands to execute prior to initialization",
1616 type: CommandOption.STRING,
1621 description: "Ex commands to execute after initialization",
1622 type: CommandOption.STRING,
1626 names: ["+purgecaches"],
1627 description: "Purge " + config.appName + " caches at startup",
1628 type: CommandOption.NOARG
1632 commands.add(["reh[ash]"],
1633 "Reload the " + config.appName + " add-on",
1636 storage.session.rehashCmd = args.trailing; // Hack.
1639 if (args["+purgecaches"])
1645 argCount: "0", // FIXME
1646 options: startupOptions
1649 commands.add(["res[tart]"],
1650 "Force " + config.host + " to restart",
1652 if (args["+purgecaches"])
1655 dactyl.restart(args.string);
1659 options: startupOptions
1662 function findToolbar(name) DOM.XPath(
1663 "//*[@toolbarname=" + util.escapeString(name, "'") + " or " +
1664 "@toolbarname=" + util.escapeString(name.trim(), "'") + "]",
1665 document).snapshotItem(0);
1667 var toolbox = document.getElementById("navigator-toolbox");
1669 let toolbarCommand = function (names, desc, action, filter) {
1670 commands.add(names, desc,
1672 let toolbar = findToolbar(args[0] || "");
1673 dactyl.assert(toolbar, _("error.invalidArgument"));
1675 events.checkFocus();
1678 completer: function (context) {
1679 completion.toolbar(context);
1681 context.filters.push(filter);
1687 toolbarCommand(["toolbars[how]", "tbs[how]"], "Show the named toolbar",
1688 function (toolbar) dactyl.setNodeVisible(toolbar, true),
1689 function ({ item }) Dactyl.toolbarHidden(item));
1690 toolbarCommand(["toolbarh[ide]", "tbh[ide]"], "Hide the named toolbar",
1691 function (toolbar) dactyl.setNodeVisible(toolbar, false),
1692 function ({ item }) !Dactyl.toolbarHidden(item));
1693 toolbarCommand(["toolbart[oggle]", "tbt[oggle]"], "Toggle the named toolbar",
1694 function (toolbar) dactyl.setNodeVisible(toolbar, Dactyl.toolbarHidden(toolbar)));
1697 commands.add(["time"],
1698 "Profile a piece of code or run a command multiple times",
1700 let count = args.count;
1701 let special = args.bang;
1702 args = args[0] || "";
1705 var func = function () commands.execute(args, null, false);
1707 func = dactyl.userFunc(args);
1711 let each, eachUnits, totalUnits;
1714 for (let i in util.interruptibleRange(0, count, 500)) {
1715 let now = Date.now();
1717 total += Date.now() - now;
1723 if (total / count >= 100) {
1724 each = total / 1000.0 / count;
1728 each = total / count;
1733 total = total / 1000.0;
1737 totalUnits = "msec";
1739 commandline.commandOutput(
1741 <tr highlight="Title" align="left">
1742 <th colspan="3">{_("title.Code execution summary")}</th>
1744 <tr><td>  {_("title.Executed")}:</td><td align="right"><span class="times-executed">{count}</span></td><td><!--L-->times</td></tr>
1745 <tr><td>  {_("title.Average time")}:</td><td align="right"><span class="time-average">{each.toFixed(2)}</span></td><td>{eachUnits}</td></tr>
1746 <tr><td>  {_("title.Total time")}:</td><td align="right"><span class="time-total">{total.toFixed(2)}</span></td><td>{totalUnits}</td></tr>
1750 let beforeTime = Date.now();
1756 let afterTime = Date.now();
1758 if (afterTime - beforeTime >= 100)
1759 dactyl.echo(_("time.total", ((afterTime - beforeTime) / 1000.0).toFixed(2) + " sec"));
1761 dactyl.echo(_("time.total", (afterTime - beforeTime) + " msec"));
1770 completer: function (context) {
1771 if (/^:/.test(context.filter))
1772 return completion.ex(context);
1774 return completion.javascript(context);
1782 commands.add(["verb[ose]"],
1783 "Execute a command with 'verbose' set",
1785 let vbs = options.get("verbose");
1786 let value = vbs.value;
1787 let setFrom = vbs.setFrom;
1790 vbs.set(args.count || 1);
1792 dactyl.execute(args[0] || "", null, true);
1796 vbs.setFrom = setFrom;
1800 completer: function (context) completion.ex(context),
1806 commands.add(["ve[rsion]"],
1807 "Show version information",
1810 dactyl.open("about:");
1812 let date = config.buildDate;
1813 date = date ? " (" + date + ")" : "";
1815 commandline.commandOutput(
1816 <div>{config.appName} {config.version}{date} running on: </div> +
1817 <div>{navigator.userAgent}</div>)
1826 completion: function () {
1827 completion.dialog = function dialog(context) {
1828 context.title = ["Dialog"];
1829 context.filters.push(function ({ item }) !item[2] || item[2]());
1830 context.completions = [[k, v[0], v[2]] for ([k, v] in Iterator(config.dialogs))];
1833 completion.menuItem = function menuItem(context) {
1834 context.title = ["Menu Path", "Label"];
1835 context.anchored = false;
1838 description: function (item) item.getAttribute("label"),
1839 highlight: function (item) item.disabled ? "Disabled" : ""
1841 context.generate = function () dactyl.menuItems;
1844 var toolbox = document.getElementById("navigator-toolbox");
1845 completion.toolbar = function toolbar(context) {
1846 context.title = ["Toolbar"];
1847 context.keys = { text: function (item) item.getAttribute("toolbarname"), description: function () "" };
1848 context.completions = DOM.XPath("//*[@toolbarname]", document);
1851 completion.window = function window(context) {
1852 context.title = ["Window", "Title"];
1853 context.keys = { text: function (win) dactyl.windows.indexOf(win) + 1, description: function (win) win.document.title };
1854 context.completions = dactyl.windows;
1858 dactyl.triggerObserver("load");
1860 dactyl.log(_("dactyl.modulesLoaded"), 3);
1862 userContext.DOM = Class("DOM", DOM, { init: function DOM_(sel, ctxt) DOM(sel, ctxt || buffer.focusedFrame.document) });
1863 userContext.$ = modules.userContext.DOM;
1865 dactyl.timeout(function () {
1867 var args = config.prefs.get("commandline-args")
1868 || storage.session.commandlineArgs
1869 || services.commandLineHandler.optionValue;
1871 config.prefs.reset("commandline-args");
1874 args = dactyl.parseCommandLine(args);
1877 dactyl.commandLineOptions.rcFile = args["+u"];
1878 dactyl.commandLineOptions.noPlugins = "++noplugin" in args;
1879 dactyl.commandLineOptions.postCommands = args["+c"];
1880 dactyl.commandLineOptions.preCommands = args["++cmd"];
1881 util.dump("Processing command-line option: " + args.string);
1885 dactyl.echoerr(_("dactyl.parsingCommandLine", e));
1888 dactyl.log(_("dactyl.commandlineOpts", util.objectToString(dactyl.commandLineOptions)), 3);
1890 if (config.prefs.get("first-run", true))
1891 dactyl.timeout(function () {
1892 config.prefs.set("first-run", false);
1893 this.withSavedValues(["forceTarget"], function () {
1894 this.forceTarget = dactyl.NEW_TAB;
1899 // TODO: we should have some class where all this guioptions stuff fits well
1900 // dactyl.hideGUI();
1902 if (dactyl.userEval("typeof document", null, "test.js") === "undefined")
1903 jsmodules.__proto__ = XPCSafeJSObjectWrapper(window);
1905 if (dactyl.commandLineOptions.preCommands)
1906 dactyl.commandLineOptions.preCommands.forEach(function (cmd) {
1907 dactyl.execute(cmd);
1910 // finally, read the RC file and source plugins
1911 let init = services.environment.get(config.idName + "_INIT");
1912 let rcFile = io.getRCFile("~");
1915 if (dactyl.commandLineOptions.rcFile) {
1916 let filename = dactyl.commandLineOptions.rcFile;
1917 if (!/^(NONE|NORC)$/.test(filename))
1918 io.source(io.File(filename).path, { group: contexts.user });
1922 dactyl.execute(init);
1925 io.source(rcFile.path, { group: contexts.user });
1926 services.environment.set("MY_" + config.idName + "RC", rcFile.path);
1929 dactyl.log(_("dactyl.noRCFile"), 3);
1932 if (options["exrc"] && !dactyl.commandLineOptions.rcFile) {
1933 let localRCFile = io.getRCFile(io.cwd);
1934 if (localRCFile && !localRCFile.equals(rcFile))
1935 io.source(localRCFile.path, { group: contexts.user });
1939 if (dactyl.commandLineOptions.rcFile == "NONE" || dactyl.commandLineOptions.noPlugins)
1940 options["loadplugins"] = [];
1942 if (options["loadplugins"])
1943 dactyl.loadPlugins();
1946 dactyl.reportError(e, true);
1949 // after sourcing the initialization files, this function will set
1950 // all gui options to their default values, if they have not been
1951 // set before by any RC file
1952 for (let option in values(options.needInit))
1955 if (dactyl.commandLineOptions.postCommands)
1956 dactyl.commandLineOptions.postCommands.forEach(function (cmd) {
1957 dactyl.execute(cmd);
1960 if (storage.session.rehashCmd)
1961 dactyl.execute(storage.session.rehashCmd);
1962 storage.session.rehashCmd = null;
1964 dactyl.fullyInitialized = true;
1965 dactyl.triggerObserver("enter", null);
1966 autocommands.trigger("Enter", {});
1969 statusline.update();
1970 dactyl.log(_("dactyl.initialized", config.appName), 0);
1971 dactyl.initialized = true;
1975 // vim: set fdm=marker sw=4 ts=4 et: