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 // Put the string on the clipboard
13 function writeToClipboard (str) {
14 var gClipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"]
15 .getService(Ci.nsIClipboardHelper);
16 gClipboardHelper.copyString(str);
20 function makeURLAbsolute (base, url) {
22 var ioService = Cc["@mozilla.org/network/io-service;1"]
23 .getService(Ci.nsIIOService);
24 var baseURI = ioService.newURI(base, null, null);
25 return ioService.newURI(baseURI.resolve(url), null, null).spec;
29 function make_file (path) {
30 if (path instanceof Ci.nsILocalFile)
33 return get_home_directory();
35 path = path.replace("/", "\\", "g");
36 if ((POSIX && path.substring(0,2) == "~/") ||
37 (WINDOWS && path.substring(0,2) == "~\\"))
39 var f = get_home_directory();
40 f.appendRelativePath(path.substring(2));
42 f = Cc["@mozilla.org/file/local;1"]
43 .createInstance(Ci.nsILocalFile);
50 function make_file_from_chrome (url) {
51 var crs = Cc['@mozilla.org/chrome/chrome-registry;1']
52 .getService(Ci.nsIChromeRegistry);
53 var file = crs.convertChromeURL(make_uri(url));
54 return make_file(file.path);
59 * file_symlink_p takes an nsIFile and returns the value of
60 * file.isSymlink(), but also catches the error and returns false if the
61 * file does not exist. Note that this cannot be tested with
62 * file.exists(), because that method automatically resolves symlinks.
64 function file_symlink_p (file) {
66 return file.isSymlink();
67 } catch (e if (e instanceof Ci.nsIException) &&
68 e.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
75 function get_document_content_disposition (document_o) {
76 var content_disposition = null;
78 content_disposition = document_o.defaultView
79 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
80 .getInterface(Components.interfaces.nsIDOMWindowUtils)
81 .getDocumentMetadata("content-disposition");
83 return content_disposition;
87 function set_focus_no_scroll (window, element) {
88 window.document.commandDispatcher.suppressFocusScroll = true;
90 window.document.commandDispatcher.suppressFocusScroll = false;
93 function do_repeatedly_positive (func, n) {
94 var args = Array.prototype.slice.call(arguments, 2);
96 func.apply(null, args);
99 function do_repeatedly (func, n, positive_args, negative_args) {
101 do func.apply(null, negative_args); while (++n < 0);
103 while (n-- > 0) func.apply(null, positive_args);
108 * Given a node, returns its position relative to the document.
110 * @param node The node to get the position of.
111 * @return An object with properties "x" and "y" representing its offset from
112 * the left and top of the document, respectively.
114 function abs_point (node) {
118 pt.x = node.offsetLeft;
119 pt.y = node.offsetTop;
120 // find imagemap's coordinates
121 if (node.tagName == "AREA") {
122 var coords = node.getAttribute("coords").split(",");
123 pt.x += Number(coords[0]);
124 pt.y += Number(coords[1]);
127 node = node.offsetParent;
128 // Sometimes this fails, so just return what we got.
130 while (node.tagName != "BODY") {
131 pt.x += node.offsetLeft;
132 pt.y += node.offsetTop;
133 node = node.offsetParent;
137 // while (node.tagName != "BODY") {
138 // alert("okay: " + node + " " + node.tagName + " " + pt.x + " " + pt.y);
139 // node = node.offsetParent;
146 function method_caller (obj, func) {
148 func.apply(obj, arguments);
153 function get_window_from_frame (frame) {
155 var window = frame.QueryInterface(Ci.nsIInterfaceRequestor)
156 .getInterface(Ci.nsIWebNavigation)
157 .QueryInterface(Ci.nsIDocShellTreeItem)
159 .QueryInterface(Ci.nsIInterfaceRequestor)
160 .getInterface(Ci.nsIDOMWindow).wrappedJSObject;
161 /* window is now an XPCSafeJSObjectWrapper */
162 window.escape_wrapper(function (w) { window = w; });
163 /* window is now completely unwrapped */
170 function get_buffer_from_frame (window, frame) {
171 var count = window.buffers.count;
172 for (var i = 0; i < count; ++i) {
173 var b = window.buffers.get_buffer(i);
174 if (b.top_frame == frame.top)
182 * Generates a QueryInterface function suitable for an implemenation
183 * of an XPCOM interface. Unlike XPCOMUtils, this uses the Function
184 * constructor to generate a slightly more efficient version. The
185 * arguments can be either Strings or elements of
186 * Components.interfaces.
188 function generate_QI () {
189 var args = Array.prototype.slice.call(arguments).map(String).concat(["nsISupports"]);
191 Array.prototype.map.call(args, function (x) {
192 return "iid.equals(Components.interfaces." + x + ")";
195 ") return this; throw Components.results.NS_ERROR_NO_INTERFACE;";
196 return new Function("iid", fstr);
199 var abort = task_canceled;
201 function get_temporary_file (name) {
204 var file = file_locator_service.get("TmpD", Ci.nsIFile);
206 // Create the file now to ensure that no exploits are possible
207 file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
212 /* FIXME: This should be moved somewhere else, perhaps. */
213 function create_info_panel (window, panel_class, row_arr) {
214 /* Show information panel above minibuffer */
216 var g = new dom_generator(window.document, XUL_NS);
218 var p = g.element("vbox", "class", "panel " + panel_class, "flex", "0");
219 var grid = g.element("grid", p);
220 var cols = g.element("columns", grid);
221 g.element("column", cols, "flex", "0");
222 g.element("column", cols, "flex", "1");
224 var rows = g.element("rows", grid);
227 for each (let [row_class, row_label, row_value] in row_arr) {
228 row = g.element("row", rows, "class", row_class);
229 g.element("label", row,
231 "class", "panel-row-label");
232 g.element("label", row,
234 "class", "panel-row-value",
237 window.minibuffer.insert_before(p);
239 p.destroy = function () {
240 this.parentNode.removeChild(this);
248 * Return clipboard contents as string. When which_clipboard is given, it
249 * may be an nsIClipboard constant specifying which clipboard to use.
251 function read_from_clipboard (which_clipboard) {
252 var clipboard = Cc["@mozilla.org/widget/clipboard;1"]
253 .getService(Ci.nsIClipboard);
254 if (which_clipboard == null)
255 which_clipboard = clipboard.kGlobalClipboard;
257 var flavors = ["text/unicode"];
259 // Don't barf if there's nothing on the clipboard
260 if (!clipboard.hasDataMatchingFlavors(flavors, flavors.length, which_clipboard))
263 // Create transferable that will transfer the text.
264 var trans = Cc["@mozilla.org/widget/transferable;1"]
265 .createInstance(Ci.nsITransferable);
267 for each (let flavor in flavors) {
268 trans.addDataFlavor(flavor);
270 clipboard.getData(trans, which_clipboard);
272 var data_flavor = {};
275 trans.getAnyTransferData(data_flavor, data, dataLen);
278 data = data.value.QueryInterface(Ci.nsISupportsString);
279 var data_length = dataLen.value;
280 if (data_flavor.value == "text/unicode")
281 data_length = dataLen.value / 2;
282 return data.data.substring(0, data_length);
284 return ""; //XXX: is this even reachable?
289 * Return selection clipboard contents as a string, or regular clipboard
290 * contents if the system does not support a selection clipboard.
292 function read_from_x_primary_selection () {
293 var clipboard = Cc["@mozilla.org/widget/clipboard;1"]
294 .getService(Ci.nsIClipboard);
295 // fall back to global clipboard if the
296 // system doesn't support a selection
297 var which_clipboard = clipboard.supportsSelectionClipboard() ?
298 clipboard.kSelectionClipboard : clipboard.kGlobalClipboard;
299 return read_from_clipboard(which_clipboard);
303 function predicate_alist_match (alist, key) {
304 for each (let i in alist) {
305 if (i[0] instanceof RegExp) {
308 } else if (i[0](key))
315 function get_meta_title (doc) {
316 var title = doc.evaluate("//meta[@name='title']/@content", doc, xpath_lookup_namespace,
317 Ci.nsIDOMXPathResult.STRING_TYPE , null);
318 if (title && title.stringValue)
319 return title.stringValue;
331 return this.input.length + this.output.length;
334 this.input[this.input.length] = x;
337 let l = this.output.length;
339 l = this.input.length;
342 this.output = this.input.reverse();
344 let x = this.output[l];
345 this.output.length--;
351 function for_each_frame (win, callback) {
353 if (win.frames && win.frames.length) {
354 for (var i = 0, n = win.frames.length; i < n; ++i)
355 for_each_frame(win.frames[i], callback);
359 function frame_iterator (root_frame, start_with) {
360 var q = new queue, x;
365 for (let i = 0, nframes = x.frames.length; i < nframes; ++i)
367 } while ((x = q.pop()));
374 for (let i = 0, nframes = x.frames.length; i < nframes; ++i)
376 } while ((x = q.pop()));
379 function xml_http_request () {
380 return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
381 .createInstance(Ci.nsIXMLHttpRequest)
382 .QueryInterface(Ci.nsIJSXMLHttpRequest)
383 .QueryInterface(Ci.nsIDOMEventTarget);
386 var xml_http_request_load_listener = {
387 // nsIBadCertListener2
388 notifyCertProblem: function SSLL_certProblem (socketInfo, status, targetSite) {
392 // nsISSLErrorListener
393 notifySSLError: function SSLL_SSLError (socketInfo, error, targetSite) {
397 // nsIInterfaceRequestor
398 getInterface: function SSLL_getInterface (iid) {
399 return this.QueryInterface(iid);
404 // FIXME: array comprehension used here to hack around the lack of
405 // Ci.nsISSLErrorListener in 2007 versions of xulrunner 1.9pre.
406 // make it a simple generateQI when xulrunner is more stable.
407 QueryInterface: XPCOMUtils.generateQI(
408 [i for each (i in [Ci.nsIBadCertListener2,
409 Ci.nsISSLErrorListener,
410 Ci.nsIInterfaceRequestor])
416 * Promise interface for sending an HTTP request and waiting for the
417 * response. (This includes so-called "AJAX" requests.)
419 * @param lspec (required) a load_spec object or URI string (see load-spec.js)
421 * The request URI is obtained from this argument. In addition, if the
422 * load spec specifies post data, a POST request is made instead of a
423 * GET request, and the post data included in the load spec is
424 * sent. Specifically, the request_mime_type and raw_post_data
425 * properties of the load spec are used.
427 * @param $user (optional) HTTP user name to include in the request headers
428 * @param $password (optional) HTTP password to include in the request headers
430 * @param $override_mime_type (optional) Force the response to be interpreted
431 * as having the specified MIME type. This is only
432 * really useful for forcing the MIME type to be
433 * text/xml or something similar, such that it is
434 * automatically parsed into a DOM document.
435 * @param $headers (optional) an array of [name,value] pairs (each specified as
436 * a two-element array) specifying additional headers to add to
439 * @returns Promise that resolves to nsIXMLHttpRequest after the request
440 * completes (either successfully or with an error). Its responseText
441 * (for any arbitrary document) or responseXML (if the response type is
442 * an XML content type) properties can be accessed to examine the
446 define_keywords("$user", "$password", "$override_mime_type", "$headers");
447 function send_http_request (lspec) {
448 // why do we get warnings in jsconsole unless we initialize the
449 // following keywords?
450 keywords(arguments, $user = undefined, $password = undefined,
451 $override_mime_type = undefined, $headers = undefined);
452 if (! (lspec instanceof load_spec))
453 lspec = load_spec(lspec);
454 var req = xml_http_request();
456 let deferred = Promise.defer();
457 req.onreadystatechange = function send_http_request__onreadystatechange () {
458 if (req.readyState != 4)
460 deferred.resolve(req);
463 if (arguments.$override_mime_type)
464 req.overrideMimeType(arguments.$override_mime_type);
466 var post_data = load_spec_raw_post_data(lspec);
468 var method = post_data ? "POST" : "GET";
470 req.open(method, load_spec_uri_string(lspec), true, arguments.$user, arguments.$password);
471 req.channel.notificationCallbacks = xml_http_request_load_listener;
473 for each (let [name,value] in arguments.$headers) {
474 req.setRequestHeader(name, value);
478 req.setRequestHeader("Content-Type", load_spec_request_mime_type(lspec));
483 return make_simple_cancelable(deferred);
488 * scroll_selection_into_view takes an editable element, and scrolls it so
489 * that the selection (or insertion point) are visible.
491 function scroll_selection_into_view (field) {
492 if (field.namespaceURI == XUL_NS)
493 field = field.inputField;
495 field.QueryInterface(Ci.nsIDOMNSEditableElement)
498 .scrollSelectionIntoView(
499 Ci.nsISelectionController.SELECTION_NORMAL,
500 Ci.nsISelectionController.SELECTION_FOCUS_REGION,
503 // we'll get here for richedit fields
508 function compute_up_url (uri) {
510 uri = uri.clone().QueryInterface(Ci.nsIURL);
514 for (let [k, p] in Iterator(["ref", "query", "param", "fileName"])) {
515 if (p in uri && uri[p] != "") {
520 return uri.resolve("..");
524 function url_path_trim (url) {
525 var uri = make_uri(url);
532 * possibly_valid_url returns true if its argument is an url-like string,
533 * meaning likely a valid thing to pass to nsIWebNavigation.loadURI.
535 function possibly_valid_url (str) {
536 // no inner space before first /
537 return /^\s*[^\/\s]*(\/|\s*$)/.test(str)
538 && !(/^\s*$/.test(str));
542 /* get_contents_synchronously returns the contents of the given
543 * url (string or nsIURI) as a string on success, or null on failure.
545 function get_contents_synchronously (url) {
546 var ioService=Cc["@mozilla.org/network/io-service;1"]
547 .getService(Ci.nsIIOService);
548 var scriptableStream=Cc["@mozilla.org/scriptableinputstream;1"]
549 .getService(Ci.nsIScriptableInputStream);
553 if (url instanceof Ci.nsIURI)
554 channel = ioService.newChannelFromURI(url);
556 channel = ioService.newChannel(url, null, null);
557 input=channel.open();
561 scriptableStream.init(input);
562 var str=scriptableStream.read(input.available());
563 scriptableStream.close();
570 * data is an an alist (array of 2 element arrays) where each pair is a key
573 * The return type is a mime input stream that can be passed as postData to
574 * nsIWebNavigation.loadURI. In terms of Conkeror's API, the return value
575 * of this function is of the correct type for the `post_data' field of a
578 function make_post_data (data) {
579 data = [(encodeURIComponent(pair[0])+'='+encodeURIComponent(pair[1]))
580 for each (pair in data)].join('&');
581 data = string_input_stream(data);
582 return mime_input_stream(
583 data, [["Content-Type", "application/x-www-form-urlencoded"]]);
588 * Centers the viewport around a given element.
590 * @param win The window to scroll.
591 * @param elem The element arund which we put the viewport.
593 function center_in_viewport (win, elem) {
594 let point = abs_point(elem);
596 point.x -= win.innerWidth / 2;
597 point.y -= win.innerHeight / 2;
599 win.scrollTo(point.x, point.y);
604 * Simple predicate returns true if elem is an nsIDOMNode or
607 function dom_node_or_window_p (elem) {
608 if (elem instanceof Ci.nsIDOMNode)
610 if (elem instanceof Ci.nsIDOMWindow)
616 * Given a hook name, a buffer and a function, waits until the buffer document
617 * has fully loaded, then calls the function with the buffer as its only
620 * @param {String} The hook name.
621 * @param {buffer} The buffer.
622 * @param {function} The function to call with the buffer as its argument once
623 * the buffer has loaded.
625 function do_when (hook, buffer, fun) {
626 if (buffer.browser.webProgress.isLoadingDocument)
627 add_hook.call(buffer, hook, fun);
634 * evaluate string s as javascript in the 'this' scope in which evaluate
637 function evaluate (s) {
639 var obs = Cc["@mozilla.org/observer-service;1"]
640 .getService(Ci.nsIObserverService);
641 obs.notifyObservers(null, "startupcache-invalidate", null);
642 var temp = get_temporary_file("conkeror-evaluate.tmp.js");
643 write_text_file(temp, s);
644 var url = make_uri(temp).spec;
645 return load_url(url, this);
647 if (temp && temp.exists())
654 * set_protocol_handler takes a protocol and a handler spec. If the
655 * handler is true, Mozilla will (try to) handle this protocol internally.
656 * If the handler null, the user will be prompted for a handler when a
657 * resource of this protocol is requested. If the handler is an nsIFile,
658 * the program it gives will be launched with the url as an argument. If
659 * the handler is a string, it will be interpreted as an URL template for
660 * a web service and the sequence '%s' within it will be replaced by the
663 function set_protocol_handler (protocol, handler) {
664 var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"]
665 .getService(Ci.nsIExternalProtocolService);
666 var info = eps.getProtocolHandlerInfo(protocol);
667 var expose_pref = "network.protocol-handler.expose."+protocol;
668 if (handler == true) {
670 clear_default_pref(expose_pref);
671 } else if (handler) {
673 if (handler instanceof Ci.nsIFile) {
674 var h = Cc["@mozilla.org/uriloader/local-handler-app;1"]
675 .createInstance(Ci.nsILocalHandlerApp);
676 h.executable = handler;
677 } else if (typeof handler == "string") {
678 h = Cc["@mozilla.org/uriloader/web-handler-app;1"]
679 .createInstance(Ci.nsIWebHandlerApp);
680 var uri = make_uri(handler);
682 h.uriTemplate = handler;
684 info.alwaysAskBeforeHandling = false;
685 info.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
686 info.possibleApplicationHandlers.clear();
687 info.possibleApplicationHandlers.appendElement(h, false);
688 info.preferredApplicationHandler = h;
689 session_pref(expose_pref, false);
692 info.alwaysAskBeforeHandling = true;
693 info.preferredAction = Ci.nsIHandlerInfo.alwaysAsk;
694 session_pref(expose_pref, false);
696 var hs = Cc["@mozilla.org/uriloader/handler-service;1"]
697 .getService(Ci.nsIHandlerService);