load_url_in_current_buffer: correct syntax error
[conkeror.git] / modules / utils.js
blobdaaa13fafda3430b133f3af415971965561472b9
1 /**
2  * (C) Copyright 2004-2007 Shawn Betts
3  * (C) Copyright 2007-2008 John J. Foerch
4  * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
5  *
6  * Use, modification, and distribution are subject to the terms specified in the
7  * COPYING file.
8 **/
10 function string_hashset() {}
12 string_hashset.prototype = {
13     constructor : string_hashset,
15     add : function(s) {
16         this["-" + s] = true;
17     },
19     contains : function(s) {
20         return (("-" + s) in this);
21     },
23     remove : function (s) {
24         delete this["-" + s];
25     },
27     for_each : function (f) {
28         for (var i in this) {
29             if (i[0] == "-")
30                 f(i.slice(1));
31         }
32     },
34     iterator : function () {
35         for (let k in this) {
36             if (i[0] == "-")
37                 yield i.slice(1);
38         }
39     }
42 function string_hashmap() {
45 string_hashmap.prototype = {
46     constructor : string_hashmap,
48     put : function(s,value) {
49         this["-" + s] = value;
50     },
52     contains : function(s) {
53         return (("-" + s) in this);
54     },
56     get : function(s, default_value) {
57         if (this.contains(s))
58             return this["-" + s];
59         return default_value;
60     },
62     get_put_default : function(s, default_value) {
63         if (this.contains(s))
64             return this["-" + s];
65         return (this["-" + s] = default_value);
66     },
68     remove : function (s) {
69         delete this["-" + s];
70     },
72     for_each : function (f) {
73         for (var i in this) {
74             if (i[0] == "-")
75                 f(i.slice(1), this[i]);
76         }
77     },
79     for_each_value : function (f) {
80         for (var i in this) {
81             if (i[0] == "-")
82                 f(this[i]);
83         }
84     },
86     iterator: function (only_keys) {
87         if (only_keys) {
88             for (let k in Iterator(this, true)) {
89                 if (k[0] == "-")
90                     yield k.slice(1);
91             }
92         } else {
93             for (let [k,v] in Iterator(this, false)) {
94                 if (k[0] == "-")
95                     yield [k.slice(1),v];
96             }
97         }
98     }
101 /// Window title formatting
104  * Default tile formatter.  The page url is ignored.  If there is a
105  * page_title, returns: "Page title - Conkeror".  Otherwise, it
106  * returns just: "Conkeror".
107  */
108 function default_title_formatter (window)
110     var page_title = window.buffers.current.title;
112     if (page_title && page_title.length > 0)
113         return page_title + " - Conkeror";
114     else
115         return "Conkeror";
118 var title_format_fn = null;
120 function set_window_title (window)
122     window.document.title = title_format_fn(window);
125 function init_window_title ()
127     title_format_fn = default_title_formatter;
129     add_hook("window_initialize_late_hook", set_window_title);
130     add_hook("current_content_buffer_location_change_hook",
131              function (buffer) {
132                  set_window_title(buffer.window);
133              });
134     add_hook("select_buffer_hook", function (buffer) { set_window_title(buffer.window); }, true);
135     add_hook("current_buffer_title_change_hook",
136              function (buffer) {
137                  set_window_title(buffer.window);
138              });
141 init_window_title ();
146 // Put the string on the clipboard
147 function writeToClipboard(str)
149     const gClipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"]
150         .getService(Components.interfaces.nsIClipboardHelper);
151     gClipboardHelper.copyString(str);
155 function makeURLAbsolute (base, url)
157     // Construct nsIURL.
158     var ioService = Components.classes["@mozilla.org/network/io-service;1"]
159         .getService(Components.interfaces.nsIIOService);
160     var baseURI  = ioService.newURI(base, null, null);
162     return ioService.newURI (baseURI.resolve (url), null, null).spec;
166 function get_link_location (element)
168     if (element && element.getAttribute("href")) {
169         var loc = element.getAttribute("href");
170         return makeURLAbsolute(element.baseURI, loc);
171     }
172     return null;
176 var io_service = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService2);
178 function make_uri(uri, charset, base_uri) {
179     if (uri instanceof Ci.nsIURI)
180         return uri;
181     return io_service.newURI(uri, charset, base_uri);
184 var makeURL = make_uri; // until all callers are fixed
186 function makeFileURL(aFile)
188     return io_service.newFileURI(aFile).QueryInterface(Ci.nsIURL);
192 function get_document_content_disposition (document_o)
194     var content_disposition = null;
195     try {
196         content_disposition =
197             document_o.defaultView
198             .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
199             .getInterface(Components.interfaces.nsIDOMWindowUtils)
200             .getDocumentMetadata("content-disposition");
201     } catch (e) { }
202     return content_disposition;
206 function set_focus_no_scroll(window, element)
208     window.document.commandDispatcher.suppressFocusScroll = true;
209     element.focus();
210     window.document.commandDispatcher.suppressFocusScroll = false;
213 function do_repeatedly_positive(func, n) {
214     var args = Array.prototype.slice.call(arguments, 2);
215     while (n-- > 0)
216         func.apply(null, args);
219 function do_repeatedly(func, n, positive_args, negative_args) {
220     if (n < 0)
221         do func.apply(null, negative_args); while (++n < 0);
222     else
223         while (n-- > 0) func.apply(null, positive_args);
226 // remove whitespace from the beginning and end
227 function trim_whitespace (str)
229     var tmp = new String (str);
230     return tmp.replace (/^\s+/, "").replace (/\s+$/, "");
233 function abs_point (node)
235     var orig = node;
236     var pt = {};
237     try {
238         pt.x = node.offsetLeft;
239         pt.y = node.offsetTop;
240         // find imagemap's coordinates
241         if (node.tagName == "AREA") {
242             var coords = node.getAttribute("coords").split(",");
243             pt.x += Number(coords[0]);
244             pt.y += Number(coords[1]);
245         }
247         node = node.offsetParent;
248         // Sometimes this fails, so just return what we got.
250         while (node.tagName != "BODY") {
251             pt.x += node.offsetLeft;
252             pt.y += node.offsetTop;
253             node = node.offsetParent;
254         }
255     } catch(e) {
256 //      node = orig;
257 //      while (node.tagName != "BODY") {
258 //          alert("okay: " + node + " " + node.tagName + " " + pt.x + " " + pt.y);
259 //          node = node.offsetParent;
260 //      }
261     }
262     return pt;
265 var xul_app_info = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
266 var xul_runtime = Cc['@mozilla.org/xre/app-info;1'].getService(Ci.nsIXULRuntime);
269 function get_os ()
271     // possible return values: 'Darwin', 'Linux', 'WINNT', ...
272     return xul_runtime.OS;
275 var default_directory = null;
277 var env = Cc['@mozilla.org/process/environment;1'].getService(Ci.nsIEnvironment);
278 function getenv (variable) {
279     if (env.exists (variable))
280         return env.get(variable);
281     return null;
284 function set_default_directory(directory_s) {
285     if (! directory_s)
286     {
287         if ( get_os() == "WINNT")
288         {
289             directory_s = getenv ('USERPROFILE') ||
290                 getenv ('HOMEDRIVE') + getenv ('HOMEPATH');
291         }
292         else {
293             directory_s = getenv ('HOME');
294         }
295     }
297     default_directory = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
298     default_directory.initWithPath (directory_s);
301 set_default_directory();
303 const XHTML_NS = "http://www.w3.org/1999/xhtml";
304 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
305 const MATHML_NS = "http://www.w3.org/1998/Math/MathML";
306 const XLINK_NS = "http://www.w3.org/1999/xlink";
308 function create_XUL(window, tag_name)
310     return window.document.createElementNS(XUL_NS, tag_name);
314 /* Used in calls to XPath evaluate */
315 function xpath_lookup_namespace(prefix) {
316     if (prefix == "xhtml")
317         return XHTML_NS;
318     if (prefix == "m")
319         return MATHML_NS;
320     if (prefix == "xul")
321         return XUL_NS;
322     return null;
325 function method_caller(obj, func) {
326     return function () {
327         func.apply(obj, arguments);
328     }
331 function shell_quote(str) {
332     var s = str.replace("\"", "\\\"", "g");
333     s = s.replace("$", "\$", "g");
334     return s;
337 /* Like perl's quotemeta. Backslash all non-alphanumerics. */
338 function quotemeta(str) {
339     return str.replace(/([^a-zA-Z0-9])/g, "\\$1");
342 /* Given a list of choices (strings), return a regex which matches any
343    of them*/
344 function choice_regex(choices) {
345     var regex = "(?:" + choices.map(quotemeta).join("|") + ")";
346     return regex;
349 function get_window_from_frame(frame) {
350     try {
351         var window = frame.QueryInterface(Ci.nsIInterfaceRequestor)
352             .getInterface(Ci.nsIWebNavigation)
353             .QueryInterface(Ci.nsIDocShellTreeItem)
354             .rootTreeItem
355             .QueryInterface(Ci.nsIInterfaceRequestor)
356             .getInterface(Ci.nsIDOMWindow).wrappedJSObject;
357         /* window is now an XPCSafeJSObjectWrapper */
358         window.escape_wrapper(function (w) { window = w; });
359         /* window is now completely unwrapped */
360         return window;
361     } catch (e) {
362         return null;
363     }
366 function get_buffer_from_frame(window, frame) {
367     var count = window.buffers.count;
368     for (var i = 0; i < count; ++i) {
369         var b = window.buffers.get_buffer(i);
370         if (b.top_frame == frame)
371             return b;
372     }
373     return null;
376 var file_locator = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
378 function get_shortdoc_string(doc) {
379     var shortdoc = null;
380     if (doc != null) {
381         var idx = doc.indexOf("\n");
382         if (idx >= 0)
383             shortdoc = doc.substring(0,idx);
384         else
385             shortdoc = doc;
386     }
387     return shortdoc;
390 var conkeror_source_code_path = null;
392 function source_code_reference(uri, line_number) {
393     this.uri = uri;
394     this.line_number = line_number;
396 source_code_reference.prototype = {
397     get module_name () {
398         if (this.uri.indexOf(module_uri_prefix) == 0)
399             return this.uri.substring(module_uri_prefix.length);
400         return null;
401     },
403     get file_name () {
404         var file_uri_prefix = "file://";
405         if (this.uri.indexOf(file_uri_prefix) == 0)
406             return this.uri.substring(file_uri_prefix.length);
407         return null;
408     },
410     get best_uri () {
411         if (conkeror_source_code_path != null) {
412             var module_name = this.module_name;
413             if (module_name != null)
414                 return "file://" + conkeror_source_code_path + "/modules/" + module_name;
415         }
416         return this.uri;
417     },
419     open_in_editor : function() {
420         yield open_with_external_editor(this.best_uri, $line = this.line_number);
421     }
424 var get_caller_source_code_reference_ignored_functions = {};
426 function get_caller_source_code_reference(extra_frames_back) {
427     /* Skip at least this function itself and whoever called it (and
428      * more if the caller wants to be skipped). */
429     var frames_to_skip = 2;
430     if (extra_frames_back != null)
431         frames_to_skip += extra_frames_back;
433     for (let f = Components.stack; f != null; f = f.caller) {
434         if (frames_to_skip > 0) {
435             --frames_to_skip;
436             continue;
437         }
438         if (get_caller_source_code_reference_ignored_functions[f.name])
439             continue;
440         return new source_code_reference(f.filename, f.lineNumber);
441     }
443     return null;
446 function ignore_function_for_get_caller_source_code_reference(func_name) {
447     get_caller_source_code_reference_ignored_functions[func_name] = 1;
450 require_later("external-editor.js");
452 function dom_generator(document, ns) {
453     this.document = document;
454     this.ns = ns;
456 dom_generator.prototype = {
457     element : function(tag, parent) {
458         var node = this.document.createElementNS(this.ns, tag);
459         var i = 1;
460         if (parent != null && (parent instanceof Ci.nsIDOMNode)) {
461             parent.appendChild(node);
462             i = 2;
463         }
464         for (; i < arguments.length; i += 2)
465             node.setAttribute(arguments[i], arguments[i+1]);
466         return node;
467     },
469     text : function(str, parent) {
470         var node = this.document.createTextNode(str);
471         if (parent)
472             parent.appendChild(node);
473         return node;
474     },
477     stylesheet_link : function(href, parent) {
478         var node = this.element("link");
479         node.setAttribute("rel", "stylesheet");
480         node.setAttribute("type", "text/css");
481         node.setAttribute("href", href);
482         if (parent)
483             parent.appendChild(node);
484         return node;
485     },
488     add_stylesheet : function (url) {
489         var head = this.document.documentElement.firstChild;
490         this.stylesheet_link(url, head);
491     }
495  * Generates a QueryInterface function suitable for an implemenation
496  * of an XPCOM interface.  Unlike XPCOMUtils, this uses the Function
497  * constructor to generate a slightly more efficient version.  The
498  * arguments can be either Strings or elements of
499  * Components.interfaces.
500  */
501 function generate_QI() {
502     var args = Array.prototype.slice.call(arguments).map(String).concat(["nsISupports"]);
503     var fstr = "if(" +
504         Array.prototype.map.call(args,
505                                  function (x)
506                                      "iid.equals(Components.interfaces." + x + ")")
507         .join("||") +
508         ") return this; throw Components.results.NS_ERROR_NO_INTERFACE;";
509     return new Function("iid", fstr);
512 function set_branch_pref(branch, name, value) {
513     if (typeof(value) == "string") {
514         branch.setCharPref(name, value);
515     } else if (typeof(value) == "number") {
516         branch.setIntPref(name, value);
517     } else if (typeof(value) == "boolean") {
518         branch.setBoolPref(name, value);
519     }
522 function default_pref(name, value) {
523     var branch = preferences.getDefaultBranch(null);
524     set_branch_pref(branch, name, value);
527 function user_pref(name, value) {
528     var branch = preferences.getBranch(null);
529     set_branch_pref(branch, name, value);
532 function get_branch_pref(branch, name) {
533     switch (branch.getPrefType(name)) {
534     case branch.PREF_STRING:
535         return branch.getCharPref(name);
536     case branch.PREF_INT:
537         return branch.getIntPref(name);
538     case branch.PREF_BOOL:
539         return branch.getBoolPref(name);
540     default:
541         return null;
542     }
545 function get_localized_pref(name) {
546     try {
547         return preferences.getBranch(null).getComplexValue(name, Ci.nsIPrefLocalizedString).data;
548     } catch (e) {
549         return null;
550     }
553 function get_pref(name) {
554     var branch = preferences.getBranch(null);
555     return get_branch_pref(branch, name);
558 function get_default_pref(name) {
559     var branch = preferences.getDefaultBranch(null);
560     return get_branch_pref(branch, name);
563 function clear_pref(name) {
564     var branch = preferences.getBranch(null);
565     return branch.clearUserPref(name);
568 function pref_has_user_value(name) {
569     var branch = preferences.getBranch(null);
570     return branch.prefHasUserValue(name);
573 function pref_has_default_value(name) {
574     var branch = preferences.getDefaultBranch(null);
575     return branch.prefHasUserValue(name);
578 function session_pref (name, value) {
579     try { clear_pref (name); }
580     catch (e) {}
581     return default_pref (name, value);
584 function watch_pref(pref, hook) {
585     /* Extract pref into branch.pref */
586     let match = pref.match(/^(.*[.])?([^.]*)$/);
587     let br = match[1];
588     let key = match[2];
589     dumpln("watch:" + br + ":" + key);
590     let branch = preferences.getBranch(br).QueryInterface(Ci.nsIPrefBranch2);
591     let observer = {
592         observe: function (subject, topic, data) {
593             dumpln("watch_pref: " + subject + ":" + topic + ":" + data);
594             if (topic == "nsPref:changed" && data == key) {
595                 hook();
596             }
597         }
598     };
600     branch.addObserver("", observer, false);
603 const LOCALE_PREF = "general.useragent.locale";
605 function get_locale() {
606     return get_localized_pref(LOCALE_PREF) || get_pref(LOCALE_PREF);
609 const USER_AGENT_OVERRIDE_PREF = "general.useragent.override";
611 function set_user_agent(str) {
612     session_pref(USER_AGENT_OVERRIDE_PREF, str);
615 function define_builtin_commands(prefix, do_command_function, toggle_mark, mark_active_predicate, mode) {
617     // Specify a docstring
618     function D(cmd, docstring) {
619         var o = new String(cmd);
620         o.doc = docstring;
621         return o;
622     }
624     // Specify a forward/reverse pair
625     function R(a, b) {
626         var o = [a,b];
627         o.is_reverse_pair = true;
628         return o;
629     }
631     // Specify a movement/select/scroll/move-caret command group.
632     function S(command, movement, select, scroll, caret) {
633         var o = [movement, select, scroll, caret];
634         o.command = command;
635         o.is_move_select_pair = true;
636         return o;
637     }
639     var builtin_commands = [
641         /*
642          * cmd_scrollBeginLine and cmd_scrollEndLine don't do what I
643          * want, either in or out of caret mode...
644          */
645         S(D("beginning-of-line", "Move or extend the selection to the beginning of the current line."),
646           D("cmd_beginLine", "Move point to the beginning of the current line."),
647           D("cmd_selectBeginLine", "Extend selection to the beginning of the current line."),
648           D("cmd_beginLine", "Scroll to the beginning of the line"),
649           D("cmd_beginLine", "Scroll to the beginning of the line")),
650         S(D("end-of-line", "Move or extend the selection to the end of the current line."),
651           D("cmd_endLine", "Move point to the end of the current line."),
652           D("cmd_selectEndLine", "Extend selection to the end of the current line."),
653           D("cmd_endLine", "Scroll to the end of the current line."),
654           D("cmd_endLine", "Scroll to the end of the current line.")),
655         D("cmd_copy", "Copy the selection into the clipboard."),
656         "cmd_copyOrDelete",
657         D("cmd_cut", "Cut the selection into the clipboard."),
658         "cmd_cutOrDelete",
659         D("cmd_deleteToBeginningOfLine", "Delete to the beginning of the current line."),
660         D("cmd_deleteToEndOfLine", "Delete to the end of the current line."),
661         S(D("beginning-of-first-line", "Move or extend the selection to the beginning of the first line."),
662           D("cmd_moveTop", "Move point to the beginning of the first line."),
663           D("cmd_selectTop", "Extend selection to the beginning of the first line."),
664           D("cmd_scrollTop", "Scroll to the top of the buffer"),
665           D("cmd_scrollTop", "Move point to the beginning of the first line.")),
666         S(D("end-of-last-line", "Move or extend the selection to the end of the last line."),
667           D("cmd_moveBottom", "Move point to the end of the last line."),
668           D("cmd_selectBottom", "Extend selection to the end of the last line."),
669           D("cmd_scrollBottom", "Scroll to the bottom of the buffer"),
670           D("cmd_scrollBottom", "Move point to the end of the last line.")),
671         D("cmd_selectAll", "Select all."),
672         "cmd_scrollBeginLine",
673         "cmd_scrollEndLine",
674         D("cmd_scrollTop", "Scroll to the top of the buffer."),
675         D("cmd_scrollBottom", "Scroll to the bottom of the buffer.")];
677     var builtin_commands_with_count = [
678         R(S(D("forward-char", "Move or extend the selection forward one character."),
679             D("cmd_charNext", "Move point forward one character."),
680             D("cmd_selectCharNext", "Extend selection forward one character."),
681             D("cmd_scrollRight", "Scroll to the right"),
682             D("cmd_scrollRight", "Scroll to the right")),
683           S(D("backward-char", "Move or extend the selection backward one character."),
684             D("cmd_charPrevious", "Move point backward one character."),
685             D("cmd_selectCharPrevious", "Extend selection backward one character."),
686             D("cmd_scrollLeft", "Scroll to the left."),
687             D("cmd_scrollLeft", "Scroll to the left."))),
688         R(D("cmd_deleteCharForward", "Delete the following character."),
689           D("cmd_deleteCharBackward", "Delete the previous character.")),
690         R(D("cmd_deleteWordForward", "Delete the following word."),
691           D("cmd_deleteWordBackward", "Delete the previous word.")),
692         R(S(D("forward-line", "Move or extend the selection forward one line."),
693             D("cmd_lineNext", "Move point forward one line."),
694             D("cmd_selectLineNext", "Extend selection forward one line."),
695             D("cmd_scrollLineDown", "Scroll down one line."),
696             D("cmd_scrollLineDown", "Scroll down one line.")),
697           S(D("backward-line", "Move or extend the selection backward one line."),
698             D("cmd_linePrevious", "Move point backward one line."),
699             D("cmd_selectLinePrevious", "Extend selection backward one line."),
700             D("cmd_scrollLineUp", "Scroll up one line."),
701             D("cmd_scrollLineUp", "Scroll up one line."))),
702         R(S(D("forward-page", "Move or extend the selection forward one page."),
703             D("cmd_movePageDown", "Move point forward one page."),
704             D("cmd_selectPageDown", "Extend selection forward one page."),
705             D("cmd_scrollPageDown", "Scroll forward one page."),
706             D("cmd_movePageDown", "Move point forward one page.")),
707           S(D("backward-page", "Move or extend the selection backward one page."),
708             D("cmd_movePageUp", "Move point backward one page."),
709             D("cmd_selectPageUp", "Extend selection backward one page."),
710             D("cmd_scrollPageUp", "Scroll backward one page."),
711             D("cmd_movePageUp", "Move point backward one page."))),
712         R(D("cmd_undo", "Undo last editing action."),
713           D("cmd_redo", "Redo last editing action.")),
714         R(S(D("forward-word", "Move or extend the selection forward one word."),
715             D("cmd_wordNext", "Move point forward one word."),
716             D("cmd_selectWordNext", "Extend selection forward one word."),
717             D("cmd_scrollRight", "Scroll to the right."),
718             D("cmd_wordNext", "Move point forward one word.")),
719           S(D("backward-word", "Move or extend the selection backward one word."),
720             D("cmd_wordPrevious", "Move point backward one word."),
721             D("cmd_selectWordPrevious", "Extend selection backward one word."),
722             D("cmd_scrollLeft", "Scroll to the left."),
723             D("cmd_wordPrevious", "Move point backward one word."))),
724         R(D("cmd_scrollPageUp", "Scroll up one page."),
725           D("cmd_scrollPageDown", "Scroll down one page.")),
726         R(D("cmd_scrollLineUp", "Scroll up one line."),
727           D("cmd_scrollLineDown", "Scroll down one line.")),
728         R(D("cmd_scrollLeft", "Scroll left."),
729           D("cmd_scrollRight", "Scroll right.")),
730         D("cmd_paste", "Insert the contents of the clipboard.")];
732     interactive(prefix + "set-mark",
733                 "Toggle whether the mark is active.\n" +
734                 "When the mark is active, movement commands affect the selection.",
735                 toggle_mark);
737     function get_mode_idx() {
738         if (mode == 'scroll') return 2;
739         else if (mode == 'caret') return 3;
740         else return 0;
741     }
743     function get_move_select_idx(I) {
744         return mark_active_predicate(I) ? 1 : get_mode_idx();
745     }
747     function doc_for_builtin(c) {
748         var s = "";
749         if (c.doc != null)
750             s += c.doc + "\n";
751         return s + "Run the built-in command " + c + ".";
752     }
754     function define_simple_command(c) {
755         interactive(prefix + c, doc_for_builtin(c), function (I) { do_command_function(I, c); });
756     }
758     function get_move_select_doc_string(c) {
759         return c.command.doc +
760             "\nSpecifically, if the mark is active, runs `" + prefix + c[1] + "'.  " +
761             "Otherwise, runs `" + prefix + c[get_mode_idx()] + "'\n"
762             "To toggle whether the mark is active, use `" + prefix + "set-mark'.";
763     }
765     for each (let c_temp in builtin_commands)  {
766         let c = c_temp;
767         if (c.is_move_select_pair) {
768             interactive(prefix + c.command, get_move_select_doc_string(c), function (I) {
769                 var idx = get_move_select_idx(I);
770                 do_command_function(I, c[idx]);
771             });
772             define_simple_command(c[0]);
773             define_simple_command(c[1]);
774         }
775         else
776             define_simple_command(c);
777     }
779     function get_reverse_pair_doc_string(main_doc, alt_command) {
780         return main_doc + "\n" +
781             "The prefix argument specifies a repeat count for this command.  " +
782             "If the count is negative, `" + prefix + alt_command + "' is performed instead with " +
783             "a corresponding positive repeat count.";
784     }
786     function define_simple_reverse_pair(a, b) {
787         interactive(prefix + a, get_reverse_pair_doc_string(doc_for_builtin(a), b),
788                     function (I) {
789                         do_repeatedly(do_command_function, I.p, [I, a], [I, b]);
790                     });
791         interactive(prefix + b, get_reverse_pair_doc_string(doc_for_builtin(b), a),
792                     function (I) {
793                         do_repeatedly(do_command_function, I.p, [I, b], [I, a]);
794                     });
795     }
797     for each (let c_temp in builtin_commands_with_count)
798     {
799         let c = c_temp;
800         if (c.is_reverse_pair) {
801             if (c[0].is_move_select_pair) {
802                 interactive(prefix + c[0].command, get_reverse_pair_doc_string(get_move_select_doc_string(c[0]),
803                                                                                c[1].command),
804                             function (I) {
805                                 var idx = get_move_select_idx(I);
806                                 do_repeatedly(do_command_function, I.p, [I, c[0][idx]], [I, c[1][idx]]);
807                             });
808                 interactive(prefix + c[1].command, get_reverse_pair_doc_string(get_move_select_doc_string(c[1]),
809                                                                                c[0].command),
810                             function (I) {
811                                 var idx = get_move_select_idx(I);
812                                 do_repeatedly(do_command_function, I.p, [I, c[1][idx]], [I, c[0][idx]]);
813                             });
814                 define_simple_reverse_pair(c[0][0], c[1][0]);
815                 define_simple_reverse_pair(c[0][1], c[1][1]);
816             } else
817                 define_simple_reverse_pair(c[0], c[1]);
818         } else {
819             let doc = doc_for_builtin(c) +
820                 "\nThe prefix argument specifies a positive repeat count for this command.";
821             interactive(prefix + c, doc, function (I) {
822                 do_repeatedly_positive(do_command_function, I.p, I, c);
823             });
824         }
825     }
828 var observer_service = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
830 function abort(str) {
831     var e = new Error(str);
832     e.__proto__ = abort.prototype;
833     return e;
835 abort.prototype.__proto__ = Error.prototype;
838 function get_temporary_file(name) {
839     if (name == null)
840         name = "temp.txt";
841     var file = file_locator.get("TmpD", Ci.nsIFile);
842     file.append(name);
843     // Create the file now to ensure that no exploits are possible
844     file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
845     return file;
849 /* FIXME: This should be moved somewhere else, perhaps. */
850 function create_info_panel(window, panel_class, row_arr) {
851     /* Show information panel above minibuffer */
853     var g = new dom_generator(window.document, XUL_NS);
855     var p = g.element("vbox", "class", "panel " + panel_class, "flex", "0");
856     var grid = g.element("grid", p);
857     var cols = g.element("columns", grid);
858     g.element("column", cols, "flex", "0");
859     g.element("column", cols, "flex", "1");
861     var rows = g.element("rows", grid);
862     var row;
864     for each (let [row_class, row_label, row_value] in row_arr) {
865         row = g.element("row", rows, "class", row_class);
866         g.element("label", row,
867                   "value", row_label,
868                   "class", "panel-row-label");
869         g.element("label", row,
870                   "value", row_value,
871                   "class", "panel-row-value");
872     }
873     window.minibuffer.insert_before(p);
875     p.destroy = function () {
876         this.parentNode.removeChild(this);
877     };
879     return p;
883 // read_from_x_primary_selection favors the X PRIMARY SELECTION, when
884 // it exists.  The builtin cmd_paste always uses X CLIPBOARD.  So this
885 // is an auxiliary utility, in case you need to work with the primary
886 // selection.
888 function read_from_x_primary_selection ()
890     // Get clipboard.
891     var clipboard = Components.classes["@mozilla.org/widget/clipboard;1"]
892         .getService(Components.interfaces.nsIClipboard);
894     // Create tranferable that will transfer the text.
895     var trans = Components.classes["@mozilla.org/widget/transferable;1"]
896         .createInstance(Components.interfaces.nsITransferable);
898     trans.addDataFlavor("text/unicode");
899     // If available, use selection clipboard, otherwise global one
900     if (clipboard.supportsSelectionClipboard())
901         clipboard.getData(trans, clipboard.kSelectionClipboard);
902     else
903         clipboard.getData(trans, clipboard.kGlobalClipboard);
905     var data = {};
906     var dataLen = {};
907     trans.getTransferData("text/unicode", data, dataLen);
909     if (data) {
910         data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
911         return data.data.substring(0, dataLen.value / 2);
912     } else {
913         return "";
914     }
917 var user_variables = new string_hashmap();
919 function define_variable(name, default_value, doc) {
920     conkeror[name] = default_value;
921     user_variables.put(name, {
922         default_value: default_value,
923         doc: doc,
924         shortdoc: get_shortdoc_string(doc),
925         source_code_reference: get_caller_source_code_reference() });
929 function register_user_stylesheet(url)
931     var uri = makeURL(url);
932     var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
933     sss.loadAndRegisterSheet(uri, sss.USER_SHEET);
936 function unregister_user_stylesheet(url)
938     var uri = makeURL(url);
939     var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
940     if (sss.sheetRegistered(uri, sss.USER_SHEET))
941         sss.unregisterSheet(uri, sss.USER_SHEET);
944 function predicate_alist_match(alist, key) {
945     for each (let i in alist) {
946         if (i[0](key))
947             return i[1];
948     }
949     return undefined;
953 function get_meta_title(doc) {
954     var title = doc.evaluate("//meta[@name='title']/@content", doc, xpath_lookup_namespace,
955                              Ci.nsIDOMXPathResult.STRING_TYPE , null);
956     if (title && title.stringValue)
957         return title.stringValue;
958     return null;
961 var rdf_service = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
963 const PREFIX_ITEM_URI     = "urn:mozilla:item:";
964 const PREFIX_NS_EM        = "http://www.mozilla.org/2004/em-rdf#";
966 var extension_manager = Cc["@mozilla.org/extensions/manager;1"].getService(Ci.nsIExtensionManager);
968 function get_extension_rdf_property(id, name, type) {
969     var value = extension_manager.datasource.GetTarget(
970         rdf_service.GetResource(PREFIX_ITEM_URI + id),
971         rdf_service.GetResource(PREFIX_NS_EM + name),
972         true);
973     if (value == null)
974         return null;
975     return value.QueryInterface(type || Ci.nsIRDFLiteral).Value;
978 function get_extension_update_item(id) {
979     return extension_manager.getItemForID(id);
982 function extension_info(id) {
983     this.id = id;
985 extension_info.prototype = {
986     // Returns the nsIUpdateItem object associated with this extension
987     get update_item () { return get_extension_update_item(this.id); },
989     get_rdf_property : function (name, type) {
990         return get_extension_rdf_property(this.id, name, type);
991     },
993     // RDF properties
994     get isDisabled () { return this.get_rdf_property("isDisabled"); },
995     get aboutURL () { return this.get_rdf_property("aboutURL"); },
996     get addonID () { return this.get_rdf_property("addonID"); },
997     get availableUpdateURL () { return this.get_rdf_property("availableUpdateURL"); },
998     get availableUpdateVersion () { return this.get_rdf_property("availableUpdateVersion"); },
999     get blocklisted () { return this.get_rdf_property("blocklisted"); },
1000     get compatible () { return this.get_rdf_property("compatible"); },
1001     get description () { return this.get_rdf_property("description"); },
1002     get downloadURL () { return this.get_rdf_property("downloadURL"); },
1003     get isDisabled () { return this.get_rdf_property("isDisabled"); },
1004     get hidden () { return this.get_rdf_property("hidden"); },
1005     get homepageURL () { return this.get_rdf_property("homepageURL"); },
1006     get iconURL () { return this.get_rdf_property("iconURL"); },
1007     get internalName () { return this.get_rdf_property("internalName"); },
1008     get locked () { return this.get_rdf_property("locked"); },
1009     get name () { return this.get_rdf_property("name"); },
1010     get optionsURL () { return this.get_rdf_property("optionsURL"); },
1011     get opType () { return this.get_rdf_property("opType"); },
1012     get plugin () { return this.get_rdf_property("plugin"); },
1013     get previewImage () { return this.get_rdf_property("previewImage"); },
1014     get satisfiesDependencies () { return this.get_rdf_property("satisfiesDependencies"); },
1015     get providesUpdatesSecurely () { return this.get_rdf_property("providesUpdatesSecurely"); },
1016     get type () { return this.get_rdf_property("type", Ci.nsIRDFInt); },
1017     get updateable () { return this.get_rdf_property("updateable"); },
1018     get updateURL () { return this.get_rdf_property("updateURL"); },
1019     get version () { return this.get_rdf_property("version"); }
1022 function extension_is_enabled(id) {
1023     var info = new extension_info(id);
1024     return info.update_item && (info.isDisabled == "false");
1027 function queue() {
1028     this.input = [];
1029     this.output = [];
1031 queue.prototype = {
1032     get length () {
1033         return this.input.length + this.output.length;
1034     },
1035     push: function (x) {
1036         this.input[this.input.length] = x;
1037     },
1038     pop: function (x) {
1039         let l = this.output.length;
1040         if (!l) {
1041             l = this.input.length;
1042             if (!l)
1043                 return undefined;
1044             this.output = this.input.reverse();
1045             this.input = [];
1046             let x = this.output[l];
1047             this.output.length--;
1048             return x;
1049         }
1050     }
1053 function frame_iterator(root_frame, start_with) {
1054     var q = new queue, x;
1055     if (start_with) {
1056         x = start_with;
1057         do {
1058             yield x;
1059             for (let i = 0; i < x.frames.length; ++i)
1060                 q.push(x.frames[i]);
1061         } while ((x = q.pop()));
1062     }
1063     x = root_frame;
1064     do {
1065         if (x == start_with)
1066             continue;
1067         yield x;
1068         for (let i = 0; i < x.frames.length; ++i)
1069             q.push(x.frames[i]);
1070     } while ((x = q.pop()));
1073 function xml_http_request() {
1074     return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest).QueryInterface(Ci.nsIJSXMLHttpRequest).QueryInterface(Ci.nsIDOMEventTarget);
1077 var xml_http_request_load_listener = {
1078   // nsIBadCertListener2
1079   notifyCertProblem: function SSLL_certProblem(socketInfo, status, targetSite) {
1080     return true;
1081   },
1083   // nsISSLErrorListener
1084   notifySSLError: function SSLL_SSLError(socketInfo, error, targetSite) {
1085     return true;
1086   },
1088   // nsIInterfaceRequestor
1089   getInterface: function SSLL_getInterface(iid) {
1090     return this.QueryInterface(iid);
1091   },
1093   // nsISupports
1094   //
1095   // FIXME: array comprehension used here to hack around the lack of
1096   // Ci.nsISSLErrorListener in 2007 versions of xulrunner 1.9pre.
1097   // make it a simple generateQI when xulrunner is more stable.
1098   QueryInterface: XPCOMUtils.generateQI (
1099       [i for each (i in [Ci.nsIBadCertListener2,
1100                          Ci.nsISSLErrorListener,
1101                          Ci.nsIInterfaceRequestor])
1102        if (i)])
1106 define_keywords("$user", "$password", "$override_mime_type", "$headers");
1107 function send_http_request(lspec) {
1108     keywords(arguments, $user = undefined, $password = undefined,
1109              $override_mime_type = undefined, $headers = undefined);
1110     var req = xml_http_request();
1111     var cc = yield CONTINUATION;
1112     var aborting = false;
1113     req.onreadystatechange = function send_http_request__onreadysatechange() {
1114         if (req.readyState != 4)
1115             return;
1116         if (aborting)
1117             return;
1118         cc();
1119     };
1121     if (arguments.$override_mime_type)
1122         req.overrideMimeType(arguments.$override_mime_type);
1124     var post_data = load_spec_raw_post_data(lspec);
1126     var method = post_data ? "POST" : "GET";
1128     req.open(method, load_spec_uri_string(lspec), true, arguments.$user, arguments.$password);
1129     req.channel.notificationCallbacks = xml_http_request_load_listener;
1131     for each (let [name,value] in arguments.$headers) {
1132         req.setRequestHeader(name, value);
1133     }
1135     if (post_data) {
1136         req.setRequestHeader("Content-Type", load_spec_request_mime_type(lspec));
1137         req.send(post_data);
1138     } else
1139         req.send(null);
1141     try {
1142         yield SUSPEND;
1143     } catch (e) {
1144         aborting = true;
1145         req.abort();
1146         throw e;
1147     }
1149     // Let the caller access the status and reponse data
1150     yield co_return(req);
1154 var JSON = ("@mozilla.org/dom/json;1" in Cc) && Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
1158 var console_service = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
1160 console_service.registerListener(
1161     {observe: function (msg) {
1162          if (msg instanceof Ci.nsIScriptError) {
1163              switch (msg.category) {
1164              case "CSS Parser":
1165              case "content javascript":
1166                  return;
1167              }
1168              msg.QueryInterface(Ci.nsIScriptError);
1169              dumpln("Console error: " + msg.message);
1170              dumpln("  Category: " + msg.category);
1171          }
1172      }});
1175 // ensure_index_is_visible ensures that the given index in the given
1176 // field (an html input field for example) is visible.
1177 function ensure_index_is_visible (window, field, index) {
1178     var start = field.selectionStart;
1179     var end = field.selectionEnd;
1180     field.setSelectionRange (index, index);
1181     send_key_as_event (window, field, "left");
1182     if (field.selectionStart < index) {
1183         send_key_as_event (window, field, "right");
1184     }
1185     field.setSelectionRange (start, end);
1188 function regex_to_string(obj) {
1189     if(obj instanceof RegExp) {
1190         obj = obj.source;
1191     } else {
1192         obj = quotemeta(obj);
1193     }
1194     return obj;
1198  * Build a regular expression to match URLs for a given web site.
1200  * Both the $domain and $path arguments can be either regexes, in
1201  * which case they will be matched as is, or strings, in which case
1202  * they will be matched literally.
1204  * $tlds specifies a list of valid top-level-domains to match, and
1205  * defaults to .com. Useful for when e.g. foo.org and foo.com are the
1206  * same.
1208  * If $allow_www is true, www.domain.tld will also be allowed.
1210  */
1211 define_keywords("$domain", "$path", "$tlds", "$allow_www");
1212 function build_url_regex() {
1213     keywords(arguments, $path = "", $tlds = ["com"], $allow_www = false);
1214     var domain = regex_to_string(arguments.$domain);
1215     if(arguments.$allow_www) {
1216         domain = "(?:www\.)?" + domain;
1217     }
1218     var path   = regex_to_string(arguments.$path);
1219     var tlds   = arguments.$tlds;
1220     var regex = "^https?://" + domain + "\\." + choice_regex(tlds) + "/" + path;
1221     return new RegExp(regex);
1226  * Given an ordered array of non-overlapping ranges, represented as
1227  * elements of [start, end], insert a new range into the array,
1228  * extending, replacing, or merging existing ranges as needed. Mutates
1229  * `arr' in place.
1231  * Examples:
1233  * splice_range([[1,3],[4,6], 5, 8)
1234  *  => [[1,3],[4,8]]
1236  * splice_range([[1,3],[4,6],[7,10]], 2, 8)
1237  *  => [[1,10]]
1238  */
1239 function splice_range(arr, start, end) {
1240     for(var i = 0; i < arr.length; ++i) {
1241         let [n,m] = arr[i];
1242         if(start > m)
1243             continue;
1244         if(end < n) {
1245             arr.splice(i, 0, [start, end]);
1246             break;
1247         }
1248         if(start < n) {
1249             arr[i][0] = start;
1250         }
1252         if(end >= n) {
1253             /*
1254              * The range we are inserting overlaps the current
1255              * range. We need to scan right to see if it also contains any other
1256              * ranges entirely, and remove them if necessary.
1257              */
1258             var j = i;
1259             while(j < arr.length && end >= arr[j][0]) j++;
1260             j--;
1261             arr[i][1] = Math.max(end, arr[j][1]);
1262             arr.splice(i + 1, j - i);
1263             break;
1264         }
1265     }
1266     if(start > arr[arr.length - 1][1]) {
1267         arr.push([start, end]);
1268     }