Further adapt debian webjumps as suggested by John J. Foerch
[conkeror.git] / modules / utils.js
blob6225c911e162766c33aa4485966844bc181ad6e5
1 /**
2  * (C) Copyright 2004-2007 Shawn Betts
3  * (C) Copyright 2007-2009 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 in_module(null);
12 function string_hashset () {}
14 string_hashset.prototype = {
15     constructor : string_hashset,
17     add : function (s) {
18         this["-" + s] = true;
19     },
21     contains : function (s) {
22         return (("-" + s) in this);
23     },
25     remove : function (s) {
26         delete this["-" + s];
27     },
29     for_each : function (f) {
30         for (var i in this) {
31             if (i[0] == "-")
32                 f(i.slice(1));
33         }
34     },
36     iterator : function () {
37         for (let k in this) {
38             if (i[0] == "-")
39                 yield i.slice(1);
40         }
41     }
44 function string_hashmap () {}
46 string_hashmap.prototype = {
47     constructor : string_hashmap,
49     put : function (s,value) {
50         this["-" + s] = value;
51     },
53     contains : function (s) {
54         return (("-" + s) in this);
55     },
57     get : function (s, default_value) {
58         if (this.contains(s))
59             return this["-" + s];
60         return default_value;
61     },
63     get_put_default : function (s, default_value) {
64         if (this.contains(s))
65             return this["-" + s];
66         return (this["-" + s] = default_value);
67     },
69     remove : function (s) {
70         delete this["-" + s];
71     },
73     for_each : function (f) {
74         for (var i in this) {
75             if (i[0] == "-")
76                 f(i.slice(1), this[i]);
77         }
78     },
80     for_each_value : function (f) {
81         for (var i in this) {
82             if (i[0] == "-")
83                 f(this[i]);
84         }
85     },
87     iterator: function (only_keys) {
88         if (only_keys) {
89             for (let k in Iterator(this, true)) {
90                 if (k[0] == "-")
91                     yield k.slice(1);
92             }
93         } else {
94             for (let [k,v] in Iterator(this, false)) {
95                 if (k[0] == "-")
96                     yield [k.slice(1),v];
97             }
98         }
99     }
103 // Put the string on the clipboard
104 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) {
112     // Construct nsIURL.
113     var ioService = Cc["@mozilla.org/network/io-service;1"]
114         .getService(Ci.nsIIOService);
115     var baseURI  = ioService.newURI(base, null, null);
116     return ioService.newURI(baseURI.resolve(url), null, null).spec;
120 function make_file (path) {
121     if (path instanceof Ci.nsILocalFile)
122         return path;
123     var f = Cc["@mozilla.org/file/local;1"]
124         .createInstance(Ci.nsILocalFile);
125     f.initWithPath(path);
126     return f;
130 function make_file_from_chrome (url) {
131     var crs = Cc['@mozilla.org/chrome/chrome-registry;1']
132         .getService(Ci.nsIChromeRegistry);
133     var file = crs.convertChromeURL(make_uri(url));
134     return make_file(file.path);
137 function get_document_content_disposition (document_o) {
138     var content_disposition = null;
139     try {
140         content_disposition = document_o.defaultView
141             .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
142             .getInterface(Components.interfaces.nsIDOMWindowUtils)
143             .getDocumentMetadata("content-disposition");
144     } catch (e) { }
145     return content_disposition;
149 function set_focus_no_scroll (window, element) {
150     window.document.commandDispatcher.suppressFocusScroll = true;
151     element.focus();
152     window.document.commandDispatcher.suppressFocusScroll = false;
155 function do_repeatedly_positive (func, n) {
156     var args = Array.prototype.slice.call(arguments, 2);
157     while (n-- > 0)
158         func.apply(null, args);
161 function do_repeatedly (func, n, positive_args, negative_args) {
162     if (n < 0)
163         do func.apply(null, negative_args); while (++n < 0);
164     else
165         while (n-- > 0) func.apply(null, positive_args);
170  * Given a node, returns its position relative to the document.
172  * @param node The node to get the position of.
173  * @return An object with properties "x" and "y" representing its offset from
174  *         the left and top of the document, respectively.
175  */
176 function abs_point (node) {
177     var orig = node;
178     var pt = {};
179     try {
180         pt.x = node.offsetLeft;
181         pt.y = node.offsetTop;
182         // find imagemap's coordinates
183         if (node.tagName == "AREA") {
184             var coords = node.getAttribute("coords").split(",");
185             pt.x += Number(coords[0]);
186             pt.y += Number(coords[1]);
187         }
189         node = node.offsetParent;
190         // Sometimes this fails, so just return what we got.
192         while (node.tagName != "BODY") {
193             pt.x += node.offsetLeft;
194             pt.y += node.offsetTop;
195             node = node.offsetParent;
196         }
197     } catch(e) {
198 //      node = orig;
199 //      while (node.tagName != "BODY") {
200 //          alert("okay: " + node + " " + node.tagName + " " + pt.x + " " + pt.y);
201 //          node = node.offsetParent;
202 //      }
203     }
204     return pt;
208 const XHTML_NS = "http://www.w3.org/1999/xhtml";
209 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
210 const MATHML_NS = "http://www.w3.org/1998/Math/MathML";
211 const XLINK_NS = "http://www.w3.org/1999/xlink";
213 function create_XUL (window, tag_name) {
214     return window.document.createElementNS(XUL_NS, tag_name);
218 /* Used in calls to XPath evaluate */
219 function xpath_lookup_namespace (prefix) {
220     return {
221         xhtml: XHTML_NS,
222         m: MATHML_NS,
223         xul: XUL_NS
224     }[prefix] || null;
227 function method_caller (obj, func) {
228     return function () {
229         func.apply(obj, arguments);
230     };
234 function get_window_from_frame (frame) {
235     try {
236         var window = frame.QueryInterface(Ci.nsIInterfaceRequestor)
237             .getInterface(Ci.nsIWebNavigation)
238             .QueryInterface(Ci.nsIDocShellTreeItem)
239             .rootTreeItem
240             .QueryInterface(Ci.nsIInterfaceRequestor)
241             .getInterface(Ci.nsIDOMWindow).wrappedJSObject;
242         /* window is now an XPCSafeJSObjectWrapper */
243         window.escape_wrapper(function (w) { window = w; });
244         /* window is now completely unwrapped */
245         return window;
246     } catch (e) {
247         return null;
248     }
251 function get_buffer_from_frame (window, frame) {
252     var count = window.buffers.count;
253     for (var i = 0; i < count; ++i) {
254         var b = window.buffers.get_buffer(i);
255         if (b.top_frame == frame.top)
256             return b;
257     }
258     return null;
262 function dom_generator (document, ns) {
263     this.document = document;
264     this.ns = ns;
266 dom_generator.prototype = {
267     element : function (tag, parent) {
268         var node = this.document.createElementNS(this.ns, tag);
269         var i = 1;
270         if (parent != null && (parent instanceof Ci.nsIDOMNode)) {
271             parent.appendChild(node);
272             i = 2;
273         }
274         for (var nargs = arguments.length; i < nargs; i += 2)
275             node.setAttribute(arguments[i], arguments[i+1]);
276         return node;
277     },
279     text : function (str, parent) {
280         var node = this.document.createTextNode(str);
281         if (parent)
282             parent.appendChild(node);
283         return node;
284     },
287     stylesheet_link : function (href, parent) {
288         var node = this.element("link");
289         node.setAttribute("rel", "stylesheet");
290         node.setAttribute("type", "text/css");
291         node.setAttribute("href", href);
292         if (parent)
293             parent.appendChild(node);
294         return node;
295     },
298     add_stylesheet : function (url) {
299         var head = this.document.documentElement.firstChild;
300         this.stylesheet_link(url, head);
301     }
305  * Generates a QueryInterface function suitable for an implemenation
306  * of an XPCOM interface.  Unlike XPCOMUtils, this uses the Function
307  * constructor to generate a slightly more efficient version.  The
308  * arguments can be either Strings or elements of
309  * Components.interfaces.
310  */
311 function generate_QI () {
312     var args = Array.prototype.slice.call(arguments).map(String).concat(["nsISupports"]);
313     var fstr = "if(" +
314         Array.prototype.map.call(args, function (x) {
315             return "iid.equals(Components.interfaces." + x + ")";
316         })
317         .join("||") +
318         ") return this; throw Components.results.NS_ERROR_NO_INTERFACE;";
319     return new Function("iid", fstr);
323 const USER_AGENT_OVERRIDE_PREF = "general.useragent.override";
325 function set_user_agent (str) {
326     session_pref(USER_AGENT_OVERRIDE_PREF, str);
330 function abort (str) {
331     var e = new Error(str);
332     e.__proto__ = abort.prototype;
333     return e;
335 abort.prototype.__proto__ = Error.prototype;
338 function get_temporary_file (name) {
339     if (name == null)
340         name = "temp.txt";
341     var file = file_locator_service.get("TmpD", Ci.nsIFile);
342     file.append(name);
343     // Create the file now to ensure that no exploits are possible
344     file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
345     return file;
349 /* FIXME: This should be moved somewhere else, perhaps. */
350 function create_info_panel (window, panel_class, row_arr) {
351     /* Show information panel above minibuffer */
353     var g = new dom_generator(window.document, XUL_NS);
355     var p = g.element("vbox", "class", "panel " + panel_class, "flex", "0");
356     var grid = g.element("grid", p);
357     var cols = g.element("columns", grid);
358     g.element("column", cols, "flex", "0");
359     g.element("column", cols, "flex", "1");
361     var rows = g.element("rows", grid);
362     var row;
364     for each (let [row_class, row_label, row_value] in row_arr) {
365         row = g.element("row", rows, "class", row_class);
366         g.element("label", row,
367                   "value", row_label,
368                   "class", "panel-row-label");
369         g.element("label", row,
370                   "value", row_value,
371                   "class", "panel-row-value",
372                   "crop", "end");
373     }
374     window.minibuffer.insert_before(p);
376     p.destroy = function () {
377         this.parentNode.removeChild(this);
378     };
380     return p;
385  * Paste from the X primary selection, unless the system doesn't support a
386  * primary selection, in which case fall back to the clipboard.
387  */
388 function read_from_x_primary_selection () {
389     // Get clipboard.
390     let clipboard = Components.classes["@mozilla.org/widget/clipboard;1"]
391         .getService(Components.interfaces.nsIClipboard);
393     // Fall back to global clipboard if the system doesn't support a selection
394     let which_clipboard = clipboard.supportsSelectionClipboard() ?
395         clipboard.kSelectionClipboard : clipboard.kGlobalClipboard;
397     let flavors = ["text/unicode"];
399     // Don't barf if there's nothing on the clipboard
400     if (!clipboard.hasDataMatchingFlavors(flavors, flavors.length, which_clipboard))
401         return "";
403     // Create transferable that will transfer the text.
404     let trans = Components.classes["@mozilla.org/widget/transferable;1"]
405         .createInstance(Components.interfaces.nsITransferable);
407     for each (let flavor in flavors) {
408         trans.addDataFlavor(flavor);
409     }
410     clipboard.getData(trans, which_clipboard);
412     var data_flavor = {};
413     var data = {};
414     var dataLen = {};
415     trans.getAnyTransferData(data_flavor, data, dataLen);
417     if (data) {
418         data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
419         let data_length = dataLen.value;
420         if (data_flavor.value == "text/unicode")
421             data_length = dataLen.value / 2;
422         return data.data.substring(0, data_length);
423     } else {
424         return "";
425     }
429 function predicate_alist_match (alist, key) {
430     for each (let i in alist) {
431         if (i[0](key))
432             return i[1];
433     }
434     return undefined;
438 function get_meta_title (doc) {
439     var title = doc.evaluate("//meta[@name='title']/@content", doc, xpath_lookup_namespace,
440                              Ci.nsIDOMXPathResult.STRING_TYPE , null);
441     if (title && title.stringValue)
442         return title.stringValue;
443     return null;
447 function queue () {
448     this.input = [];
449     this.output = [];
451 queue.prototype = {
452     get length () {
453         return this.input.length + this.output.length;
454     },
455     push: function (x) {
456         this.input[this.input.length] = x;
457     },
458     pop: function (x) {
459         let l = this.output.length;
460         if (!l) {
461             l = this.input.length;
462             if (!l)
463                 return undefined;
464             this.output = this.input.reverse();
465             this.input = [];
466             let x = this.output[l];
467             this.output.length--;
468             return x;
469         }
470     }
473 function frame_iterator (root_frame, start_with) {
474     var q = new queue, x;
475     if (start_with) {
476         x = start_with;
477         do {
478             yield x;
479             for (let i = 0, nframes = x.frames.length; i < nframes; ++i)
480                 q.push(x.frames[i]);
481         } while ((x = q.pop()));
482     }
483     x = root_frame;
484     do {
485         if (x == start_with)
486             continue;
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()));
493 function xml_http_request () {
494     return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
495         .createInstance(Ci.nsIXMLHttpRequest)
496         .QueryInterface(Ci.nsIJSXMLHttpRequest)
497         .QueryInterface(Ci.nsIDOMEventTarget);
500 var xml_http_request_load_listener = {
501   // nsIBadCertListener2
502   notifyCertProblem: function SSLL_certProblem (socketInfo, status, targetSite) {
503     return true;
504   },
506   // nsISSLErrorListener
507   notifySSLError: function SSLL_SSLError (socketInfo, error, targetSite) {
508     return true;
509   },
511   // nsIInterfaceRequestor
512   getInterface: function SSLL_getInterface (iid) {
513     return this.QueryInterface(iid);
514   },
516   // nsISupports
517   //
518   // FIXME: array comprehension used here to hack around the lack of
519   // Ci.nsISSLErrorListener in 2007 versions of xulrunner 1.9pre.
520   // make it a simple generateQI when xulrunner is more stable.
521   QueryInterface: XPCOMUtils.generateQI(
522       [i for each (i in [Ci.nsIBadCertListener2,
523                          Ci.nsISSLErrorListener,
524                          Ci.nsIInterfaceRequestor])
525        if (i)])
530  * Coroutine interface for sending an HTTP request and waiting for the
531  * response. (This includes so-called "AJAX" requests.)
533  * @param lspec (required) a load_spec object or URI string (see load-spec.js)
535  * The request URI is obtained from this argument. In addition, if the
536  * load spec specifies post data, a POST request is made instead of a
537  * GET request, and the post data included in the load spec is
538  * sent. Specifically, the request_mime_type and raw_post_data
539  * properties of the load spec are used.
541  * @param $user (optional) HTTP user name to include in the request headers
542  * @param $password (optional) HTTP password to include in the request headers
544  * @param $override_mime_type (optional) Force the response to be interpreted
545  *                            as having the specified MIME type.  This is only
546  *                            really useful for forcing the MIME type to be
547  *                            text/xml or something similar, such that it is
548  *                            automatically parsed into a DOM document.
549  * @param $headers (optional) an array of [name,value] pairs (each specified as
550  *                 a two-element array) specifying additional headers to add to
551  *                 the request.
553  * @returns After the request completes (either successfully or with an error),
554  *          the nsIXMLHttpRequest object is returned.  Its responseText (for any
555  *          arbitrary document) or responseXML (if the response type is an XML
556  *          content type) properties can be accessed to examine the response
557  *          document.
559  * If an exception is thrown to the continutation (which can be obtained by the
560  * caller by calling yield CONTINUATION prior to calling this function) while the
561  * request is in progress (i.e. before this coroutine returns), the request will
562  * be aborted, and the exception will be propagated to the caller.
564  **/
565 define_keywords("$user", "$password", "$override_mime_type", "$headers");
566 function send_http_request (lspec) {
567     // why do we get warnings in jsconsole unless we initialize the
568     // following keywords?
569     keywords(arguments, $user = undefined, $password = undefined,
570              $override_mime_type = undefined, $headers = undefined);
571     if (! (lspec instanceof load_spec))
572         lspec = load_spec(lspec);
573     var req = xml_http_request();
574     var cc = yield CONTINUATION;
575     var aborting = false;
576     req.onreadystatechange = function send_http_request__onreadysatechange () {
577         if (req.readyState != 4)
578             return;
579         if (aborting)
580             return;
581         cc();
582     };
584     if (arguments.$override_mime_type)
585         req.overrideMimeType(arguments.$override_mime_type);
587     var post_data = load_spec_raw_post_data(lspec);
589     var method = post_data ? "POST" : "GET";
591     req.open(method, load_spec_uri_string(lspec), true, arguments.$user, arguments.$password);
592     req.channel.notificationCallbacks = xml_http_request_load_listener;
594     for each (let [name,value] in arguments.$headers) {
595         req.setRequestHeader(name, value);
596     }
598     if (post_data) {
599         req.setRequestHeader("Content-Type", load_spec_request_mime_type(lspec));
600         req.send(post_data);
601     } else
602         req.send(null);
604     try {
605         yield SUSPEND;
606     } catch (e) {
607         aborting = true;
608         req.abort();
609         throw e;
610     }
612     // Let the caller access the status and reponse data
613     yield co_return(req);
618  * scroll_selection_into_view takes an editable element, and scrolls it so
619  * that the selection (or insertion point) are visible.
620  */
621 function scroll_selection_into_view (field) {
622     if (field.namespaceURI == XUL_NS)
623         field = field.inputField;
624     try {
625         field.QueryInterface(Ci.nsIDOMNSEditableElement)
626             .editor
627             .selectionController
628             .scrollSelectionIntoView(
629                 Ci.nsISelectionController.SELECTION_NORMAL,
630                 Ci.nsISelectionController.SELECTION_FOCUS_REGION,
631                 true);
632     } catch (e) {
633         // we'll get here for richedit fields
634     }
639  * build_url_regex builds a regular expression to match URLs for a given
640  * web site.
642  * Both the $domain and $path arguments can be either regexes, in
643  * which case they will be matched as is, or strings, in which case
644  * they will be matched literally.
646  * $tlds specifies a list of valid top-level-domains to match, and
647  * defaults to .com. Useful for when e.g. foo.org and foo.com are the
648  * same.
650  * If $allow_www is true, www.domain.tld will also be allowed.
651  */
652 define_keywords("$domain", "$path", "$tlds", "$allow_www");
653 function build_url_regex () {
654     function regex_to_string (obj) {
655         if (obj instanceof RegExp)
656             return obj.source;
657         return quotemeta(obj);
658     }
660     keywords(arguments, $path = "", $tlds = ["com"], $allow_www = false);
661     var domain = regex_to_string(arguments.$domain);
662     if(arguments.$allow_www) {
663         domain = "(?:www\.)?" + domain;
664     }
665     var path   = regex_to_string(arguments.$path);
666     var tlds   = arguments.$tlds;
667     var regex = "^https?://" + domain + "\\." + choice_regex(tlds) + "/" + path;
668     return new RegExp(regex);
672 function compute_url_up_path (url) {
673     var new_url = Cc["@mozilla.org/network/standard-url;1"]
674         .createInstance (Ci.nsIURL);
675     new_url.spec = url;
676     var up;
677     if (new_url.param != "" || new_url.query != "")
678         up = new_url.filePath;
679     else if (new_url.fileName != "")
680         up = ".";
681     else
682         up = "..";
683     return up;
687 function url_path_trim (url) {
688     var uri = make_uri(url);
689     uri.spec = url;
690     uri.path = "";
691     return uri.spec;
694 /* possibly_valid_url returns true if the string might be a valid
695  * thing to pass to nsIWebNavigation.loadURI.  Currently just checks
696  * that there's no whitespace in the middle and that it's not entirely
697  * whitespace.
698  */
699 function possibly_valid_url (url) {
700     return !(/\S\s+\S/.test(url)) && !(/^\s*$/.test(url));
705  * Convenience function for making simple XPath lookups in a document.
707  * @param doc The document to look in.
708  * @param exp The XPath expression to search for.
709  * @return The XPathResult object representing the set of found nodes.
710  */
711 function xpath_lookup (doc, exp) {
712     return doc.evaluate(exp, doc, null, Ci.nsIDOMXPathResult.ANY_TYPE, null);
716 /* get_contents_synchronously returns the contents of the given
717  * url (string or nsIURI) as a string on success, or null on failure.
718  */
719 function get_contents_synchronously (url) {
720     var ioService=Cc["@mozilla.org/network/io-service;1"]
721         .getService(Ci.nsIIOService);
722     var scriptableStream=Cc["@mozilla.org/scriptableinputstream;1"]
723         .getService(Ci.nsIScriptableInputStream);
724     var channel;
725     var input;
726     try {
727         if (url instanceof Ci.nsIURI)
728             channel = ioService.newChannelFromURI(url);
729         else
730             channel = ioService.newChannel(url, null, null);
731         input=channel.open();
732     } catch (e) {
733         return null;
734     }
735     scriptableStream.init(input);
736     var str=scriptableStream.read(input.available());
737     scriptableStream.close();
738     input.close();
739     return str;
744  * dom_add_class adds a css class to the given dom node.
745  */
746 function dom_add_class (node, cssclass) {
747     if (node.className)
748         node.className += " "+cssclass;
749     else
750         node.className = cssclass;
754  * dom_remove_class removes the given css class from the given dom node.
755  */
756 function dom_remove_class (node, cssclass) {
757     if (! node.className)
758         return;
759     var classes = node.className.split(" ");
760     classes = classes.filter(function (x) { return x != cssclass; });
761     node.className = classes.join(" ");
766  * dom_node_flash adds the given cssclass to the node for a brief interval.
767  * this class can be styled, to create a flashing effect.
768  */
769 function dom_node_flash (node, cssclass) {
770     dom_add_class(node, cssclass);
771     call_after_timeout(
772         function () {
773             dom_remove_class(node, cssclass);
774         },
775         400);
780  * data is an an alist (array of 2 element arrays) where each pair is a key
781  * and a value.
783  * The return type is a mime input stream that can be passed as postData to
784  * nsIWebNavigation.loadURI.  In terms of Conkeror's API, the return value
785  * of this function is of the correct type for the `post_data' field of a
786  * load_spec.
787  */
788 function make_post_data (data) {
789     data = [(encodeURIComponent(pair[0])+'='+encodeURIComponent(pair[1]))
790             for each (pair in data)].join('&');
791     data = string_input_stream(data);
792     return mime_input_stream(
793         data, [["Content-Type", "application/x-www-form-urlencoded"]]);
798  * Centers the viewport around a given element.
800  * @param win  The window to scroll.
801  * @param elem The element arund which we put the viewport.
802  */
803 function center_in_viewport (win, elem) {
804     let point = abs_point(elem);
806     point.x -= win.innerWidth / 2;
807     point.y -= win.innerHeight / 2;
809     win.scrollTo(point.x, point.y);
814  * Takes an interactive context and a function to call with the word
815  * at point as its sole argument, and which returns a modified word.
816  */
817 //XXX: this should be implemented in terms of modify_region,
818 //     in order to work in richedit fields.
819 function modify_word_at_point (I, func) {
820     var focused = I.buffer.focused_element;
822     // Skip any whitespaces at point and move point to the right place.
823     var point = focused.selectionStart;
824     var rest = focused.value.substring(point);
826     // Skip any whitespaces.
827     for (var i = 0, rlen = rest.length; i < rlen; i++) {
828         if (" \n".indexOf(rest.charAt(i)) == -1) {
829             point += i;
830             break;
831         }
832     }
834     // Find the next whitespace, as it is the end of the word.  If no next
835     // whitespace is found, we're at the end of input.  TODO: Add "\n" support.
836     goal = focused.value.indexOf(" ", point);
837     if (goal == -1)
838         goal = focused.value.length;
840     // Change the value of the text field.
841     var input = focused.value;
842     focused.value =
843         input.substring(0, point) +
844         func(input.substring(point, goal)) +
845         input.substring(goal);
847     // Move point.
848     focused.selectionStart = goal;
849     focused.selectionEnd = goal;
854  * Simple predicate returns true if elem is an nsIDOMNode or
855  * nsIDOMWindow.
856  */
857 function element_dom_node_or_window_p (elem) {
858     if (elem instanceof Ci.nsIDOMNode)
859         return true;
860     if (elem instanceof Ci.nsIDOMWindow)
861         return true;
862     return false;
866  * Given a hook name, a buffer and a function, waits until the buffer document
867  * has fully loaded, then calls the function with the buffer as its only
868  * argument.
870  * @param {String} The hook name.
871  * @param {buffer} The buffer.
872  * @param {function} The function to call with the buffer as its argument once
873  *                   the buffer has loaded.
874  */
875 function do_when (hook, buffer, fun) {
876     if (buffer.browser.webProgress.isLoadingDocument)
877         add_hook.call(buffer, hook, fun);
878     else
879         fun(buffer);
882 provide("utils");