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
);
57 function get_document_content_disposition (document_o
) {
58 var content_disposition
= null;
60 content_disposition
= document_o
.defaultView
61 .QueryInterface(Components
.interfaces
.nsIInterfaceRequestor
)
62 .getInterface(Components
.interfaces
.nsIDOMWindowUtils
)
63 .getDocumentMetadata("content-disposition");
65 return content_disposition
;
69 function set_focus_no_scroll (window
, element
) {
70 window
.document
.commandDispatcher
.suppressFocusScroll
= true;
72 window
.document
.commandDispatcher
.suppressFocusScroll
= false;
75 function do_repeatedly_positive (func
, n
) {
76 var args
= Array
.prototype.slice
.call(arguments
, 2);
78 func
.apply(null, args
);
81 function do_repeatedly (func
, n
, positive_args
, negative_args
) {
83 do func
.apply(null, negative_args
); while (++n
< 0);
85 while (n
-- > 0) func
.apply(null, positive_args
);
90 * Given a node, returns its position relative to the document.
92 * @param node The node to get the position of.
93 * @return An object with properties "x" and "y" representing its offset from
94 * the left and top of the document, respectively.
96 function abs_point (node
) {
100 pt
.x
= node
.offsetLeft
;
101 pt
.y
= node
.offsetTop
;
102 // find imagemap's coordinates
103 if (node
.tagName
== "AREA") {
104 var coords
= node
.getAttribute("coords").split(",");
105 pt
.x
+= Number(coords
[0]);
106 pt
.y
+= Number(coords
[1]);
109 node
= node
.offsetParent
;
110 // Sometimes this fails, so just return what we got.
112 while (node
.tagName
!= "BODY") {
113 pt
.x
+= node
.offsetLeft
;
114 pt
.y
+= node
.offsetTop
;
115 node
= node
.offsetParent
;
119 // while (node.tagName != "BODY") {
120 // alert("okay: " + node + " " + node.tagName + " " + pt.x + " " + pt.y);
121 // node = node.offsetParent;
128 function method_caller (obj
, func
) {
130 func
.apply(obj
, arguments
);
135 function get_window_from_frame (frame
) {
137 var window
= frame
.QueryInterface(Ci
.nsIInterfaceRequestor
)
138 .getInterface(Ci
.nsIWebNavigation
)
139 .QueryInterface(Ci
.nsIDocShellTreeItem
)
141 .QueryInterface(Ci
.nsIInterfaceRequestor
)
142 .getInterface(Ci
.nsIDOMWindow
).wrappedJSObject
;
143 /* window is now an XPCSafeJSObjectWrapper */
144 window
.escape_wrapper(function (w
) { window
= w
; });
145 /* window is now completely unwrapped */
152 function get_buffer_from_frame (window
, frame
) {
153 var count
= window
.buffers
.count
;
154 for (var i
= 0; i
< count
; ++i
) {
155 var b
= window
.buffers
.get_buffer(i
);
156 if (b
.top_frame
== frame
.top
)
164 * Generates a QueryInterface function suitable for an implemenation
165 * of an XPCOM interface. Unlike XPCOMUtils, this uses the Function
166 * constructor to generate a slightly more efficient version. The
167 * arguments can be either Strings or elements of
168 * Components.interfaces.
170 function generate_QI () {
171 var args
= Array
.prototype.slice
.call(arguments
).map(String
).concat(["nsISupports"]);
173 Array
.prototype.map
.call(args
, function (x
) {
174 return "iid.equals(Components.interfaces." + x
+ ")";
177 ") return this; throw Components.results.NS_ERROR_NO_INTERFACE;";
178 return new Function("iid", fstr
);
182 function abort (str
) {
183 var e
= new Error(str
);
184 e
.__proto__
= abort
.prototype;
187 abort
.prototype.__proto__
= Error
.prototype;
190 function get_temporary_file (name
) {
193 var file
= file_locator_service
.get("TmpD", Ci
.nsIFile
);
195 // Create the file now to ensure that no exploits are possible
196 file
.createUnique(Ci
.nsIFile
.NORMAL_FILE_TYPE
, 0600);
201 /* FIXME: This should be moved somewhere else, perhaps. */
202 function create_info_panel (window
, panel_class
, row_arr
) {
203 /* Show information panel above minibuffer */
205 var g
= new dom_generator(window
.document
, XUL_NS
);
207 var p
= g
.element("vbox", "class", "panel " + panel_class
, "flex", "0");
208 var grid
= g
.element("grid", p
);
209 var cols
= g
.element("columns", grid
);
210 g
.element("column", cols
, "flex", "0");
211 g
.element("column", cols
, "flex", "1");
213 var rows
= g
.element("rows", grid
);
216 for each (let [row_class
, row_label
, row_value
] in row_arr
) {
217 row
= g
.element("row", rows
, "class", row_class
);
218 g
.element("label", row
,
220 "class", "panel-row-label");
221 g
.element("label", row
,
223 "class", "panel-row-value",
226 window
.minibuffer
.insert_before(p
);
228 p
.destroy = function () {
229 this.parentNode
.removeChild(this);
237 * Return clipboard contents as string. When which_clipboard is given, it
238 * may be an nsIClipboard constant specifying which clipboard to use.
240 function read_from_clipboard (which_clipboard
) {
241 var clipboard
= Cc
["@mozilla.org/widget/clipboard;1"]
242 .getService(Ci
.nsIClipboard
);
243 if (which_clipboard
== null)
244 which_clipboard
= clipboard
.kGlobalClipboard
;
246 var flavors
= ["text/unicode"];
248 // Don't barf if there's nothing on the clipboard
249 if (!clipboard
.hasDataMatchingFlavors(flavors
, flavors
.length
, which_clipboard
))
252 // Create transferable that will transfer the text.
253 var trans
= Cc
["@mozilla.org/widget/transferable;1"]
254 .createInstance(Ci
.nsITransferable
);
256 for each (let flavor
in flavors
) {
257 trans
.addDataFlavor(flavor
);
259 clipboard
.getData(trans
, which_clipboard
);
261 var data_flavor
= {};
264 trans
.getAnyTransferData(data_flavor
, data
, dataLen
);
267 data
= data
.value
.QueryInterface(Ci
.nsISupportsString
);
268 var data_length
= dataLen
.value
;
269 if (data_flavor
.value
== "text/unicode")
270 data_length
= dataLen
.value
/ 2;
271 return data
.data
.substring(0, data_length
);
273 return ""; //XXX: is this even reachable?
278 * Return selection clipboard contents as a string, or regular clipboard
279 * contents if the system does not support a selection clipboard.
281 function read_from_x_primary_selection () {
282 var clipboard
= Cc
["@mozilla.org/widget/clipboard;1"]
283 .getService(Ci
.nsIClipboard
);
284 // fall back to global clipboard if the
285 // system doesn't support a selection
286 var which_clipboard
= clipboard
.supportsSelectionClipboard() ?
287 clipboard
.kSelectionClipboard
: clipboard
.kGlobalClipboard
;
288 return read_from_clipboard(which_clipboard
);
292 function predicate_alist_match (alist
, key
) {
293 for each (let i
in alist
) {
294 if (i
[0] instanceof RegExp
) {
297 } else if (i
[0](key
))
304 function get_meta_title (doc
) {
305 var title
= doc
.evaluate("//meta[@name='title']/@content", doc
, xpath_lookup_namespace
,
306 Ci
.nsIDOMXPathResult
.STRING_TYPE
, null);
307 if (title
&& title
.stringValue
)
308 return title
.stringValue
;
320 return this.input
.length
+ this.output
.length
;
323 this.input
[this.input
.length
] = x
;
326 let l
= this.output
.length
;
328 l
= this.input
.length
;
331 this.output
= this.input
.reverse();
333 let x
= this.output
[l
];
334 this.output
.length
--;
340 function frame_iterator (root_frame
, start_with
) {
341 var q
= new queue
, x
;
346 for (let i
= 0, nframes
= x
.frames
.length
; i
< nframes
; ++i
)
348 } while ((x
= q
.pop()));
355 for (let i
= 0, nframes
= x
.frames
.length
; i
< nframes
; ++i
)
357 } while ((x
= q
.pop()));
360 function xml_http_request () {
361 return Cc
["@mozilla.org/xmlextras/xmlhttprequest;1"]
362 .createInstance(Ci
.nsIXMLHttpRequest
)
363 .QueryInterface(Ci
.nsIJSXMLHttpRequest
)
364 .QueryInterface(Ci
.nsIDOMEventTarget
);
367 var xml_http_request_load_listener
= {
368 // nsIBadCertListener2
369 notifyCertProblem
: function SSLL_certProblem (socketInfo
, status
, targetSite
) {
373 // nsISSLErrorListener
374 notifySSLError
: function SSLL_SSLError (socketInfo
, error
, targetSite
) {
378 // nsIInterfaceRequestor
379 getInterface
: function SSLL_getInterface (iid
) {
380 return this.QueryInterface(iid
);
385 // FIXME: array comprehension used here to hack around the lack of
386 // Ci.nsISSLErrorListener in 2007 versions of xulrunner 1.9pre.
387 // make it a simple generateQI when xulrunner is more stable.
388 QueryInterface
: XPCOMUtils
.generateQI(
389 [i
for each (i
in [Ci
.nsIBadCertListener2
,
390 Ci
.nsISSLErrorListener
,
391 Ci
.nsIInterfaceRequestor
])
397 * Coroutine interface for sending an HTTP request and waiting for the
398 * response. (This includes so-called "AJAX" requests.)
400 * @param lspec (required) a load_spec object or URI string (see load-spec.js)
402 * The request URI is obtained from this argument. In addition, if the
403 * load spec specifies post data, a POST request is made instead of a
404 * GET request, and the post data included in the load spec is
405 * sent. Specifically, the request_mime_type and raw_post_data
406 * properties of the load spec are used.
408 * @param $user (optional) HTTP user name to include in the request headers
409 * @param $password (optional) HTTP password to include in the request headers
411 * @param $override_mime_type (optional) Force the response to be interpreted
412 * as having the specified MIME type. This is only
413 * really useful for forcing the MIME type to be
414 * text/xml or something similar, such that it is
415 * automatically parsed into a DOM document.
416 * @param $headers (optional) an array of [name,value] pairs (each specified as
417 * a two-element array) specifying additional headers to add to
420 * @returns After the request completes (either successfully or with an error),
421 * the nsIXMLHttpRequest object is returned. Its responseText (for any
422 * arbitrary document) or responseXML (if the response type is an XML
423 * content type) properties can be accessed to examine the response
426 * If an exception is thrown to the continutation (which can be obtained by the
427 * caller by calling yield CONTINUATION prior to calling this function) while the
428 * request is in progress (i.e. before this coroutine returns), the request will
429 * be aborted, and the exception will be propagated to the caller.
432 define_keywords("$user", "$password", "$override_mime_type", "$headers");
433 function send_http_request (lspec
) {
434 // why do we get warnings in jsconsole unless we initialize the
435 // following keywords?
436 keywords(arguments
, $user
= undefined, $password
= undefined,
437 $override_mime_type
= undefined, $headers
= undefined);
438 if (! (lspec
instanceof load_spec
))
439 lspec
= load_spec(lspec
);
440 var req
= xml_http_request();
441 var cc
= yield CONTINUATION
;
442 var aborting
= false;
443 req
.onreadystatechange
= function send_http_request__onreadystatechange () {
444 if (req
.readyState
!= 4)
451 if (arguments
.$override_mime_type
)
452 req
.overrideMimeType(arguments
.$override_mime_type
);
454 var post_data
= load_spec_raw_post_data(lspec
);
456 var method
= post_data
? "POST" : "GET";
458 req
.open(method
, load_spec_uri_string(lspec
), true, arguments
.$user
, arguments
.$password
);
459 req
.channel
.notificationCallbacks
= xml_http_request_load_listener
;
461 for each (let [name
,value
] in arguments
.$headers
) {
462 req
.setRequestHeader(name
, value
);
466 req
.setRequestHeader("Content-Type", load_spec_request_mime_type(lspec
));
479 // Let the caller access the status and reponse data
480 yield co_return(req
);
485 * scroll_selection_into_view takes an editable element, and scrolls it so
486 * that the selection (or insertion point) are visible.
488 function scroll_selection_into_view (field
) {
489 if (field
.namespaceURI
== XUL_NS
)
490 field
= field
.inputField
;
492 field
.QueryInterface(Ci
.nsIDOMNSEditableElement
)
495 .scrollSelectionIntoView(
496 Ci
.nsISelectionController
.SELECTION_NORMAL
,
497 Ci
.nsISelectionController
.SELECTION_FOCUS_REGION
,
500 // we'll get here for richedit fields
505 function compute_up_url (uri
) {
507 uri
= uri
.clone().QueryInterface(Ci
.nsIURL
);
511 for (let [k
, p
] in Iterator(["ref", "query", "param", "fileName"])) {
512 if (p
in uri
&& uri
[p
] != "") {
517 return uri
.resolve("..");
521 function url_path_trim (url
) {
522 var uri
= make_uri(url
);
529 * possibly_valid_url returns true if its argument is an url-like string,
530 * meaning likely a valid thing to pass to nsIWebNavigation.loadURI.
532 function possibly_valid_url (str
) {
533 // no inner space before first /
534 return /^\s*[^\/\s]*(\/|\s*$)/.test(str
)
535 && !(/^\s*$/.test(str
));
539 /* get_contents_synchronously returns the contents of the given
540 * url (string or nsIURI) as a string on success, or null on failure.
542 function get_contents_synchronously (url
) {
543 var ioService
=Cc
["@mozilla.org/network/io-service;1"]
544 .getService(Ci
.nsIIOService
);
545 var scriptableStream
=Cc
["@mozilla.org/scriptableinputstream;1"]
546 .getService(Ci
.nsIScriptableInputStream
);
550 if (url
instanceof Ci
.nsIURI
)
551 channel
= ioService
.newChannelFromURI(url
);
553 channel
= ioService
.newChannel(url
, null, null);
554 input
=channel
.open();
558 scriptableStream
.init(input
);
559 var str
=scriptableStream
.read(input
.available());
560 scriptableStream
.close();
567 * data is an an alist (array of 2 element arrays) where each pair is a key
570 * The return type is a mime input stream that can be passed as postData to
571 * nsIWebNavigation.loadURI. In terms of Conkeror's API, the return value
572 * of this function is of the correct type for the `post_data' field of a
575 function make_post_data (data
) {
576 data
= [(encodeURIComponent(pair
[0])+'='+encodeURIComponent(pair
[1]))
577 for each (pair
in data
)].join('&');
578 data
= string_input_stream(data
);
579 return mime_input_stream(
580 data
, [["Content-Type", "application/x-www-form-urlencoded"]]);
585 * Centers the viewport around a given element.
587 * @param win The window to scroll.
588 * @param elem The element arund which we put the viewport.
590 function center_in_viewport (win
, elem
) {
591 let point
= abs_point(elem
);
593 point
.x
-= win
.innerWidth
/ 2;
594 point
.y
-= win
.innerHeight
/ 2;
596 win
.scrollTo(point
.x
, point
.y
);
601 * Simple predicate returns true if elem is an nsIDOMNode or
604 function dom_node_or_window_p (elem
) {
605 if (elem
instanceof Ci
.nsIDOMNode
)
607 if (elem
instanceof Ci
.nsIDOMWindow
)
613 * Given a hook name, a buffer and a function, waits until the buffer document
614 * has fully loaded, then calls the function with the buffer as its only
617 * @param {String} The hook name.
618 * @param {buffer} The buffer.
619 * @param {function} The function to call with the buffer as its argument once
620 * the buffer has loaded.
622 function do_when (hook
, buffer
, fun
) {
623 if (buffer
.browser
.webProgress
.isLoadingDocument
)
624 add_hook
.call(buffer
, hook
, fun
);
631 * evaluate string s as javascript in the 'this' scope in which evaluate
634 function evaluate (s
) {
636 var obs
= Cc
["@mozilla.org/observer-service;1"]
637 .getService(Ci
.nsIObserverService
);
638 obs
.notifyObservers(null, "startupcache-invalidate", null);
639 var temp
= get_temporary_file("conkeror-evaluate.tmp.js");
640 write_text_file(temp
, s
);
641 var url
= make_uri(temp
).spec
;
642 return load_url(url
, this);
644 if (temp
&& temp
.exists())
651 * set_protocol_handler takes a protocol and a handler spec. If the
652 * handler is true, Mozilla will (try to) handle this protocol internally.
653 * If the handler null, the user will be prompted for a handler when a
654 * resource of this protocol is requested. If the handler is an nsIFile,
655 * the program it gives will be launched with the url as an argument. If
656 * the handler is a string, it will be interpreted as an URL template for
657 * a web service and the sequence '%s' within it will be replaced by the
660 function set_protocol_handler (protocol
, handler
) {
661 var eps
= Cc
["@mozilla.org/uriloader/external-protocol-service;1"]
662 .getService(Ci
.nsIExternalProtocolService
);
663 var info
= eps
.getProtocolHandlerInfo(protocol
);
664 var expose_pref
= "network.protocol-handler.expose."+protocol
;
665 if (handler
== true) {
667 clear_default_pref(expose_pref
);
668 } else if (handler
) {
670 if (handler
instanceof Ci
.nsIFile
) {
671 var h
= Cc
["@mozilla.org/uriloader/local-handler-app;1"]
672 .createInstance(Ci
.nsILocalHandlerApp
);
673 h
.executable
= handler
;
674 } else if (typeof handler
== "string") {
675 h
= Cc
["@mozilla.org/uriloader/web-handler-app;1"]
676 .createInstance(Ci
.nsIWebHandlerApp
);
677 var uri
= make_uri(handler
);
679 h
.uriTemplate
= handler
;
681 info
.alwaysAskBeforeHandling
= false;
682 info
.preferredAction
= Ci
.nsIHandlerInfo
.useHelperApp
;
683 info
.possibleApplicationHandlers
.clear();
684 info
.possibleApplicationHandlers
.appendElement(h
, false);
685 info
.preferredApplicationHandler
= h
;
686 session_pref(expose_pref
, false);
689 info
.alwaysAskBeforeHandling
= true;
690 info
.preferredAction
= Ci
.nsIHandlerInfo
.alwaysAsk
;
691 session_pref(expose_pref
, false);
693 var hs
= Cc
["@mozilla.org/uriloader/handler-service;1"]
694 .getService(Ci
.nsIHandlerService
);