permission-manager: remove internal use of string_hashmap
[conkeror.git] / modules / utils.js
blob8164bc9d12ac49ef0b960790c617885e9b0ef7fc
1 /**
2  * (C) Copyright 2004-2007 Shawn Betts
3  * (C) Copyright 2007-2011 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 require("io");
12 function string_hashset () {}
13 string_hashset.prototype = {
14     constructor: string_hashset,
16     add: function (s) {
17         this["-" + s] = true;
18     },
20     contains: function (s) {
21         return (("-" + s) in this);
22     },
24     remove: function (s) {
25         delete this["-" + s];
26     },
28     for_each: function (f) {
29         for (var i in this) {
30             if (i[0] == "-")
31                 f(i.slice(1));
32         }
33     },
35     iterator: function () {
36         for (let k in this) {
37             if (i[0] == "-")
38                 yield i.slice(1);
39         }
40     }
43 function string_hashmap () {}
44 string_hashmap.prototype = {
45     constructor: string_hashmap,
47     put: function (s,value) {
48         this["-" + s] = value;
49     },
51     contains: function (s) {
52         return (("-" + s) in this);
53     },
55     get: function (s, default_value) {
56         if (this.contains(s))
57             return this["-" + s];
58         return default_value;
59     },
61     get_put_default: function (s, default_value) {
62         if (this.contains(s))
63             return this["-" + s];
64         return (this["-" + s] = default_value);
65     },
67     remove: function (s) {
68         delete this["-" + s];
69     },
71     for_each: function (f) {
72         for (var i in this) {
73             if (i[0] == "-")
74                 f(i.slice(1), this[i]);
75         }
76     },
78     for_each_value: function (f) {
79         for (var i in this) {
80             if (i[0] == "-")
81                 f(this[i]);
82         }
83     },
85     iterator: function (only_keys) {
86         if (only_keys) {
87             for (let k in Iterator(this, true)) {
88                 if (k[0] == "-")
89                     yield k.slice(1);
90             }
91         } else {
92             for (let [k,v] in Iterator(this, false)) {
93                 if (k[0] == "-")
94                     yield [k.slice(1),v];
95             }
96         }
97     }
101 // Put the string on the clipboard
102 function writeToClipboard (str) {
103     var gClipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"]
104         .getService(Ci.nsIClipboardHelper);
105     gClipboardHelper.copyString(str);
109 function makeURLAbsolute (base, url) {
110     // Construct nsIURL.
111     var ioService = Cc["@mozilla.org/network/io-service;1"]
112         .getService(Ci.nsIIOService);
113     var baseURI  = ioService.newURI(base, null, null);
114     return ioService.newURI(baseURI.resolve(url), null, null).spec;
118 function make_file (path) {
119     if (path instanceof Ci.nsILocalFile)
120         return path;
121     if (path == "~")
122         return get_home_directory();
123     if (WINDOWS)
124         path = path.replace("/", "\\", "g");
125     if ((POSIX && path.substring(0,2) == "~/") ||
126         (WINDOWS && path.substring(0,2) == "~\\"))
127     {
128         var f = get_home_directory();
129         f.appendRelativePath(path.substring(2));
130     } else {
131         f = Cc["@mozilla.org/file/local;1"]
132             .createInstance(Ci.nsILocalFile);
133         f.initWithPath(path);
134     }
135     return f;
139 function make_file_from_chrome (url) {
140     var crs = Cc['@mozilla.org/chrome/chrome-registry;1']
141         .getService(Ci.nsIChromeRegistry);
142     var file = crs.convertChromeURL(make_uri(url));
143     return make_file(file.path);
146 function get_document_content_disposition (document_o) {
147     var content_disposition = null;
148     try {
149         content_disposition = document_o.defaultView
150             .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
151             .getInterface(Components.interfaces.nsIDOMWindowUtils)
152             .getDocumentMetadata("content-disposition");
153     } catch (e) { }
154     return content_disposition;
158 function set_focus_no_scroll (window, element) {
159     window.document.commandDispatcher.suppressFocusScroll = true;
160     element.focus();
161     window.document.commandDispatcher.suppressFocusScroll = false;
164 function do_repeatedly_positive (func, n) {
165     var args = Array.prototype.slice.call(arguments, 2);
166     while (n-- > 0)
167         func.apply(null, args);
170 function do_repeatedly (func, n, positive_args, negative_args) {
171     if (n < 0)
172         do func.apply(null, negative_args); while (++n < 0);
173     else
174         while (n-- > 0) func.apply(null, positive_args);
179  * Given a node, returns its position relative to the document.
181  * @param node The node to get the position of.
182  * @return An object with properties "x" and "y" representing its offset from
183  *         the left and top of the document, respectively.
184  */
185 function abs_point (node) {
186     var orig = node;
187     var pt = {};
188     try {
189         pt.x = node.offsetLeft;
190         pt.y = node.offsetTop;
191         // find imagemap's coordinates
192         if (node.tagName == "AREA") {
193             var coords = node.getAttribute("coords").split(",");
194             pt.x += Number(coords[0]);
195             pt.y += Number(coords[1]);
196         }
198         node = node.offsetParent;
199         // Sometimes this fails, so just return what we got.
201         while (node.tagName != "BODY") {
202             pt.x += node.offsetLeft;
203             pt.y += node.offsetTop;
204             node = node.offsetParent;
205         }
206     } catch(e) {
207 //      node = orig;
208 //      while (node.tagName != "BODY") {
209 //          alert("okay: " + node + " " + node.tagName + " " + pt.x + " " + pt.y);
210 //          node = node.offsetParent;
211 //      }
212     }
213     return pt;
217 const XHTML_NS = "http://www.w3.org/1999/xhtml";
218 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
219 const MATHML_NS = "http://www.w3.org/1998/Math/MathML";
220 const XLINK_NS = "http://www.w3.org/1999/xlink";
221 const SVG_NS = "http://www.w3.org/2000/svg";
223 function create_XUL (window, tag_name) {
224     return window.document.createElementNS(XUL_NS, tag_name);
228 /* Used in calls to XPath evaluate */
229 function xpath_lookup_namespace (prefix) {
230     return {
231         xhtml: XHTML_NS,
232         m: MATHML_NS,
233         xul: XUL_NS,
234         svg: SVG_NS
235     }[prefix] || null;
238 function method_caller (obj, func) {
239     return function () {
240         func.apply(obj, arguments);
241     };
245 function get_window_from_frame (frame) {
246     try {
247         var window = frame.QueryInterface(Ci.nsIInterfaceRequestor)
248             .getInterface(Ci.nsIWebNavigation)
249             .QueryInterface(Ci.nsIDocShellTreeItem)
250             .rootTreeItem
251             .QueryInterface(Ci.nsIInterfaceRequestor)
252             .getInterface(Ci.nsIDOMWindow).wrappedJSObject;
253         /* window is now an XPCSafeJSObjectWrapper */
254         window.escape_wrapper(function (w) { window = w; });
255         /* window is now completely unwrapped */
256         return window;
257     } catch (e) {
258         return null;
259     }
262 function get_buffer_from_frame (window, frame) {
263     var count = window.buffers.count;
264     for (var i = 0; i < count; ++i) {
265         var b = window.buffers.get_buffer(i);
266         if (b.top_frame == frame.top)
267             return b;
268     }
269     return null;
273 function dom_generator (document, ns) {
274     this.document = document;
275     this.ns = ns;
277 dom_generator.prototype = {
278     constructor: dom_generator,
279     element: function (tag, parent) {
280         var node = this.document.createElementNS(this.ns, tag);
281         var i = 1;
282         if (parent != null && (parent instanceof Ci.nsIDOMNode)) {
283             parent.appendChild(node);
284             i = 2;
285         }
286         for (var nargs = arguments.length; i < nargs; i += 2)
287             node.setAttribute(arguments[i], arguments[i+1]);
288         return node;
289     },
291     text: function (str, parent) {
292         var node = this.document.createTextNode(str);
293         if (parent)
294             parent.appendChild(node);
295         return node;
296     },
299     stylesheet_link: function (href, parent) {
300         var node = this.element("link");
301         node.setAttribute("rel", "stylesheet");
302         node.setAttribute("type", "text/css");
303         node.setAttribute("href", href);
304         if (parent)
305             parent.appendChild(node);
306         return node;
307     },
310     add_stylesheet: function (url) {
311         var head = this.document.documentElement.firstChild;
312         this.stylesheet_link(url, head);
313     }
317  * Generates a QueryInterface function suitable for an implemenation
318  * of an XPCOM interface.  Unlike XPCOMUtils, this uses the Function
319  * constructor to generate a slightly more efficient version.  The
320  * arguments can be either Strings or elements of
321  * Components.interfaces.
322  */
323 function generate_QI () {
324     var args = Array.prototype.slice.call(arguments).map(String).concat(["nsISupports"]);
325     var fstr = "if(" +
326         Array.prototype.map.call(args, function (x) {
327             return "iid.equals(Components.interfaces." + x + ")";
328         })
329         .join("||") +
330         ") return this; throw Components.results.NS_ERROR_NO_INTERFACE;";
331     return new Function("iid", fstr);
335 function abort (str) {
336     var e = new Error(str);
337     e.__proto__ = abort.prototype;
338     return e;
340 abort.prototype.__proto__ = Error.prototype;
343 function get_temporary_file (name) {
344     if (name == null)
345         name = "temp.txt";
346     var file = file_locator_service.get("TmpD", Ci.nsIFile);
347     file.append(name);
348     // Create the file now to ensure that no exploits are possible
349     file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
350     return file;
354 /* FIXME: This should be moved somewhere else, perhaps. */
355 function create_info_panel (window, panel_class, row_arr) {
356     /* Show information panel above minibuffer */
358     var g = new dom_generator(window.document, XUL_NS);
360     var p = g.element("vbox", "class", "panel " + panel_class, "flex", "0");
361     var grid = g.element("grid", p);
362     var cols = g.element("columns", grid);
363     g.element("column", cols, "flex", "0");
364     g.element("column", cols, "flex", "1");
366     var rows = g.element("rows", grid);
367     var row;
369     for each (let [row_class, row_label, row_value] in row_arr) {
370         row = g.element("row", rows, "class", row_class);
371         g.element("label", row,
372                   "value", row_label,
373                   "class", "panel-row-label");
374         g.element("label", row,
375                   "value", row_value,
376                   "class", "panel-row-value",
377                   "crop", "end");
378     }
379     window.minibuffer.insert_before(p);
381     p.destroy = function () {
382         this.parentNode.removeChild(this);
383     };
385     return p;
390  * Paste from the X primary selection, unless the system doesn't support a
391  * primary selection, in which case fall back to the clipboard.
392  */
393 function read_from_x_primary_selection () {
394     // Get clipboard.
395     let clipboard = Components.classes["@mozilla.org/widget/clipboard;1"]
396         .getService(Components.interfaces.nsIClipboard);
398     // Fall back to global clipboard if the system doesn't support a selection
399     let which_clipboard = clipboard.supportsSelectionClipboard() ?
400         clipboard.kSelectionClipboard : clipboard.kGlobalClipboard;
402     let flavors = ["text/unicode"];
404     // Don't barf if there's nothing on the clipboard
405     if (!clipboard.hasDataMatchingFlavors(flavors, flavors.length, which_clipboard))
406         return "";
408     // Create transferable that will transfer the text.
409     let trans = Components.classes["@mozilla.org/widget/transferable;1"]
410         .createInstance(Components.interfaces.nsITransferable);
412     for each (let flavor in flavors) {
413         trans.addDataFlavor(flavor);
414     }
415     clipboard.getData(trans, which_clipboard);
417     var data_flavor = {};
418     var data = {};
419     var dataLen = {};
420     trans.getAnyTransferData(data_flavor, data, dataLen);
422     if (data) {
423         data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
424         let data_length = dataLen.value;
425         if (data_flavor.value == "text/unicode")
426             data_length = dataLen.value / 2;
427         return data.data.substring(0, data_length);
428     } else {
429         return "";
430     }
434 function predicate_alist_match (alist, key) {
435     for each (let i in alist) {
436         if (i[0] instanceof RegExp) {
437             if (i[0].exec(key))
438                 return i[1];
439         } else if (i[0](key))
440             return i[1];
441     }
442     return undefined;
446 function get_meta_title (doc) {
447     var title = doc.evaluate("//meta[@name='title']/@content", doc, xpath_lookup_namespace,
448                              Ci.nsIDOMXPathResult.STRING_TYPE , null);
449     if (title && title.stringValue)
450         return title.stringValue;
451     return null;
455 function queue () {
456     this.input = [];
457     this.output = [];
459 queue.prototype = {
460     constructor: queue,
461     get length () {
462         return this.input.length + this.output.length;
463     },
464     push: function (x) {
465         this.input[this.input.length] = x;
466     },
467     pop: function (x) {
468         let l = this.output.length;
469         if (!l) {
470             l = this.input.length;
471             if (!l)
472                 return undefined;
473             this.output = this.input.reverse();
474             this.input = [];
475             let x = this.output[l];
476             this.output.length--;
477             return x;
478         }
479     }
482 function frame_iterator (root_frame, start_with) {
483     var q = new queue, x;
484     if (start_with) {
485         x = start_with;
486         do {
487             yield x;
488             for (let i = 0, nframes = x.frames.length; i < nframes; ++i)
489                 q.push(x.frames[i]);
490         } while ((x = q.pop()));
491     }
492     x = root_frame;
493     do {
494         if (x == start_with)
495             continue;
496         yield x;
497         for (let i = 0, nframes = x.frames.length; i < nframes; ++i)
498             q.push(x.frames[i]);
499     } while ((x = q.pop()));
502 function xml_http_request () {
503     return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
504         .createInstance(Ci.nsIXMLHttpRequest)
505         .QueryInterface(Ci.nsIJSXMLHttpRequest)
506         .QueryInterface(Ci.nsIDOMEventTarget);
509 var xml_http_request_load_listener = {
510   // nsIBadCertListener2
511   notifyCertProblem: function SSLL_certProblem (socketInfo, status, targetSite) {
512     return true;
513   },
515   // nsISSLErrorListener
516   notifySSLError: function SSLL_SSLError (socketInfo, error, targetSite) {
517     return true;
518   },
520   // nsIInterfaceRequestor
521   getInterface: function SSLL_getInterface (iid) {
522     return this.QueryInterface(iid);
523   },
525   // nsISupports
526   //
527   // FIXME: array comprehension used here to hack around the lack of
528   // Ci.nsISSLErrorListener in 2007 versions of xulrunner 1.9pre.
529   // make it a simple generateQI when xulrunner is more stable.
530   QueryInterface: XPCOMUtils.generateQI(
531       [i for each (i in [Ci.nsIBadCertListener2,
532                          Ci.nsISSLErrorListener,
533                          Ci.nsIInterfaceRequestor])
534        if (i)])
539  * Coroutine interface for sending an HTTP request and waiting for the
540  * response. (This includes so-called "AJAX" requests.)
542  * @param lspec (required) a load_spec object or URI string (see load-spec.js)
544  * The request URI is obtained from this argument. In addition, if the
545  * load spec specifies post data, a POST request is made instead of a
546  * GET request, and the post data included in the load spec is
547  * sent. Specifically, the request_mime_type and raw_post_data
548  * properties of the load spec are used.
550  * @param $user (optional) HTTP user name to include in the request headers
551  * @param $password (optional) HTTP password to include in the request headers
553  * @param $override_mime_type (optional) Force the response to be interpreted
554  *                            as having the specified MIME type.  This is only
555  *                            really useful for forcing the MIME type to be
556  *                            text/xml or something similar, such that it is
557  *                            automatically parsed into a DOM document.
558  * @param $headers (optional) an array of [name,value] pairs (each specified as
559  *                 a two-element array) specifying additional headers to add to
560  *                 the request.
562  * @returns After the request completes (either successfully or with an error),
563  *          the nsIXMLHttpRequest object is returned.  Its responseText (for any
564  *          arbitrary document) or responseXML (if the response type is an XML
565  *          content type) properties can be accessed to examine the response
566  *          document.
568  * If an exception is thrown to the continutation (which can be obtained by the
569  * caller by calling yield CONTINUATION prior to calling this function) while the
570  * request is in progress (i.e. before this coroutine returns), the request will
571  * be aborted, and the exception will be propagated to the caller.
573  **/
574 define_keywords("$user", "$password", "$override_mime_type", "$headers");
575 function send_http_request (lspec) {
576     // why do we get warnings in jsconsole unless we initialize the
577     // following keywords?
578     keywords(arguments, $user = undefined, $password = undefined,
579              $override_mime_type = undefined, $headers = undefined);
580     if (! (lspec instanceof load_spec))
581         lspec = load_spec(lspec);
582     var req = xml_http_request();
583     var cc = yield CONTINUATION;
584     var aborting = false;
585     req.onreadystatechange = function send_http_request__onreadystatechange () {
586         if (req.readyState != 4)
587             return;
588         if (aborting)
589             return;
590         cc();
591     };
593     if (arguments.$override_mime_type)
594         req.overrideMimeType(arguments.$override_mime_type);
596     var post_data = load_spec_raw_post_data(lspec);
598     var method = post_data ? "POST" : "GET";
600     req.open(method, load_spec_uri_string(lspec), true, arguments.$user, arguments.$password);
601     req.channel.notificationCallbacks = xml_http_request_load_listener;
603     for each (let [name,value] in arguments.$headers) {
604         req.setRequestHeader(name, value);
605     }
607     if (post_data) {
608         req.setRequestHeader("Content-Type", load_spec_request_mime_type(lspec));
609         req.send(post_data);
610     } else
611         req.send(null);
613     try {
614         yield SUSPEND;
615     } catch (e) {
616         aborting = true;
617         req.abort();
618         throw e;
619     }
621     // Let the caller access the status and reponse data
622     yield co_return(req);
627  * scroll_selection_into_view takes an editable element, and scrolls it so
628  * that the selection (or insertion point) are visible.
629  */
630 function scroll_selection_into_view (field) {
631     if (field.namespaceURI == XUL_NS)
632         field = field.inputField;
633     try {
634         field.QueryInterface(Ci.nsIDOMNSEditableElement)
635             .editor
636             .selectionController
637             .scrollSelectionIntoView(
638                 Ci.nsISelectionController.SELECTION_NORMAL,
639                 Ci.nsISelectionController.SELECTION_FOCUS_REGION,
640                 true);
641     } catch (e) {
642         // we'll get here for richedit fields
643     }
648  * build_url_regexp builds a regular expression to match URLs for a given
649  * web site.
651  * Both the $domain and $path arguments can be either regexps, in
652  * which case they will be matched as is, or strings, in which case
653  * they will be matched literally.
655  * $tlds specifies a list of valid top-level-domains to match, and
656  * defaults to .com. Useful for when e.g. foo.org and foo.com are the
657  * same.
659  * If $allow_www is true, www.domain.tld will also be allowed.
660  */
661 define_keywords("$domain", "$path", "$tlds", "$allow_www");
662 function build_url_regexp () {
663     function regexp_to_string (obj) {
664         if (typeof obj == "object" && "source" in obj)
665             return obj.source;
666         return quotemeta(obj);
667     }
669     keywords(arguments, $path = "", $tlds = ["com"], $allow_www = false);
670     var domain = regexp_to_string(arguments.$domain);
671     if (arguments.$allow_www) {
672         domain = "(?:www\.)?" + domain;
673     }
674     var path = regexp_to_string(arguments.$path);
675     var tlds = arguments.$tlds;
676     var regexp = "^https?://" + domain + "\\." + choice_regexp(tlds) + "/" + path;
677     return new RegExp(regexp);
681 function compute_up_url (uri) {
682     uri = uri.clone().QueryInterface(Ci.nsIURL);
683     for each (var p in ["ref", "query", "param", "fileName"]) {
684         if (uri[p] != "") {
685             uri[p] = "";
686             return uri.spec;
687         }
688     }
689     return uri.resolve("..");
693 function url_path_trim (url) {
694     var uri = make_uri(url);
695     uri.spec = url;
696     uri.path = "";
697     return uri.spec;
700 /* possibly_valid_url returns true if the string might be a valid
701  * thing to pass to nsIWebNavigation.loadURI.  Currently just checks
702  * that there's no whitespace in the middle and that it's not entirely
703  * whitespace.
704  */
705 function possibly_valid_url (url) {
706     return !(/\S\s+\S/.test(url)) && !(/^\s*$/.test(url));
711  * Convenience function for making simple XPath lookups in a document.
713  * @param doc The document to look in.
714  * @param exp The XPath expression to search for.
715  * @return The XPathResult object representing the set of found nodes.
716  */
717 function xpath_lookup (doc, exp) {
718     return doc.evaluate(exp, doc, null, Ci.nsIDOMXPathResult.ANY_TYPE, null);
722 /* get_contents_synchronously returns the contents of the given
723  * url (string or nsIURI) as a string on success, or null on failure.
724  */
725 function get_contents_synchronously (url) {
726     var ioService=Cc["@mozilla.org/network/io-service;1"]
727         .getService(Ci.nsIIOService);
728     var scriptableStream=Cc["@mozilla.org/scriptableinputstream;1"]
729         .getService(Ci.nsIScriptableInputStream);
730     var channel;
731     var input;
732     try {
733         if (url instanceof Ci.nsIURI)
734             channel = ioService.newChannelFromURI(url);
735         else
736             channel = ioService.newChannel(url, null, null);
737         input=channel.open();
738     } catch (e) {
739         return null;
740     }
741     scriptableStream.init(input);
742     var str=scriptableStream.read(input.available());
743     scriptableStream.close();
744     input.close();
745     return str;
750  * dom_add_class adds a css class to the given dom node.
751  */
752 function dom_add_class (node, cssclass) {
753     if (node.className) {
754         var cs = node.className.split(" ");
755         if (cs.indexOf(cssclass) != -1)
756             return;
757         cs.push(cssclass);
758         node.className = cs.join(" ");
759     } else
760         node.className = cssclass;
764  * dom_remove_class removes the given css class from the given dom node.
765  */
766 function dom_remove_class (node, cssclass) {
767     if (! node.className)
768         return;
769     var classes = node.className.split(" ");
770     classes = classes.filter(function (x) { return x != cssclass; });
771     node.className = classes.join(" ");
776  * dom_node_flash adds the given cssclass to the node for a brief interval.
777  * this class can be styled, to create a flashing effect.
778  */
779 function dom_node_flash (node, cssclass) {
780     dom_add_class(node, cssclass);
781     call_after_timeout(
782         function () {
783             dom_remove_class(node, cssclass);
784         },
785         400);
790  * data is an an alist (array of 2 element arrays) where each pair is a key
791  * and a value.
793  * The return type is a mime input stream that can be passed as postData to
794  * nsIWebNavigation.loadURI.  In terms of Conkeror's API, the return value
795  * of this function is of the correct type for the `post_data' field of a
796  * load_spec.
797  */
798 function make_post_data (data) {
799     data = [(encodeURIComponent(pair[0])+'='+encodeURIComponent(pair[1]))
800             for each (pair in data)].join('&');
801     data = string_input_stream(data);
802     return mime_input_stream(
803         data, [["Content-Type", "application/x-www-form-urlencoded"]]);
808  * Centers the viewport around a given element.
810  * @param win  The window to scroll.
811  * @param elem The element arund which we put the viewport.
812  */
813 function center_in_viewport (win, elem) {
814     let point = abs_point(elem);
816     point.x -= win.innerWidth / 2;
817     point.y -= win.innerHeight / 2;
819     win.scrollTo(point.x, point.y);
824  * Simple predicate returns true if elem is an nsIDOMNode or
825  * nsIDOMWindow.
826  */
827 function dom_node_or_window_p (elem) {
828     if (elem instanceof Ci.nsIDOMNode)
829         return true;
830     if (elem instanceof Ci.nsIDOMWindow)
831         return true;
832     return false;
836  * Given a hook name, a buffer and a function, waits until the buffer document
837  * has fully loaded, then calls the function with the buffer as its only
838  * argument.
840  * @param {String} The hook name.
841  * @param {buffer} The buffer.
842  * @param {function} The function to call with the buffer as its argument once
843  *                   the buffer has loaded.
844  */
845 function do_when (hook, buffer, fun) {
846     if (buffer.browser.webProgress.isLoadingDocument)
847         add_hook.call(buffer, hook, fun);
848     else
849         fun(buffer);
854  * evaluate string s as javascript in the 'this' scope in which evaluate
855  * is called.
856  */
857 function evaluate (s) {
858     try {
859         var obs = Cc["@mozilla.org/observer-service;1"]
860             .getService(Ci.nsIObserverService);
861         obs.notifyObservers(null, "startupcache-invalidate", null);
862         var temp = get_temporary_file("conkeror-evaluate.tmp.js");
863         write_text_file(temp, s);
864         var url = make_uri(temp).spec;
865         return load_url(url, this);
866     } finally {
867         if (temp && temp.exists())
868             temp.remove(false);
869     }
874  * set_protocol_handler takes a protocol and a handler spec.  If the
875  * handler is true, Mozilla will (try to) handle this protocol internally.
876  * If the handler null, the user will be prompted for a handler when a
877  * resource of this protocol is requested.  If the handler is an nsIFile,
878  * the program it gives will be launched with the url as an argument.  If
879  * the handler is a string, it will be interpreted as an URL template for
880  * a web service and the sequence '%s' within it will be replaced by the
881  * url-encoded url.
882  */
883 function set_protocol_handler (protocol, handler) {
884     var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"]
885         .getService(Ci.nsIExternalProtocolService);
886     var info = eps.getProtocolHandlerInfo(protocol);
887     var expose_pref = "network.protocol-handler.expose."+protocol;
888     if (handler == true) {
889         // internal handling
890         clear_default_pref(expose_pref);
891     } else if (handler) {
892         // external handling
893         if (handler instanceof Ci.nsIFile) {
894             var h = Cc["@mozilla.org/uriloader/local-handler-app;1"]
895                 .createInstance(Ci.nsILocalHandlerApp);
896             h.executable = handler;
897         } else if (typeof handler == "string") {
898             h = Cc["@mozilla.org/uriloader/web-handler-app;1"]
899                 .createInstance(Ci.nsIWebHandlerApp);
900             var uri = make_uri(handler);
901             h.name = uri.host;
902             h.uriTemplate = handler;
903         }
904         info.alwaysAskBeforeHandling = false;
905         info.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
906         info.possibleApplicationHandlers.clear();
907         info.possibleApplicationHandlers.appendElement(h, false);
908         info.preferredApplicationHandler = h;
909         session_pref(expose_pref, false);
910     } else {
911         // prompt
912         info.alwaysAskBeforeHandling = true;
913         info.preferredAction = Ci.nsIHandlerInfo.alwaysAsk;
914         session_pref(expose_pref, false);
915     }
916     var hs = Cc["@mozilla.org/uriloader/handler-service;1"]
917         .getService(Ci.nsIHandlerService);
918     hs.store(info);
921 provide("utils");