Add facility to allow user functions to transform typed URLs.
[conkeror.git] / modules / utils.js
blob6e403cb4f584a2243ebbe05cb60e10dd8223fb42
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     }
102 // Put the string on the clipboard
103 function writeToClipboard(str)
105     const gClipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"]
106         .getService(Components.interfaces.nsIClipboardHelper);
107     gClipboardHelper.copyString(str);
111 function makeURLAbsolute (base, url)
113     // Construct nsIURL.
114     var ioService = Components.classes["@mozilla.org/network/io-service;1"]
115         .getService(Components.interfaces.nsIIOService);
116     var baseURI  = ioService.newURI(base, null, null);
118     return ioService.newURI (baseURI.resolve (url), null, null).spec;
122 function get_link_location (element)
124     if (element && element.getAttribute("href")) {
125         var loc = element.getAttribute("href");
126         return makeURLAbsolute(element.baseURI, loc);
127     }
128     return null;
132 var io_service = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService2);
134 function make_uri(uri, charset, base_uri) {
135     if (uri instanceof Ci.nsIURI)
136         return uri;
137     return io_service.newURI(uri, charset, base_uri);
140 var makeURL = make_uri; // until all callers are fixed
142 function makeFileURL(aFile)
144     return io_service.newFileURI(aFile).QueryInterface(Ci.nsIURL);
148 function get_document_content_disposition (document_o)
150     var content_disposition = null;
151     try {
152         content_disposition =
153             document_o.defaultView
154             .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
155             .getInterface(Components.interfaces.nsIDOMWindowUtils)
156             .getDocumentMetadata("content-disposition");
157     } catch (e) { }
158     return content_disposition;
162 function set_focus_no_scroll(window, element)
164     window.document.commandDispatcher.suppressFocusScroll = true;
165     element.focus();
166     window.document.commandDispatcher.suppressFocusScroll = false;
169 function do_repeatedly_positive(func, n) {
170     var args = Array.prototype.slice.call(arguments, 2);
171     while (n-- > 0)
172         func.apply(null, args);
175 function do_repeatedly(func, n, positive_args, negative_args) {
176     if (n < 0)
177         do func.apply(null, negative_args); while (++n < 0);
178     else
179         while (n-- > 0) func.apply(null, positive_args);
182 // remove whitespace from the beginning and end
183 function trim_whitespace (str)
185     var tmp = new String (str);
186     return tmp.replace (/^\s+/, "").replace (/\s+$/, "");
189 function abs_point (node)
191     var orig = node;
192     var pt = {};
193     try {
194         pt.x = node.offsetLeft;
195         pt.y = node.offsetTop;
196         // find imagemap's coordinates
197         if (node.tagName == "AREA") {
198             var coords = node.getAttribute("coords").split(",");
199             pt.x += Number(coords[0]);
200             pt.y += Number(coords[1]);
201         }
203         node = node.offsetParent;
204         // Sometimes this fails, so just return what we got.
206         while (node.tagName != "BODY") {
207             pt.x += node.offsetLeft;
208             pt.y += node.offsetTop;
209             node = node.offsetParent;
210         }
211     } catch(e) {
212 //      node = orig;
213 //      while (node.tagName != "BODY") {
214 //          alert("okay: " + node + " " + node.tagName + " " + pt.x + " " + pt.y);
215 //          node = node.offsetParent;
216 //      }
217     }
218     return pt;
221 var xul_app_info = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
222 var xul_runtime = Cc['@mozilla.org/xre/app-info;1'].getService(Ci.nsIXULRuntime);
225 function get_os ()
227     // possible return values: 'Darwin', 'Linux', 'WINNT', ...
228     return xul_runtime.OS;
231 var default_directory = null;
233 var env = Cc['@mozilla.org/process/environment;1'].getService(Ci.nsIEnvironment);
234 function getenv (variable) {
235     if (env.exists (variable))
236         return env.get(variable);
237     return null;
240 function get_home_directory () {
241     if (get_os() == "WINNT")
242         return (getenv ('USERPROFILE') ||
243                 getenv ('HOMEDRIVE') + getenv ('HOMEPATH'));
244     else
245         return getenv ('HOME');
248 function set_default_directory(directory_s) {
249     default_directory = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
250     default_directory.initWithPath(directory_s || get_home_directory());
253 set_default_directory();
255 const XHTML_NS = "http://www.w3.org/1999/xhtml";
256 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
257 const MATHML_NS = "http://www.w3.org/1998/Math/MathML";
258 const XLINK_NS = "http://www.w3.org/1999/xlink";
260 function create_XUL(window, tag_name)
262     return window.document.createElementNS(XUL_NS, tag_name);
266 /* Used in calls to XPath evaluate */
267 function xpath_lookup_namespace(prefix) {
268     if (prefix == "xhtml")
269         return XHTML_NS;
270     if (prefix == "m")
271         return MATHML_NS;
272     if (prefix == "xul")
273         return XUL_NS;
274     return null;
277 function method_caller(obj, func) {
278     return function () {
279         func.apply(obj, arguments);
280     };
283 function shell_quote(str) {
284     var s = str.replace("\"", "\\\"", "g");
285     s = s.replace("$", "\$", "g");
286     return s;
289 /* Like perl's quotemeta. Backslash all non-alphanumerics. */
290 function quotemeta(str) {
291     return str.replace(/([^a-zA-Z0-9])/g, "\\$1");
294 /* Given a list of choices (strings), return a regex which matches any
295    of them*/
296 function choice_regex(choices) {
297     var regex = "(?:" + choices.map(quotemeta).join("|") + ")";
298     return regex;
301 function get_window_from_frame(frame) {
302     try {
303         var window = frame.QueryInterface(Ci.nsIInterfaceRequestor)
304             .getInterface(Ci.nsIWebNavigation)
305             .QueryInterface(Ci.nsIDocShellTreeItem)
306             .rootTreeItem
307             .QueryInterface(Ci.nsIInterfaceRequestor)
308             .getInterface(Ci.nsIDOMWindow).wrappedJSObject;
309         /* window is now an XPCSafeJSObjectWrapper */
310         window.escape_wrapper(function (w) { window = w; });
311         /* window is now completely unwrapped */
312         return window;
313     } catch (e) {
314         return null;
315     }
318 function get_buffer_from_frame(window, frame) {
319     var count = window.buffers.count;
320     for (var i = 0; i < count; ++i) {
321         var b = window.buffers.get_buffer(i);
322         if (b.top_frame == frame)
323             return b;
324     }
325     return null;
328 var file_locator = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
330 function get_shortdoc_string(doc) {
331     var shortdoc = null;
332     if (doc != null) {
333         var idx = doc.indexOf("\n");
334         if (idx >= 0)
335             shortdoc = doc.substring(0,idx);
336         else
337             shortdoc = doc;
338     }
339     return shortdoc;
342 var conkeror_source_code_path = null;
344 function source_code_reference(uri, line_number) {
345     this.uri = uri;
346     this.line_number = line_number;
348 source_code_reference.prototype = {
349     get module_name () {
350         if (this.uri.indexOf(module_uri_prefix) == 0)
351             return this.uri.substring(module_uri_prefix.length);
352         return null;
353     },
355     get file_name () {
356         var file_uri_prefix = "file://";
357         if (this.uri.indexOf(file_uri_prefix) == 0)
358             return this.uri.substring(file_uri_prefix.length);
359         return null;
360     },
362     get best_uri () {
363         if (conkeror_source_code_path != null) {
364             var module_name = this.module_name;
365             if (module_name != null)
366                 return "file://" + conkeror_source_code_path + "/modules/" + module_name;
367         }
368         return this.uri;
369     },
371     open_in_editor : function() {
372         yield open_with_external_editor(this.best_uri, $line = this.line_number);
373     }
376 var get_caller_source_code_reference_ignored_functions = {};
378 function get_caller_source_code_reference(extra_frames_back) {
379     /* Skip at least this function itself and whoever called it (and
380      * more if the caller wants to be skipped). */
381     var frames_to_skip = 2;
382     if (extra_frames_back != null)
383         frames_to_skip += extra_frames_back;
385     for (let f = Components.stack; f != null; f = f.caller) {
386         if (frames_to_skip > 0) {
387             --frames_to_skip;
388             continue;
389         }
390         if (get_caller_source_code_reference_ignored_functions[f.name])
391             continue;
392         return new source_code_reference(f.filename, f.lineNumber);
393     }
395     return null;
398 function ignore_function_for_get_caller_source_code_reference(func_name) {
399     get_caller_source_code_reference_ignored_functions[func_name] = 1;
402 require_later("external-editor.js");
404 function dom_generator(document, ns) {
405     this.document = document;
406     this.ns = ns;
408 dom_generator.prototype = {
409     element : function(tag, parent) {
410         var node = this.document.createElementNS(this.ns, tag);
411         var i = 1;
412         if (parent != null && (parent instanceof Ci.nsIDOMNode)) {
413             parent.appendChild(node);
414             i = 2;
415         }
416         for (; i < arguments.length; i += 2)
417             node.setAttribute(arguments[i], arguments[i+1]);
418         return node;
419     },
421     text : function(str, parent) {
422         var node = this.document.createTextNode(str);
423         if (parent)
424             parent.appendChild(node);
425         return node;
426     },
429     stylesheet_link : function(href, parent) {
430         var node = this.element("link");
431         node.setAttribute("rel", "stylesheet");
432         node.setAttribute("type", "text/css");
433         node.setAttribute("href", href);
434         if (parent)
435             parent.appendChild(node);
436         return node;
437     },
440     add_stylesheet : function (url) {
441         var head = this.document.documentElement.firstChild;
442         this.stylesheet_link(url, head);
443     }
447  * Generates a QueryInterface function suitable for an implemenation
448  * of an XPCOM interface.  Unlike XPCOMUtils, this uses the Function
449  * constructor to generate a slightly more efficient version.  The
450  * arguments can be either Strings or elements of
451  * Components.interfaces.
452  */
453 function generate_QI() {
454     var args = Array.prototype.slice.call(arguments).map(String).concat(["nsISupports"]);
455     var fstr = "if(" +
456         Array.prototype.map.call(args,
457                                  function (x)
458                                      "iid.equals(Components.interfaces." + x + ")")
459         .join("||") +
460         ") return this; throw Components.results.NS_ERROR_NO_INTERFACE;";
461     return new Function("iid", fstr);
464 function set_branch_pref(branch, name, value) {
465     if (typeof(value) == "string") {
466         branch.setCharPref(name, value);
467     } else if (typeof(value) == "number") {
468         branch.setIntPref(name, value);
469     } else if (typeof(value) == "boolean") {
470         branch.setBoolPref(name, value);
471     }
474 function default_pref(name, value) {
475     var branch = preferences.getDefaultBranch(null);
476     set_branch_pref(branch, name, value);
479 function user_pref(name, value) {
480     var branch = preferences.getBranch(null);
481     set_branch_pref(branch, name, value);
484 function get_branch_pref(branch, name) {
485     switch (branch.getPrefType(name)) {
486     case branch.PREF_STRING:
487         return branch.getCharPref(name);
488     case branch.PREF_INT:
489         return branch.getIntPref(name);
490     case branch.PREF_BOOL:
491         return branch.getBoolPref(name);
492     default:
493         return null;
494     }
497 function get_localized_pref(name) {
498     try {
499         return preferences.getBranch(null).getComplexValue(name, Ci.nsIPrefLocalizedString).data;
500     } catch (e) {
501         return null;
502     }
505 function get_pref(name) {
506     var branch = preferences.getBranch(null);
507     return get_branch_pref(branch, name);
510 function get_default_pref(name) {
511     var branch = preferences.getDefaultBranch(null);
512     return get_branch_pref(branch, name);
515 function clear_pref(name) {
516     var branch = preferences.getBranch(null);
517     return branch.clearUserPref(name);
520 function pref_has_user_value(name) {
521     var branch = preferences.getBranch(null);
522     return branch.prefHasUserValue(name);
525 function pref_has_default_value(name) {
526     var branch = preferences.getDefaultBranch(null);
527     return branch.prefHasUserValue(name);
530 function session_pref (name, value) {
531     try { clear_pref (name); }
532     catch (e) {}
533     return default_pref (name, value);
536 function watch_pref(pref, hook) {
537     /* Extract pref into branch.pref */
538     let match = pref.match(/^(.*[.])?([^.]*)$/);
539     let br = match[1];
540     let key = match[2];
541     dumpln("watch:" + br + ":" + key);
542     let branch = preferences.getBranch(br).QueryInterface(Ci.nsIPrefBranch2);
543     let observer = {
544         observe: function (subject, topic, data) {
545             dumpln("watch_pref: " + subject + ":" + topic + ":" + data);
546             if (topic == "nsPref:changed" && data == key) {
547                 hook();
548             }
549         }
550     };
552     branch.addObserver("", observer, false);
555 const LOCALE_PREF = "general.useragent.locale";
557 function get_locale() {
558     return get_localized_pref(LOCALE_PREF) || get_pref(LOCALE_PREF);
561 const USER_AGENT_OVERRIDE_PREF = "general.useragent.override";
563 function set_user_agent(str) {
564     session_pref(USER_AGENT_OVERRIDE_PREF, str);
567 function define_builtin_commands(prefix, do_command_function, toggle_mark, mark_active_predicate, mode) {
569     // Specify a docstring
570     function D(cmd, docstring) {
571         var o = new String(cmd);
572         o.doc = docstring;
573         return o;
574     }
576     // Specify a forward/reverse pair
577     function R(a, b) {
578         var o = [a,b];
579         o.is_reverse_pair = true;
580         return o;
581     }
583     // Specify a movement/select/scroll/move-caret command group.
584     function S(command, movement, select, scroll, caret) {
585         var o = [movement, select, scroll, caret];
586         o.command = command;
587         o.is_move_select_pair = true;
588         return o;
589     }
591     var builtin_commands = [
593         /*
594          * cmd_scrollBeginLine and cmd_scrollEndLine don't do what I
595          * want, either in or out of caret mode...
596          */
597         S(D("beginning-of-line", "Move or extend the selection to the beginning of the current line."),
598           D("cmd_beginLine", "Move point to the beginning of the current line."),
599           D("cmd_selectBeginLine", "Extend selection to the beginning of the current line."),
600           D("cmd_beginLine", "Scroll to the beginning of the line"),
601           D("cmd_beginLine", "Scroll to the beginning of the line")),
602         S(D("end-of-line", "Move or extend the selection to the end of the current line."),
603           D("cmd_endLine", "Move point to the end of the current line."),
604           D("cmd_selectEndLine", "Extend selection to the end of the current line."),
605           D("cmd_endLine", "Scroll to the end of the current line."),
606           D("cmd_endLine", "Scroll to the end of the current line.")),
607         D("cmd_copy", "Copy the selection into the clipboard."),
608         "cmd_copyOrDelete",
609         D("cmd_cut", "Cut the selection into the clipboard."),
610         "cmd_cutOrDelete",
611         D("cmd_deleteToBeginningOfLine", "Delete to the beginning of the current line."),
612         D("cmd_deleteToEndOfLine", "Delete to the end of the current line."),
613         S(D("beginning-of-first-line", "Move or extend the selection to the beginning of the first line."),
614           D("cmd_moveTop", "Move point to the beginning of the first line."),
615           D("cmd_selectTop", "Extend selection to the beginning of the first line."),
616           D("cmd_scrollTop", "Scroll to the top of the buffer"),
617           D("cmd_scrollTop", "Move point to the beginning of the first line.")),
618         S(D("end-of-last-line", "Move or extend the selection to the end of the last line."),
619           D("cmd_moveBottom", "Move point to the end of the last line."),
620           D("cmd_selectBottom", "Extend selection to the end of the last line."),
621           D("cmd_scrollBottom", "Scroll to the bottom of the buffer"),
622           D("cmd_scrollBottom", "Move point to the end of the last line.")),
623         D("cmd_selectAll", "Select all."),
624         "cmd_scrollBeginLine",
625         "cmd_scrollEndLine",
626         D("cmd_scrollTop", "Scroll to the top of the buffer."),
627         D("cmd_scrollBottom", "Scroll to the bottom of the buffer.")];
629     var builtin_commands_with_count = [
630         R(S(D("forward-char", "Move or extend the selection forward one character."),
631             D("cmd_charNext", "Move point forward one character."),
632             D("cmd_selectCharNext", "Extend selection forward one character."),
633             D("cmd_scrollRight", "Scroll to the right"),
634             D("cmd_scrollRight", "Scroll to the right")),
635           S(D("backward-char", "Move or extend the selection backward one character."),
636             D("cmd_charPrevious", "Move point backward one character."),
637             D("cmd_selectCharPrevious", "Extend selection backward one character."),
638             D("cmd_scrollLeft", "Scroll to the left."),
639             D("cmd_scrollLeft", "Scroll to the left."))),
640         R(D("cmd_deleteCharForward", "Delete the following character."),
641           D("cmd_deleteCharBackward", "Delete the previous character.")),
642         R(D("cmd_deleteWordForward", "Delete the following word."),
643           D("cmd_deleteWordBackward", "Delete the previous word.")),
644         R(S(D("forward-line", "Move or extend the selection forward one line."),
645             D("cmd_lineNext", "Move point forward one line."),
646             D("cmd_selectLineNext", "Extend selection forward one line."),
647             D("cmd_scrollLineDown", "Scroll down one line."),
648             D("cmd_scrollLineDown", "Scroll down one line.")),
649           S(D("backward-line", "Move or extend the selection backward one line."),
650             D("cmd_linePrevious", "Move point backward one line."),
651             D("cmd_selectLinePrevious", "Extend selection backward one line."),
652             D("cmd_scrollLineUp", "Scroll up one line."),
653             D("cmd_scrollLineUp", "Scroll up one line."))),
654         R(S(D("forward-page", "Move or extend the selection forward one page."),
655             D("cmd_movePageDown", "Move point forward one page."),
656             D("cmd_selectPageDown", "Extend selection forward one page."),
657             D("cmd_scrollPageDown", "Scroll forward one page."),
658             D("cmd_movePageDown", "Move point forward one page.")),
659           S(D("backward-page", "Move or extend the selection backward one page."),
660             D("cmd_movePageUp", "Move point backward one page."),
661             D("cmd_selectPageUp", "Extend selection backward one page."),
662             D("cmd_scrollPageUp", "Scroll backward one page."),
663             D("cmd_movePageUp", "Move point backward one page."))),
664         R(D("cmd_undo", "Undo last editing action."),
665           D("cmd_redo", "Redo last editing action.")),
666         R(S(D("forward-word", "Move or extend the selection forward one word."),
667             D("cmd_wordNext", "Move point forward one word."),
668             D("cmd_selectWordNext", "Extend selection forward one word."),
669             D("cmd_scrollRight", "Scroll to the right."),
670             D("cmd_wordNext", "Move point forward one word.")),
671           S(D("backward-word", "Move or extend the selection backward one word."),
672             D("cmd_wordPrevious", "Move point backward one word."),
673             D("cmd_selectWordPrevious", "Extend selection backward one word."),
674             D("cmd_scrollLeft", "Scroll to the left."),
675             D("cmd_wordPrevious", "Move point backward one word."))),
676         R(D("cmd_scrollPageUp", "Scroll up one page."),
677           D("cmd_scrollPageDown", "Scroll down one page.")),
678         R(D("cmd_scrollLineUp", "Scroll up one line."),
679           D("cmd_scrollLineDown", "Scroll down one line.")),
680         R(D("cmd_scrollLeft", "Scroll left."),
681           D("cmd_scrollRight", "Scroll right.")),
682         D("cmd_paste", "Insert the contents of the clipboard.")];
684     interactive(prefix + "set-mark",
685                 "Toggle whether the mark is active.\n" +
686                 "When the mark is active, movement commands affect the selection.",
687                 toggle_mark);
689     function get_mode_idx() {
690         if (mode == 'scroll') return 2;
691         else if (mode == 'caret') return 3;
692         else return 0;
693     }
695     function get_move_select_idx(I) {
696         return mark_active_predicate(I) ? 1 : get_mode_idx();
697     }
699     function doc_for_builtin(c) {
700         var s = "";
701         if (c.doc != null)
702             s += c.doc + "\n";
703         return s + "Run the built-in command " + c + ".";
704     }
706     function define_simple_command(c) {
707         interactive(prefix + c, doc_for_builtin(c), function (I) { do_command_function(I, c); });
708     }
710     function get_move_select_doc_string(c) {
711         return c.command.doc +
712             "\nSpecifically, if the mark is active, runs `" + prefix + c[1] + "'.  " +
713             "Otherwise, runs `" + prefix + c[get_mode_idx()] + "'\n" +
714             "To toggle whether the mark is active, use `" + prefix + "set-mark'.";
715     }
717     for each (let c_temp in builtin_commands)  {
718         let c = c_temp;
719         if (c.is_move_select_pair) {
720             interactive(prefix + c.command, get_move_select_doc_string(c), function (I) {
721                 var idx = get_move_select_idx(I);
722                 do_command_function(I, c[idx]);
723             });
724             define_simple_command(c[0]);
725             define_simple_command(c[1]);
726         }
727         else
728             define_simple_command(c);
729     }
731     function get_reverse_pair_doc_string(main_doc, alt_command) {
732         return main_doc + "\n" +
733             "The prefix argument specifies a repeat count for this command.  " +
734             "If the count is negative, `" + prefix + alt_command + "' is performed instead with " +
735             "a corresponding positive repeat count.";
736     }
738     function define_simple_reverse_pair(a, b) {
739         interactive(prefix + a, get_reverse_pair_doc_string(doc_for_builtin(a), b),
740                     function (I) {
741                         do_repeatedly(do_command_function, I.p, [I, a], [I, b]);
742                     });
743         interactive(prefix + b, get_reverse_pair_doc_string(doc_for_builtin(b), a),
744                     function (I) {
745                         do_repeatedly(do_command_function, I.p, [I, b], [I, a]);
746                     });
747     }
749     for each (let c_temp in builtin_commands_with_count)
750     {
751         let c = c_temp;
752         if (c.is_reverse_pair) {
753             if (c[0].is_move_select_pair) {
754                 interactive(prefix + c[0].command, get_reverse_pair_doc_string(get_move_select_doc_string(c[0]),
755                                                                                c[1].command),
756                             function (I) {
757                                 var idx = get_move_select_idx(I);
758                                 do_repeatedly(do_command_function, I.p, [I, c[0][idx]], [I, c[1][idx]]);
759                             });
760                 interactive(prefix + c[1].command, get_reverse_pair_doc_string(get_move_select_doc_string(c[1]),
761                                                                                c[0].command),
762                             function (I) {
763                                 var idx = get_move_select_idx(I);
764                                 do_repeatedly(do_command_function, I.p, [I, c[1][idx]], [I, c[0][idx]]);
765                             });
766                 define_simple_reverse_pair(c[0][0], c[1][0]);
767                 define_simple_reverse_pair(c[0][1], c[1][1]);
768             } else
769                 define_simple_reverse_pair(c[0], c[1]);
770         } else {
771             let doc = doc_for_builtin(c) +
772                 "\nThe prefix argument specifies a positive repeat count for this command.";
773             interactive(prefix + c, doc, function (I) {
774                 do_repeatedly_positive(do_command_function, I.p, I, c);
775             });
776         }
777     }
780 var observer_service = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
782 function abort(str) {
783     var e = new Error(str);
784     e.__proto__ = abort.prototype;
785     return e;
787 abort.prototype.__proto__ = Error.prototype;
790 function get_temporary_file(name) {
791     if (name == null)
792         name = "temp.txt";
793     var file = file_locator.get("TmpD", Ci.nsIFile);
794     file.append(name);
795     // Create the file now to ensure that no exploits are possible
796     file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
797     return file;
801 /* FIXME: This should be moved somewhere else, perhaps. */
802 function create_info_panel(window, panel_class, row_arr) {
803     /* Show information panel above minibuffer */
805     var g = new dom_generator(window.document, XUL_NS);
807     var p = g.element("vbox", "class", "panel " + panel_class, "flex", "0");
808     var grid = g.element("grid", p);
809     var cols = g.element("columns", grid);
810     g.element("column", cols, "flex", "0");
811     g.element("column", cols, "flex", "1");
813     var rows = g.element("rows", grid);
814     var row;
816     for each (let [row_class, row_label, row_value] in row_arr) {
817         row = g.element("row", rows, "class", row_class);
818         g.element("label", row,
819                   "value", row_label,
820                   "class", "panel-row-label");
821         g.element("label", row,
822                   "value", row_value,
823                   "class", "panel-row-value");
824     }
825     window.minibuffer.insert_before(p);
827     p.destroy = function () {
828         this.parentNode.removeChild(this);
829     };
831     return p;
836  * Paste from the X primary selection, unless the system doesn't support a
837  * primary selection, in which case fall back to the clipboard.
838  */
839 function read_from_x_primary_selection ()
841     // Get clipboard.
842     let clipboard = Components.classes["@mozilla.org/widget/clipboard;1"]
843         .getService(Components.interfaces.nsIClipboard);
845     // Fall back to global clipboard if the system doesn't support a selection
846     let which_clipboard = clipboard.supportsSelectionClipboard() ?
847         clipboard.kSelectionClipboard : clipboard.kGlobalClipboard;
849     let flavors = ["text/unicode"];
851     // Don't barf if there's nothing on the clipboard
852     if (!clipboard.hasDataMatchingFlavors(flavors, flavors.length, which_clipboard))
853         return "";
855     // Create transferable that will transfer the text.
856     let trans = Components.classes["@mozilla.org/widget/transferable;1"]
857         .createInstance(Components.interfaces.nsITransferable);
859     for each (let flavor in flavors) {
860         trans.addDataFlavor(flavor);
861     }
862     clipboard.getData(trans, which_clipboard);
864     var data_flavor = {};
865     var data = {};
866     var dataLen = {};
867     trans.getAnyTransferData(data_flavor, data, dataLen);
869     if (data) {
870         data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
871         let data_length = dataLen.value;
872         if (data_flavor.value == "text/unicode")
873             data_length = dataLen.value / 2;
874         return data.data.substring(0, data_length);
875     } else {
876         return "";
877     }
880 var user_variables = new string_hashmap();
882 function define_variable(name, default_value, doc) {
883     conkeror[name] = default_value;
884     user_variables.put(name, {
885         default_value: default_value,
886         doc: doc,
887         shortdoc: get_shortdoc_string(doc),
888         source_code_reference: get_caller_source_code_reference() });
891 function define_special_variable(name, getter, setter, doc) {
892     conkeror.__defineGetter__(name, getter);
893     conkeror.__defineSetter__(name, setter);
894     user_variables.put(name,
895                        {
896                            default_value: undefined,
897                            doc: doc,
898                            shortdoc: get_shortdoc_string(doc),
899                            source_code_reference: get_caller_source_code_reference()
900                        });
903 /* Re-define load_paths as a user variable. */
904 define_variable("load_paths", load_paths,
905                 "Array of URL prefixes searched in order when loading a module.\n" +
906                 "Each entry must end in a slash, and should begin with file:// or chrome://.");
908 function register_user_stylesheet(url)
910     var uri = makeURL(url);
911     var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
912     sss.loadAndRegisterSheet(uri, sss.USER_SHEET);
915 function unregister_user_stylesheet(url)
917     var uri = makeURL(url);
918     var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
919     if (sss.sheetRegistered(uri, sss.USER_SHEET))
920         sss.unregisterSheet(uri, sss.USER_SHEET);
923 function predicate_alist_match(alist, key) {
924     for each (let i in alist) {
925         if (i[0](key))
926             return i[1];
927     }
928     return undefined;
932 function get_meta_title(doc) {
933     var title = doc.evaluate("//meta[@name='title']/@content", doc, xpath_lookup_namespace,
934                              Ci.nsIDOMXPathResult.STRING_TYPE , null);
935     if (title && title.stringValue)
936         return title.stringValue;
937     return null;
940 var rdf_service = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
942 const PREFIX_ITEM_URI     = "urn:mozilla:item:";
943 const PREFIX_NS_EM        = "http://www.mozilla.org/2004/em-rdf#";
945 var extension_manager = Cc["@mozilla.org/extensions/manager;1"].getService(Ci.nsIExtensionManager);
947 function get_extension_rdf_property(id, name, type) {
948     var value = extension_manager.datasource.GetTarget(
949         rdf_service.GetResource(PREFIX_ITEM_URI + id),
950         rdf_service.GetResource(PREFIX_NS_EM + name),
951         true);
952     if (value == null)
953         return null;
954     return value.QueryInterface(type || Ci.nsIRDFLiteral).Value;
957 function get_extension_update_item(id) {
958     return extension_manager.getItemForID(id);
961 function extension_info(id) {
962     this.id = id;
964 extension_info.prototype = {
965     // Returns the nsIUpdateItem object associated with this extension
966     get update_item () { return get_extension_update_item(this.id); },
968     get_rdf_property : function (name, type) {
969         return get_extension_rdf_property(this.id, name, type);
970     },
972     // RDF properties
973     get isDisabled () { return this.get_rdf_property("isDisabled"); },
974     get aboutURL () { return this.get_rdf_property("aboutURL"); },
975     get addonID () { return this.get_rdf_property("addonID"); },
976     get availableUpdateURL () { return this.get_rdf_property("availableUpdateURL"); },
977     get availableUpdateVersion () { return this.get_rdf_property("availableUpdateVersion"); },
978     get blocklisted () { return this.get_rdf_property("blocklisted"); },
979     get compatible () { return this.get_rdf_property("compatible"); },
980     get description () { return this.get_rdf_property("description"); },
981     get downloadURL () { return this.get_rdf_property("downloadURL"); },
982     get isDisabled () { return this.get_rdf_property("isDisabled"); },
983     get hidden () { return this.get_rdf_property("hidden"); },
984     get homepageURL () { return this.get_rdf_property("homepageURL"); },
985     get iconURL () { return this.get_rdf_property("iconURL"); },
986     get internalName () { return this.get_rdf_property("internalName"); },
987     get locked () { return this.get_rdf_property("locked"); },
988     get name () { return this.get_rdf_property("name"); },
989     get optionsURL () { return this.get_rdf_property("optionsURL"); },
990     get opType () { return this.get_rdf_property("opType"); },
991     get plugin () { return this.get_rdf_property("plugin"); },
992     get previewImage () { return this.get_rdf_property("previewImage"); },
993     get satisfiesDependencies () { return this.get_rdf_property("satisfiesDependencies"); },
994     get providesUpdatesSecurely () { return this.get_rdf_property("providesUpdatesSecurely"); },
995     get type () { return this.get_rdf_property("type", Ci.nsIRDFInt); },
996     get updateable () { return this.get_rdf_property("updateable"); },
997     get updateURL () { return this.get_rdf_property("updateURL"); },
998     get version () { return this.get_rdf_property("version"); }
1001 function extension_is_enabled(id) {
1002     var info = new extension_info(id);
1003     return info.update_item && (info.isDisabled == "false");
1006 function queue() {
1007     this.input = [];
1008     this.output = [];
1010 queue.prototype = {
1011     get length () {
1012         return this.input.length + this.output.length;
1013     },
1014     push: function (x) {
1015         this.input[this.input.length] = x;
1016     },
1017     pop: function (x) {
1018         let l = this.output.length;
1019         if (!l) {
1020             l = this.input.length;
1021             if (!l)
1022                 return undefined;
1023             this.output = this.input.reverse();
1024             this.input = [];
1025             let x = this.output[l];
1026             this.output.length--;
1027             return x;
1028         }
1029     }
1032 function frame_iterator(root_frame, start_with) {
1033     var q = new queue, x;
1034     if (start_with) {
1035         x = start_with;
1036         do {
1037             yield x;
1038             for (let i = 0; i < x.frames.length; ++i)
1039                 q.push(x.frames[i]);
1040         } while ((x = q.pop()));
1041     }
1042     x = root_frame;
1043     do {
1044         if (x == start_with)
1045             continue;
1046         yield x;
1047         for (let i = 0; i < x.frames.length; ++i)
1048             q.push(x.frames[i]);
1049     } while ((x = q.pop()));
1052 function xml_http_request() {
1053     return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest).QueryInterface(Ci.nsIJSXMLHttpRequest).QueryInterface(Ci.nsIDOMEventTarget);
1056 var xml_http_request_load_listener = {
1057   // nsIBadCertListener2
1058   notifyCertProblem: function SSLL_certProblem(socketInfo, status, targetSite) {
1059     return true;
1060   },
1062   // nsISSLErrorListener
1063   notifySSLError: function SSLL_SSLError(socketInfo, error, targetSite) {
1064     return true;
1065   },
1067   // nsIInterfaceRequestor
1068   getInterface: function SSLL_getInterface(iid) {
1069     return this.QueryInterface(iid);
1070   },
1072   // nsISupports
1073   //
1074   // FIXME: array comprehension used here to hack around the lack of
1075   // Ci.nsISSLErrorListener in 2007 versions of xulrunner 1.9pre.
1076   // make it a simple generateQI when xulrunner is more stable.
1077   QueryInterface: XPCOMUtils.generateQI (
1078       [i for each (i in [Ci.nsIBadCertListener2,
1079                          Ci.nsISSLErrorListener,
1080                          Ci.nsIInterfaceRequestor])
1081        if (i)])
1085 define_keywords("$user", "$password", "$override_mime_type", "$headers");
1086 function send_http_request(lspec) {
1087     // why do we get warnings in jsconsole unless we initialize the
1088     // following keywords?
1089     keywords(arguments, $user = undefined, $password = undefined,
1090              $override_mime_type = undefined, $headers = undefined);
1091     var req = xml_http_request();
1092     var cc = yield CONTINUATION;
1093     var aborting = false;
1094     req.onreadystatechange = function send_http_request__onreadysatechange() {
1095         if (req.readyState != 4)
1096             return;
1097         if (aborting)
1098             return;
1099         cc();
1100     };
1102     if (arguments.$override_mime_type)
1103         req.overrideMimeType(arguments.$override_mime_type);
1105     var post_data = load_spec_raw_post_data(lspec);
1107     var method = post_data ? "POST" : "GET";
1109     req.open(method, load_spec_uri_string(lspec), true, arguments.$user, arguments.$password);
1110     req.channel.notificationCallbacks = xml_http_request_load_listener;
1112     for each (let [name,value] in arguments.$headers) {
1113         req.setRequestHeader(name, value);
1114     }
1116     if (post_data) {
1117         req.setRequestHeader("Content-Type", load_spec_request_mime_type(lspec));
1118         req.send(post_data);
1119     } else
1120         req.send(null);
1122     try {
1123         yield SUSPEND;
1124     } catch (e) {
1125         aborting = true;
1126         req.abort();
1127         throw e;
1128     }
1130     // Let the caller access the status and reponse data
1131     yield co_return(req);
1135 var JSON = ("@mozilla.org/dom/json;1" in Cc) && Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
1139 var console_service = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
1141 console_service.registerListener(
1142     {observe: function (msg) {
1143          if (msg instanceof Ci.nsIScriptError) {
1144              switch (msg.category) {
1145              case "CSS Parser":
1146              case "content javascript":
1147                  return;
1148              }
1149              msg.QueryInterface(Ci.nsIScriptError);
1150              dumpln("Console error: " + msg.message);
1151              dumpln("  Category: " + msg.category);
1152          }
1153      }});
1156 // ensure_index_is_visible ensures that the given index in the given
1157 // field (an html input field for example) is visible.
1158 function ensure_index_is_visible (window, field, index) {
1159     var start = field.selectionStart;
1160     var end = field.selectionEnd;
1161     field.setSelectionRange (index, index);
1162     send_key_as_event (window, field, "left");
1163     if (field.selectionStart < index) {
1164         send_key_as_event (window, field, "right");
1165     }
1166     field.setSelectionRange (start, end);
1169 function regex_to_string(obj) {
1170     if(obj instanceof RegExp) {
1171         obj = obj.source;
1172     } else {
1173         obj = quotemeta(obj);
1174     }
1175     return obj;
1179  * Build a regular expression to match URLs for a given web site.
1181  * Both the $domain and $path arguments can be either regexes, in
1182  * which case they will be matched as is, or strings, in which case
1183  * they will be matched literally.
1185  * $tlds specifies a list of valid top-level-domains to match, and
1186  * defaults to .com. Useful for when e.g. foo.org and foo.com are the
1187  * same.
1189  * If $allow_www is true, www.domain.tld will also be allowed.
1191  */
1192 define_keywords("$domain", "$path", "$tlds", "$allow_www");
1193 function build_url_regex() {
1194     keywords(arguments, $path = "", $tlds = ["com"], $allow_www = false);
1195     var domain = regex_to_string(arguments.$domain);
1196     if(arguments.$allow_www) {
1197         domain = "(?:www\.)?" + domain;
1198     }
1199     var path   = regex_to_string(arguments.$path);
1200     var tlds   = arguments.$tlds;
1201     var regex = "^https?://" + domain + "\\." + choice_regex(tlds) + "/" + path;
1202     return new RegExp(regex);
1207  * Given an ordered array of non-overlapping ranges, represented as
1208  * elements of [start, end], insert a new range into the array,
1209  * extending, replacing, or merging existing ranges as needed. Mutates
1210  * `arr' in place.
1212  * Examples:
1214  * splice_range([[1,3],[4,6], 5, 8)
1215  *  => [[1,3],[4,8]]
1217  * splice_range([[1,3],[4,6],[7,10]], 2, 8)
1218  *  => [[1,10]]
1219  */
1220 function splice_range(arr, start, end) {
1221     for(var i = 0; i < arr.length; ++i) {
1222         let [n,m] = arr[i];
1223         if(start > m)
1224             continue;
1225         if(end < n) {
1226             arr.splice(i, 0, [start, end]);
1227             break;
1228         }
1229         if(start < n) {
1230             arr[i][0] = start;
1231         }
1233         if(end >= n) {
1234             /*
1235              * The range we are inserting overlaps the current
1236              * range. We need to scan right to see if it also contains any other
1237              * ranges entirely, and remove them if necessary.
1238              */
1239             var j = i;
1240             while(j < arr.length && end >= arr[j][0]) j++;
1241             j--;
1242             arr[i][1] = Math.max(end, arr[j][1]);
1243             arr.splice(i + 1, j - i);
1244             break;
1245         }
1246     }
1247     if(start > arr[arr.length - 1][1]) {
1248         arr.push([start, end]);
1249     }
1253 function compute_url_up_path (url)
1255     var new_url = Cc["@mozilla.org/network/standard-url;1"]
1256         .createInstance (Ci.nsIURL);
1257     new_url.spec = url;
1258     var up;
1259     if (new_url.param != "" || new_url.query != "")
1260         up = new_url.filePath;
1261     else if (new_url.fileName != "")
1262         up = ".";
1263     else
1264         up = "..";
1265     return up;
1269 function compute_url_pre_path (url)
1271     var new_url = Cc["@mozilla.org/network/standard-url;1"]
1272         .createInstance (Ci.nsIURL);
1273     new_url.spec = url;
1274     return new_url.prePath;
1278 /* possibly_valid_url returns true if the string might be a valid
1279  * thing to pass to nsIWebNavigation.loadURI.  Currently just checks
1280  * that there's no whitespace in the middle and that it's not entirely
1281  * whitespace.
1282  */
1283 function possibly_valid_url (url) {
1284     return !(/\S\s+\S/.test(url)) && !(/^\s*$/.test(url));
1288 /* remove_duplicates_filter returns a function that can be
1289  * used in Array.filter.  It removes duplicates.
1290  */
1291 function remove_duplicates_filter () {
1292     var acc = {};
1293     return function (x) {
1294         if (acc[x]) return false;
1295         acc[x] = 1;
1296         return true;
1297     };