user_variables: replace string_hashmap with ordinary javascript object implementation
[conkeror.git] / modules / utils.js
blob40cec4139f24d28f11ad14bfa1f55b2e272c8bf4
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     var gClipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"]
106         .getService(Ci.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 function make_file(path) {
133     var f = Cc["@mozilla.org/file/local;1"]
134         .createInstance(Ci.nsILocalFile);
135     f.initWithPath(path);
136     return f;
139 var io_service = Cc["@mozilla.org/network/io-service;1"]
140     .getService(Ci.nsIIOService2);
142 function make_uri(uri, charset, base_uri) {
143     if (uri instanceof Ci.nsIURI)
144         return uri;
145     if (uri instanceof Ci.nsIFile)
146         return io_service.newFileURI(uri);
147     return io_service.newURI(uri, charset, base_uri);
151 function get_document_content_disposition (document_o)
153     var content_disposition = null;
154     try {
155         content_disposition =
156             document_o.defaultView
157             .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
158             .getInterface(Components.interfaces.nsIDOMWindowUtils)
159             .getDocumentMetadata("content-disposition");
160     } catch (e) { }
161     return content_disposition;
165 function set_focus_no_scroll(window, element)
167     window.document.commandDispatcher.suppressFocusScroll = true;
168     element.focus();
169     window.document.commandDispatcher.suppressFocusScroll = false;
172 function do_repeatedly_positive(func, n) {
173     var args = Array.prototype.slice.call(arguments, 2);
174     while (n-- > 0)
175         func.apply(null, args);
178 function do_repeatedly(func, n, positive_args, negative_args) {
179     if (n < 0)
180         do func.apply(null, negative_args); while (++n < 0);
181     else
182         while (n-- > 0) func.apply(null, positive_args);
185 // remove whitespace from the beginning and end
186 function trim_whitespace (str)
188     var tmp = new String (str);
189     return tmp.replace (/^\s+/, "").replace (/\s+$/, "");
193  * Given a node, returns its position relative to the document.
195  * @param node The node to get the position of.
196  * @return An object with properties "x" and "y" representing its offset from
197  *         the left and top of the document, respectively.
198  */
199 function abs_point (node)
201     var orig = node;
202     var pt = {};
203     try {
204         pt.x = node.offsetLeft;
205         pt.y = node.offsetTop;
206         // find imagemap's coordinates
207         if (node.tagName == "AREA") {
208             var coords = node.getAttribute("coords").split(",");
209             pt.x += Number(coords[0]);
210             pt.y += Number(coords[1]);
211         }
213         node = node.offsetParent;
214         // Sometimes this fails, so just return what we got.
216         while (node.tagName != "BODY") {
217             pt.x += node.offsetLeft;
218             pt.y += node.offsetTop;
219             node = node.offsetParent;
220         }
221     } catch(e) {
222 //      node = orig;
223 //      while (node.tagName != "BODY") {
224 //          alert("okay: " + node + " " + node.tagName + " " + pt.x + " " + pt.y);
225 //          node = node.offsetParent;
226 //      }
227     }
228     return pt;
231 var xul_app_info = Cc["@mozilla.org/xre/app-info;1"]
232     .getService(Ci.nsIXULAppInfo);
233 var xul_runtime = Cc['@mozilla.org/xre/app-info;1']
234     .getService(Ci.nsIXULRuntime);
237 function get_os ()
239     // possible return values: 'Darwin', 'Linux', 'WINNT', ...
240     return xul_runtime.OS;
243 var default_directory = null;
245 var env = Cc['@mozilla.org/process/environment;1'].getService(Ci.nsIEnvironment);
246 function getenv (variable) {
247     if (env.exists (variable))
248         return env.get(variable);
249     return null;
252 function get_home_directory () {
253     var dir = Cc["@mozilla.org/file/local;1"]
254         .createInstance(Ci.nsILocalFile);
255     if (get_os() == "WINNT")
256         dir.initWithPath(getenv('USERPROFILE') ||
257                          getenv('HOMEDRIVE') + getenv('HOMEPATH'));
258     else
259         dir.initWithPath(getenv('HOME'));
260     return dir;
263 function set_default_directory (directory) {
264     if (directory) {
265         if (directory instanceof Ci.nsILocalFile)
266             default_directory = directory.clone();
267         else {
268             default_directory = Cc["@mozilla.org/file/local;1"]
269                 .createInstance(Ci.nsILocalFile);
270             default_directory.initWithPath(directory);
271         }
272     } else {
273         default_directory = get_home_directory();
274     }
277 set_default_directory();
279 const XHTML_NS = "http://www.w3.org/1999/xhtml";
280 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
281 const MATHML_NS = "http://www.w3.org/1998/Math/MathML";
282 const XLINK_NS = "http://www.w3.org/1999/xlink";
284 function create_XUL(window, tag_name)
286     return window.document.createElementNS(XUL_NS, tag_name);
290 /* Used in calls to XPath evaluate */
291 function xpath_lookup_namespace(prefix) {
292     if (prefix == "xhtml")
293         return XHTML_NS;
294     if (prefix == "m")
295         return MATHML_NS;
296     if (prefix == "xul")
297         return XUL_NS;
298     return null;
301 function method_caller(obj, func) {
302     return function () {
303         func.apply(obj, arguments);
304     };
307 function shell_quote(str) {
308     var s = str.replace("\"", "\\\"", "g");
309     s = s.replace("$", "\$", "g");
310     return s;
313 /* Like perl's quotemeta. Backslash all non-alphanumerics. */
314 function quotemeta(str) {
315     return str.replace(/([^a-zA-Z0-9])/g, "\\$1");
318 /* Given a list of choices (strings), return a regex which matches any
319    of them*/
320 function choice_regex(choices) {
321     var regex = "(?:" + choices.map(quotemeta).join("|") + ")";
322     return regex;
325 function get_window_from_frame(frame) {
326     try {
327         var window = frame.QueryInterface(Ci.nsIInterfaceRequestor)
328             .getInterface(Ci.nsIWebNavigation)
329             .QueryInterface(Ci.nsIDocShellTreeItem)
330             .rootTreeItem
331             .QueryInterface(Ci.nsIInterfaceRequestor)
332             .getInterface(Ci.nsIDOMWindow).wrappedJSObject;
333         /* window is now an XPCSafeJSObjectWrapper */
334         window.escape_wrapper(function (w) { window = w; });
335         /* window is now completely unwrapped */
336         return window;
337     } catch (e) {
338         return null;
339     }
342 function get_buffer_from_frame(window, frame) {
343     var count = window.buffers.count;
344     for (var i = 0; i < count; ++i) {
345         var b = window.buffers.get_buffer(i);
346         if (b.top_frame == frame)
347             return b;
348     }
349     return null;
352 var file_locator = Cc["@mozilla.org/file/directory_service;1"]
353     .getService(Ci.nsIProperties);
355 function get_shortdoc_string(doc) {
356     var shortdoc = null;
357     if (doc != null) {
358         var idx = doc.indexOf("\n");
359         if (idx >= 0)
360             shortdoc = doc.substring(0,idx);
361         else
362             shortdoc = doc;
363     }
364     return shortdoc;
367 var conkeror_source_code_path = null;
369 function source_code_reference(uri, line_number) {
370     this.uri = uri;
371     this.line_number = line_number;
373 source_code_reference.prototype = {
374     get module_name () {
375         if (this.uri.indexOf(module_uri_prefix) == 0)
376             return this.uri.substring(module_uri_prefix.length);
377         return null;
378     },
380     get file_name () {
381         var file_uri_prefix = "file://";
382         if (this.uri.indexOf(file_uri_prefix) == 0)
383             return this.uri.substring(file_uri_prefix.length);
384         return null;
385     },
387     get best_uri () {
388         if (conkeror_source_code_path != null) {
389             var module_name = this.module_name;
390             if (module_name != null)
391                 return "file://" + conkeror_source_code_path + "/modules/" + module_name;
392         }
393         return this.uri;
394     },
396     open_in_editor : function() {
397         yield open_with_external_editor(this.best_uri, $line = this.line_number);
398     }
401 var get_caller_source_code_reference_ignored_functions = {};
403 function get_caller_source_code_reference(extra_frames_back) {
404     /* Skip at least this function itself and whoever called it (and
405      * more if the caller wants to be skipped). */
406     var frames_to_skip = 2;
407     if (extra_frames_back != null)
408         frames_to_skip += extra_frames_back;
410     for (let f = Components.stack; f != null; f = f.caller) {
411         if (frames_to_skip > 0) {
412             --frames_to_skip;
413             continue;
414         }
415         if (get_caller_source_code_reference_ignored_functions[f.name])
416             continue;
417         return new source_code_reference(f.filename, f.lineNumber);
418     }
420     return null;
423 function ignore_function_for_get_caller_source_code_reference(func_name) {
424     get_caller_source_code_reference_ignored_functions[func_name] = 1;
427 require_later("external-editor.js");
429 function dom_generator(document, ns) {
430     this.document = document;
431     this.ns = ns;
433 dom_generator.prototype = {
434     element : function(tag, parent) {
435         var node = this.document.createElementNS(this.ns, tag);
436         var i = 1;
437         if (parent != null && (parent instanceof Ci.nsIDOMNode)) {
438             parent.appendChild(node);
439             i = 2;
440         }
441         for (; i < arguments.length; i += 2)
442             node.setAttribute(arguments[i], arguments[i+1]);
443         return node;
444     },
446     text : function(str, parent) {
447         var node = this.document.createTextNode(str);
448         if (parent)
449             parent.appendChild(node);
450         return node;
451     },
454     stylesheet_link : function(href, parent) {
455         var node = this.element("link");
456         node.setAttribute("rel", "stylesheet");
457         node.setAttribute("type", "text/css");
458         node.setAttribute("href", href);
459         if (parent)
460             parent.appendChild(node);
461         return node;
462     },
465     add_stylesheet : function (url) {
466         var head = this.document.documentElement.firstChild;
467         this.stylesheet_link(url, head);
468     }
472  * Generates a QueryInterface function suitable for an implemenation
473  * of an XPCOM interface.  Unlike XPCOMUtils, this uses the Function
474  * constructor to generate a slightly more efficient version.  The
475  * arguments can be either Strings or elements of
476  * Components.interfaces.
477  */
478 function generate_QI() {
479     var args = Array.prototype.slice.call(arguments).map(String).concat(["nsISupports"]);
480     var fstr = "if(" +
481         Array.prototype.map.call(args,
482                                  function (x)
483                                      "iid.equals(Components.interfaces." + x + ")")
484         .join("||") +
485         ") return this; throw Components.results.NS_ERROR_NO_INTERFACE;";
486     return new Function("iid", fstr);
489 function set_branch_pref(branch, name, value) {
490     if (typeof(value) == "string") {
491         branch.setCharPref(name, value);
492     } else if (typeof(value) == "number") {
493         branch.setIntPref(name, value);
494     } else if (typeof(value) == "boolean") {
495         branch.setBoolPref(name, value);
496     }
499 function default_pref(name, value) {
500     var branch = preferences.getDefaultBranch(null);
501     set_branch_pref(branch, name, value);
504 function user_pref(name, value) {
505     var branch = preferences.getBranch(null);
506     set_branch_pref(branch, name, value);
509 function get_branch_pref(branch, name) {
510     switch (branch.getPrefType(name)) {
511     case branch.PREF_STRING:
512         return branch.getCharPref(name);
513     case branch.PREF_INT:
514         return branch.getIntPref(name);
515     case branch.PREF_BOOL:
516         return branch.getBoolPref(name);
517     default:
518         return null;
519     }
522 function get_localized_pref(name) {
523     try {
524         return preferences.getBranch(null).getComplexValue(name, Ci.nsIPrefLocalizedString).data;
525     } catch (e) {
526         return null;
527     }
530 function get_pref(name) {
531     var branch = preferences.getBranch(null);
532     return get_branch_pref(branch, name);
535 function get_default_pref(name) {
536     var branch = preferences.getDefaultBranch(null);
537     return get_branch_pref(branch, name);
540 function clear_pref(name) {
541     var branch = preferences.getBranch(null);
542     return branch.clearUserPref(name);
545 function pref_has_user_value(name) {
546     var branch = preferences.getBranch(null);
547     return branch.prefHasUserValue(name);
550 function pref_has_default_value(name) {
551     var branch = preferences.getDefaultBranch(null);
552     return branch.prefHasUserValue(name);
555 function session_pref (name, value) {
556     try { clear_pref (name); }
557     catch (e) {}
558     return default_pref (name, value);
561 function watch_pref(pref, hook) {
562     /* Extract pref into branch.pref */
563     let match = pref.match(/^(.*[.])?([^.]*)$/);
564     let br = match[1];
565     let key = match[2];
566     let branch = preferences.getBranch(br).QueryInterface(Ci.nsIPrefBranch2);
567     let observer = {
568         observe: function (subject, topic, data) {
569             if (topic == "nsPref:changed" && data == key) {
570                 hook();
571             }
572         }
573     };
575     branch.addObserver("", observer, false);
578 const LOCALE_PREF = "general.useragent.locale";
580 function get_locale() {
581     return get_localized_pref(LOCALE_PREF) || get_pref(LOCALE_PREF);
584 const USER_AGENT_OVERRIDE_PREF = "general.useragent.override";
586 function set_user_agent(str) {
587     session_pref(USER_AGENT_OVERRIDE_PREF, str);
590 function define_builtin_commands(prefix, do_command_function, toggle_mark, mark_active_predicate, mode) {
592     // Specify a docstring
593     function D(cmd, docstring) {
594         var o = new String(cmd);
595         o.doc = docstring;
596         return o;
597     }
599     // Specify a forward/reverse pair
600     function R(a, b) {
601         var o = [a,b];
602         o.is_reverse_pair = true;
603         return o;
604     }
606     // Specify a movement/select/scroll/move-caret command group.
607     function S(command, movement, select, scroll, caret) {
608         var o = [movement, select, scroll, caret];
609         o.command = command;
610         o.is_move_select_pair = true;
611         return o;
612     }
614     var builtin_commands = [
616         /*
617          * cmd_scrollBeginLine and cmd_scrollEndLine don't do what I
618          * want, either in or out of caret mode...
619          */
620         S(D("beginning-of-line", "Move or extend the selection to the beginning of the current line."),
621           D("cmd_beginLine", "Move point to the beginning of the current line."),
622           D("cmd_selectBeginLine", "Extend selection to the beginning of the current line."),
623           D("cmd_beginLine", "Scroll to the beginning of the line"),
624           D("cmd_beginLine", "Scroll to the beginning of the line")),
625         S(D("end-of-line", "Move or extend the selection to the end of the current line."),
626           D("cmd_endLine", "Move point to the end of the current line."),
627           D("cmd_selectEndLine", "Extend selection to the end of the current line."),
628           D("cmd_endLine", "Scroll to the end of the current line."),
629           D("cmd_endLine", "Scroll to the end of the current line.")),
630         S(D("beginning-of-first-line", "Move or extend the selection to the beginning of the first line."),
631           D("cmd_moveTop", "Move point to the beginning of the first line."),
632           D("cmd_selectTop", "Extend selection to the beginning of the first line."),
633           D("cmd_scrollTop", "Scroll to the top of the buffer"),
634           D("cmd_scrollTop", "Move point to the beginning of the first line.")),
635         S(D("end-of-last-line", "Move or extend the selection to the end of the last line."),
636           D("cmd_moveBottom", "Move point to the end of the last line."),
637           D("cmd_selectBottom", "Extend selection to the end of the last line."),
638           D("cmd_scrollBottom", "Scroll to the bottom of the buffer"),
639           D("cmd_scrollBottom", "Move point to the end of the last line.")),
640         "cmd_copyOrDelete",
641         "cmd_scrollBeginLine",
642         "cmd_scrollEndLine",
643         "cmd_cutOrDelete",
644         D("cmd_copy", "Copy the selection into the clipboard."),
645         D("cmd_cut", "Cut the selection into the clipboard."),
646         D("cmd_deleteToBeginningOfLine", "Delete to the beginning of the current line."),
647         D("cmd_deleteToEndOfLine", "Delete to the end of the current line."),
648         D("cmd_selectAll", "Select all."),
649         D("cmd_scrollTop", "Scroll to the top of the buffer."),
650         D("cmd_scrollBottom", "Scroll to the bottom of the buffer.")];
652     var builtin_commands_with_count = [
653         R(S(D("forward-char", "Move or extend the selection forward one character."),
654             D("cmd_charNext", "Move point forward one character."),
655             D("cmd_selectCharNext", "Extend selection forward one character."),
656             D("cmd_scrollRight", "Scroll to the right"),
657             D("cmd_scrollRight", "Scroll to the right")),
658           S(D("backward-char", "Move or extend the selection backward one character."),
659             D("cmd_charPrevious", "Move point backward one character."),
660             D("cmd_selectCharPrevious", "Extend selection backward one character."),
661             D("cmd_scrollLeft", "Scroll to the left."),
662             D("cmd_scrollLeft", "Scroll to the left."))),
663         R(D("cmd_deleteCharForward", "Delete the following character."),
664           D("cmd_deleteCharBackward", "Delete the previous character.")),
665         R(D("cmd_deleteWordForward", "Delete the following word."),
666           D("cmd_deleteWordBackward", "Delete the previous word.")),
667         R(S(D("forward-line", "Move or extend the selection forward one line."),
668             D("cmd_lineNext", "Move point forward one line."),
669             D("cmd_selectLineNext", "Extend selection forward one line."),
670             D("cmd_scrollLineDown", "Scroll down one line."),
671             D("cmd_scrollLineDown", "Scroll down one line.")),
672           S(D("backward-line", "Move or extend the selection backward one line."),
673             D("cmd_linePrevious", "Move point backward one line."),
674             D("cmd_selectLinePrevious", "Extend selection backward one line."),
675             D("cmd_scrollLineUp", "Scroll up one line."),
676             D("cmd_scrollLineUp", "Scroll up one line."))),
677         R(S(D("forward-page", "Move or extend the selection forward one page."),
678             D("cmd_movePageDown", "Move point forward one page."),
679             D("cmd_selectPageDown", "Extend selection forward one page."),
680             D("cmd_scrollPageDown", "Scroll forward one page."),
681             D("cmd_movePageDown", "Move point forward one page.")),
682           S(D("backward-page", "Move or extend the selection backward one page."),
683             D("cmd_movePageUp", "Move point backward one page."),
684             D("cmd_selectPageUp", "Extend selection backward one page."),
685             D("cmd_scrollPageUp", "Scroll backward one page."),
686             D("cmd_movePageUp", "Move point backward one page."))),
687         R(D("cmd_undo", "Undo last editing action."),
688           D("cmd_redo", "Redo last editing action.")),
689         R(S(D("forward-word", "Move or extend the selection forward one word."),
690             D("cmd_wordNext", "Move point forward one word."),
691             D("cmd_selectWordNext", "Extend selection forward one word."),
692             D("cmd_scrollRight", "Scroll to the right."),
693             D("cmd_wordNext", "Move point forward one word.")),
694           S(D("backward-word", "Move or extend the selection backward one word."),
695             D("cmd_wordPrevious", "Move point backward one word."),
696             D("cmd_selectWordPrevious", "Extend selection backward one word."),
697             D("cmd_scrollLeft", "Scroll to the left."),
698             D("cmd_wordPrevious", "Move point backward one word."))),
699         R(D("cmd_scrollPageUp", "Scroll up one page."),
700           D("cmd_scrollPageDown", "Scroll down one page.")),
701         R(D("cmd_scrollLineUp", "Scroll up one line."),
702           D("cmd_scrollLineDown", "Scroll down one line.")),
703         R(D("cmd_scrollLeft", "Scroll left."),
704           D("cmd_scrollRight", "Scroll right.")),
705         D("cmd_paste", "Insert the contents of the clipboard.")];
707     interactive(prefix + "set-mark",
708                 "Toggle whether the mark is active.\n" +
709                 "When the mark is active, movement commands affect the selection.",
710                 toggle_mark);
712     function get_mode_idx() {
713         if (mode == 'scroll') return 2;
714         else if (mode == 'caret') return 3;
715         else return 0;
716     }
718     function get_move_select_idx(I) {
719         return mark_active_predicate(I) ? 1 : get_mode_idx();
720     }
722     function doc_for_builtin(c) {
723         var s = "";
724         if (c.doc != null)
725             s += c.doc + "\n";
726         return s + "Run the built-in command " + c + ".";
727     }
729     function define_simple_command(c) {
730         interactive(prefix + c, doc_for_builtin(c), function (I) { do_command_function(I, c); });
731     }
733     function get_move_select_doc_string(c) {
734         return c.command.doc +
735             "\nSpecifically, if the mark is active, runs `" + prefix + c[1] + "'.  " +
736             "Otherwise, runs `" + prefix + c[get_mode_idx()] + "'\n" +
737             "To toggle whether the mark is active, use `" + prefix + "set-mark'.";
738     }
740     for each (let c_temp in builtin_commands)  {
741         let c = c_temp;
742         if (c.is_move_select_pair) {
743             interactive(prefix + c.command, get_move_select_doc_string(c), function (I) {
744                 var idx = get_move_select_idx(I);
745                 do_command_function(I, c[idx]);
746             });
747             define_simple_command(c[0]);
748             define_simple_command(c[1]);
749         }
750         else
751             define_simple_command(c);
752     }
754     function get_reverse_pair_doc_string(main_doc, alt_command) {
755         return main_doc + "\n" +
756             "The prefix argument specifies a repeat count for this command.  " +
757             "If the count is negative, `" + prefix + alt_command + "' is performed instead with " +
758             "a corresponding positive repeat count.";
759     }
761     function define_simple_reverse_pair(a, b) {
762         interactive(prefix + a, get_reverse_pair_doc_string(doc_for_builtin(a), b),
763                     function (I) {
764                         do_repeatedly(do_command_function, I.p, [I, a], [I, b]);
765                     });
766         interactive(prefix + b, get_reverse_pair_doc_string(doc_for_builtin(b), a),
767                     function (I) {
768                         do_repeatedly(do_command_function, I.p, [I, b], [I, a]);
769                     });
770     }
772     for each (let c_temp in builtin_commands_with_count)
773     {
774         let c = c_temp;
775         if (c.is_reverse_pair) {
776             if (c[0].is_move_select_pair) {
777                 interactive(prefix + c[0].command, get_reverse_pair_doc_string(get_move_select_doc_string(c[0]),
778                                                                                c[1].command),
779                             function (I) {
780                                 var idx = get_move_select_idx(I);
781                                 do_repeatedly(do_command_function, I.p, [I, c[0][idx]], [I, c[1][idx]]);
782                             });
783                 interactive(prefix + c[1].command, get_reverse_pair_doc_string(get_move_select_doc_string(c[1]),
784                                                                                c[0].command),
785                             function (I) {
786                                 var idx = get_move_select_idx(I);
787                                 do_repeatedly(do_command_function, I.p, [I, c[1][idx]], [I, c[0][idx]]);
788                             });
789                 define_simple_reverse_pair(c[0][0], c[1][0]);
790                 define_simple_reverse_pair(c[0][1], c[1][1]);
791             } else
792                 define_simple_reverse_pair(c[0], c[1]);
793         } else {
794             let doc = doc_for_builtin(c) +
795                 "\nThe prefix argument specifies a positive repeat count for this command.";
796             interactive(prefix + c, doc, function (I) {
797                 do_repeatedly_positive(do_command_function, I.p, I, c);
798             });
799         }
800     }
804 var observer_service = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
806 function abort(str) {
807     var e = new Error(str);
808     e.__proto__ = abort.prototype;
809     return e;
811 abort.prototype.__proto__ = Error.prototype;
814 function get_temporary_file(name) {
815     if (name == null)
816         name = "temp.txt";
817     var file = file_locator.get("TmpD", Ci.nsIFile);
818     file.append(name);
819     // Create the file now to ensure that no exploits are possible
820     file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
821     return file;
825 /* FIXME: This should be moved somewhere else, perhaps. */
826 function create_info_panel(window, panel_class, row_arr) {
827     /* Show information panel above minibuffer */
829     var g = new dom_generator(window.document, XUL_NS);
831     var p = g.element("vbox", "class", "panel " + panel_class, "flex", "0");
832     var grid = g.element("grid", p);
833     var cols = g.element("columns", grid);
834     g.element("column", cols, "flex", "0");
835     g.element("column", cols, "flex", "1");
837     var rows = g.element("rows", grid);
838     var row;
840     for each (let [row_class, row_label, row_value] in row_arr) {
841         row = g.element("row", rows, "class", row_class);
842         g.element("label", row,
843                   "value", row_label,
844                   "class", "panel-row-label");
845         g.element("label", row,
846                   "value", row_value,
847                   "class", "panel-row-value",
848                   "crop", "end");
849     }
850     window.minibuffer.insert_before(p);
852     p.destroy = function () {
853         this.parentNode.removeChild(this);
854     };
856     return p;
861  * Paste from the X primary selection, unless the system doesn't support a
862  * primary selection, in which case fall back to the clipboard.
863  */
864 function read_from_x_primary_selection ()
866     // Get clipboard.
867     let clipboard = Components.classes["@mozilla.org/widget/clipboard;1"]
868         .getService(Components.interfaces.nsIClipboard);
870     // Fall back to global clipboard if the system doesn't support a selection
871     let which_clipboard = clipboard.supportsSelectionClipboard() ?
872         clipboard.kSelectionClipboard : clipboard.kGlobalClipboard;
874     let flavors = ["text/unicode"];
876     // Don't barf if there's nothing on the clipboard
877     if (!clipboard.hasDataMatchingFlavors(flavors, flavors.length, which_clipboard))
878         return "";
880     // Create transferable that will transfer the text.
881     let trans = Components.classes["@mozilla.org/widget/transferable;1"]
882         .createInstance(Components.interfaces.nsITransferable);
884     for each (let flavor in flavors) {
885         trans.addDataFlavor(flavor);
886     }
887     clipboard.getData(trans, which_clipboard);
889     var data_flavor = {};
890     var data = {};
891     var dataLen = {};
892     trans.getAnyTransferData(data_flavor, data, dataLen);
894     if (data) {
895         data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
896         let data_length = dataLen.value;
897         if (data_flavor.value == "text/unicode")
898             data_length = dataLen.value / 2;
899         return data.data.substring(0, data_length);
900     } else {
901         return "";
902     }
905 var user_variables = {};
907 function define_variable(name, default_value, doc) {
908     conkeror[name] = default_value;
909     user_variables[name] = {
910         default_value: default_value,
911         doc: doc,
912         shortdoc: get_shortdoc_string(doc),
913         source_code_reference: get_caller_source_code_reference()
914     };
917 function define_special_variable(name, getter, setter, doc) {
918     conkeror.__defineGetter__(name, getter);
919     conkeror.__defineSetter__(name, setter);
920     user_variables[name] = {
921         default_value: undefined,
922         doc: doc,
923         shortdoc: get_shortdoc_string(doc),
924         source_code_reference: get_caller_source_code_reference()
925     };
928 /* Re-define load_paths as a user variable. */
929 define_variable("load_paths", load_paths,
930                 "Array of URL prefixes searched in order when loading a module.\n" +
931                 "Each entry must end in a slash, and should begin with file:// or chrome://.");
934  * Stylesheets
935  */
936 function register_user_stylesheet (url) {
937     var uri = make_uri(url);
938     var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
939         .getService(Ci.nsIStyleSheetService);
940     sss.loadAndRegisterSheet(uri, sss.USER_SHEET);
943 function unregister_user_stylesheet (url) {
944     var uri = make_uri(url);
945     var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
946         .getService(Ci.nsIStyleSheetService);
947     if (sss.sheetRegistered(uri, sss.USER_SHEET))
948         sss.unregisterSheet(uri, sss.USER_SHEET);
951 function register_agent_stylesheet (url) {
952     var uri = make_uri(url);
953     var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
954         .getService(Ci.nsIStyleSheetService);
955     sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET);
958 function unregister_agent_stylesheet (url) {
959     var uri = make_uri(url);
960     var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
961         .getService(Ci.nsIStyleSheetService);
962     if (sss.sheetRegistered(uri, sss.AGENT_SHEET))
963         sss.unregisterSheet(uri, sss.AGENT_SHEET);
966 function agent_stylesheet_registered_p (url) {
967     var uri = make_uri(url);
968     var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
969         .getService(Ci.nsIStyleSheetService);
970     return sss.sheetRegistered(uri, sss.AGENT_SHEET);
973 function user_stylesheet_registered_p (url) {
974     var uri = make_uri(url);
975     var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
976         .getService(Ci.nsIStyleSheetService);
977     return sss.sheetRegistered(uri, sss.USER_SHEET);
980 function predicate_alist_match(alist, key) {
981     for each (let i in alist) {
982         if (i[0](key))
983             return i[1];
984     }
985     return undefined;
989 function get_meta_title(doc) {
990     var title = doc.evaluate("//meta[@name='title']/@content", doc, xpath_lookup_namespace,
991                              Ci.nsIDOMXPathResult.STRING_TYPE , null);
992     if (title && title.stringValue)
993         return title.stringValue;
994     return null;
997 var rdf_service = Cc["@mozilla.org/rdf/rdf-service;1"]
998     .getService(Ci.nsIRDFService);
1000 const PREFIX_ITEM_URI     = "urn:mozilla:item:";
1001 const PREFIX_NS_EM        = "http://www.mozilla.org/2004/em-rdf#";
1003 var extension_manager = Cc["@mozilla.org/extensions/manager;1"]
1004     .getService(Ci.nsIExtensionManager);
1006 function get_extension_rdf_property(id, name, type) {
1007     var value = extension_manager.datasource.GetTarget(
1008         rdf_service.GetResource(PREFIX_ITEM_URI + id),
1009         rdf_service.GetResource(PREFIX_NS_EM + name),
1010         true);
1011     if (value == null)
1012         return null;
1013     return value.QueryInterface(type || Ci.nsIRDFLiteral).Value;
1016 function get_extension_update_item(id) {
1017     return extension_manager.getItemForID(id);
1020 function extension_info(id) {
1021     this.id = id;
1023 extension_info.prototype = {
1024     // Returns the nsIUpdateItem object associated with this extension
1025     get update_item () { return get_extension_update_item(this.id); },
1027     get_rdf_property : function (name, type) {
1028         return get_extension_rdf_property(this.id, name, type);
1029     },
1031     // RDF properties
1032     get isDisabled () { return this.get_rdf_property("isDisabled"); },
1033     get aboutURL () { return this.get_rdf_property("aboutURL"); },
1034     get addonID () { return this.get_rdf_property("addonID"); },
1035     get availableUpdateURL () { return this.get_rdf_property("availableUpdateURL"); },
1036     get availableUpdateVersion () { return this.get_rdf_property("availableUpdateVersion"); },
1037     get blocklisted () { return this.get_rdf_property("blocklisted"); },
1038     get compatible () { return this.get_rdf_property("compatible"); },
1039     get description () { return this.get_rdf_property("description"); },
1040     get downloadURL () { return this.get_rdf_property("downloadURL"); },
1041     get isDisabled () { return this.get_rdf_property("isDisabled"); },
1042     get hidden () { return this.get_rdf_property("hidden"); },
1043     get homepageURL () { return this.get_rdf_property("homepageURL"); },
1044     get iconURL () { return this.get_rdf_property("iconURL"); },
1045     get internalName () { return this.get_rdf_property("internalName"); },
1046     get locked () { return this.get_rdf_property("locked"); },
1047     get name () { return this.get_rdf_property("name"); },
1048     get optionsURL () { return this.get_rdf_property("optionsURL"); },
1049     get opType () { return this.get_rdf_property("opType"); },
1050     get plugin () { return this.get_rdf_property("plugin"); },
1051     get previewImage () { return this.get_rdf_property("previewImage"); },
1052     get satisfiesDependencies () { return this.get_rdf_property("satisfiesDependencies"); },
1053     get providesUpdatesSecurely () { return this.get_rdf_property("providesUpdatesSecurely"); },
1054     get type () { return this.get_rdf_property("type", Ci.nsIRDFInt); },
1055     get updateable () { return this.get_rdf_property("updateable"); },
1056     get updateURL () { return this.get_rdf_property("updateURL"); },
1057     get version () { return this.get_rdf_property("version"); }
1060 function extension_is_enabled(id) {
1061     var info = new extension_info(id);
1062     return info.update_item && (info.isDisabled == "false");
1065 function queue() {
1066     this.input = [];
1067     this.output = [];
1069 queue.prototype = {
1070     get length () {
1071         return this.input.length + this.output.length;
1072     },
1073     push: function (x) {
1074         this.input[this.input.length] = x;
1075     },
1076     pop: function (x) {
1077         let l = this.output.length;
1078         if (!l) {
1079             l = this.input.length;
1080             if (!l)
1081                 return undefined;
1082             this.output = this.input.reverse();
1083             this.input = [];
1084             let x = this.output[l];
1085             this.output.length--;
1086             return x;
1087         }
1088     }
1091 function frame_iterator(root_frame, start_with) {
1092     var q = new queue, x;
1093     if (start_with) {
1094         x = start_with;
1095         do {
1096             yield x;
1097             for (let i = 0; i < x.frames.length; ++i)
1098                 q.push(x.frames[i]);
1099         } while ((x = q.pop()));
1100     }
1101     x = root_frame;
1102     do {
1103         if (x == start_with)
1104             continue;
1105         yield x;
1106         for (let i = 0; i < x.frames.length; ++i)
1107             q.push(x.frames[i]);
1108     } while ((x = q.pop()));
1111 function xml_http_request() {
1112     return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
1113         .createInstance(Ci.nsIXMLHttpRequest)
1114         .QueryInterface(Ci.nsIJSXMLHttpRequest)
1115         .QueryInterface(Ci.nsIDOMEventTarget);
1118 var xml_http_request_load_listener = {
1119   // nsIBadCertListener2
1120   notifyCertProblem: function SSLL_certProblem(socketInfo, status, targetSite) {
1121     return true;
1122   },
1124   // nsISSLErrorListener
1125   notifySSLError: function SSLL_SSLError(socketInfo, error, targetSite) {
1126     return true;
1127   },
1129   // nsIInterfaceRequestor
1130   getInterface: function SSLL_getInterface(iid) {
1131     return this.QueryInterface(iid);
1132   },
1134   // nsISupports
1135   //
1136   // FIXME: array comprehension used here to hack around the lack of
1137   // Ci.nsISSLErrorListener in 2007 versions of xulrunner 1.9pre.
1138   // make it a simple generateQI when xulrunner is more stable.
1139   QueryInterface: XPCOMUtils.generateQI (
1140       [i for each (i in [Ci.nsIBadCertListener2,
1141                          Ci.nsISSLErrorListener,
1142                          Ci.nsIInterfaceRequestor])
1143        if (i)])
1148  * Coroutine interface for sending an HTTP request and waiting for the
1149  * response. (This includes so-called "AJAX" requests.)
1151  * @param lspec (required) a load_spec object or URI string (see load-spec.js)
1153  * The request URI is obtained from this argument. In addition, if the
1154  * load spec specifies post data, a POST request is made instead of a
1155  * GET request, and the post data included in the load spec is
1156  * sent. Specifically, the request_mime_type and raw_post_data
1157  * properties of the load spec are used.
1159  * @param $user (optional) HTTP user name to include in the request headers
1160  * @param $password (optional) HTTP password to include in the request headers
1162  * @param $override_mime_type (optional) Force the response to be interpreted
1163  *                            as having the specified MIME type.  This is only
1164  *                            really useful for forcing the MIME type to be
1165  *                            text/xml or something similar, such that it is
1166  *                            automatically parsed into a DOM document.
1167  * @param $headers (optional) an array of [name,value] pairs (each specified as
1168  *                 a two-element array) specifying additional headers to add to
1169  *                 the request.
1171  * @returns After the request completes (either successfully or with an error),
1172  *          the nsIXMLHttpRequest object is returned.  Its responseText (for any
1173  *          arbitrary document) or responseXML (if the response type is an XML
1174  *          content type) properties can be accessed to examine the response
1175  *          document.
1177  * If an exception is thrown to the continutation (which can be obtained by the
1178  * caller by calling yield CONTINUATION prior to calling this function) while the
1179  * request is in progress (i.e. before this coroutine returns), the request will
1180  * be aborted, and the exception will be propagated to the caller.
1182  **/
1183 define_keywords("$user", "$password", "$override_mime_type", "$headers");
1184 function send_http_request(lspec) {
1185     // why do we get warnings in jsconsole unless we initialize the
1186     // following keywords?
1187     keywords(arguments, $user = undefined, $password = undefined,
1188              $override_mime_type = undefined, $headers = undefined);
1189     if (! (lspec instanceof load_spec))
1190         lspec = load_spec(lspec);
1191     var req = xml_http_request();
1192     var cc = yield CONTINUATION;
1193     var aborting = false;
1194     req.onreadystatechange = function send_http_request__onreadysatechange() {
1195         if (req.readyState != 4)
1196             return;
1197         if (aborting)
1198             return;
1199         cc();
1200     };
1202     if (arguments.$override_mime_type)
1203         req.overrideMimeType(arguments.$override_mime_type);
1205     var post_data = load_spec_raw_post_data(lspec);
1207     var method = post_data ? "POST" : "GET";
1209     req.open(method, load_spec_uri_string(lspec), true, arguments.$user, arguments.$password);
1210     req.channel.notificationCallbacks = xml_http_request_load_listener;
1212     for each (let [name,value] in arguments.$headers) {
1213         req.setRequestHeader(name, value);
1214     }
1216     if (post_data) {
1217         req.setRequestHeader("Content-Type", load_spec_request_mime_type(lspec));
1218         req.send(post_data);
1219     } else
1220         req.send(null);
1222     try {
1223         yield SUSPEND;
1224     } catch (e) {
1225         aborting = true;
1226         req.abort();
1227         throw e;
1228     }
1230     // Let the caller access the status and reponse data
1231     yield co_return(req);
1235 var JSON = ("@mozilla.org/dom/json;1" in Cc) &&
1236     Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
1240 var console_service = Cc["@mozilla.org/consoleservice;1"]
1241     .getService(Ci.nsIConsoleService);
1243 console_service.registerListener(
1244     {observe: function (msg) {
1245          if (msg instanceof Ci.nsIScriptError) {
1246              switch (msg.category) {
1247              case "CSS Parser":
1248              case "content javascript":
1249                  return;
1250              }
1251              msg.QueryInterface(Ci.nsIScriptError);
1252              dumpln("Console error: " + msg.message);
1253              dumpln("  Category: " + msg.category);
1254          }
1255      }});
1258 // ensure_index_is_visible ensures that the given index in the given
1259 // field (an html input field for example) is visible.
1260 function ensure_index_is_visible (window, field, index) {
1261     var start = field.selectionStart;
1262     var end = field.selectionEnd;
1263     field.setSelectionRange (index, index);
1264     send_key_as_event (window, field, "left");
1265     if (field.selectionStart < index) {
1266         send_key_as_event (window, field, "right");
1267     }
1268     field.setSelectionRange (start, end);
1271 function regex_to_string(obj) {
1272     if(obj instanceof RegExp) {
1273         obj = obj.source;
1274     } else {
1275         obj = quotemeta(obj);
1276     }
1277     return obj;
1281  * Build a regular expression to match URLs for a given web site.
1283  * Both the $domain and $path arguments can be either regexes, in
1284  * which case they will be matched as is, or strings, in which case
1285  * they will be matched literally.
1287  * $tlds specifies a list of valid top-level-domains to match, and
1288  * defaults to .com. Useful for when e.g. foo.org and foo.com are the
1289  * same.
1291  * If $allow_www is true, www.domain.tld will also be allowed.
1293  */
1294 define_keywords("$domain", "$path", "$tlds", "$allow_www");
1295 function build_url_regex() {
1296     keywords(arguments, $path = "", $tlds = ["com"], $allow_www = false);
1297     var domain = regex_to_string(arguments.$domain);
1298     if(arguments.$allow_www) {
1299         domain = "(?:www\.)?" + domain;
1300     }
1301     var path   = regex_to_string(arguments.$path);
1302     var tlds   = arguments.$tlds;
1303     var regex = "^https?://" + domain + "\\." + choice_regex(tlds) + "/" + path;
1304     return new RegExp(regex);
1309  * Given an ordered array of non-overlapping ranges, represented as
1310  * elements of [start, end], insert a new range into the array,
1311  * extending, replacing, or merging existing ranges as needed. Mutates
1312  * `arr' in place.
1314  * Examples:
1316  * splice_range([[1,3],[4,6], 5, 8)
1317  *  => [[1,3],[4,8]]
1319  * splice_range([[1,3],[4,6],[7,10]], 2, 8)
1320  *  => [[1,10]]
1321  */
1322 function splice_range(arr, start, end) {
1323     for(var i = 0; i < arr.length; ++i) {
1324         let [n,m] = arr[i];
1325         if(start > m)
1326             continue;
1327         if(end < n) {
1328             arr.splice(i, 0, [start, end]);
1329             break;
1330         }
1331         if(start < n) {
1332             arr[i][0] = start;
1333         }
1335         if(end >= n) {
1336             /*
1337              * The range we are inserting overlaps the current
1338              * range. We need to scan right to see if it also contains any other
1339              * ranges entirely, and remove them if necessary.
1340              */
1341             var j = i;
1342             while(j < arr.length && end >= arr[j][0]) j++;
1343             j--;
1344             arr[i][1] = Math.max(end, arr[j][1]);
1345             arr.splice(i + 1, j - i);
1346             break;
1347         }
1348     }
1349     if(start > arr[arr.length - 1][1]) {
1350         arr.push([start, end]);
1351     }
1355 function compute_url_up_path (url)
1357     var new_url = Cc["@mozilla.org/network/standard-url;1"]
1358         .createInstance (Ci.nsIURL);
1359     new_url.spec = url;
1360     var up;
1361     if (new_url.param != "" || new_url.query != "")
1362         up = new_url.filePath;
1363     else if (new_url.fileName != "")
1364         up = ".";
1365     else
1366         up = "..";
1367     return up;
1371 function url_path_trim (url) {
1372     var uri = make_uri(url);
1373     uri.spec = url;
1374     uri.path = "";
1375     return uri.spec;
1378 /* possibly_valid_url returns true if the string might be a valid
1379  * thing to pass to nsIWebNavigation.loadURI.  Currently just checks
1380  * that there's no whitespace in the middle and that it's not entirely
1381  * whitespace.
1382  */
1383 function possibly_valid_url (url) {
1384     return !(/\S\s+\S/.test(url)) && !(/^\s*$/.test(url));
1388 /* remove_duplicates_filter returns a function that can be
1389  * used in Array.filter.  It removes duplicates.
1390  */
1391 function remove_duplicates_filter () {
1392     var acc = {};
1393     return function (x) {
1394         if (acc[x]) return false;
1395         acc[x] = 1;
1396         return true;
1397     };
1401 /* get_current_profile returns the name of the current profile, or null
1402  * if that information cannot be found.  The result is cached in the
1403  * variable profile_name, for quick repeat lookup.  This is safe because
1404  * xulrunner does not support switching profiles on the fly.
1406  * Profiles don't necessarily have a name--as such this information should
1407  * not be depended on for anything important.  It is mainly intended for
1408  * decoration of the window title and mode-line.
1409  */
1410 var profile_name;
1411 function get_current_profile () {
1412     if (profile_name)
1413         return profile_name;
1414     if ("@mozilla.org/profile/manager;1" in Cc) {
1415         profile_name = Cc["@mozilla.org/profile/manager;1"]
1416             .getService(Ci.nsIProfile)
1417             .currentProfile;
1418         return profile_name;
1419     }
1420     var current_profile_path = Cc["@mozilla.org/file/directory_service;1"]
1421         .getService(Ci.nsIProperties)
1422         .get("ProfD", Ci.nsIFile).path;
1423     var profile_service = Cc["@mozilla.org/toolkit/profile-service;1"]
1424         .getService(Components.interfaces.nsIToolkitProfileService);
1425     var profiles = profile_service.profiles;
1426     while (profiles.hasMoreElements()) {
1427         var p = profiles.getNext().QueryInterface(Ci.nsIToolkitProfile);
1428         if (current_profile_path == p.localDir.path ||
1429             current_profile_path == p.rootDir.path)
1430         {
1431             profile_name = p.name;
1432             return p.name;
1433         }
1434     }
1435     return null;
1440  * Given an array, switches places on the subarrays at index i1 to i2 and j1 to
1441  * j2. Leaves the rest of the array unchanged.
1442  */
1443 function switch_subarrays(arr, i1, i2, j1, j2) {
1444     return arr.slice(0, i1) +
1445         arr.slice(j1, j2) +
1446         arr.slice(i2, j1) +
1447         arr.slice(i1, i2) +
1448         arr.slice(j2, arr.length);
1452  * Makes an AJAX request to the given URI and calls the callback function upon
1453  * retrieval. In the callback function, the XMLHttpRequest can be accessed
1454  * through, assuming that the first parameter is called "evt", evt.target.
1456  * @param uri The URI to make the request to.
1457  * @param callback The callback function.
1458  * @param s (Optional) Settings object.
1459  */
1460 function ajax_request(uri, callback, s) {
1462     // Set up the default settings.
1463     var sets = {
1464         method : 'GET',
1465         data   : null
1466     };
1468     // Let the user's provided settings override our defaults.
1469     if (s)
1470         sets = {
1471             method : s.method ? s.method : 'GET',
1472             data   : s.data   ? s.data   : null
1473         };
1475     var httpRequest = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
1476         .createInstance(Ci.nsIXMLHttpRequest);
1477     httpRequest.onreadystatechange = callback;
1479     // If we're POSTing something, we should make sure the headers are set
1480     // correctly.
1481     if (sets.method == 'POST')
1482         httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
1484     httpRequest.open(sets.method, uri, true);
1485     httpRequest.send(sets.data);
1490  * Convenience function for making simple XPath lookups in a document.
1492  * @param doc The document to look in.
1493  * @param exp The XPath expression to search for.
1494  * @return The XPathResult object representing the set of found nodes.
1495  */
1496 function xpath_lookup(doc, exp) {
1497     return doc.evaluate(exp, doc, null, Ci.nsIDOMXPathResult.ANY_TYPE, null);
1501 /* get_contents_synchronously returns the contents of the given
1502  * url (string or nsIURI) as a string on success, or null on failure.
1503  */
1504 function get_contents_synchronously (url) {
1505     var ioService=Cc["@mozilla.org/network/io-service;1"]
1506         .getService(Ci.nsIIOService);
1507     var scriptableStream=Cc["@mozilla.org/scriptableinputstream;1"]
1508         .getService(Ci.nsIScriptableInputStream);
1509     var channel;
1510     var input;
1511     try {
1512         if (url instanceof Ci.nsIURI)
1513             channel = ioService.newChannelFromURI(url);
1514         else
1515             channel = ioService.newChannel(url, null, null);
1516         input=channel.open();
1517     } catch (e) {
1518         return null;
1519     }
1520     scriptableStream.init(input);
1521     var str=scriptableStream.read(input.available());
1522     scriptableStream.close();
1523     input.close();
1524     return str;
1529  * string_format takes a format-string containing %X style format codes,
1530  * and an object mapping the code-letters to replacement text.  It
1531  * returns a string with the formatting codes replaced by the replacement
1532  * text.
1533  */
1534 function string_format (spec, substitutions) {
1535     return spec.replace(/%(.)/g, function (a,b) { return substitutions[b]; });
1540  * dom_add_class adds a css class to the given dom node.
1541  */
1542 function dom_add_class (node, cssclass) {
1543     if (node.className)
1544         node.className += " "+cssclass;
1545     else
1546         node.className = cssclass;
1550  * dom_remove_class removes the given css class from the given dom node.
1551  */
1552 function dom_remove_class (node, cssclass) {
1553     if (! node.className)
1554         return;
1555     var classes = node.className.split(" ");
1556     classes = classes.filter(function (x) { return x != cssclass; });
1557     node.className = classes.join(" ");
1562  * dom_node_flash adds the given cssclass to the node for a brief interval.
1563  * this class can be styled, to create a flashing effect.
1564  */
1565 function dom_node_flash (node, cssclass) {
1566     dom_add_class(node, cssclass);
1567     call_after_timeout(
1568         function () {
1569             dom_remove_class(node, cssclass);
1570         },
1571         400);
1576  * data is an an alist (array of 2 element arrays) where each pair is a key
1577  * and a value.
1579  * The return type is a mime input stream that can be passed as postData to
1580  * nsIWebNavigation.loadURI.  In terms of Conkeror's API, the return value
1581  * of this function is of the correct type for the `post_data' field of a
1582  * load_spec.
1583  */
1584 function make_post_data (data) {
1585     data = [(encodeURIComponent(pair[0])+'='+encodeURIComponent(pair[1]))
1586             for each (pair in data)].join('&');
1587     data = string_input_stream(data);
1588     return mime_input_stream(
1589         data, [["Content-Type", "application/x-www-form-urlencoded"]]);
1594  * Centers the viewport around a given element.
1596  * @param win  The window to scroll.
1597  * @param elem The element arund which we put the viewport.
1598  */
1599 function center_in_viewport(win, elem) {
1600     let point = abs_point(elem);
1602     point.x -= win.innerWidth / 2;
1603     point.y -= win.innerHeight / 2;
1605     win.scrollTo(point.x, point.y);
1610  * Takes an interactive context and a function to call with the word
1611  * at point as its sole argument, and which returns a modified word.
1612  */
1613 function modify_word_at_point (I, func) {
1614     var focused = I.buffer.focused_element;
1616     // Skip any whitespaces at point and move point to the right place.
1617     var point = focused.selectionStart;
1618     var rest = focused.value.substring(point);
1620     // Skip any whitespaces.
1621     for (var i = 0; i < rest.length; i++) {
1622         if (" \n".indexOf(rest.charAt(i)) == -1) {
1623             point += i;
1624             break;
1625         }
1626     }
1628     // Find the next whitespace, as it is the end of the word.  If no next
1629     // whitespace is found, we're at the end of input.  TODO: Add "\n" support.
1630     goal = focused.value.indexOf(" ", point);
1631     if (goal == -1)
1632         goal = focused.value.length;
1634     // Change the value of the text field.
1635     var input = focused.value;
1636     focused.value =
1637         input.substring(0, point) +
1638         func(input.substring(point, goal)) +
1639         input.substring(goal);
1641     // Move point.
1642     focused.selectionStart = goal;
1643     focused.selectionEnd = goal;