Enable within-session XUL caching, since there is no reason not to
[conkeror.git] / modules / utils.js
blobcdb0d2dd153eb08e22642646ef6dc43601ae4fa9
1 /***** BEGIN LICENSE BLOCK *****
2 Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 The contents of this file are subject to the Mozilla Public License Version
5 1.1 (the "License"); you may not use this file except in compliance with
6 the License. You may obtain a copy of the License at
7 http://www.mozilla.org/MPL/
9 Software distributed under the License is distributed on an "AS IS" basis,
10 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 for the specific language governing rights and limitations under the
12 License.
14 The Initial Developer of the Original Code is Shawn Betts.
15 Portions created by the Initial Developer are Copyright (C) 2004,2005
16 by the Initial Developer. All Rights Reserved.
18 Alternatively, the contents of this file may be used under the terms of
19 either the GNU General Public License Version 2 or later (the "GPL"), or
20 the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
21 in which case the provisions of the GPL or the LGPL are applicable instead
22 of those above. If you wish to allow use of your version of this file only
23 under the terms of either the GPL or the LGPL, and not to allow others to
24 use your version of this file under the terms of the MPL, indicate your
25 decision by deleting the provisions above and replace them with the notice
26 and other provisions required by the GPL or the LGPL. If you do not delete
27 the provisions above, a recipient may use your version of this file under
28 the terms of any one of the MPL, the GPL or the LGPL.
29 ***** END LICENSE BLOCK *****/
32  * Utility functions for application scope.
33  *
34  */
37 function string_hashset() {}
39 string_hashset.prototype = {
40     constructor : string_hashset,
42     add : function(s) {
43         this["-" + s] = true;
44     },
46     contains : function(s) {
47         return (("-" + s) in this);
48     },
50     remove : function (s) {
51         delete this["-" + s];
52     },
54     for_each : function (f) {
55         for (var i in this) {
56             if (i[0] == "-")
57                 f(i.slice(1));
58         }
59     },
61     iterator : function () {
62         for (let k in this) {
63             if (i[0] == "-")
64                 yield i.slice(1);
65         }
66     }
69 function string_hashmap() {
72 string_hashmap.prototype = {
73     constructor : string_hashmap,
75     put : function(s,value) {
76         this["-" + s] = value;
77     },
79     contains : function(s) {
80         return (("-" + s) in this);
81     },
83     get : function(s, default_value) {
84         if (this.contains(s))
85             return this["-" + s];
86         return default_value;
87     },
89     get_put_default : function(s, default_value) {
90         if (this.contains(s))
91             return this["-" + s];
92         return (this["-" + s] = default_value);
93     },
95     remove : function (s) {
96         delete this["-" + s];
97     },
99     for_each : function (f) {
100         for (var i in this) {
101             if (i[0] == "-")
102                 f(i.slice(1), this[i]);
103         }
104     },
106     for_each_value : function (f) {
107         for (var i in this) {
108             if (i[0] == "-")
109                 f(this[i]);
110         }
111     },
113     iterator: function (only_keys) {
114         if (only_keys) {
115             for (let k in Iterator(this, true)) {
116                 if (k[0] == "-")
117                     yield k.slice(1);
118             }
119         } else {
120             for (let [k,v] in Iterator(this, false)) {
121                 if (k[0] == "-")
122                     yield [k.slice(1),v];
123             }
124         }
125     }
128 /// Window title formatting
131  * Default tile formatter.  The page url is ignored.  If there is a
132  * page_title, returns: "Page title - Conkeror".  Otherwise, it
133  * returns just: "Conkeror".
134  */
135 function default_title_formatter (window)
137     var page_title = window.buffers.current.title;
139     if (page_title && page_title.length > 0)
140         return page_title + " - Conkeror";
141     else
142         return "Conkeror";
145 var title_format_fn = null;
147 function set_window_title (window)
149     window.document.title = title_format_fn(window);
152 function init_window_title ()
154     title_format_fn = default_title_formatter;
156     add_hook("window_initialize_late_hook", set_window_title);
157     add_hook("current_content_buffer_location_change_hook",
158              function (buffer) {
159                  set_window_title(buffer.window);
160              });
161     add_hook("select_buffer_hook", function (buffer) { set_window_title(buffer.window); }, true);
162     add_hook("current_buffer_title_change_hook",
163              function (buffer) {
164                  set_window_title(buffer.window);
165              });
168 init_window_title ();
173 // Put the string on the clipboard
174 function writeToClipboard(str)
176     const gClipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"]
177         .getService(Components.interfaces.nsIClipboardHelper);
178     gClipboardHelper.copyString(str);
182 function makeURLAbsolute (base, url)
184     // Construct nsIURL.
185     var ioService = Components.classes["@mozilla.org/network/io-service;1"]
186         .getService(Components.interfaces.nsIIOService);
187     var baseURI  = ioService.newURI(base, null, null);
189     return ioService.newURI (baseURI.resolve (url), null, null).spec;
193 function get_link_location (element)
195     if (element && element.getAttribute("href")) {
196         var loc = element.getAttribute("href");
197         return makeURLAbsolute(element.baseURI, loc);
198     }
199     return null;
203 var io_service = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
205 function make_uri(uri, charset, base_uri) {
206     if (uri instanceof Ci.nsIURI)
207         return uri;
208     return io_service.newURI(uri, charset, base_uri);
211 var makeURL = make_uri; // until all callers are fixed
213 function makeFileURL(aFile)
215     return io_service.newFileURI(aFile).QueryInterface(Ci.nsIURL);
219 function get_document_content_disposition (document_o)
221     var content_disposition = null;
222     try {
223         content_disposition =
224             document_o.defaultView
225             .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
226             .getInterface(Components.interfaces.nsIDOMWindowUtils)
227             .getDocumentMetadata("content-disposition");
228     } catch (e) { }
229     return content_disposition;
233 function set_focus_no_scroll(window, element)
235     window.document.commandDispatcher.suppressFocusScroll = true;
236     element.focus();
237     window.document.commandDispatcher.suppressFocusScroll = false;
240 function do_repeatedly_positive(func, n) {
241     var args = Array.prototype.slice.call(arguments, 2);
242     while (n-- > 0)
243         func.apply(null, args);
246 function do_repeatedly(func, n, positive_args, negative_args) {
247     if (n < 0)
248         do func.apply(null, negative_args); while (++n < 0);
249     else
250         while (n-- > 0) func.apply(null, positive_args);
253 // remove whitespace from the beginning and end
254 function trim_whitespace (str)
256     var tmp = new String (str);
257     return tmp.replace (/^\s+/, "").replace (/\s+$/, "");
260 function abs_point (node)
262     var orig = node;
263     var pt = {};
264     try {
265         pt.x = node.offsetLeft;
266         pt.y = node.offsetTop;
267         // find imagemap's coordinates
268         if (node.tagName == "AREA") {
269             var coords = node.getAttribute("coords").split(",");
270             pt.x += Number(coords[0]);
271             pt.y += Number(coords[1]);
272         }
274         node = node.offsetParent;
275         // Sometimes this fails, so just return what we got.
277         while (node.tagName != "BODY") {
278             pt.x += node.offsetLeft;
279             pt.y += node.offsetTop;
280             node = node.offsetParent;
281         }
282     } catch(e) {
283 //      node = orig;
284 //      while (node.tagName != "BODY") {
285 //          alert("okay: " + node + " " + node.tagName + " " + pt.x + " " + pt.y);
286 //          node = node.offsetParent;
287 //      }
288     }
289     return pt;
292 var xul_app_info = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
293 var xul_runtime = Cc['@mozilla.org/xre/app-info;1'].getService(Ci.nsIXULRuntime);
296 function get_os ()
298     // possible return values: 'Darwin', 'Linux', 'WINNT', ...
299     return xul_runtime.OS;
302 var default_directory = null;
304 var env = Cc['@mozilla.org/process/environment;1'].getService(Ci.nsIEnvironment);
305 function getenv (variable) {
306     if (env.exists (variable))
307         return env.get(variable);
308     return null;
311 function set_default_directory(directory_s) {
312     if (! directory_s)
313     {
314         if ( get_os() == "WINNT")
315         {
316             directory_s = getenv ('USERPROFILE') ||
317                 getenv ('HOMEDRIVE') + getenv ('HOMEPATH');
318         }
319         else {
320             directory_s = getenv ('HOME');
321         }
322     }
324     default_directory = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
325     default_directory.initWithPath (directory_s);
328 set_default_directory();
330 const XHTML_NS = "http://www.w3.org/1999/xhtml";
331 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
332 const MATHML_NS = "http://www.w3.org/1998/Math/MathML";
333 const XLINK_NS = "http://www.w3.org/1999/xlink";
335 function create_XUL(window, tag_name)
337     return window.document.createElementNS(XUL_NS, tag_name);
341 /* Used in calls to XPath evaluate */
342 function xpath_lookup_namespace(prefix) {
343     if (prefix == "xhtml")
344         return XHTML_NS;
345     if (prefix == "m")
346         return MATHML_NS;
347     if (prefix == "xul")
348         return XUL_NS;
349     return null;
352 function method_caller(obj, func) {
353     return function () {
354         func.apply(obj, arguments);
355     }
358 function shell_quote(str) {
359     var s = str.replace("\"", "\\\"", "g");
360     s = s.replace("$", "\$", "g");
361     return s;
364 function get_window_from_frame(frame) {
365     try {
366         var window = frame.QueryInterface(Ci.nsIInterfaceRequestor)
367             .getInterface(Ci.nsIWebNavigation)
368             .QueryInterface(Ci.nsIDocShellTreeItem)
369             .rootTreeItem
370             .QueryInterface(Ci.nsIInterfaceRequestor)
371             .getInterface(Ci.nsIDOMWindow).wrappedJSObject;
372         /* window is now an XPCSafeJSObjectWrapper */
373         window.escape_wrapper(function (w) { window = w; });
374         /* window is now completely unwrapped */
375         return window;
376     } catch (e) {
377         return null;
378     }
381 function get_buffer_from_frame(window, frame) {
382     var count = window.buffers.count;
383     for (var i = 0; i < count; ++i) {
384         var b = window.buffers.get_buffer(i);
385         if (b.top_frame == frame)
386             return b;
387     }
388     return null;
391 var file_locator = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
393 function get_shortdoc_string(doc) {
394     var shortdoc = null;
395     if (doc != null) {
396         var idx = doc.indexOf("\n");
397         if (idx >= 0)
398             shortdoc = doc.substring(0,idx);
399         else
400             shortdoc = doc;
401     }
402     return shortdoc;
405 var conkeror_source_code_path = null;
407 function source_code_reference(uri, line_number) {
408     this.uri = uri;
409     this.line_number = line_number;
411 source_code_reference.prototype = {
412     get module_name () {
413         if (this.uri.indexOf(module_uri_prefix) == 0)
414             return this.uri.substring(module_uri_prefix.length);
415         return null;
416     },
418     get file_name () {
419         var file_uri_prefix = "file://";
420         if (this.uri.indexOf(file_uri_prefix) == 0)
421             return this.uri.substring(file_uri_prefix.length);
422         return null;
423     },
425     get best_uri () {
426         if (conkeror_source_code_path != null) {
427             var module_name = this.module_name;
428             if (module_name != null)
429                 return "file://" + conkeror_source_code_path + "/modules/" + module_name;
430         }
431         return this.uri;
432     },
434     open_in_editor : function() {
435         yield open_with_external_editor(this.best_uri, $line = this.line_number);
436     }
439 var get_caller_source_code_reference_ignored_functions = {};
441 function get_caller_source_code_reference(extra_frames_back) {
442     /* Skip at least this function itself and whoever called it (and
443      * more if the caller wants to be skipped). */
444     var frames_to_skip = 2;
445     if (extra_frames_back != null)
446         frames_to_skip += extra_frames_back;
448     for (let f = Components.stack; f != null; f = f.caller) {
449         if (frames_to_skip > 0) {
450             --frames_to_skip;
451             continue;
452         }
453         if (get_caller_source_code_reference_ignored_functions[f.name])
454             continue;
455         return new source_code_reference(f.filename, f.lineNumber);
456     }
458     return null;
461 function ignore_function_for_get_caller_source_code_reference(func_name) {
462     get_caller_source_code_reference_ignored_functions[func_name] = 1;
465 require_later("external-editor.js");
467 function dom_generator(document, ns) {
468     this.document = document;
469     this.ns = ns;
471 dom_generator.prototype = {
472     element : function(tag, parent) {
473         var node = this.document.createElementNS(this.ns, tag);
474         var i = 1;
475         if (parent != null && (parent instanceof Ci.nsIDOMNode)) {
476             parent.appendChild(node);
477             i = 2;
478         }
479         for (; i < arguments.length; i += 2)
480             node.setAttribute(arguments[i], arguments[i+1]);
481         return node;
482     },
484     text : function(str, parent) {
485         var node = this.document.createTextNode(str);
486         if (parent)
487             parent.appendChild(node);
488         return node;
489     },
492     stylesheet_link : function(href, parent) {
493         var node = this.element("link");
494         node.setAttribute("rel", "stylesheet");
495         node.setAttribute("type", "text/css");
496         node.setAttribute("href", href);
497         if (parent)
498             parent.appendChild(node);
499         return node;
500     },
503     add_stylesheet : function (url) {
504         var head = this.document.documentElement.firstChild;
505         this.stylesheet_link(url, head);
506     }
510  * Generates a QueryInterface function suitable for an implemenation
511  * of an XPCOM interface.  Unlike XPCOMUtils, this uses the Function
512  * constructor to generate a slightly more efficient version.  The
513  * arguments can be either Strings or elements of
514  * Components.interfaces.
515  */
516 function generate_QI() {
517     var args = Array.prototype.slice.call(arguments).map(String).concat(["nsISupports"]);
518     var fstr = "if(" +
519         Array.prototype.map.call(args,
520                                  function (x)
521                                      "iid.equals(Components.interfaces." + x + ")")
522         .join("||") +
523         ") return this; throw Components.results.NS_ERROR_NO_INTERFACE;";
524     return new Function("iid", fstr);
527 function set_branch_pref(branch, name, value) {
528     if (typeof(value) == "string") {
529         branch.setCharPref(name, value);
530     } else if (typeof(value) == "number") {
531         branch.setIntPref(name, value);
532     } else if (typeof(value) == "boolean") {
533         branch.setBoolPref(name, value);
534     }
537 function default_pref(name, value) {
538     var branch = preferences.getDefaultBranch(null);
539     set_branch_pref(branch, name, value);
542 function user_pref(name, value) {
543     var branch = preferences.getBranch(null);
544     set_branch_pref(branch, name, value);
547 function get_branch_pref(branch, name) {
548     switch (branch.getPrefType(name)) {
549     case branch.PREF_STRING:
550         return branch.getCharPref(name);
551     case branch.PREF_INT:
552         return branch.getIntPref(name);
553     case branch.PREF_BOOL:
554         return branch.getBoolPref(name);
555     default:
556         return null;
557     }
560 function get_localized_pref(name) {
561     try {
562         return preferences.getBranch(null).getComplexValue(name, Ci.nsIPrefLocalizedString).data;
563     } catch (e) {
564         return null;
565     }
568 function get_pref(name) {
569     var branch = preferences.getBranch(null);
570     return get_branch_pref(branch, name);
573 function get_default_pref(name) {
574     var branch = preferences.getDefaultBranch(null);
575     return get_branch_pref(branch, name);
578 function clear_pref(name) {
579     var branch = preferences.getBranch(null);
580     return branch.clearUserPref(name);
583 function pref_has_user_value(name) {
584     var branch = preferences.getBranch(null);
585     return branch.prefHasUserValue(name);
588 function pref_has_default_value(name) {
589     var branch = preferences.getDefaultBranch(null);
590     return branch.prefHasUserValue(name);
593 function session_pref (name, value) {
594     try { clear_pref (name); }
595     catch (e) {}
596     return default_pref (name, value);
599 const LOCALE_PREF = "general.useragent.locale";
601 function get_locale() {
602     return get_localized_pref(LOCALE_PREF) || get_pref(LOCALE_PREF);
605 const USER_AGENT_OVERRIDE_PREF = "general.useragent.override";
607 function set_user_agent(str) {
608     session_pref(USER_AGENT_OVERRIDE_PREF, str);
611 function define_builtin_commands(prefix, do_command_function, toggle_mark, mark_active_predicate) {
613     // Specify a docstring
614     function D(cmd, docstring) {
615         var o = new String(cmd);
616         o.doc = docstring;
617         return o;
618     }
620     // Specify a forward/reverse pair
621     function R(a, b) {
622         var o = [a,b];
623         o.is_reverse_pair = true;
624         return o;
625     }
627     // Specify a movement/select command pair.
628     function S(command, movement, select) {
629         var o = [movement, select];
630         o.command = command;
631         o.is_move_select_pair = true;
632         return o;
633     }
635     var builtin_commands = [
636         S(D("beginning-of-line", "Move or extend the selection to the beginning of the current line."),
637           D("cmd_beginLine", "Move point to the beginning of the current line."),
638           D("cmd_selectBeginLine", "Extend selection to the beginning of the current line.")),
639         S(D("end-of-line", "Move or extend the selection to the end of the current line."),
640           D("cmd_endLine", "Move point to the end of the current line."),
641           D("cmd_selectEndLine", "Extend selection to the end of the current line.")),
642         D("cmd_copy", "Copy the selection into the clipboard."),
643         "cmd_copyOrDelete",
644         D("cmd_cut", "Cut the selection into the clipboard."),
645         "cmd_cutOrDelete",
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         S(D("beginning-of-first-line", "Move or extend the selection to the beginning of the first line."),
649           D("cmd_moveTop", "Move point to the beginning of the first line."),
650           D("cmd_selectTop", "Extend selection to the beginning of the first line.")),
651         S(D("end-of-last-line", "Move or extend the selection to the end of the last line."),
652           D("cmd_moveBottom", "Move point to the end of the last line."),
653           D("cmd_selectBottom", "Extend selection to the end of the last line.")),
654         D("cmd_selectAll", "Select all."),
655         "cmd_scrollBeginLine",
656         "cmd_scrollEndLine",
657         D("cmd_scrollTop", "Scroll to the top of the buffer."),
658         D("cmd_scrollBottom", "Scroll to the bottom of the buffer.")];
660     var builtin_commands_with_count = [
661         R(S(D("forward-char", "Move or extend the selection forward one character."),
662             D("cmd_charNext", "Move point forward one character."),
663             D("cmd_selectCharNext", "Extend selection forward one character.")),
664           S(D("backward-char", "Move or extend the selection backward one character."),
665             D("cmd_charPrevious", "Move point backward one character."),
666             D("cmd_selectCharPrevious", "Extend selection backward one character."))),
667         R(D("cmd_deleteCharForward", "Delete the following character."),
668           D("cmd_deleteCharBackward", "Delete the previous character.")),
669         R(D("cmd_deleteWordForward", "Delete the following word."),
670           D("cmd_deleteWordBackward", "Delete the previous word.")),
671         R(S(D("forward-line", "Move or extend the selection forward one line."),
672             D("cmd_lineNext", "Move point forward one line."),
673             "cmd_selectLineNext", "Extend selection forward one line."),
674           S(D("backward-line", "Move or extend the selection backward one line."),
675             D("cmd_linePrevious", "Move point backward one line."),
676             D("cmd_selectLinePrevious", "Extend selection backward 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           S(D("backward-page", "Move or extend the selection backward one page."),
681             D("cmd_movePageUp", "Move point backward one page."),
682             D("cmd_selectPageUp", "Extend selection backward one page."))),
683         R(D("cmd_undo", "Undo last editing action."),
684           D("cmd_redo", "Redo last editing action.")),
685         R(S(D("forward-word", "Move or extend the selection forward one word."),
686             D("cmd_wordNext", "Move point forward one word."),
687             D("cmd_selectWordNext", "Extend selection forward one word.")),
688           S(D("backward-word", "Move or extend the selection backward one word."),
689             D("cmd_wordPrevious", "Move point backward one word."),
690             D("cmd_selectWordPrevious", "Extend selection backward one word."))),
691         R(D("cmd_scrollPageUp", "Scroll up one page."),
692           D("cmd_scrollPageDown", "Scroll down one page.")),
693         R(D("cmd_scrollLineUp", "Scroll up one line."),
694           D("cmd_scrollLineDown", "Scroll down one line.")),
695         R(D("cmd_scrollLeft", "Scroll left."),
696           D("cmd_scrollRight", "Scroll right.")),
697         D("cmd_paste", "Insert the contents of the clipboard.")];
699     interactive(prefix + "set-mark",
700                 "Toggle whether the mark is active.\n" +
701                 "When the mark is active, movement commands affect the selection.",
702                 toggle_mark);
704     function doc_for_builtin(c) {
705         var s = "";
706         if (c.doc != null)
707             s += c.doc + "\n";
708         return s + "Run the built-in command " + c + ".";
709     }
711     function define_simple_command(c) {
712         interactive(prefix + c, doc_for_builtin(c), function (I) { do_command_function(I, c); });
713     }
715     function get_move_select_doc_string(c) {
716         return c.command.doc +
717             "\nSpecifically, if the mark is inactive, runs `" + prefix + c[0] + "'.  " +
718             "Otherwise, runs `" + prefix + c[1] + "'.\n" +
719             "To toggle whether the mark is active, use `" + prefix + "set-mark'.";
720     }
722     for each (let c_temp in builtin_commands)  {
723         let c = c_temp;
724         if (c.is_move_select_pair) {
725             interactive(prefix + c.command, get_move_select_doc_string(c), function (I) {
726                 do_command_function(I, mark_active_predicate(I) ? c[1] : c[0]);
727             });
728             define_simple_command(c[0]);
729             define_simple_command(c[1]);
730         }
731         else
732             define_simple_command(c);
733     }
735     function get_reverse_pair_doc_string(main_doc, alt_command) {
736         return main_doc + "\n" +
737             "The prefix argument specifies a repeat count for this command.  " +
738             "If the count is negative, `" + prefix + alt_command + "' is performed instead with " +
739             "a corresponding positive repeat count.";
740     }
742     function define_simple_reverse_pair(a, b) {
743         interactive(prefix + a, get_reverse_pair_doc_string(doc_for_builtin(a), b),
744                     function (I) {
745                         do_repeatedly(do_command_function, I.p, [I, a], [I, b]);
746                     });
747         interactive(prefix + b, get_reverse_pair_doc_string(doc_for_builtin(b), a),
748                     function (I) {
749                         do_repeatedly(do_command_function, I.p, [I, b], [I, a]);
750                     });
751     }
753     for each (let c_temp in builtin_commands_with_count)
754     {
755         let c = c_temp;
756         if (c.is_reverse_pair) {
757             if (c[0].is_move_select_pair) {
758                 interactive(prefix + c[0].command, get_reverse_pair_doc_string(get_move_select_doc_string(c[0]),
759                                                                                c[1].command),
760                             function (I) {
761                                 var idx = mark_active_predicate(I) ? 1 : 0;
762                                 do_repeatedly(do_command_function, I.p, [I, c[0][idx]], [I, c[1][idx]]);
763                             });
764                 interactive(prefix + c[1].command, get_reverse_pair_doc_string(get_move_select_doc_string(c[1]),
765                                                                                c[0].command),
766                             function (I) {
767                                 var idx = mark_active_predicate(I) ? 1 : 0;
768                                 do_repeatedly(do_command_function, I.p, [I, c[1][idx]], [I, c[0][idx]]);
769                             });
770                 define_simple_reverse_pair(c[0][0], c[1][0]);
771                 define_simple_reverse_pair(c[0][1], c[1][1]);
772             } else
773                 define_simple_reverse_pair(c[0], c[1]);
774         } else {
775             let doc = doc_for_builtin(c) +
776                 "\nThe prefix argument specifies a positive repeat count for this command.";
777             interactive(prefix + c, doc, function (I) {
778                 do_repeatedly_positive(do_command_function, I.p, I, c);
779             });
780         }
781     }
784 var observer_service = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
786 function abort(str) {
787     var e = new Error(str);
788     e.__proto__ = abort.prototype;
789     return e;
791 abort.prototype.__proto__ = Error.prototype;
794 function get_temporary_file(name) {
795     if (name == null)
796         name = "temp.txt";
797     var file = file_locator.get("TmpD", Ci.nsIFile);
798     file.append(name);
799     // Create the file now to ensure that no exploits are possible
800     file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
801     return file;
805 /* FIXME: This should be moved somewhere else, perhaps. */
806 function create_info_panel(window, panel_class, row_arr) {
807     /* Show information panel above minibuffer */
809     var g = new dom_generator(window.document, XUL_NS);
811     var p = g.element("vbox", "class", "panel " + panel_class, "flex", "0");
812     var grid = g.element("grid", p);
813     var cols = g.element("columns", grid);
814     g.element("column", cols, "flex", "0");
815     g.element("column", cols, "flex", "1");
817     var rows = g.element("rows", grid);
818     var row;
820     for each (let [row_class, row_label, row_value] in row_arr) {
821         row = g.element("row", rows, "class", row_class);
822         g.element("label", row,
823                   "value", row_label,
824                   "class", "panel-row-label");
825         g.element("label", row,
826                   "value", row_value,
827                   "class", "panel-row-value");
828     }
829     window.minibuffer.insert_before(p);
831     p.destroy = function () {
832         this.parentNode.removeChild(this);
833     };
835     return p;
839 // read_from_x_primary_selection favors the X PRIMARY SELECTION, when
840 // it exists.  The builtin cmd_paste always uses X CLIPBOARD.  So this
841 // is an auxiliary utility, in case you need to work with the primary
842 // selection.
844 function read_from_x_primary_selection ()
846     // Get clipboard.
847     var clipboard = Components.classes["@mozilla.org/widget/clipboard;1"]
848         .getService(Components.interfaces.nsIClipboard);
850     // Create tranferable that will transfer the text.
851     var trans = Components.classes["@mozilla.org/widget/transferable;1"]
852         .createInstance(Components.interfaces.nsITransferable);
854     trans.addDataFlavor("text/unicode");
855     // If available, use selection clipboard, otherwise global one
856     if (clipboard.supportsSelectionClipboard())
857         clipboard.getData(trans, clipboard.kSelectionClipboard);
858     else
859         clipboard.getData(trans, clipboard.kGlobalClipboard);
861     var data = {};
862     var dataLen = {};
863     trans.getTransferData("text/unicode", data, dataLen);
865     if (data) {
866         data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
867         return data.data.substring(0, dataLen.value / 2);
868     } else {
869         return "";
870     }
873 var user_variables = new string_hashmap();
875 function define_variable(name, default_value, doc) {
876     conkeror[name] = default_value;
877     user_variables.put(name, {
878         default_value: default_value,
879         doc: doc,
880         shortdoc: get_shortdoc_string(doc),
881         source_code_reference: get_caller_source_code_reference() });
885 function register_user_stylesheet(url)
887     var uri = makeURL(url);
888     var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
889     sss.loadAndRegisterSheet(uri, sss.USER_SHEET);
892 function unregister_user_stylesheet(url)
894     var uri = makeURL(url);
895     var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
896     if (sss.sheetRegistered(uri, sss.USER_SHEET))
897         ss.unregisterSheet(uri, sss.USER_SHEET);
900 function predicate_alist_match(alist, key) {
901     for each (let i in alist) {
902         if (i[0](key))
903             return i[1];
904     }
905     return undefined;
909 function get_meta_title(doc) {
910     var title = doc.evaluate("//meta[@name='title']/@content", doc, xpath_lookup_namespace,
911                              Ci.nsIDOMXPathResult.STRING_TYPE , null);
912     if (title && title.stringValue)
913         return title.stringValue;
914     return null;
917 var rdf_service = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
919 const PREFIX_ITEM_URI     = "urn:mozilla:item:";
920 const PREFIX_NS_EM        = "http://www.mozilla.org/2004/em-rdf#";
922 var extension_manager = Cc["@mozilla.org/extensions/manager;1"].getService(Ci.nsIExtensionManager);
924 function get_extension_rdf_property(id, name, type) {
925     var value = extension_manager.datasource.GetTarget(
926         rdf_service.GetResource(PREFIX_ITEM_URI + id),
927         rdf_service.GetResource(PREFIX_NS_EM + name),
928         true);
929     if (value == null)
930         return null;
931     return value.QueryInterface(type || Ci.nsIRDFLiteral).Value;
934 function get_extension_update_item(id) {
935     return extension_manager.getItemForID(id);
938 function extension_info(id) {
939     this.id = id;
941 extension_info.prototype = {
942     // Returns the nsIUpdateItem object associated with this extension
943     get update_item () { return get_extension_update_item(this.id); },
945     get_rdf_property : function (name, type) {
946         return get_extension_rdf_property(this.id, name, type);
947     },
949     // RDF properties
950     get isDisabled () { return this.get_rdf_property("isDisabled"); },
951     get aboutURL () { return this.get_rdf_property("aboutURL"); },
952     get addonID () { return this.get_rdf_property("addonID"); },
953     get availableUpdateURL () { return this.get_rdf_property("availableUpdateURL"); },
954     get availableUpdateVersion () { return this.get_rdf_property("availableUpdateVersion"); },
955     get blocklisted () { return this.get_rdf_property("blocklisted"); },
956     get compatible () { return this.get_rdf_property("compatible"); },
957     get description () { return this.get_rdf_property("description"); },
958     get downloadURL () { return this.get_rdf_property("downloadURL"); },
959     get isDisabled () { return this.get_rdf_property("isDisabled"); },
960     get hidden () { return this.get_rdf_property("hidden"); },
961     get homepageURL () { return this.get_rdf_property("homepageURL"); },
962     get iconURL () { return this.get_rdf_property("iconURL"); },
963     get internalName () { return this.get_rdf_property("internalName"); },
964     get locked () { return this.get_rdf_property("locked"); },
965     get name () { return this.get_rdf_property("name"); },
966     get optionsURL () { return this.get_rdf_property("optionsURL"); },
967     get opType () { return this.get_rdf_property("opType"); },
968     get plugin () { return this.get_rdf_property("plugin"); },
969     get previewImage () { return this.get_rdf_property("previewImage"); },
970     get satisfiesDependencies () { return this.get_rdf_property("satisfiesDependencies"); },
971     get providesUpdatesSecurely () { return this.get_rdf_property("providesUpdatesSecurely"); },
972     get type () { return this.get_rdf_property("type", Ci.nsIRDFInt); },
973     get updateable () { return this.get_rdf_property("updateable"); },
974     get updateURL () { return this.get_rdf_property("updateURL"); },
975     get version () { return this.get_rdf_property("version"); }
978 function extension_is_enabled(id) {
979     var info = new extension_info(id);
980     return info.update_item && (info.isDisabled == "false");
983 function for_each_frame(root_frame, callback, start_with) {
984     if (start_with)
985         callback(start_with);
986     function helper(f) {
987         if (f == start_with)
988             return;
989         callback(f);
990         for (let i = 0; i < f.frames.length; ++i) {
991             helper(f.frames[i]);
992         }
993     }
994     helper(root_frame);
997 // Co-routine version of for_each_frame
998 function co_for_each_frame(root_frame, callback, start_with) {
999     if (start_with)
1000         yield callback(start_with);
1001     function helper(f) {
1002         if (f == start_with)
1003             return;
1004         yield callback(f);
1005         for (let i = 0; i < f.frames.length; ++i) {
1006             yield helper(f.frames[i]);
1007         }
1008     }
1009     yield helper(root_frame);
1012 function xml_http_request() {
1013     return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest).QueryInterface(Ci.nsIJSXMLHttpRequest).QueryInterface(Ci.nsIDOMEventTarget);
1016 var xml_http_request_load_listener = {
1017   // nsIBadCertListener2
1018   notifyCertProblem: function SSLL_certProblem(socketInfo, status, targetSite) {
1019     return true;
1020   },
1022   // nsISSLErrorListener
1023   notifySSLError: function SSLL_SSLError(socketInfo, error, targetSite) {
1024     return true;
1025   },
1027   // nsIInterfaceRequestor
1028   getInterface: function SSLL_getInterface(iid) {
1029     return this.QueryInterface(iid);
1030   },
1032   // nsISupports
1033   //
1034   // FIXME: array comprehension used here to hack around the lack of
1035   // Ci.nsISSLErrorListener in 2007 versions of xulrunner 1.9pre.
1036   // make it a simple generateQI when xulrunner is more stable.
1037   QueryInterface: XPCOMUtils.generateQI (
1038       [i for each (i in [Ci.nsIBadCertListener2,
1039                          Ci.nsISSLErrorListener,
1040                          Ci.nsIInterfaceRequestor])
1041        if (i)])
1045 define_keywords("$user", "$password", "$override_mime_type", "$headers");
1046 function send_http_request(lspec) {
1047     keywords(arguments, $user = undefined, $password = undefined,
1048              $override_mime_type = undefined, $headers = undefined);
1049     var req = xml_http_request();
1050     var cc = yield CONTINUATION;
1051     req.onreadystatechange = function () {
1052         if (req.readyState != 4)
1053             return;
1054         cc();
1055     };
1057     if (arguments.$override_mime_type)
1058         req.overrideMimeType(arguments.$override_mime_type);
1060     var post_data = load_spec_raw_post_data(lspec);
1062     var method = post_data ? "POST" : "GET";
1064     req.open(method, load_spec_uri_string(lspec), true, arguments.$user, arguments.$password);
1065     req.channel.notificationCallbacks = xml_http_request_load_listener;
1067     for each (let [name,value] in arguments.$headers) {
1068         req.setRequestHeader(name, value);
1069     }
1071     if (post_data) {
1072         req.setRequestHeader("Content-Type", load_spec_request_mime_type(lspec));
1073         req.send(post_data);
1074     } else
1075         req.send(null);
1077     try {
1078         yield SUSPEND;
1079     } catch (e) {
1080         req.abort();
1081         throw e;
1082     }
1084     // Let the caller access the status and reponse data
1085     yield co_return(req);
1089 var JSON = ("@mozilla.org/dom/json;1" in Cc) && Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);