move source-code-related stuff out of utils.js, into source-code.js
[conkeror.git] / modules / utils.js
bloba4c662badd8cbf9de2d7eb39e5abcfec5e0a2d55
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 function string_hashset () {}
12 string_hashset.prototype = {
13     constructor : string_hashset,
15     add : function (s) {
16         this["-" + s] = true;
17     },
19     contains : function (s) {
20         return (("-" + s) in this);
21     },
23     remove : function (s) {
24         delete this["-" + s];
25     },
27     for_each : function (f) {
28         for (var i in this) {
29             if (i[0] == "-")
30                 f(i.slice(1));
31         }
32     },
34     iterator : function () {
35         for (let k in this) {
36             if (i[0] == "-")
37                 yield i.slice(1);
38         }
39     }
42 function string_hashmap () {}
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     var f = Cc["@mozilla.org/file/local;1"]
122         .createInstance(Ci.nsILocalFile);
123     f.initWithPath(path);
124     return f;
128 function make_uri (uri, charset, base_uri) {
129     const io_service = Cc["@mozilla.org/network/io-service;1"]
130         .getService(Ci.nsIIOService2);
131     if (uri instanceof Ci.nsIURI)
132         return uri;
133     if (uri instanceof Ci.nsIFile)
134         return io_service.newFileURI(uri);
135     return io_service.newURI(uri, charset, base_uri);
138 function make_file_from_chrome (url) {
139     var crs = Cc['@mozilla.org/chrome/chrome-registry;1']
140         .getService(Ci.nsIChromeRegistry);
141     var file = crs.convertChromeURL(make_uri(url));
142     return make_file(file.path);
145 function get_document_content_disposition (document_o) {
146     var content_disposition = null;
147     try {
148         content_disposition = document_o.defaultView
149             .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
150             .getInterface(Components.interfaces.nsIDOMWindowUtils)
151             .getDocumentMetadata("content-disposition");
152     } catch (e) { }
153     return content_disposition;
157 function set_focus_no_scroll (window, element) {
158     window.document.commandDispatcher.suppressFocusScroll = true;
159     element.focus();
160     window.document.commandDispatcher.suppressFocusScroll = false;
163 function do_repeatedly_positive (func, n) {
164     var args = Array.prototype.slice.call(arguments, 2);
165     while (n-- > 0)
166         func.apply(null, args);
169 function do_repeatedly (func, n, positive_args, negative_args) {
170     if (n < 0)
171         do func.apply(null, negative_args); while (++n < 0);
172     else
173         while (n-- > 0) func.apply(null, positive_args);
178  * Given a node, returns its position relative to the document.
180  * @param node The node to get the position of.
181  * @return An object with properties "x" and "y" representing its offset from
182  *         the left and top of the document, respectively.
183  */
184 function abs_point (node) {
185     var orig = node;
186     var pt = {};
187     try {
188         pt.x = node.offsetLeft;
189         pt.y = node.offsetTop;
190         // find imagemap's coordinates
191         if (node.tagName == "AREA") {
192             var coords = node.getAttribute("coords").split(",");
193             pt.x += Number(coords[0]);
194             pt.y += Number(coords[1]);
195         }
197         node = node.offsetParent;
198         // Sometimes this fails, so just return what we got.
200         while (node.tagName != "BODY") {
201             pt.x += node.offsetLeft;
202             pt.y += node.offsetTop;
203             node = node.offsetParent;
204         }
205     } catch(e) {
206 //      node = orig;
207 //      while (node.tagName != "BODY") {
208 //          alert("okay: " + node + " " + node.tagName + " " + pt.x + " " + pt.y);
209 //          node = node.offsetParent;
210 //      }
211     }
212     return pt;
216 const XHTML_NS = "http://www.w3.org/1999/xhtml";
217 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
218 const MATHML_NS = "http://www.w3.org/1998/Math/MathML";
219 const XLINK_NS = "http://www.w3.org/1999/xlink";
221 function create_XUL (window, tag_name) {
222     return window.document.createElementNS(XUL_NS, tag_name);
226 /* Used in calls to XPath evaluate */
227 function xpath_lookup_namespace (prefix) {
228     return {
229         xhtml: XHTML_NS,
230         m: MATHML_NS,
231         xul: XUL_NS
232     }[prefix] || null;
235 function method_caller (obj, func) {
236     return function () {
237         func.apply(obj, arguments);
238     };
242 function get_window_from_frame (frame) {
243     try {
244         var window = frame.QueryInterface(Ci.nsIInterfaceRequestor)
245             .getInterface(Ci.nsIWebNavigation)
246             .QueryInterface(Ci.nsIDocShellTreeItem)
247             .rootTreeItem
248             .QueryInterface(Ci.nsIInterfaceRequestor)
249             .getInterface(Ci.nsIDOMWindow).wrappedJSObject;
250         /* window is now an XPCSafeJSObjectWrapper */
251         window.escape_wrapper(function (w) { window = w; });
252         /* window is now completely unwrapped */
253         return window;
254     } catch (e) {
255         return null;
256     }
259 function get_buffer_from_frame (window, frame) {
260     var count = window.buffers.count;
261     for (var i = 0; i < count; ++i) {
262         var b = window.buffers.get_buffer(i);
263         if (b.top_frame == frame.top)
264             return b;
265     }
266     return null;
270 function dom_generator (document, ns) {
271     this.document = document;
272     this.ns = ns;
274 dom_generator.prototype = {
275     element : function (tag, parent) {
276         var node = this.document.createElementNS(this.ns, tag);
277         var i = 1;
278         if (parent != null && (parent instanceof Ci.nsIDOMNode)) {
279             parent.appendChild(node);
280             i = 2;
281         }
282         for (var nargs = arguments.length; i < nargs; i += 2)
283             node.setAttribute(arguments[i], arguments[i+1]);
284         return node;
285     },
287     text : function (str, parent) {
288         var node = this.document.createTextNode(str);
289         if (parent)
290             parent.appendChild(node);
291         return node;
292     },
295     stylesheet_link : function (href, parent) {
296         var node = this.element("link");
297         node.setAttribute("rel", "stylesheet");
298         node.setAttribute("type", "text/css");
299         node.setAttribute("href", href);
300         if (parent)
301             parent.appendChild(node);
302         return node;
303     },
306     add_stylesheet : function (url) {
307         var head = this.document.documentElement.firstChild;
308         this.stylesheet_link(url, head);
309     }
313  * Generates a QueryInterface function suitable for an implemenation
314  * of an XPCOM interface.  Unlike XPCOMUtils, this uses the Function
315  * constructor to generate a slightly more efficient version.  The
316  * arguments can be either Strings or elements of
317  * Components.interfaces.
318  */
319 function generate_QI () {
320     var args = Array.prototype.slice.call(arguments).map(String).concat(["nsISupports"]);
321     var fstr = "if(" +
322         Array.prototype.map.call(args, function (x) {
323             return "iid.equals(Components.interfaces." + x + ")";
324         })
325         .join("||") +
326         ") return this; throw Components.results.NS_ERROR_NO_INTERFACE;";
327     return new Function("iid", fstr);
331 const USER_AGENT_OVERRIDE_PREF = "general.useragent.override";
333 function set_user_agent (str) {
334     session_pref(USER_AGENT_OVERRIDE_PREF, str);
338 function abort (str) {
339     var e = new Error(str);
340     e.__proto__ = abort.prototype;
341     return e;
343 abort.prototype.__proto__ = Error.prototype;
346 function get_temporary_file (name) {
347     if (name == null)
348         name = "temp.txt";
349     var file = file_locator_service.get("TmpD", Ci.nsIFile);
350     file.append(name);
351     // Create the file now to ensure that no exploits are possible
352     file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
353     return file;
357 /* FIXME: This should be moved somewhere else, perhaps. */
358 function create_info_panel (window, panel_class, row_arr) {
359     /* Show information panel above minibuffer */
361     var g = new dom_generator(window.document, XUL_NS);
363     var p = g.element("vbox", "class", "panel " + panel_class, "flex", "0");
364     var grid = g.element("grid", p);
365     var cols = g.element("columns", grid);
366     g.element("column", cols, "flex", "0");
367     g.element("column", cols, "flex", "1");
369     var rows = g.element("rows", grid);
370     var row;
372     for each (let [row_class, row_label, row_value] in row_arr) {
373         row = g.element("row", rows, "class", row_class);
374         g.element("label", row,
375                   "value", row_label,
376                   "class", "panel-row-label");
377         g.element("label", row,
378                   "value", row_value,
379                   "class", "panel-row-value",
380                   "crop", "end");
381     }
382     window.minibuffer.insert_before(p);
384     p.destroy = function () {
385         this.parentNode.removeChild(this);
386     };
388     return p;
393  * Paste from the X primary selection, unless the system doesn't support a
394  * primary selection, in which case fall back to the clipboard.
395  */
396 function read_from_x_primary_selection () {
397     // Get clipboard.
398     let clipboard = Components.classes["@mozilla.org/widget/clipboard;1"]
399         .getService(Components.interfaces.nsIClipboard);
401     // Fall back to global clipboard if the system doesn't support a selection
402     let which_clipboard = clipboard.supportsSelectionClipboard() ?
403         clipboard.kSelectionClipboard : clipboard.kGlobalClipboard;
405     let flavors = ["text/unicode"];
407     // Don't barf if there's nothing on the clipboard
408     if (!clipboard.hasDataMatchingFlavors(flavors, flavors.length, which_clipboard))
409         return "";
411     // Create transferable that will transfer the text.
412     let trans = Components.classes["@mozilla.org/widget/transferable;1"]
413         .createInstance(Components.interfaces.nsITransferable);
415     for each (let flavor in flavors) {
416         trans.addDataFlavor(flavor);
417     }
418     clipboard.getData(trans, which_clipboard);
420     var data_flavor = {};
421     var data = {};
422     var dataLen = {};
423     trans.getAnyTransferData(data_flavor, data, dataLen);
425     if (data) {
426         data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
427         let data_length = dataLen.value;
428         if (data_flavor.value == "text/unicode")
429             data_length = dataLen.value / 2;
430         return data.data.substring(0, data_length);
431     } else {
432         return "";
433     }
437 function predicate_alist_match (alist, key) {
438     for each (let i in alist) {
439         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     get length () {
461         return this.input.length + this.output.length;
462     },
463     push: function (x) {
464         this.input[this.input.length] = x;
465     },
466     pop: function (x) {
467         let l = this.output.length;
468         if (!l) {
469             l = this.input.length;
470             if (!l)
471                 return undefined;
472             this.output = this.input.reverse();
473             this.input = [];
474             let x = this.output[l];
475             this.output.length--;
476             return x;
477         }
478     }
481 function frame_iterator (root_frame, start_with) {
482     var q = new queue, x;
483     if (start_with) {
484         x = start_with;
485         do {
486             yield x;
487             for (let i = 0, nframes = x.frames.length; i < nframes; ++i)
488                 q.push(x.frames[i]);
489         } while ((x = q.pop()));
490     }
491     x = root_frame;
492     do {
493         if (x == start_with)
494             continue;
495         yield x;
496         for (let i = 0, nframes = x.frames.length; i < nframes; ++i)
497             q.push(x.frames[i]);
498     } while ((x = q.pop()));
501 function xml_http_request () {
502     return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
503         .createInstance(Ci.nsIXMLHttpRequest)
504         .QueryInterface(Ci.nsIJSXMLHttpRequest)
505         .QueryInterface(Ci.nsIDOMEventTarget);
508 var xml_http_request_load_listener = {
509   // nsIBadCertListener2
510   notifyCertProblem: function SSLL_certProblem (socketInfo, status, targetSite) {
511     return true;
512   },
514   // nsISSLErrorListener
515   notifySSLError: function SSLL_SSLError (socketInfo, error, targetSite) {
516     return true;
517   },
519   // nsIInterfaceRequestor
520   getInterface: function SSLL_getInterface (iid) {
521     return this.QueryInterface(iid);
522   },
524   // nsISupports
525   //
526   // FIXME: array comprehension used here to hack around the lack of
527   // Ci.nsISSLErrorListener in 2007 versions of xulrunner 1.9pre.
528   // make it a simple generateQI when xulrunner is more stable.
529   QueryInterface: XPCOMUtils.generateQI(
530       [i for each (i in [Ci.nsIBadCertListener2,
531                          Ci.nsISSLErrorListener,
532                          Ci.nsIInterfaceRequestor])
533        if (i)])
538  * Coroutine interface for sending an HTTP request and waiting for the
539  * response. (This includes so-called "AJAX" requests.)
541  * @param lspec (required) a load_spec object or URI string (see load-spec.js)
543  * The request URI is obtained from this argument. In addition, if the
544  * load spec specifies post data, a POST request is made instead of a
545  * GET request, and the post data included in the load spec is
546  * sent. Specifically, the request_mime_type and raw_post_data
547  * properties of the load spec are used.
549  * @param $user (optional) HTTP user name to include in the request headers
550  * @param $password (optional) HTTP password to include in the request headers
552  * @param $override_mime_type (optional) Force the response to be interpreted
553  *                            as having the specified MIME type.  This is only
554  *                            really useful for forcing the MIME type to be
555  *                            text/xml or something similar, such that it is
556  *                            automatically parsed into a DOM document.
557  * @param $headers (optional) an array of [name,value] pairs (each specified as
558  *                 a two-element array) specifying additional headers to add to
559  *                 the request.
561  * @returns After the request completes (either successfully or with an error),
562  *          the nsIXMLHttpRequest object is returned.  Its responseText (for any
563  *          arbitrary document) or responseXML (if the response type is an XML
564  *          content type) properties can be accessed to examine the response
565  *          document.
567  * If an exception is thrown to the continutation (which can be obtained by the
568  * caller by calling yield CONTINUATION prior to calling this function) while the
569  * request is in progress (i.e. before this coroutine returns), the request will
570  * be aborted, and the exception will be propagated to the caller.
572  **/
573 define_keywords("$user", "$password", "$override_mime_type", "$headers");
574 function send_http_request (lspec) {
575     // why do we get warnings in jsconsole unless we initialize the
576     // following keywords?
577     keywords(arguments, $user = undefined, $password = undefined,
578              $override_mime_type = undefined, $headers = undefined);
579     if (! (lspec instanceof load_spec))
580         lspec = load_spec(lspec);
581     var req = xml_http_request();
582     var cc = yield CONTINUATION;
583     var aborting = false;
584     req.onreadystatechange = function send_http_request__onreadysatechange () {
585         if (req.readyState != 4)
586             return;
587         if (aborting)
588             return;
589         cc();
590     };
592     if (arguments.$override_mime_type)
593         req.overrideMimeType(arguments.$override_mime_type);
595     var post_data = load_spec_raw_post_data(lspec);
597     var method = post_data ? "POST" : "GET";
599     req.open(method, load_spec_uri_string(lspec), true, arguments.$user, arguments.$password);
600     req.channel.notificationCallbacks = xml_http_request_load_listener;
602     for each (let [name,value] in arguments.$headers) {
603         req.setRequestHeader(name, value);
604     }
606     if (post_data) {
607         req.setRequestHeader("Content-Type", load_spec_request_mime_type(lspec));
608         req.send(post_data);
609     } else
610         req.send(null);
612     try {
613         yield SUSPEND;
614     } catch (e) {
615         aborting = true;
616         req.abort();
617         throw e;
618     }
620     // Let the caller access the status and reponse data
621     yield co_return(req);
626  * scroll_selection_into_view takes an editable element, and scrolls it so
627  * that the selection (or insertion point) are visible.
628  */
629 function scroll_selection_into_view (field) {
630     if (field.namespaceURI == XUL_NS)
631         field = field.inputField;
632     try {
633         field.QueryInterface(Ci.nsIDOMNSEditableElement)
634             .editor
635             .selectionController
636             .scrollSelectionIntoView(
637                 Ci.nsISelectionController.SELECTION_NORMAL,
638                 Ci.nsISelectionController.SELECTION_FOCUS_REGION,
639                 true);
640     } catch (e) {
641         // we'll get here for richedit fields
642     }
647  * build_url_regex builds a regular expression to match URLs for a given
648  * web site.
650  * Both the $domain and $path arguments can be either regexes, in
651  * which case they will be matched as is, or strings, in which case
652  * they will be matched literally.
654  * $tlds specifies a list of valid top-level-domains to match, and
655  * defaults to .com. Useful for when e.g. foo.org and foo.com are the
656  * same.
658  * If $allow_www is true, www.domain.tld will also be allowed.
659  */
660 define_keywords("$domain", "$path", "$tlds", "$allow_www");
661 function build_url_regex () {
662     function regex_to_string (obj) {
663         if (obj instanceof RegExp)
664             return obj.source;
665         return quotemeta(obj);
666     }
668     keywords(arguments, $path = "", $tlds = ["com"], $allow_www = false);
669     var domain = regex_to_string(arguments.$domain);
670     if(arguments.$allow_www) {
671         domain = "(?:www\.)?" + domain;
672     }
673     var path   = regex_to_string(arguments.$path);
674     var tlds   = arguments.$tlds;
675     var regex = "^https?://" + domain + "\\." + choice_regex(tlds) + "/" + path;
676     return new RegExp(regex);
680 function compute_url_up_path (url) {
681     var new_url = Cc["@mozilla.org/network/standard-url;1"]
682         .createInstance (Ci.nsIURL);
683     new_url.spec = url;
684     var up;
685     if (new_url.param != "" || new_url.query != "")
686         up = new_url.filePath;
687     else if (new_url.fileName != "")
688         up = ".";
689     else
690         up = "..";
691     return up;
695 function url_path_trim (url) {
696     var uri = make_uri(url);
697     uri.spec = url;
698     uri.path = "";
699     return uri.spec;
702 /* possibly_valid_url returns true if the string might be a valid
703  * thing to pass to nsIWebNavigation.loadURI.  Currently just checks
704  * that there's no whitespace in the middle and that it's not entirely
705  * whitespace.
706  */
707 function possibly_valid_url (url) {
708     return !(/\S\s+\S/.test(url)) && !(/^\s*$/.test(url));
713  * Convenience function for making simple XPath lookups in a document.
715  * @param doc The document to look in.
716  * @param exp The XPath expression to search for.
717  * @return The XPathResult object representing the set of found nodes.
718  */
719 function xpath_lookup (doc, exp) {
720     return doc.evaluate(exp, doc, null, Ci.nsIDOMXPathResult.ANY_TYPE, null);
724 /* get_contents_synchronously returns the contents of the given
725  * url (string or nsIURI) as a string on success, or null on failure.
726  */
727 function get_contents_synchronously (url) {
728     var ioService=Cc["@mozilla.org/network/io-service;1"]
729         .getService(Ci.nsIIOService);
730     var scriptableStream=Cc["@mozilla.org/scriptableinputstream;1"]
731         .getService(Ci.nsIScriptableInputStream);
732     var channel;
733     var input;
734     try {
735         if (url instanceof Ci.nsIURI)
736             channel = ioService.newChannelFromURI(url);
737         else
738             channel = ioService.newChannel(url, null, null);
739         input=channel.open();
740     } catch (e) {
741         return null;
742     }
743     scriptableStream.init(input);
744     var str=scriptableStream.read(input.available());
745     scriptableStream.close();
746     input.close();
747     return str;
752  * dom_add_class adds a css class to the given dom node.
753  */
754 function dom_add_class (node, cssclass) {
755     if (node.className)
756         node.className += " "+cssclass;
757     else
758         node.className = cssclass;
762  * dom_remove_class removes the given css class from the given dom node.
763  */
764 function dom_remove_class (node, cssclass) {
765     if (! node.className)
766         return;
767     var classes = node.className.split(" ");
768     classes = classes.filter(function (x) { return x != cssclass; });
769     node.className = classes.join(" ");
774  * dom_node_flash adds the given cssclass to the node for a brief interval.
775  * this class can be styled, to create a flashing effect.
776  */
777 function dom_node_flash (node, cssclass) {
778     dom_add_class(node, cssclass);
779     call_after_timeout(
780         function () {
781             dom_remove_class(node, cssclass);
782         },
783         400);
788  * data is an an alist (array of 2 element arrays) where each pair is a key
789  * and a value.
791  * The return type is a mime input stream that can be passed as postData to
792  * nsIWebNavigation.loadURI.  In terms of Conkeror's API, the return value
793  * of this function is of the correct type for the `post_data' field of a
794  * load_spec.
795  */
796 function make_post_data (data) {
797     data = [(encodeURIComponent(pair[0])+'='+encodeURIComponent(pair[1]))
798             for each (pair in data)].join('&');
799     data = string_input_stream(data);
800     return mime_input_stream(
801         data, [["Content-Type", "application/x-www-form-urlencoded"]]);
806  * Centers the viewport around a given element.
808  * @param win  The window to scroll.
809  * @param elem The element arund which we put the viewport.
810  */
811 function center_in_viewport (win, elem) {
812     let point = abs_point(elem);
814     point.x -= win.innerWidth / 2;
815     point.y -= win.innerHeight / 2;
817     win.scrollTo(point.x, point.y);
822  * Takes an interactive context and a function to call with the word
823  * at point as its sole argument, and which returns a modified word.
824  */
825 //XXX: this should be implemented in terms of modify_region,
826 //     in order to work in richedit fields.
827 function modify_word_at_point (I, func) {
828     var focused = I.buffer.focused_element;
830     // Skip any whitespaces at point and move point to the right place.
831     var point = focused.selectionStart;
832     var rest = focused.value.substring(point);
834     // Skip any whitespaces.
835     for (var i = 0, rlen = rest.length; i < rlen; i++) {
836         if (" \n".indexOf(rest.charAt(i)) == -1) {
837             point += i;
838             break;
839         }
840     }
842     // Find the next whitespace, as it is the end of the word.  If no next
843     // whitespace is found, we're at the end of input.  TODO: Add "\n" support.
844     goal = focused.value.indexOf(" ", point);
845     if (goal == -1)
846         goal = focused.value.length;
848     // Change the value of the text field.
849     var input = focused.value;
850     focused.value =
851         input.substring(0, point) +
852         func(input.substring(point, goal)) +
853         input.substring(goal);
855     // Move point.
856     focused.selectionStart = goal;
857     focused.selectionEnd = goal;
862  * Simple predicate returns true if elem is an nsIDOMNode or
863  * nsIDOMWindow.
864  */
865 function element_dom_node_or_window_p (elem) {
866     if (elem instanceof Ci.nsIDOMNode)
867         return true;
868     if (elem instanceof Ci.nsIDOMWindow)
869         return true;
870     return false;
874  * Given a hook name, a buffer and a function, waits until the buffer document
875  * has fully loaded, then calls the function with the buffer as its only
876  * argument.
878  * @param {String} The hook name.
879  * @param {buffer} The buffer.
880  * @param {function} The function to call with the buffer as its argument once
881  *                   the buffer has loaded.
882  */
883 function do_when (hook, buffer, fun) {
884     if (buffer.browser.webProgress.isLoadingDocument)
885         add_hook.call(buffer, hook, fun);
886     else
887         fun(buffer);