2 * (C) Copyright 2004-2007 Shawn Betts
3 * (C) Copyright 2007-2011 John J. Foerch
4 * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
6 * Use, modification, and distribution are subject to the terms specified in the
12 function string_hashset () {}
13 string_hashset.prototype = {
14 constructor: string_hashset,
20 contains: function (s) {
21 return (("-" + s) in this);
24 remove: function (s) {
28 for_each: function (f) {
35 iterator: function () {
44 // Put the string on the clipboard
45 function writeToClipboard (str) {
46 var gClipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"]
47 .getService(Ci.nsIClipboardHelper);
48 gClipboardHelper.copyString(str);
52 function makeURLAbsolute (base, url) {
54 var ioService = Cc["@mozilla.org/network/io-service;1"]
55 .getService(Ci.nsIIOService);
56 var baseURI = ioService.newURI(base, null, null);
57 return ioService.newURI(baseURI.resolve(url), null, null).spec;
61 function make_file (path) {
62 if (path instanceof Ci.nsILocalFile)
65 return get_home_directory();
67 path = path.replace("/", "\\", "g");
68 if ((POSIX && path.substring(0,2) == "~/") ||
69 (WINDOWS && path.substring(0,2) == "~\\"))
71 var f = get_home_directory();
72 f.appendRelativePath(path.substring(2));
74 f = Cc["@mozilla.org/file/local;1"]
75 .createInstance(Ci.nsILocalFile);
82 function make_file_from_chrome (url) {
83 var crs = Cc['@mozilla.org/chrome/chrome-registry;1']
84 .getService(Ci.nsIChromeRegistry);
85 var file = crs.convertChromeURL(make_uri(url));
86 return make_file(file.path);
89 function get_document_content_disposition (document_o) {
90 var content_disposition = null;
92 content_disposition = document_o.defaultView
93 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
94 .getInterface(Components.interfaces.nsIDOMWindowUtils)
95 .getDocumentMetadata("content-disposition");
97 return content_disposition;
101 function set_focus_no_scroll (window, element) {
102 window.document.commandDispatcher.suppressFocusScroll = true;
104 window.document.commandDispatcher.suppressFocusScroll = false;
107 function do_repeatedly_positive (func, n) {
108 var args = Array.prototype.slice.call(arguments, 2);
110 func.apply(null, args);
113 function do_repeatedly (func, n, positive_args, negative_args) {
115 do func.apply(null, negative_args); while (++n < 0);
117 while (n-- > 0) func.apply(null, positive_args);
122 * Given a node, returns its position relative to the document.
124 * @param node The node to get the position of.
125 * @return An object with properties "x" and "y" representing its offset from
126 * the left and top of the document, respectively.
128 function abs_point (node) {
132 pt.x = node.offsetLeft;
133 pt.y = node.offsetTop;
134 // find imagemap's coordinates
135 if (node.tagName == "AREA") {
136 var coords = node.getAttribute("coords").split(",");
137 pt.x += Number(coords[0]);
138 pt.y += Number(coords[1]);
141 node = node.offsetParent;
142 // Sometimes this fails, so just return what we got.
144 while (node.tagName != "BODY") {
145 pt.x += node.offsetLeft;
146 pt.y += node.offsetTop;
147 node = node.offsetParent;
151 // while (node.tagName != "BODY") {
152 // alert("okay: " + node + " " + node.tagName + " " + pt.x + " " + pt.y);
153 // node = node.offsetParent;
160 const XHTML_NS = "http://www.w3.org/1999/xhtml";
161 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
162 const MATHML_NS = "http://www.w3.org/1998/Math/MathML";
163 const XLINK_NS = "http://www.w3.org/1999/xlink";
164 const SVG_NS = "http://www.w3.org/2000/svg";
166 function create_XUL (window, tag_name) {
167 return window.document.createElementNS(XUL_NS, tag_name);
171 /* Used in calls to XPath evaluate */
172 function xpath_lookup_namespace (prefix) {
181 function method_caller (obj, func) {
183 func.apply(obj, arguments);
188 function get_window_from_frame (frame) {
190 var window = frame.QueryInterface(Ci.nsIInterfaceRequestor)
191 .getInterface(Ci.nsIWebNavigation)
192 .QueryInterface(Ci.nsIDocShellTreeItem)
194 .QueryInterface(Ci.nsIInterfaceRequestor)
195 .getInterface(Ci.nsIDOMWindow).wrappedJSObject;
196 /* window is now an XPCSafeJSObjectWrapper */
197 window.escape_wrapper(function (w) { window = w; });
198 /* window is now completely unwrapped */
205 function get_buffer_from_frame (window, frame) {
206 var count = window.buffers.count;
207 for (var i = 0; i < count; ++i) {
208 var b = window.buffers.get_buffer(i);
209 if (b.top_frame == frame.top)
216 function dom_generator (document, ns) {
217 this.document = document;
220 dom_generator.prototype = {
221 constructor: dom_generator,
222 element: function (tag, parent) {
223 var node = this.document.createElementNS(this.ns, tag);
225 if (parent != null && (parent instanceof Ci.nsIDOMNode)) {
226 parent.appendChild(node);
229 for (var nargs = arguments.length; i < nargs; i += 2)
230 node.setAttribute(arguments[i], arguments[i+1]);
234 text: function (str, parent) {
235 var node = this.document.createTextNode(str);
237 parent.appendChild(node);
242 stylesheet_link: function (href, parent) {
243 var node = this.element("link");
244 node.setAttribute("rel", "stylesheet");
245 node.setAttribute("type", "text/css");
246 node.setAttribute("href", href);
248 parent.appendChild(node);
253 add_stylesheet: function (url) {
254 var head = this.document.documentElement.firstChild;
255 this.stylesheet_link(url, head);
260 * Generates a QueryInterface function suitable for an implemenation
261 * of an XPCOM interface. Unlike XPCOMUtils, this uses the Function
262 * constructor to generate a slightly more efficient version. The
263 * arguments can be either Strings or elements of
264 * Components.interfaces.
266 function generate_QI () {
267 var args = Array.prototype.slice.call(arguments).map(String).concat(["nsISupports"]);
269 Array.prototype.map.call(args, function (x) {
270 return "iid.equals(Components.interfaces." + x + ")";
273 ") return this; throw Components.results.NS_ERROR_NO_INTERFACE;";
274 return new Function("iid", fstr);
278 function abort (str) {
279 var e = new Error(str);
280 e.__proto__ = abort.prototype;
283 abort.prototype.__proto__ = Error.prototype;
286 function get_temporary_file (name) {
289 var file = file_locator_service.get("TmpD", Ci.nsIFile);
291 // Create the file now to ensure that no exploits are possible
292 file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
297 /* FIXME: This should be moved somewhere else, perhaps. */
298 function create_info_panel (window, panel_class, row_arr) {
299 /* Show information panel above minibuffer */
301 var g = new dom_generator(window.document, XUL_NS);
303 var p = g.element("vbox", "class", "panel " + panel_class, "flex", "0");
304 var grid = g.element("grid", p);
305 var cols = g.element("columns", grid);
306 g.element("column", cols, "flex", "0");
307 g.element("column", cols, "flex", "1");
309 var rows = g.element("rows", grid);
312 for each (let [row_class, row_label, row_value] in row_arr) {
313 row = g.element("row", rows, "class", row_class);
314 g.element("label", row,
316 "class", "panel-row-label");
317 g.element("label", row,
319 "class", "panel-row-value",
322 window.minibuffer.insert_before(p);
324 p.destroy = function () {
325 this.parentNode.removeChild(this);
333 * Paste from the X primary selection, unless the system doesn't support a
334 * primary selection, in which case fall back to the clipboard.
336 function read_from_x_primary_selection () {
338 let clipboard = Components.classes["@mozilla.org/widget/clipboard;1"]
339 .getService(Components.interfaces.nsIClipboard);
341 // Fall back to global clipboard if the system doesn't support a selection
342 let which_clipboard = clipboard.supportsSelectionClipboard() ?
343 clipboard.kSelectionClipboard : clipboard.kGlobalClipboard;
345 let flavors = ["text/unicode"];
347 // Don't barf if there's nothing on the clipboard
348 if (!clipboard.hasDataMatchingFlavors(flavors, flavors.length, which_clipboard))
351 // Create transferable that will transfer the text.
352 let trans = Components.classes["@mozilla.org/widget/transferable;1"]
353 .createInstance(Components.interfaces.nsITransferable);
355 for each (let flavor in flavors) {
356 trans.addDataFlavor(flavor);
358 clipboard.getData(trans, which_clipboard);
360 var data_flavor = {};
363 trans.getAnyTransferData(data_flavor, data, dataLen);
366 data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
367 let data_length = dataLen.value;
368 if (data_flavor.value == "text/unicode")
369 data_length = dataLen.value / 2;
370 return data.data.substring(0, data_length);
377 function predicate_alist_match (alist, key) {
378 for each (let i in alist) {
379 if (i[0] instanceof RegExp) {
382 } else if (i[0](key))
389 function get_meta_title (doc) {
390 var title = doc.evaluate("//meta[@name='title']/@content", doc, xpath_lookup_namespace,
391 Ci.nsIDOMXPathResult.STRING_TYPE , null);
392 if (title && title.stringValue)
393 return title.stringValue;
405 return this.input.length + this.output.length;
408 this.input[this.input.length] = x;
411 let l = this.output.length;
413 l = this.input.length;
416 this.output = this.input.reverse();
418 let x = this.output[l];
419 this.output.length--;
425 function frame_iterator (root_frame, start_with) {
426 var q = new queue, x;
431 for (let i = 0, nframes = x.frames.length; i < nframes; ++i)
433 } while ((x = q.pop()));
440 for (let i = 0, nframes = x.frames.length; i < nframes; ++i)
442 } while ((x = q.pop()));
445 function xml_http_request () {
446 return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
447 .createInstance(Ci.nsIXMLHttpRequest)
448 .QueryInterface(Ci.nsIJSXMLHttpRequest)
449 .QueryInterface(Ci.nsIDOMEventTarget);
452 var xml_http_request_load_listener = {
453 // nsIBadCertListener2
454 notifyCertProblem: function SSLL_certProblem (socketInfo, status, targetSite) {
458 // nsISSLErrorListener
459 notifySSLError: function SSLL_SSLError (socketInfo, error, targetSite) {
463 // nsIInterfaceRequestor
464 getInterface: function SSLL_getInterface (iid) {
465 return this.QueryInterface(iid);
470 // FIXME: array comprehension used here to hack around the lack of
471 // Ci.nsISSLErrorListener in 2007 versions of xulrunner 1.9pre.
472 // make it a simple generateQI when xulrunner is more stable.
473 QueryInterface: XPCOMUtils.generateQI(
474 [i for each (i in [Ci.nsIBadCertListener2,
475 Ci.nsISSLErrorListener,
476 Ci.nsIInterfaceRequestor])
482 * Coroutine interface for sending an HTTP request and waiting for the
483 * response. (This includes so-called "AJAX" requests.)
485 * @param lspec (required) a load_spec object or URI string (see load-spec.js)
487 * The request URI is obtained from this argument. In addition, if the
488 * load spec specifies post data, a POST request is made instead of a
489 * GET request, and the post data included in the load spec is
490 * sent. Specifically, the request_mime_type and raw_post_data
491 * properties of the load spec are used.
493 * @param $user (optional) HTTP user name to include in the request headers
494 * @param $password (optional) HTTP password to include in the request headers
496 * @param $override_mime_type (optional) Force the response to be interpreted
497 * as having the specified MIME type. This is only
498 * really useful for forcing the MIME type to be
499 * text/xml or something similar, such that it is
500 * automatically parsed into a DOM document.
501 * @param $headers (optional) an array of [name,value] pairs (each specified as
502 * a two-element array) specifying additional headers to add to
505 * @returns After the request completes (either successfully or with an error),
506 * the nsIXMLHttpRequest object is returned. Its responseText (for any
507 * arbitrary document) or responseXML (if the response type is an XML
508 * content type) properties can be accessed to examine the response
511 * If an exception is thrown to the continutation (which can be obtained by the
512 * caller by calling yield CONTINUATION prior to calling this function) while the
513 * request is in progress (i.e. before this coroutine returns), the request will
514 * be aborted, and the exception will be propagated to the caller.
517 define_keywords("$user", "$password", "$override_mime_type", "$headers");
518 function send_http_request (lspec) {
519 // why do we get warnings in jsconsole unless we initialize the
520 // following keywords?
521 keywords(arguments, $user = undefined, $password = undefined,
522 $override_mime_type = undefined, $headers = undefined);
523 if (! (lspec instanceof load_spec))
524 lspec = load_spec(lspec);
525 var req = xml_http_request();
526 var cc = yield CONTINUATION;
527 var aborting = false;
528 req.onreadystatechange = function send_http_request__onreadystatechange () {
529 if (req.readyState != 4)
536 if (arguments.$override_mime_type)
537 req.overrideMimeType(arguments.$override_mime_type);
539 var post_data = load_spec_raw_post_data(lspec);
541 var method = post_data ? "POST" : "GET";
543 req.open(method, load_spec_uri_string(lspec), true, arguments.$user, arguments.$password);
544 req.channel.notificationCallbacks = xml_http_request_load_listener;
546 for each (let [name,value] in arguments.$headers) {
547 req.setRequestHeader(name, value);
551 req.setRequestHeader("Content-Type", load_spec_request_mime_type(lspec));
564 // Let the caller access the status and reponse data
565 yield co_return(req);
570 * scroll_selection_into_view takes an editable element, and scrolls it so
571 * that the selection (or insertion point) are visible.
573 function scroll_selection_into_view (field) {
574 if (field.namespaceURI == XUL_NS)
575 field = field.inputField;
577 field.QueryInterface(Ci.nsIDOMNSEditableElement)
580 .scrollSelectionIntoView(
581 Ci.nsISelectionController.SELECTION_NORMAL,
582 Ci.nsISelectionController.SELECTION_FOCUS_REGION,
585 // we'll get here for richedit fields
591 * build_url_regexp builds a regular expression to match URLs for a given
594 * Both the $domain and $path arguments can be either regexps, in
595 * which case they will be matched as is, or strings, in which case
596 * they will be matched literally.
598 * $tlds specifies a list of valid top-level-domains to match, and
599 * defaults to .com. Useful for when e.g. foo.org and foo.com are the
602 * If $allow_www is true, www.domain.tld will also be allowed.
604 define_keywords("$domain", "$path", "$tlds", "$allow_www");
605 function build_url_regexp () {
606 function regexp_to_string (obj) {
607 if (typeof obj == "object" && "source" in obj)
609 return quotemeta(obj);
612 keywords(arguments, $path = "", $tlds = ["com"], $allow_www = false);
613 var domain = regexp_to_string(arguments.$domain);
614 if (arguments.$allow_www) {
615 domain = "(?:www\.)?" + domain;
617 var path = regexp_to_string(arguments.$path);
618 var tlds = arguments.$tlds;
619 var regexp = "^https?://" + domain + "\\." + choice_regexp(tlds) + "/" + path;
620 return new RegExp(regexp);
624 function compute_up_url (uri) {
625 uri = uri.clone().QueryInterface(Ci.nsIURL);
626 for each (var p in ["ref", "query", "param", "fileName"]) {
632 return uri.resolve("..");
636 function url_path_trim (url) {
637 var uri = make_uri(url);
643 /* possibly_valid_url returns true if the string might be a valid
644 * thing to pass to nsIWebNavigation.loadURI. Currently just checks
645 * that there's no whitespace in the middle and that it's not entirely
648 function possibly_valid_url (url) {
649 return !(/\S\s+\S/.test(url)) && !(/^\s*$/.test(url));
654 * Convenience function for making simple XPath lookups in a document.
656 * @param doc The document to look in.
657 * @param exp The XPath expression to search for.
658 * @return The XPathResult object representing the set of found nodes.
660 function xpath_lookup (doc, exp) {
661 return doc.evaluate(exp, doc, null, Ci.nsIDOMXPathResult.ANY_TYPE, null);
665 /* get_contents_synchronously returns the contents of the given
666 * url (string or nsIURI) as a string on success, or null on failure.
668 function get_contents_synchronously (url) {
669 var ioService=Cc["@mozilla.org/network/io-service;1"]
670 .getService(Ci.nsIIOService);
671 var scriptableStream=Cc["@mozilla.org/scriptableinputstream;1"]
672 .getService(Ci.nsIScriptableInputStream);
676 if (url instanceof Ci.nsIURI)
677 channel = ioService.newChannelFromURI(url);
679 channel = ioService.newChannel(url, null, null);
680 input=channel.open();
684 scriptableStream.init(input);
685 var str=scriptableStream.read(input.available());
686 scriptableStream.close();
693 * dom_add_class adds a css class to the given dom node.
695 function dom_add_class (node, cssclass) {
696 if (node.className) {
697 var cs = node.className.split(" ");
698 if (cs.indexOf(cssclass) != -1)
701 node.className = cs.join(" ");
703 node.className = cssclass;
707 * dom_remove_class removes the given css class from the given dom node.
709 function dom_remove_class (node, cssclass) {
710 if (! node.className)
712 var classes = node.className.split(" ");
713 classes = classes.filter(function (x) { return x != cssclass; });
714 node.className = classes.join(" ");
719 * dom_node_flash adds the given cssclass to the node for a brief interval.
720 * this class can be styled, to create a flashing effect.
722 function dom_node_flash (node, cssclass) {
723 dom_add_class(node, cssclass);
726 dom_remove_class(node, cssclass);
733 * data is an an alist (array of 2 element arrays) where each pair is a key
736 * The return type is a mime input stream that can be passed as postData to
737 * nsIWebNavigation.loadURI. In terms of Conkeror's API, the return value
738 * of this function is of the correct type for the `post_data' field of a
741 function make_post_data (data) {
742 data = [(encodeURIComponent(pair[0])+'='+encodeURIComponent(pair[1]))
743 for each (pair in data)].join('&');
744 data = string_input_stream(data);
745 return mime_input_stream(
746 data, [["Content-Type", "application/x-www-form-urlencoded"]]);
751 * Centers the viewport around a given element.
753 * @param win The window to scroll.
754 * @param elem The element arund which we put the viewport.
756 function center_in_viewport (win, elem) {
757 let point = abs_point(elem);
759 point.x -= win.innerWidth / 2;
760 point.y -= win.innerHeight / 2;
762 win.scrollTo(point.x, point.y);
767 * Simple predicate returns true if elem is an nsIDOMNode or
770 function dom_node_or_window_p (elem) {
771 if (elem instanceof Ci.nsIDOMNode)
773 if (elem instanceof Ci.nsIDOMWindow)
779 * Given a hook name, a buffer and a function, waits until the buffer document
780 * has fully loaded, then calls the function with the buffer as its only
783 * @param {String} The hook name.
784 * @param {buffer} The buffer.
785 * @param {function} The function to call with the buffer as its argument once
786 * the buffer has loaded.
788 function do_when (hook, buffer, fun) {
789 if (buffer.browser.webProgress.isLoadingDocument)
790 add_hook.call(buffer, hook, fun);
797 * evaluate string s as javascript in the 'this' scope in which evaluate
800 function evaluate (s) {
802 var obs = Cc["@mozilla.org/observer-service;1"]
803 .getService(Ci.nsIObserverService);
804 obs.notifyObservers(null, "startupcache-invalidate", null);
805 var temp = get_temporary_file("conkeror-evaluate.tmp.js");
806 write_text_file(temp, s);
807 var url = make_uri(temp).spec;
808 return load_url(url, this);
810 if (temp && temp.exists())
817 * set_protocol_handler takes a protocol and a handler spec. If the
818 * handler is true, Mozilla will (try to) handle this protocol internally.
819 * If the handler null, the user will be prompted for a handler when a
820 * resource of this protocol is requested. If the handler is an nsIFile,
821 * the program it gives will be launched with the url as an argument. If
822 * the handler is a string, it will be interpreted as an URL template for
823 * a web service and the sequence '%s' within it will be replaced by the
826 function set_protocol_handler (protocol, handler) {
827 var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"]
828 .getService(Ci.nsIExternalProtocolService);
829 var info = eps.getProtocolHandlerInfo(protocol);
830 var expose_pref = "network.protocol-handler.expose."+protocol;
831 if (handler == true) {
833 clear_default_pref(expose_pref);
834 } else if (handler) {
836 if (handler instanceof Ci.nsIFile) {
837 var h = Cc["@mozilla.org/uriloader/local-handler-app;1"]
838 .createInstance(Ci.nsILocalHandlerApp);
839 h.executable = handler;
840 } else if (typeof handler == "string") {
841 h = Cc["@mozilla.org/uriloader/web-handler-app;1"]
842 .createInstance(Ci.nsIWebHandlerApp);
843 var uri = make_uri(handler);
845 h.uriTemplate = handler;
847 info.alwaysAskBeforeHandling = false;
848 info.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
849 info.possibleApplicationHandlers.clear();
850 info.possibleApplicationHandlers.appendElement(h, false);
851 info.preferredApplicationHandler = h;
852 session_pref(expose_pref, false);
855 info.alwaysAskBeforeHandling = true;
856 info.preferredAction = Ci.nsIHandlerInfo.alwaysAsk;
857 session_pref(expose_pref, false);
859 var hs = Cc["@mozilla.org/uriloader/handler-service;1"]
860 .getService(Ci.nsIHandlerService);