Added API to manipulate external launchers for MIME types.
[conkeror.git] / modules / utils.js
blob4e3f2ce54d6a6d6fca1676303a332d7cdfd0eae2
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 **/
11  * merge_defaults does a soft merge of the key/value pairs of
12  * `defaults' into `obj'.  That is, a key/value pair will only be
13  * copied over when key does not already exist in `obj'.
14  *
15  * When `obj' is not of type "object", a clone of `defaults' will be
16  * returned if `defaults' is an object, and a new empty object
17  * otherwise.  When `obj' is of type "object", the key/value pairs
18  * from `defaults' will be soft-merged into it, and it will be
19  * returned.  Thus this function always returns something of type
20  * "object".
21  */
22 function merge_defaults(obj, defaults)
24     if (typeof obj != "object")
25         obj = {};
26     if (typeof defaults != "object")
27         return obj;
28     for (var k in defaults) {
29         if (obj[k] === undefined)
30             obj[k] = defaults[k];
31     }
32     return obj;
36 function string_hashset() {}
38 string_hashset.prototype = {
39     constructor : string_hashset,
41     add : function(s) {
42         this["-" + s] = true;
43     },
45     contains : function(s) {
46         return (("-" + s) in this);
47     },
49     remove : function (s) {
50         delete this["-" + s];
51     },
53     for_each : function (f) {
54         for (var i in this) {
55             if (i[0] == "-")
56                 f(i.slice(1));
57         }
58     },
60     iterator : function () {
61         for (let k in this) {
62             if (i[0] == "-")
63                 yield i.slice(1);
64         }
65     }
68 function string_hashmap() {
71 string_hashmap.prototype = {
72     constructor : string_hashmap,
74     put : function(s,value) {
75         this["-" + s] = value;
76     },
78     contains : function(s) {
79         return (("-" + s) in this);
80     },
82     get : function(s, default_value) {
83         if (this.contains(s))
84             return this["-" + s];
85         return default_value;
86     },
88     get_put_default : function(s, default_value) {
89         if (this.contains(s))
90             return this["-" + s];
91         return (this["-" + s] = default_value);
92     },
94     remove : function (s) {
95         delete this["-" + s];
96     },
98     for_each : function (f) {
99         for (var i in this) {
100             if (i[0] == "-")
101                 f(i.slice(1), this[i]);
102         }
103     },
105     for_each_value : function (f) {
106         for (var i in this) {
107             if (i[0] == "-")
108                 f(this[i]);
109         }
110     },
112     iterator: function (only_keys) {
113         if (only_keys) {
114             for (let k in Iterator(this, true)) {
115                 if (k[0] == "-")
116                     yield k.slice(1);
117             }
118         } else {
119             for (let [k,v] in Iterator(this, false)) {
120                 if (k[0] == "-")
121                     yield [k.slice(1),v];
122             }
123         }
124     }
127 /// Window title formatting
130  * Default tile formatter.  The page url is ignored.  If there is a
131  * page_title, returns: "Page title - Conkeror".  Otherwise, it
132  * returns just: "Conkeror".
133  */
134 function default_title_formatter (window)
136     var page_title = window.buffers.current.title;
138     if (page_title && page_title.length > 0)
139         return page_title + " - Conkeror";
140     else
141         return "Conkeror";
144 var title_format_fn = null;
146 function set_window_title (window)
148     window.document.title = title_format_fn(window);
151 function init_window_title ()
153     title_format_fn = default_title_formatter;
155     add_hook("window_initialize_late_hook", set_window_title);
156     add_hook("current_content_buffer_location_change_hook",
157              function (buffer) {
158                  set_window_title(buffer.window);
159              });
160     add_hook("select_buffer_hook", function (buffer) { set_window_title(buffer.window); }, true);
161     add_hook("current_buffer_title_change_hook",
162              function (buffer) {
163                  set_window_title(buffer.window);
164              });
167 init_window_title ();
172 // Put the string on the clipboard
173 function writeToClipboard(str)
175     const gClipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"]
176         .getService(Components.interfaces.nsIClipboardHelper);
177     gClipboardHelper.copyString(str);
181 function makeURLAbsolute (base, url)
183     // Construct nsIURL.
184     var ioService = Components.classes["@mozilla.org/network/io-service;1"]
185         .getService(Components.interfaces.nsIIOService);
186     var baseURI  = ioService.newURI(base, null, null);
188     return ioService.newURI (baseURI.resolve (url), null, null).spec;
192 function get_link_location (element)
194     if (element && element.getAttribute("href")) {
195         var loc = element.getAttribute("href");
196         return makeURLAbsolute(element.baseURI, loc);
197     }
198     return null;
202 var io_service = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService2);
204 function make_uri(uri, charset, base_uri) {
205     if (uri instanceof Ci.nsIURI)
206         return uri;
207     return io_service.newURI(uri, charset, base_uri);
210 var makeURL = make_uri; // until all callers are fixed
212 function makeFileURL(aFile)
214     return io_service.newFileURI(aFile).QueryInterface(Ci.nsIURL);
218 function get_document_content_disposition (document_o)
220     var content_disposition = null;
221     try {
222         content_disposition =
223             document_o.defaultView
224             .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
225             .getInterface(Components.interfaces.nsIDOMWindowUtils)
226             .getDocumentMetadata("content-disposition");
227     } catch (e) { }
228     return content_disposition;
232 function set_focus_no_scroll(window, element)
234     window.document.commandDispatcher.suppressFocusScroll = true;
235     element.focus();
236     window.document.commandDispatcher.suppressFocusScroll = false;
239 function do_repeatedly_positive(func, n) {
240     var args = Array.prototype.slice.call(arguments, 2);
241     while (n-- > 0)
242         func.apply(null, args);
245 function do_repeatedly(func, n, positive_args, negative_args) {
246     if (n < 0)
247         do func.apply(null, negative_args); while (++n < 0);
248     else
249         while (n-- > 0) func.apply(null, positive_args);
252 // remove whitespace from the beginning and end
253 function trim_whitespace (str)
255     var tmp = new String (str);
256     return tmp.replace (/^\s+/, "").replace (/\s+$/, "");
259 function abs_point (node)
261     var orig = node;
262     var pt = {};
263     try {
264         pt.x = node.offsetLeft;
265         pt.y = node.offsetTop;
266         // find imagemap's coordinates
267         if (node.tagName == "AREA") {
268             var coords = node.getAttribute("coords").split(",");
269             pt.x += Number(coords[0]);
270             pt.y += Number(coords[1]);
271         }
273         node = node.offsetParent;
274         // Sometimes this fails, so just return what we got.
276         while (node.tagName != "BODY") {
277             pt.x += node.offsetLeft;
278             pt.y += node.offsetTop;
279             node = node.offsetParent;
280         }
281     } catch(e) {
282 //      node = orig;
283 //      while (node.tagName != "BODY") {
284 //          alert("okay: " + node + " " + node.tagName + " " + pt.x + " " + pt.y);
285 //          node = node.offsetParent;
286 //      }
287     }
288     return pt;
291 var xul_app_info = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
292 var xul_runtime = Cc['@mozilla.org/xre/app-info;1'].getService(Ci.nsIXULRuntime);
295 function get_os ()
297     // possible return values: 'Darwin', 'Linux', 'WINNT', ...
298     return xul_runtime.OS;
301 var default_directory = null;
303 var env = Cc['@mozilla.org/process/environment;1'].getService(Ci.nsIEnvironment);
304 function getenv (variable) {
305     if (env.exists (variable))
306         return env.get(variable);
307     return null;
310 function get_home_directory () {
311     if (get_os() == "WINNT")
312         return (getenv ('USERPROFILE') ||
313                 getenv ('HOMEDRIVE') + getenv ('HOMEPATH'));
314     else
315         return getenv ('HOME');
318 function set_default_directory(directory_s) {
319     default_directory = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
320     default_directory.initWithPath(directory_s || get_home_directory());
323 set_default_directory();
325 const XHTML_NS = "http://www.w3.org/1999/xhtml";
326 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
327 const MATHML_NS = "http://www.w3.org/1998/Math/MathML";
328 const XLINK_NS = "http://www.w3.org/1999/xlink";
330 function create_XUL(window, tag_name)
332     return window.document.createElementNS(XUL_NS, tag_name);
336 /* Used in calls to XPath evaluate */
337 function xpath_lookup_namespace(prefix) {
338     if (prefix == "xhtml")
339         return XHTML_NS;
340     if (prefix == "m")
341         return MATHML_NS;
342     if (prefix == "xul")
343         return XUL_NS;
344     return null;
347 function method_caller(obj, func) {
348     return function () {
349         func.apply(obj, arguments);
350     }
353 function shell_quote(str) {
354     var s = str.replace("\"", "\\\"", "g");
355     s = s.replace("$", "\$", "g");
356     return s;
359 /* Like perl's quotemeta. Backslash all non-alphanumerics. */
360 function quotemeta(str) {
361     return str.replace(/([^a-zA-Z0-9])/g, "\\$1");
364 /* Given a list of choices (strings), return a regex which matches any
365    of them*/
366 function choice_regex(choices) {
367     var regex = "(?:" + choices.map(quotemeta).join("|") + ")";
368     return regex;
371 function get_window_from_frame(frame) {
372     try {
373         var window = frame.QueryInterface(Ci.nsIInterfaceRequestor)
374             .getInterface(Ci.nsIWebNavigation)
375             .QueryInterface(Ci.nsIDocShellTreeItem)
376             .rootTreeItem
377             .QueryInterface(Ci.nsIInterfaceRequestor)
378             .getInterface(Ci.nsIDOMWindow).wrappedJSObject;
379         /* window is now an XPCSafeJSObjectWrapper */
380         window.escape_wrapper(function (w) { window = w; });
381         /* window is now completely unwrapped */
382         return window;
383     } catch (e) {
384         return null;
385     }
388 function get_buffer_from_frame(window, frame) {
389     var count = window.buffers.count;
390     for (var i = 0; i < count; ++i) {
391         var b = window.buffers.get_buffer(i);
392         if (b.top_frame == frame)
393             return b;
394     }
395     return null;
398 var file_locator = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
400 function get_shortdoc_string(doc) {
401     var shortdoc = null;
402     if (doc != null) {
403         var idx = doc.indexOf("\n");
404         if (idx >= 0)
405             shortdoc = doc.substring(0,idx);
406         else
407             shortdoc = doc;
408     }
409     return shortdoc;
412 var conkeror_source_code_path = null;
414 function source_code_reference(uri, line_number) {
415     this.uri = uri;
416     this.line_number = line_number;
418 source_code_reference.prototype = {
419     get module_name () {
420         if (this.uri.indexOf(module_uri_prefix) == 0)
421             return this.uri.substring(module_uri_prefix.length);
422         return null;
423     },
425     get file_name () {
426         var file_uri_prefix = "file://";
427         if (this.uri.indexOf(file_uri_prefix) == 0)
428             return this.uri.substring(file_uri_prefix.length);
429         return null;
430     },
432     get best_uri () {
433         if (conkeror_source_code_path != null) {
434             var module_name = this.module_name;
435             if (module_name != null)
436                 return "file://" + conkeror_source_code_path + "/modules/" + module_name;
437         }
438         return this.uri;
439     },
441     open_in_editor : function() {
442         yield open_with_external_editor(this.best_uri, $line = this.line_number);
443     }
446 var get_caller_source_code_reference_ignored_functions = {};
448 function get_caller_source_code_reference(extra_frames_back) {
449     /* Skip at least this function itself and whoever called it (and
450      * more if the caller wants to be skipped). */
451     var frames_to_skip = 2;
452     if (extra_frames_back != null)
453         frames_to_skip += extra_frames_back;
455     for (let f = Components.stack; f != null; f = f.caller) {
456         if (frames_to_skip > 0) {
457             --frames_to_skip;
458             continue;
459         }
460         if (get_caller_source_code_reference_ignored_functions[f.name])
461             continue;
462         return new source_code_reference(f.filename, f.lineNumber);
463     }
465     return null;
468 function ignore_function_for_get_caller_source_code_reference(func_name) {
469     get_caller_source_code_reference_ignored_functions[func_name] = 1;
472 require_later("external-editor.js");
474 function dom_generator(document, ns) {
475     this.document = document;
476     this.ns = ns;
478 dom_generator.prototype = {
479     element : function(tag, parent) {
480         var node = this.document.createElementNS(this.ns, tag);
481         var i = 1;
482         if (parent != null && (parent instanceof Ci.nsIDOMNode)) {
483             parent.appendChild(node);
484             i = 2;
485         }
486         for (; i < arguments.length; i += 2)
487             node.setAttribute(arguments[i], arguments[i+1]);
488         return node;
489     },
491     text : function(str, parent) {
492         var node = this.document.createTextNode(str);
493         if (parent)
494             parent.appendChild(node);
495         return node;
496     },
499     stylesheet_link : function(href, parent) {
500         var node = this.element("link");
501         node.setAttribute("rel", "stylesheet");
502         node.setAttribute("type", "text/css");
503         node.setAttribute("href", href);
504         if (parent)
505             parent.appendChild(node);
506         return node;
507     },
510     add_stylesheet : function (url) {
511         var head = this.document.documentElement.firstChild;
512         this.stylesheet_link(url, head);
513     }
517  * Generates a QueryInterface function suitable for an implemenation
518  * of an XPCOM interface.  Unlike XPCOMUtils, this uses the Function
519  * constructor to generate a slightly more efficient version.  The
520  * arguments can be either Strings or elements of
521  * Components.interfaces.
522  */
523 function generate_QI() {
524     var args = Array.prototype.slice.call(arguments).map(String).concat(["nsISupports"]);
525     var fstr = "if(" +
526         Array.prototype.map.call(args,
527                                  function (x)
528                                      "iid.equals(Components.interfaces." + x + ")")
529         .join("||") +
530         ") return this; throw Components.results.NS_ERROR_NO_INTERFACE;";
531     return new Function("iid", fstr);
534 function set_branch_pref(branch, name, value) {
535     if (typeof(value) == "string") {
536         branch.setCharPref(name, value);
537     } else if (typeof(value) == "number") {
538         branch.setIntPref(name, value);
539     } else if (typeof(value) == "boolean") {
540         branch.setBoolPref(name, value);
541     }
544 function default_pref(name, value) {
545     var branch = preferences.getDefaultBranch(null);
546     set_branch_pref(branch, name, value);
549 function user_pref(name, value) {
550     var branch = preferences.getBranch(null);
551     set_branch_pref(branch, name, value);
554 function get_branch_pref(branch, name) {
555     switch (branch.getPrefType(name)) {
556     case branch.PREF_STRING:
557         return branch.getCharPref(name);
558     case branch.PREF_INT:
559         return branch.getIntPref(name);
560     case branch.PREF_BOOL:
561         return branch.getBoolPref(name);
562     default:
563         return null;
564     }
567 function get_localized_pref(name) {
568     try {
569         return preferences.getBranch(null).getComplexValue(name, Ci.nsIPrefLocalizedString).data;
570     } catch (e) {
571         return null;
572     }
575 function get_pref(name) {
576     var branch = preferences.getBranch(null);
577     return get_branch_pref(branch, name);
580 function get_default_pref(name) {
581     var branch = preferences.getDefaultBranch(null);
582     return get_branch_pref(branch, name);
585 function clear_pref(name) {
586     var branch = preferences.getBranch(null);
587     return branch.clearUserPref(name);
590 function pref_has_user_value(name) {
591     var branch = preferences.getBranch(null);
592     return branch.prefHasUserValue(name);
595 function pref_has_default_value(name) {
596     var branch = preferences.getDefaultBranch(null);
597     return branch.prefHasUserValue(name);
600 function session_pref (name, value) {
601     try { clear_pref (name); }
602     catch (e) {}
603     return default_pref (name, value);
606 function watch_pref(pref, hook) {
607     /* Extract pref into branch.pref */
608     let match = pref.match(/^(.*[.])?([^.]*)$/);
609     let br = match[1];
610     let key = match[2];
611     dumpln("watch:" + br + ":" + key);
612     let branch = preferences.getBranch(br).QueryInterface(Ci.nsIPrefBranch2);
613     let observer = {
614         observe: function (subject, topic, data) {
615             dumpln("watch_pref: " + subject + ":" + topic + ":" + data);
616             if (topic == "nsPref:changed" && data == key) {
617                 hook();
618             }
619         }
620     };
622     branch.addObserver("", observer, false);
625 const LOCALE_PREF = "general.useragent.locale";
627 function get_locale() {
628     return get_localized_pref(LOCALE_PREF) || get_pref(LOCALE_PREF);
631 const USER_AGENT_OVERRIDE_PREF = "general.useragent.override";
633 function set_user_agent(str) {
634     session_pref(USER_AGENT_OVERRIDE_PREF, str);
637 function define_builtin_commands(prefix, do_command_function, toggle_mark, mark_active_predicate, mode) {
639     // Specify a docstring
640     function D(cmd, docstring) {
641         var o = new String(cmd);
642         o.doc = docstring;
643         return o;
644     }
646     // Specify a forward/reverse pair
647     function R(a, b) {
648         var o = [a,b];
649         o.is_reverse_pair = true;
650         return o;
651     }
653     // Specify a movement/select/scroll/move-caret command group.
654     function S(command, movement, select, scroll, caret) {
655         var o = [movement, select, scroll, caret];
656         o.command = command;
657         o.is_move_select_pair = true;
658         return o;
659     }
661     var builtin_commands = [
663         /*
664          * cmd_scrollBeginLine and cmd_scrollEndLine don't do what I
665          * want, either in or out of caret mode...
666          */
667         S(D("beginning-of-line", "Move or extend the selection to the beginning of the current line."),
668           D("cmd_beginLine", "Move point to the beginning of the current line."),
669           D("cmd_selectBeginLine", "Extend selection to the beginning of the current line."),
670           D("cmd_beginLine", "Scroll to the beginning of the line"),
671           D("cmd_beginLine", "Scroll to the beginning of the line")),
672         S(D("end-of-line", "Move or extend the selection to the end of the current line."),
673           D("cmd_endLine", "Move point to the end of the current line."),
674           D("cmd_selectEndLine", "Extend selection to the end of the current line."),
675           D("cmd_endLine", "Scroll to the end of the current line."),
676           D("cmd_endLine", "Scroll to the end of the current line.")),
677         D("cmd_copy", "Copy the selection into the clipboard."),
678         "cmd_copyOrDelete",
679         D("cmd_cut", "Cut the selection into the clipboard."),
680         "cmd_cutOrDelete",
681         D("cmd_deleteToBeginningOfLine", "Delete to the beginning of the current line."),
682         D("cmd_deleteToEndOfLine", "Delete to the end of the current line."),
683         S(D("beginning-of-first-line", "Move or extend the selection to the beginning of the first line."),
684           D("cmd_moveTop", "Move point to the beginning of the first line."),
685           D("cmd_selectTop", "Extend selection to the beginning of the first line."),
686           D("cmd_scrollTop", "Scroll to the top of the buffer"),
687           D("cmd_scrollTop", "Move point to the beginning of the first line.")),
688         S(D("end-of-last-line", "Move or extend the selection to the end of the last line."),
689           D("cmd_moveBottom", "Move point to the end of the last line."),
690           D("cmd_selectBottom", "Extend selection to the end of the last line."),
691           D("cmd_scrollBottom", "Scroll to the bottom of the buffer"),
692           D("cmd_scrollBottom", "Move point to the end of the last line.")),
693         D("cmd_selectAll", "Select all."),
694         "cmd_scrollBeginLine",
695         "cmd_scrollEndLine",
696         D("cmd_scrollTop", "Scroll to the top of the buffer."),
697         D("cmd_scrollBottom", "Scroll to the bottom of the buffer.")];
699     var builtin_commands_with_count = [
700         R(S(D("forward-char", "Move or extend the selection forward one character."),
701             D("cmd_charNext", "Move point forward one character."),
702             D("cmd_selectCharNext", "Extend selection forward one character."),
703             D("cmd_scrollRight", "Scroll to the right"),
704             D("cmd_scrollRight", "Scroll to the right")),
705           S(D("backward-char", "Move or extend the selection backward one character."),
706             D("cmd_charPrevious", "Move point backward one character."),
707             D("cmd_selectCharPrevious", "Extend selection backward one character."),
708             D("cmd_scrollLeft", "Scroll to the left."),
709             D("cmd_scrollLeft", "Scroll to the left."))),
710         R(D("cmd_deleteCharForward", "Delete the following character."),
711           D("cmd_deleteCharBackward", "Delete the previous character.")),
712         R(D("cmd_deleteWordForward", "Delete the following word."),
713           D("cmd_deleteWordBackward", "Delete the previous word.")),
714         R(S(D("forward-line", "Move or extend the selection forward one line."),
715             D("cmd_lineNext", "Move point forward one line."),
716             D("cmd_selectLineNext", "Extend selection forward one line."),
717             D("cmd_scrollLineDown", "Scroll down one line."),
718             D("cmd_scrollLineDown", "Scroll down one line.")),
719           S(D("backward-line", "Move or extend the selection backward one line."),
720             D("cmd_linePrevious", "Move point backward one line."),
721             D("cmd_selectLinePrevious", "Extend selection backward one line."),
722             D("cmd_scrollLineUp", "Scroll up one line."),
723             D("cmd_scrollLineUp", "Scroll up one line."))),
724         R(S(D("forward-page", "Move or extend the selection forward one page."),
725             D("cmd_movePageDown", "Move point forward one page."),
726             D("cmd_selectPageDown", "Extend selection forward one page."),
727             D("cmd_scrollPageDown", "Scroll forward one page."),
728             D("cmd_movePageDown", "Move point forward one page.")),
729           S(D("backward-page", "Move or extend the selection backward one page."),
730             D("cmd_movePageUp", "Move point backward one page."),
731             D("cmd_selectPageUp", "Extend selection backward one page."),
732             D("cmd_scrollPageUp", "Scroll backward one page."),
733             D("cmd_movePageUp", "Move point backward one page."))),
734         R(D("cmd_undo", "Undo last editing action."),
735           D("cmd_redo", "Redo last editing action.")),
736         R(S(D("forward-word", "Move or extend the selection forward one word."),
737             D("cmd_wordNext", "Move point forward one word."),
738             D("cmd_selectWordNext", "Extend selection forward one word."),
739             D("cmd_scrollRight", "Scroll to the right."),
740             D("cmd_wordNext", "Move point forward one word.")),
741           S(D("backward-word", "Move or extend the selection backward one word."),
742             D("cmd_wordPrevious", "Move point backward one word."),
743             D("cmd_selectWordPrevious", "Extend selection backward one word."),
744             D("cmd_scrollLeft", "Scroll to the left."),
745             D("cmd_wordPrevious", "Move point backward one word."))),
746         R(D("cmd_scrollPageUp", "Scroll up one page."),
747           D("cmd_scrollPageDown", "Scroll down one page.")),
748         R(D("cmd_scrollLineUp", "Scroll up one line."),
749           D("cmd_scrollLineDown", "Scroll down one line.")),
750         R(D("cmd_scrollLeft", "Scroll left."),
751           D("cmd_scrollRight", "Scroll right.")),
752         D("cmd_paste", "Insert the contents of the clipboard.")];
754     interactive(prefix + "set-mark",
755                 "Toggle whether the mark is active.\n" +
756                 "When the mark is active, movement commands affect the selection.",
757                 toggle_mark);
759     function get_mode_idx() {
760         if (mode == 'scroll') return 2;
761         else if (mode == 'caret') return 3;
762         else return 0;
763     }
765     function get_move_select_idx(I) {
766         return mark_active_predicate(I) ? 1 : get_mode_idx();
767     }
769     function doc_for_builtin(c) {
770         var s = "";
771         if (c.doc != null)
772             s += c.doc + "\n";
773         return s + "Run the built-in command " + c + ".";
774     }
776     function define_simple_command(c) {
777         interactive(prefix + c, doc_for_builtin(c), function (I) { do_command_function(I, c); });
778     }
780     function get_move_select_doc_string(c) {
781         return c.command.doc +
782             "\nSpecifically, if the mark is active, runs `" + prefix + c[1] + "'.  " +
783             "Otherwise, runs `" + prefix + c[get_mode_idx()] + "'\n" +
784             "To toggle whether the mark is active, use `" + prefix + "set-mark'.";
785     }
787     for each (let c_temp in builtin_commands)  {
788         let c = c_temp;
789         if (c.is_move_select_pair) {
790             interactive(prefix + c.command, get_move_select_doc_string(c), function (I) {
791                 var idx = get_move_select_idx(I);
792                 do_command_function(I, c[idx]);
793             });
794             define_simple_command(c[0]);
795             define_simple_command(c[1]);
796         }
797         else
798             define_simple_command(c);
799     }
801     function get_reverse_pair_doc_string(main_doc, alt_command) {
802         return main_doc + "\n" +
803             "The prefix argument specifies a repeat count for this command.  " +
804             "If the count is negative, `" + prefix + alt_command + "' is performed instead with " +
805             "a corresponding positive repeat count.";
806     }
808     function define_simple_reverse_pair(a, b) {
809         interactive(prefix + a, get_reverse_pair_doc_string(doc_for_builtin(a), b),
810                     function (I) {
811                         do_repeatedly(do_command_function, I.p, [I, a], [I, b]);
812                     });
813         interactive(prefix + b, get_reverse_pair_doc_string(doc_for_builtin(b), a),
814                     function (I) {
815                         do_repeatedly(do_command_function, I.p, [I, b], [I, a]);
816                     });
817     }
819     for each (let c_temp in builtin_commands_with_count)
820     {
821         let c = c_temp;
822         if (c.is_reverse_pair) {
823             if (c[0].is_move_select_pair) {
824                 interactive(prefix + c[0].command, get_reverse_pair_doc_string(get_move_select_doc_string(c[0]),
825                                                                                c[1].command),
826                             function (I) {
827                                 var idx = get_move_select_idx(I);
828                                 do_repeatedly(do_command_function, I.p, [I, c[0][idx]], [I, c[1][idx]]);
829                             });
830                 interactive(prefix + c[1].command, get_reverse_pair_doc_string(get_move_select_doc_string(c[1]),
831                                                                                c[0].command),
832                             function (I) {
833                                 var idx = get_move_select_idx(I);
834                                 do_repeatedly(do_command_function, I.p, [I, c[1][idx]], [I, c[0][idx]]);
835                             });
836                 define_simple_reverse_pair(c[0][0], c[1][0]);
837                 define_simple_reverse_pair(c[0][1], c[1][1]);
838             } else
839                 define_simple_reverse_pair(c[0], c[1]);
840         } else {
841             let doc = doc_for_builtin(c) +
842                 "\nThe prefix argument specifies a positive repeat count for this command.";
843             interactive(prefix + c, doc, function (I) {
844                 do_repeatedly_positive(do_command_function, I.p, I, c);
845             });
846         }
847     }
850 var observer_service = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
852 function abort(str) {
853     var e = new Error(str);
854     e.__proto__ = abort.prototype;
855     return e;
857 abort.prototype.__proto__ = Error.prototype;
860 function get_temporary_file(name) {
861     if (name == null)
862         name = "temp.txt";
863     var file = file_locator.get("TmpD", Ci.nsIFile);
864     file.append(name);
865     // Create the file now to ensure that no exploits are possible
866     file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
867     return file;
871 /* FIXME: This should be moved somewhere else, perhaps. */
872 function create_info_panel(window, panel_class, row_arr) {
873     /* Show information panel above minibuffer */
875     var g = new dom_generator(window.document, XUL_NS);
877     var p = g.element("vbox", "class", "panel " + panel_class, "flex", "0");
878     var grid = g.element("grid", p);
879     var cols = g.element("columns", grid);
880     g.element("column", cols, "flex", "0");
881     g.element("column", cols, "flex", "1");
883     var rows = g.element("rows", grid);
884     var row;
886     for each (let [row_class, row_label, row_value] in row_arr) {
887         row = g.element("row", rows, "class", row_class);
888         g.element("label", row,
889                   "value", row_label,
890                   "class", "panel-row-label");
891         g.element("label", row,
892                   "value", row_value,
893                   "class", "panel-row-value");
894     }
895     window.minibuffer.insert_before(p);
897     p.destroy = function () {
898         this.parentNode.removeChild(this);
899     };
901     return p;
906  * Paste from the X primary selection, unless the system doesn't support a
907  * primary selection, in which case fall back to the clipboard.
908  */
909 function read_from_x_primary_selection ()
911     // Get clipboard.
912     var clipboard = Components.classes["@mozilla.org/widget/clipboard;1"]
913         .getService(Components.interfaces.nsIClipboard);
915     // Fall back to global clipboard if the system doesn't support a selection
916     var selection = clipboard.supportsSelectionClipboard ?
917         clipboard.kSelectionClipboard : clipboard.kGlobalClipboard;
919     // Don't barf if there's nothing on the clipboard
920     if (!clipboard.hasDataMatchingFlavors(["text/unicode"], 1, selection))
921         return "";
923     // Create tranferable that will transfer the text.
924     var trans = Components.classes["@mozilla.org/widget/transferable;1"]
925         .createInstance(Components.interfaces.nsITransferable);
927     trans.addDataFlavor("text/unicode");
928     clipboard.getData(trans, selection);
930     var data = {};
931     var dataLen = {};
932     trans.getTransferData("text/unicode", data, dataLen);
934     if (data) {
935         data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
936         return data.data.substring(0, dataLen.value / 2);
937     } else {
938         return "";
939     }
942 var user_variables = new string_hashmap();
944 function define_variable(name, default_value, doc) {
945     conkeror[name] = default_value;
946     user_variables.put(name, {
947         default_value: default_value,
948         doc: doc,
949         shortdoc: get_shortdoc_string(doc),
950         source_code_reference: get_caller_source_code_reference() });
954 function register_user_stylesheet(url)
956     var uri = makeURL(url);
957     var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
958     sss.loadAndRegisterSheet(uri, sss.USER_SHEET);
961 function unregister_user_stylesheet(url)
963     var uri = makeURL(url);
964     var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
965     if (sss.sheetRegistered(uri, sss.USER_SHEET))
966         sss.unregisterSheet(uri, sss.USER_SHEET);
969 function predicate_alist_match(alist, key) {
970     for each (let i in alist) {
971         if (i[0](key))
972             return i[1];
973     }
974     return undefined;
978 function get_meta_title(doc) {
979     var title = doc.evaluate("//meta[@name='title']/@content", doc, xpath_lookup_namespace,
980                              Ci.nsIDOMXPathResult.STRING_TYPE , null);
981     if (title && title.stringValue)
982         return title.stringValue;
983     return null;
986 var rdf_service = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
988 const PREFIX_ITEM_URI     = "urn:mozilla:item:";
989 const PREFIX_NS_EM        = "http://www.mozilla.org/2004/em-rdf#";
991 var extension_manager = Cc["@mozilla.org/extensions/manager;1"].getService(Ci.nsIExtensionManager);
993 function get_extension_rdf_property(id, name, type) {
994     var value = extension_manager.datasource.GetTarget(
995         rdf_service.GetResource(PREFIX_ITEM_URI + id),
996         rdf_service.GetResource(PREFIX_NS_EM + name),
997         true);
998     if (value == null)
999         return null;
1000     return value.QueryInterface(type || Ci.nsIRDFLiteral).Value;
1003 function get_extension_update_item(id) {
1004     return extension_manager.getItemForID(id);
1007 function extension_info(id) {
1008     this.id = id;
1010 extension_info.prototype = {
1011     // Returns the nsIUpdateItem object associated with this extension
1012     get update_item () { return get_extension_update_item(this.id); },
1014     get_rdf_property : function (name, type) {
1015         return get_extension_rdf_property(this.id, name, type);
1016     },
1018     // RDF properties
1019     get isDisabled () { return this.get_rdf_property("isDisabled"); },
1020     get aboutURL () { return this.get_rdf_property("aboutURL"); },
1021     get addonID () { return this.get_rdf_property("addonID"); },
1022     get availableUpdateURL () { return this.get_rdf_property("availableUpdateURL"); },
1023     get availableUpdateVersion () { return this.get_rdf_property("availableUpdateVersion"); },
1024     get blocklisted () { return this.get_rdf_property("blocklisted"); },
1025     get compatible () { return this.get_rdf_property("compatible"); },
1026     get description () { return this.get_rdf_property("description"); },
1027     get downloadURL () { return this.get_rdf_property("downloadURL"); },
1028     get isDisabled () { return this.get_rdf_property("isDisabled"); },
1029     get hidden () { return this.get_rdf_property("hidden"); },
1030     get homepageURL () { return this.get_rdf_property("homepageURL"); },
1031     get iconURL () { return this.get_rdf_property("iconURL"); },
1032     get internalName () { return this.get_rdf_property("internalName"); },
1033     get locked () { return this.get_rdf_property("locked"); },
1034     get name () { return this.get_rdf_property("name"); },
1035     get optionsURL () { return this.get_rdf_property("optionsURL"); },
1036     get opType () { return this.get_rdf_property("opType"); },
1037     get plugin () { return this.get_rdf_property("plugin"); },
1038     get previewImage () { return this.get_rdf_property("previewImage"); },
1039     get satisfiesDependencies () { return this.get_rdf_property("satisfiesDependencies"); },
1040     get providesUpdatesSecurely () { return this.get_rdf_property("providesUpdatesSecurely"); },
1041     get type () { return this.get_rdf_property("type", Ci.nsIRDFInt); },
1042     get updateable () { return this.get_rdf_property("updateable"); },
1043     get updateURL () { return this.get_rdf_property("updateURL"); },
1044     get version () { return this.get_rdf_property("version"); }
1047 function extension_is_enabled(id) {
1048     var info = new extension_info(id);
1049     return info.update_item && (info.isDisabled == "false");
1052 function queue() {
1053     this.input = [];
1054     this.output = [];
1056 queue.prototype = {
1057     get length () {
1058         return this.input.length + this.output.length;
1059     },
1060     push: function (x) {
1061         this.input[this.input.length] = x;
1062     },
1063     pop: function (x) {
1064         let l = this.output.length;
1065         if (!l) {
1066             l = this.input.length;
1067             if (!l)
1068                 return undefined;
1069             this.output = this.input.reverse();
1070             this.input = [];
1071             let x = this.output[l];
1072             this.output.length--;
1073             return x;
1074         }
1075     }
1078 function frame_iterator(root_frame, start_with) {
1079     var q = new queue, x;
1080     if (start_with) {
1081         x = start_with;
1082         do {
1083             yield x;
1084             for (let i = 0; i < x.frames.length; ++i)
1085                 q.push(x.frames[i]);
1086         } while ((x = q.pop()));
1087     }
1088     x = root_frame;
1089     do {
1090         if (x == start_with)
1091             continue;
1092         yield x;
1093         for (let i = 0; i < x.frames.length; ++i)
1094             q.push(x.frames[i]);
1095     } while ((x = q.pop()));
1098 function xml_http_request() {
1099     return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest).QueryInterface(Ci.nsIJSXMLHttpRequest).QueryInterface(Ci.nsIDOMEventTarget);
1102 var xml_http_request_load_listener = {
1103   // nsIBadCertListener2
1104   notifyCertProblem: function SSLL_certProblem(socketInfo, status, targetSite) {
1105     return true;
1106   },
1108   // nsISSLErrorListener
1109   notifySSLError: function SSLL_SSLError(socketInfo, error, targetSite) {
1110     return true;
1111   },
1113   // nsIInterfaceRequestor
1114   getInterface: function SSLL_getInterface(iid) {
1115     return this.QueryInterface(iid);
1116   },
1118   // nsISupports
1119   //
1120   // FIXME: array comprehension used here to hack around the lack of
1121   // Ci.nsISSLErrorListener in 2007 versions of xulrunner 1.9pre.
1122   // make it a simple generateQI when xulrunner is more stable.
1123   QueryInterface: XPCOMUtils.generateQI (
1124       [i for each (i in [Ci.nsIBadCertListener2,
1125                          Ci.nsISSLErrorListener,
1126                          Ci.nsIInterfaceRequestor])
1127        if (i)])
1131 define_keywords("$user", "$password", "$override_mime_type", "$headers");
1132 function send_http_request(lspec) {
1133     keywords(arguments, $user = undefined, $password = undefined,
1134              $override_mime_type = undefined, $headers = undefined);
1135     var req = xml_http_request();
1136     var cc = yield CONTINUATION;
1137     var aborting = false;
1138     req.onreadystatechange = function send_http_request__onreadysatechange() {
1139         if (req.readyState != 4)
1140             return;
1141         if (aborting)
1142             return;
1143         cc();
1144     };
1146     if (arguments.$override_mime_type)
1147         req.overrideMimeType(arguments.$override_mime_type);
1149     var post_data = load_spec_raw_post_data(lspec);
1151     var method = post_data ? "POST" : "GET";
1153     req.open(method, load_spec_uri_string(lspec), true, arguments.$user, arguments.$password);
1154     req.channel.notificationCallbacks = xml_http_request_load_listener;
1156     for each (let [name,value] in arguments.$headers) {
1157         req.setRequestHeader(name, value);
1158     }
1160     if (post_data) {
1161         req.setRequestHeader("Content-Type", load_spec_request_mime_type(lspec));
1162         req.send(post_data);
1163     } else
1164         req.send(null);
1166     try {
1167         yield SUSPEND;
1168     } catch (e) {
1169         aborting = true;
1170         req.abort();
1171         throw e;
1172     }
1174     // Let the caller access the status and reponse data
1175     yield co_return(req);
1179 var JSON = ("@mozilla.org/dom/json;1" in Cc) && Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
1183 var console_service = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
1185 console_service.registerListener(
1186     {observe: function (msg) {
1187          if (msg instanceof Ci.nsIScriptError) {
1188              switch (msg.category) {
1189              case "CSS Parser":
1190              case "content javascript":
1191                  return;
1192              }
1193              msg.QueryInterface(Ci.nsIScriptError);
1194              dumpln("Console error: " + msg.message);
1195              dumpln("  Category: " + msg.category);
1196          }
1197      }});
1200 // ensure_index_is_visible ensures that the given index in the given
1201 // field (an html input field for example) is visible.
1202 function ensure_index_is_visible (window, field, index) {
1203     var start = field.selectionStart;
1204     var end = field.selectionEnd;
1205     field.setSelectionRange (index, index);
1206     send_key_as_event (window, field, "left");
1207     if (field.selectionStart < index) {
1208         send_key_as_event (window, field, "right");
1209     }
1210     field.setSelectionRange (start, end);
1213 function regex_to_string(obj) {
1214     if(obj instanceof RegExp) {
1215         obj = obj.source;
1216     } else {
1217         obj = quotemeta(obj);
1218     }
1219     return obj;
1223  * Build a regular expression to match URLs for a given web site.
1225  * The arg `options' is an object that can contain the following
1226  * keys to control the generation of the regexp.
1228  * domain
1230  * path
1232  *   Both the domain and path arguments can be either regexes, in
1233  * which case they will be matched as is, or strings, in which case
1234  * they will be matched literally.
1236  * tlds - tlds specifies a list of valid top-level-domains to match,
1237  * and defaults to .com. Useful for when e.g. foo.org and foo.com are
1238  * the same.
1240  * allow_www - If allow_www is true, www.domain.tld will also be allowed.
1242  */
1243 function build_url_regex(options) {
1244     options = merge_defaults(options,
1245                              { path: "",
1246                                tlds: ["com"]
1247                              });
1248     return new RegExp("^https?://" +
1249                       (options.allow_www ? "(?:www\.)?" : "") +
1250                       regex_to_string(options.domain) +
1251                       "\\." +
1252                       choice_regex(options.tlds) +
1253                       "/" +
1254                       regex_to_string(options.path));
1259  * Given an ordered array of non-overlapping ranges, represented as
1260  * elements of [start, end], insert a new range into the array,
1261  * extending, replacing, or merging existing ranges as needed. Mutates
1262  * `arr' in place.
1264  * Examples:
1266  * splice_range([[1,3],[4,6], 5, 8)
1267  *  => [[1,3],[4,8]]
1269  * splice_range([[1,3],[4,6],[7,10]], 2, 8)
1270  *  => [[1,10]]
1271  */
1272 function splice_range(arr, start, end) {
1273     for(var i = 0; i < arr.length; ++i) {
1274         let [n,m] = arr[i];
1275         if(start > m)
1276             continue;
1277         if(end < n) {
1278             arr.splice(i, 0, [start, end]);
1279             break;
1280         }
1281         if(start < n) {
1282             arr[i][0] = start;
1283         }
1285         if(end >= n) {
1286             /*
1287              * The range we are inserting overlaps the current
1288              * range. We need to scan right to see if it also contains any other
1289              * ranges entirely, and remove them if necessary.
1290              */
1291             var j = i;
1292             while(j < arr.length && end >= arr[j][0]) j++;
1293             j--;
1294             arr[i][1] = Math.max(end, arr[j][1]);
1295             arr.splice(i + 1, j - i);
1296             break;
1297         }
1298     }
1299     if(start > arr[arr.length - 1][1]) {
1300         arr.push([start, end]);
1301     }