2 * (C) Copyright 2004-2007 Shawn Betts
3 * (C) Copyright 2007-2009 John J. Foerch
4 * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
6 * Use, modification, and distribution are subject to the terms specified in the
10 function string_hashset () {}
12 string_hashset.prototype = {
13 constructor : string_hashset,
19 contains : function (s) {
20 return (("-" + s) in this);
23 remove : function (s) {
27 for_each : function (f) {
34 iterator : function () {
42 function string_hashmap () {}
44 string_hashmap.prototype = {
45 constructor : string_hashmap,
47 put : function (s,value) {
48 this["-" + s] = value;
51 contains : function (s) {
52 return (("-" + s) in this);
55 get : function (s, default_value) {
61 get_put_default : function (s, default_value) {
64 return (this["-" + s] = default_value);
67 remove : function (s) {
71 for_each : function (f) {
74 f(i.slice(1), this[i]);
78 for_each_value : function (f) {
85 iterator: function (only_keys) {
87 for (let k in Iterator(this, true)) {
92 for (let [k,v] in Iterator(this, false)) {
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) {
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)
121 var f = Cc["@mozilla.org/file/local;1"]
122 .createInstance(Ci.nsILocalFile);
123 f.initWithPath(path);
127 var io_service = Cc["@mozilla.org/network/io-service;1"]
128 .getService(Ci.nsIIOService2);
130 function make_uri (uri, charset, base_uri) {
131 if (uri instanceof Ci.nsIURI)
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;
148 content_disposition = document_o.defaultView
149 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
150 .getInterface(Components.interfaces.nsIDOMWindowUtils)
151 .getDocumentMetadata("content-disposition");
153 return content_disposition;
157 function set_focus_no_scroll (window, element) {
158 window.document.commandDispatcher.suppressFocusScroll = true;
160 window.document.commandDispatcher.suppressFocusScroll = false;
163 function do_repeatedly_positive (func, n) {
164 var args = Array.prototype.slice.call(arguments, 2);
166 func.apply(null, args);
169 function do_repeatedly (func, n, positive_args, negative_args) {
171 do func.apply(null, negative_args); while (++n < 0);
173 while (n-- > 0) func.apply(null, positive_args);
176 // remove whitespace from the beginning and end
177 function trim_whitespace (str) {
178 var tmp = new String(str);
179 return tmp.replace(/^\s+/, "").replace(/\s+$/, "");
183 * Given a node, returns its position relative to the document.
185 * @param node The node to get the position of.
186 * @return An object with properties "x" and "y" representing its offset from
187 * the left and top of the document, respectively.
189 function abs_point (node) {
193 pt.x = node.offsetLeft;
194 pt.y = node.offsetTop;
195 // find imagemap's coordinates
196 if (node.tagName == "AREA") {
197 var coords = node.getAttribute("coords").split(",");
198 pt.x += Number(coords[0]);
199 pt.y += Number(coords[1]);
202 node = node.offsetParent;
203 // Sometimes this fails, so just return what we got.
205 while (node.tagName != "BODY") {
206 pt.x += node.offsetLeft;
207 pt.y += node.offsetTop;
208 node = node.offsetParent;
212 // while (node.tagName != "BODY") {
213 // alert("okay: " + node + " " + node.tagName + " " + pt.x + " " + pt.y);
214 // node = node.offsetParent;
222 * get_os returns a string identifying the current OS.
223 * possible values include 'Darwin', 'Linux' and 'WINNT'.
225 let (xul_runtime = Cc['@mozilla.org/xre/app-info;1']
226 .getService(Ci.nsIXULRuntime)) {
228 return xul_runtime.OS;
234 * getenv returns the value of a named environment variable or null if
235 * the environment variable does not exist.
237 let (env = Cc['@mozilla.org/process/environment;1']
238 .getService(Ci.nsIEnvironment)) {
239 function getenv (variable) {
240 if (env.exists(variable))
241 return env.get(variable);
248 * get_home_directory returns an nsILocalFile object of the user's
251 function get_home_directory () {
252 var dir = Cc["@mozilla.org/file/local;1"]
253 .createInstance(Ci.nsILocalFile);
254 if (get_os() == "WINNT")
255 dir.initWithPath(getenv('USERPROFILE') ||
256 getenv('HOMEDRIVE') + getenv('HOMEPATH'));
258 dir.initWithPath(getenv('HOME'));
263 const XHTML_NS = "http://www.w3.org/1999/xhtml";
264 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
265 const MATHML_NS = "http://www.w3.org/1998/Math/MathML";
266 const XLINK_NS = "http://www.w3.org/1999/xlink";
268 function create_XUL (window, tag_name) {
269 return window.document.createElementNS(XUL_NS, tag_name);
273 /* Used in calls to XPath evaluate */
274 function xpath_lookup_namespace (prefix) {
282 function method_caller (obj, func) {
284 func.apply(obj, arguments);
288 function shell_quote (str) {
289 var s = str.replace("\"", "\\\"", "g");
290 s = s.replace("$", "\$", "g");
294 /* Like perl's quotemeta. Backslash all non-alphanumerics. */
295 function quotemeta (str) {
296 return str.replace(/([^a-zA-Z0-9])/g, "\\$1");
299 /* Given a list of choices (strings), return a regex which matches any
301 function choice_regex (choices) {
302 var regex = "(?:" + choices.map(quotemeta).join("|") + ")";
306 function get_window_from_frame (frame) {
308 var window = frame.QueryInterface(Ci.nsIInterfaceRequestor)
309 .getInterface(Ci.nsIWebNavigation)
310 .QueryInterface(Ci.nsIDocShellTreeItem)
312 .QueryInterface(Ci.nsIInterfaceRequestor)
313 .getInterface(Ci.nsIDOMWindow).wrappedJSObject;
314 /* window is now an XPCSafeJSObjectWrapper */
315 window.escape_wrapper(function (w) { window = w; });
316 /* window is now completely unwrapped */
323 function get_buffer_from_frame (window, frame) {
324 var count = window.buffers.count;
325 for (var i = 0; i < count; ++i) {
326 var b = window.buffers.get_buffer(i);
327 if (b.top_frame == frame.top)
333 var file_locator = Cc["@mozilla.org/file/directory_service;1"]
334 .getService(Ci.nsIProperties);
336 function get_shortdoc_string (doc) {
339 var idx = doc.indexOf("\n");
341 shortdoc = doc.substring(0,idx);
348 var conkeror_source_code_path = null;
350 function source_code_reference (uri, line_number) {
352 this.line_number = line_number;
354 source_code_reference.prototype = {
356 if (this.uri.indexOf(module_uri_prefix) == 0)
357 return this.uri.substring(module_uri_prefix.length);
362 var file_uri_prefix = "file://";
363 if (this.uri.indexOf(file_uri_prefix) == 0)
364 return this.uri.substring(file_uri_prefix.length);
369 if (conkeror_source_code_path != null) {
370 var module_name = this.module_name;
371 if (module_name != null)
372 return "file://" + conkeror_source_code_path + "/modules/" + module_name;
377 open_in_editor : function() {
378 yield open_with_external_editor(load_spec(this.best_uri),
379 $line = this.line_number);
383 var get_caller_source_code_reference_ignored_functions = {};
385 function get_caller_source_code_reference (extra_frames_back) {
386 /* Skip at least this function itself and whoever called it (and
387 * more if the caller wants to be skipped). */
388 var frames_to_skip = 2;
389 if (extra_frames_back != null)
390 frames_to_skip += extra_frames_back;
392 for (let f = Components.stack; f != null; f = f.caller) {
393 if (frames_to_skip > 0) {
397 if (get_caller_source_code_reference_ignored_functions[f.name])
399 return new source_code_reference(f.filename, f.lineNumber);
405 function ignore_function_for_get_caller_source_code_reference (func_name) {
406 get_caller_source_code_reference_ignored_functions[func_name] = 1;
409 require_later("external-editor.js");
411 function dom_generator (document, ns) {
412 this.document = document;
415 dom_generator.prototype = {
416 element : function (tag, parent) {
417 var node = this.document.createElementNS(this.ns, tag);
419 if (parent != null && (parent instanceof Ci.nsIDOMNode)) {
420 parent.appendChild(node);
423 for (var nargs = arguments.length; i < nargs; i += 2)
424 node.setAttribute(arguments[i], arguments[i+1]);
428 text : function (str, parent) {
429 var node = this.document.createTextNode(str);
431 parent.appendChild(node);
436 stylesheet_link : function (href, parent) {
437 var node = this.element("link");
438 node.setAttribute("rel", "stylesheet");
439 node.setAttribute("type", "text/css");
440 node.setAttribute("href", href);
442 parent.appendChild(node);
447 add_stylesheet : function (url) {
448 var head = this.document.documentElement.firstChild;
449 this.stylesheet_link(url, head);
454 * Generates a QueryInterface function suitable for an implemenation
455 * of an XPCOM interface. Unlike XPCOMUtils, this uses the Function
456 * constructor to generate a slightly more efficient version. The
457 * arguments can be either Strings or elements of
458 * Components.interfaces.
460 function generate_QI () {
461 var args = Array.prototype.slice.call(arguments).map(String).concat(["nsISupports"]);
463 Array.prototype.map.call(args, function (x) {
464 return "iid.equals(Components.interfaces." + x + ")";
467 ") return this; throw Components.results.NS_ERROR_NO_INTERFACE;";
468 return new Function("iid", fstr);
471 function set_branch_pref (branch, name, value) {
472 if (typeof(value) == "string") {
473 branch.setCharPref(name, value);
474 } else if (typeof(value) == "number") {
475 branch.setIntPref(name, value);
476 } else if (typeof(value) == "boolean") {
477 branch.setBoolPref(name, value);
481 function default_pref (name, value) {
482 var branch = preferences.getDefaultBranch(null);
483 set_branch_pref(branch, name, value);
486 function user_pref (name, value) {
487 var branch = preferences.getBranch(null);
488 set_branch_pref(branch, name, value);
491 function get_branch_pref (branch, name) {
492 switch (branch.getPrefType(name)) {
493 case branch.PREF_STRING:
494 return branch.getCharPref(name);
495 case branch.PREF_INT:
496 return branch.getIntPref(name);
497 case branch.PREF_BOOL:
498 return branch.getBoolPref(name);
504 function get_localized_pref (name) {
506 return preferences.getBranch(null).getComplexValue(name, Ci.nsIPrefLocalizedString).data;
512 function get_pref (name) {
513 var branch = preferences.getBranch(null);
514 return get_branch_pref(branch, name);
517 function get_default_pref (name) {
518 var branch = preferences.getDefaultBranch(null);
519 return get_branch_pref(branch, name);
522 function clear_pref (name) {
523 var branch = preferences.getBranch(null);
524 return branch.clearUserPref(name);
527 function pref_has_user_value (name) {
528 var branch = preferences.getBranch(null);
529 return branch.prefHasUserValue(name);
532 function pref_has_default_value (name) {
533 var branch = preferences.getDefaultBranch(null);
534 return branch.prefHasUserValue(name);
537 function session_pref (name, value) {
541 return default_pref(name, value);
544 function watch_pref (pref, hook) {
545 /* Extract pref into branch.pref */
546 let match = pref.match(/^(.*[.])?([^.]*)$/);
549 let branch = preferences.getBranch(br).QueryInterface(Ci.nsIPrefBranch2);
551 observe: function (subject, topic, data) {
552 if (topic == "nsPref:changed" && data == key) {
557 branch.addObserver("", observer, false);
560 const LOCALE_PREF = "general.useragent.locale";
562 function get_locale () {
563 return get_localized_pref(LOCALE_PREF) || get_pref(LOCALE_PREF);
566 const USER_AGENT_OVERRIDE_PREF = "general.useragent.override";
568 function set_user_agent (str) {
569 session_pref(USER_AGENT_OVERRIDE_PREF, str);
572 function define_builtin_commands (prefix, do_command_function, toggle_mark, mark_active_predicate, mode) {
574 // Specify a docstring
575 function D (cmd, docstring) {
576 var o = new String(cmd);
581 // Specify a forward/reverse pair
584 o.is_reverse_pair = true;
588 // Specify a movement/select/scroll/move-caret command group.
589 function S (command, movement, select, scroll, caret) {
590 var o = [movement, select, scroll, caret];
592 o.is_move_select_pair = true;
596 var builtin_commands = [
599 * cmd_scrollBeginLine and cmd_scrollEndLine don't do what I
600 * want, either in or out of caret mode...
602 S(D("beginning-of-line", "Move or extend the selection to the beginning of the current line."),
603 D("cmd_beginLine", "Move point to the beginning of the current line."),
604 D("cmd_selectBeginLine", "Extend selection to the beginning of the current line."),
605 D("cmd_beginLine", "Scroll to the beginning of the line"),
606 D("cmd_beginLine", "Scroll to the beginning of the line")),
607 S(D("end-of-line", "Move or extend the selection to the end of the current line."),
608 D("cmd_endLine", "Move point to the end of the current line."),
609 D("cmd_selectEndLine", "Extend selection to the end of the current line."),
610 D("cmd_endLine", "Scroll to the end of the current line."),
611 D("cmd_endLine", "Scroll to the end of the current line.")),
612 S(D("beginning-of-first-line", "Move or extend the selection to the beginning of the first line."),
613 D("cmd_moveTop", "Move point to the beginning of the first line."),
614 D("cmd_selectTop", "Extend selection to the beginning of the first line."),
615 D("cmd_scrollTop", "Scroll to the top of the buffer"),
616 D("cmd_scrollTop", "Move point to the beginning of the first line.")),
617 S(D("end-of-last-line", "Move or extend the selection to the end of the last line."),
618 D("cmd_moveBottom", "Move point to the end of the last line."),
619 D("cmd_selectBottom", "Extend selection to the end of the last line."),
620 D("cmd_scrollBottom", "Scroll to the bottom of the buffer"),
621 D("cmd_scrollBottom", "Move point to the end of the last line.")),
623 "cmd_scrollBeginLine",
626 D("cmd_copy", "Copy the selection into the clipboard."),
627 D("cmd_cut", "Cut the selection into the clipboard."),
628 D("cmd_deleteToBeginningOfLine", "Delete to the beginning of the current line."),
629 D("cmd_deleteToEndOfLine", "Delete to the end of the current line."),
630 D("cmd_selectAll", "Select all."),
631 D("cmd_scrollTop", "Scroll to the top of the buffer."),
632 D("cmd_scrollBottom", "Scroll to the bottom of the buffer.")];
634 var builtin_commands_with_count = [
635 R(S(D("forward-char", "Move or extend the selection forward one character."),
636 D("cmd_charNext", "Move point forward one character."),
637 D("cmd_selectCharNext", "Extend selection forward one character."),
638 D("cmd_scrollRight", "Scroll to the right"),
639 D("cmd_scrollRight", "Scroll to the right")),
640 S(D("backward-char", "Move or extend the selection backward one character."),
641 D("cmd_charPrevious", "Move point backward one character."),
642 D("cmd_selectCharPrevious", "Extend selection backward one character."),
643 D("cmd_scrollLeft", "Scroll to the left."),
644 D("cmd_scrollLeft", "Scroll to the left."))),
645 R(D("cmd_deleteCharForward", "Delete the following character."),
646 D("cmd_deleteCharBackward", "Delete the previous character.")),
647 R(D("cmd_deleteWordForward", "Delete the following word."),
648 D("cmd_deleteWordBackward", "Delete the previous word.")),
649 R(S(D("forward-line", "Move or extend the selection forward one line."),
650 D("cmd_lineNext", "Move point forward one line."),
651 D("cmd_selectLineNext", "Extend selection forward one line."),
652 D("cmd_scrollLineDown", "Scroll down one line."),
653 D("cmd_scrollLineDown", "Scroll down one line.")),
654 S(D("backward-line", "Move or extend the selection backward one line."),
655 D("cmd_linePrevious", "Move point backward one line."),
656 D("cmd_selectLinePrevious", "Extend selection backward one line."),
657 D("cmd_scrollLineUp", "Scroll up one line."),
658 D("cmd_scrollLineUp", "Scroll up one line."))),
659 R(S(D("forward-page", "Move or extend the selection forward one page."),
660 D("cmd_movePageDown", "Move point forward one page."),
661 D("cmd_selectPageDown", "Extend selection forward one page."),
662 D("cmd_scrollPageDown", "Scroll forward one page."),
663 D("cmd_movePageDown", "Move point forward one page.")),
664 S(D("backward-page", "Move or extend the selection backward one page."),
665 D("cmd_movePageUp", "Move point backward one page."),
666 D("cmd_selectPageUp", "Extend selection backward one page."),
667 D("cmd_scrollPageUp", "Scroll backward one page."),
668 D("cmd_movePageUp", "Move point backward one page."))),
669 R(D("cmd_undo", "Undo last editing action."),
670 D("cmd_redo", "Redo last editing action.")),
671 R(S(D("forward-word", "Move or extend the selection forward one word."),
672 D("cmd_wordNext", "Move point forward one word."),
673 D("cmd_selectWordNext", "Extend selection forward one word."),
674 D("cmd_scrollRight", "Scroll to the right."),
675 D("cmd_wordNext", "Move point forward one word.")),
676 S(D("backward-word", "Move or extend the selection backward one word."),
677 D("cmd_wordPrevious", "Move point backward one word."),
678 D("cmd_selectWordPrevious", "Extend selection backward one word."),
679 D("cmd_scrollLeft", "Scroll to the left."),
680 D("cmd_wordPrevious", "Move point backward one word."))),
681 R(D("cmd_scrollPageUp", "Scroll up one page."),
682 D("cmd_scrollPageDown", "Scroll down one page.")),
683 R(D("cmd_scrollLineUp", "Scroll up one line."),
684 D("cmd_scrollLineDown", "Scroll down one line.")),
685 R(D("cmd_scrollLeft", "Scroll left."),
686 D("cmd_scrollRight", "Scroll right.")),
687 D("cmd_paste", "Insert the contents of the clipboard.")];
689 interactive(prefix + "set-mark",
690 "Toggle whether the mark is active.\n" +
691 "When the mark is active, movement commands affect the selection.",
694 function get_mode_idx () {
695 if (mode == 'scroll') return 2;
696 else if (mode == 'caret') return 3;
700 function get_move_select_idx (I) {
701 return mark_active_predicate(I) ? 1 : get_mode_idx();
704 function doc_for_builtin (c) {
708 return s + "Run the built-in command " + c + ".";
711 function define_simple_command (c) {
712 interactive(prefix + c, doc_for_builtin(c), function (I) { do_command_function(I, c); });
715 function get_move_select_doc_string (c) {
716 return c.command.doc +
717 "\nSpecifically, if the mark is active, runs `" + prefix + c[1] + "'. " +
718 "Otherwise, runs `" + prefix + c[get_mode_idx()] + "'\n" +
719 "To toggle whether the mark is active, use `" + prefix + "set-mark'.";
722 for each (let c_temp in builtin_commands) {
724 if (c.is_move_select_pair) {
725 interactive(prefix + c.command, get_move_select_doc_string(c), function (I) {
726 var idx = get_move_select_idx(I);
727 do_command_function(I, c[idx]);
729 define_simple_command(c[0]);
730 define_simple_command(c[1]);
733 define_simple_command(c);
736 function get_reverse_pair_doc_string (main_doc, alt_command) {
737 return main_doc + "\n" +
738 "The prefix argument specifies a repeat count for this command. " +
739 "If the count is negative, `" + prefix + alt_command + "' is performed instead with " +
740 "a corresponding positive repeat count.";
743 function define_simple_reverse_pair (a, b) {
744 interactive(prefix + a, get_reverse_pair_doc_string(doc_for_builtin(a), b),
746 do_repeatedly(do_command_function, I.p, [I, a], [I, b]);
748 interactive(prefix + b, get_reverse_pair_doc_string(doc_for_builtin(b), a),
750 do_repeatedly(do_command_function, I.p, [I, b], [I, a]);
754 for each (let c_temp in builtin_commands_with_count) {
756 if (c.is_reverse_pair) {
757 if (c[0].is_move_select_pair) {
758 interactive(prefix + c[0].command, get_reverse_pair_doc_string(get_move_select_doc_string(c[0]),
761 var idx = get_move_select_idx(I);
762 do_repeatedly(do_command_function, I.p, [I, c[0][idx]], [I, c[1][idx]]);
764 interactive(prefix + c[1].command, get_reverse_pair_doc_string(get_move_select_doc_string(c[1]),
767 var idx = get_move_select_idx(I);
768 do_repeatedly(do_command_function, I.p, [I, c[1][idx]], [I, c[0][idx]]);
770 define_simple_reverse_pair(c[0][0], c[1][0]);
771 define_simple_reverse_pair(c[0][1], c[1][1]);
773 define_simple_reverse_pair(c[0], c[1]);
775 let doc = doc_for_builtin(c) +
776 "\nThe prefix argument specifies a positive repeat count for this command.";
777 interactive(prefix + c, doc, function (I) {
778 do_repeatedly_positive(do_command_function, I.p, I, c);
784 var observer_service = Cc["@mozilla.org/observer-service;1"]
785 .getService(Ci.nsIObserverService);
787 function abort (str) {
788 var e = new Error(str);
789 e.__proto__ = abort.prototype;
792 abort.prototype.__proto__ = Error.prototype;
795 function get_temporary_file (name) {
798 var file = file_locator.get("TmpD", Ci.nsIFile);
800 // Create the file now to ensure that no exploits are possible
801 file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
806 /* FIXME: This should be moved somewhere else, perhaps. */
807 function create_info_panel (window, panel_class, row_arr) {
808 /* Show information panel above minibuffer */
810 var g = new dom_generator(window.document, XUL_NS);
812 var p = g.element("vbox", "class", "panel " + panel_class, "flex", "0");
813 var grid = g.element("grid", p);
814 var cols = g.element("columns", grid);
815 g.element("column", cols, "flex", "0");
816 g.element("column", cols, "flex", "1");
818 var rows = g.element("rows", grid);
821 for each (let [row_class, row_label, row_value] in row_arr) {
822 row = g.element("row", rows, "class", row_class);
823 g.element("label", row,
825 "class", "panel-row-label");
826 g.element("label", row,
828 "class", "panel-row-value",
831 window.minibuffer.insert_before(p);
833 p.destroy = function () {
834 this.parentNode.removeChild(this);
842 * Paste from the X primary selection, unless the system doesn't support a
843 * primary selection, in which case fall back to the clipboard.
845 function read_from_x_primary_selection () {
847 let clipboard = Components.classes["@mozilla.org/widget/clipboard;1"]
848 .getService(Components.interfaces.nsIClipboard);
850 // Fall back to global clipboard if the system doesn't support a selection
851 let which_clipboard = clipboard.supportsSelectionClipboard() ?
852 clipboard.kSelectionClipboard : clipboard.kGlobalClipboard;
854 let flavors = ["text/unicode"];
856 // Don't barf if there's nothing on the clipboard
857 if (!clipboard.hasDataMatchingFlavors(flavors, flavors.length, which_clipboard))
860 // Create transferable that will transfer the text.
861 let trans = Components.classes["@mozilla.org/widget/transferable;1"]
862 .createInstance(Components.interfaces.nsITransferable);
864 for each (let flavor in flavors) {
865 trans.addDataFlavor(flavor);
867 clipboard.getData(trans, which_clipboard);
869 var data_flavor = {};
872 trans.getAnyTransferData(data_flavor, data, dataLen);
875 data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
876 let data_length = dataLen.value;
877 if (data_flavor.value == "text/unicode")
878 data_length = dataLen.value / 2;
879 return data.data.substring(0, data_length);
885 var user_variables = {};
887 function define_variable (name, default_value, doc) {
888 conkeror[name] = default_value;
889 user_variables[name] = {
890 default_value: default_value,
892 shortdoc: get_shortdoc_string(doc),
893 source_code_reference: get_caller_source_code_reference()
897 function define_special_variable (name, getter, setter, doc) {
898 conkeror.__defineGetter__(name, getter);
899 conkeror.__defineSetter__(name, setter);
900 user_variables[name] = {
901 default_value: undefined,
903 shortdoc: get_shortdoc_string(doc),
904 source_code_reference: get_caller_source_code_reference()
908 /* Re-define load_paths as a user variable. */
909 define_variable("load_paths", load_paths,
910 "Array of URL prefixes searched in order when loading a module.\n" +
911 "Each entry must end in a slash, and should begin with file:// or chrome://.");
916 function register_user_stylesheet (url) {
917 var uri = make_uri(url);
918 var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
919 .getService(Ci.nsIStyleSheetService);
920 sss.loadAndRegisterSheet(uri, sss.USER_SHEET);
923 function unregister_user_stylesheet (url) {
924 var uri = make_uri(url);
925 var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
926 .getService(Ci.nsIStyleSheetService);
927 if (sss.sheetRegistered(uri, sss.USER_SHEET))
928 sss.unregisterSheet(uri, sss.USER_SHEET);
931 function register_agent_stylesheet (url) {
932 var uri = make_uri(url);
933 var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
934 .getService(Ci.nsIStyleSheetService);
935 sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET);
938 function unregister_agent_stylesheet (url) {
939 var uri = make_uri(url);
940 var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
941 .getService(Ci.nsIStyleSheetService);
942 if (sss.sheetRegistered(uri, sss.AGENT_SHEET))
943 sss.unregisterSheet(uri, sss.AGENT_SHEET);
946 function agent_stylesheet_registered_p (url) {
947 var uri = make_uri(url);
948 var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
949 .getService(Ci.nsIStyleSheetService);
950 return sss.sheetRegistered(uri, sss.AGENT_SHEET);
953 function user_stylesheet_registered_p (url) {
954 var uri = make_uri(url);
955 var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
956 .getService(Ci.nsIStyleSheetService);
957 return sss.sheetRegistered(uri, sss.USER_SHEET);
962 function predicate_alist_match (alist, key) {
963 for each (let i in alist) {
971 function get_meta_title (doc) {
972 var title = doc.evaluate("//meta[@name='title']/@content", doc, xpath_lookup_namespace,
973 Ci.nsIDOMXPathResult.STRING_TYPE , null);
974 if (title && title.stringValue)
975 return title.stringValue;
979 var rdf_service = Cc["@mozilla.org/rdf/rdf-service;1"]
980 .getService(Ci.nsIRDFService);
982 const PREFIX_ITEM_URI = "urn:mozilla:item:";
983 const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#";
985 var extension_manager = Cc["@mozilla.org/extensions/manager;1"]
986 .getService(Ci.nsIExtensionManager);
988 function get_extension_rdf_property (id, name, type) {
989 var value = extension_manager.datasource.GetTarget(
990 rdf_service.GetResource(PREFIX_ITEM_URI + id),
991 rdf_service.GetResource(PREFIX_NS_EM + name),
995 return value.QueryInterface(type || Ci.nsIRDFLiteral).Value;
998 function get_extension_update_item (id) {
999 return extension_manager.getItemForID(id);
1002 function extension_info (id) {
1005 extension_info.prototype = {
1006 // Returns the nsIUpdateItem object associated with this extension
1007 get update_item () { return get_extension_update_item(this.id); },
1009 get_rdf_property : function (name, type) {
1010 return get_extension_rdf_property(this.id, name, type);
1014 get isDisabled () { return this.get_rdf_property("isDisabled"); },
1015 get aboutURL () { return this.get_rdf_property("aboutURL"); },
1016 get addonID () { return this.get_rdf_property("addonID"); },
1017 get availableUpdateURL () { return this.get_rdf_property("availableUpdateURL"); },
1018 get availableUpdateVersion () { return this.get_rdf_property("availableUpdateVersion"); },
1019 get blocklisted () { return this.get_rdf_property("blocklisted"); },
1020 get compatible () { return this.get_rdf_property("compatible"); },
1021 get description () { return this.get_rdf_property("description"); },
1022 get downloadURL () { return this.get_rdf_property("downloadURL"); },
1023 get isDisabled () { return this.get_rdf_property("isDisabled"); },
1024 get hidden () { return this.get_rdf_property("hidden"); },
1025 get homepageURL () { return this.get_rdf_property("homepageURL"); },
1026 get iconURL () { return this.get_rdf_property("iconURL"); },
1027 get internalName () { return this.get_rdf_property("internalName"); },
1028 get locked () { return this.get_rdf_property("locked"); },
1029 get name () { return this.get_rdf_property("name"); },
1030 get optionsURL () { return this.get_rdf_property("optionsURL"); },
1031 get opType () { return this.get_rdf_property("opType"); },
1032 get plugin () { return this.get_rdf_property("plugin"); },
1033 get previewImage () { return this.get_rdf_property("previewImage"); },
1034 get satisfiesDependencies () { return this.get_rdf_property("satisfiesDependencies"); },
1035 get providesUpdatesSecurely () { return this.get_rdf_property("providesUpdatesSecurely"); },
1036 get type () { return this.get_rdf_property("type", Ci.nsIRDFInt); },
1037 get updateable () { return this.get_rdf_property("updateable"); },
1038 get updateURL () { return this.get_rdf_property("updateURL"); },
1039 get version () { return this.get_rdf_property("version"); }
1042 function extension_is_enabled (id) {
1043 var info = new extension_info(id);
1044 return info.update_item && (info.isDisabled == "false");
1053 return this.input.length + this.output.length;
1055 push: function (x) {
1056 this.input[this.input.length] = x;
1059 let l = this.output.length;
1061 l = this.input.length;
1064 this.output = this.input.reverse();
1066 let x = this.output[l];
1067 this.output.length--;
1073 function frame_iterator (root_frame, start_with) {
1074 var q = new queue, x;
1079 for (let i = 0, nframes = x.frames.length; i < nframes; ++i)
1080 q.push(x.frames[i]);
1081 } while ((x = q.pop()));
1085 if (x == start_with)
1088 for (let i = 0, nframes = x.frames.length; i < nframes; ++i)
1089 q.push(x.frames[i]);
1090 } while ((x = q.pop()));
1093 function xml_http_request () {
1094 return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
1095 .createInstance(Ci.nsIXMLHttpRequest)
1096 .QueryInterface(Ci.nsIJSXMLHttpRequest)
1097 .QueryInterface(Ci.nsIDOMEventTarget);
1100 var xml_http_request_load_listener = {
1101 // nsIBadCertListener2
1102 notifyCertProblem: function SSLL_certProblem (socketInfo, status, targetSite) {
1106 // nsISSLErrorListener
1107 notifySSLError: function SSLL_SSLError (socketInfo, error, targetSite) {
1111 // nsIInterfaceRequestor
1112 getInterface: function SSLL_getInterface (iid) {
1113 return this.QueryInterface(iid);
1118 // FIXME: array comprehension used here to hack around the lack of
1119 // Ci.nsISSLErrorListener in 2007 versions of xulrunner 1.9pre.
1120 // make it a simple generateQI when xulrunner is more stable.
1121 QueryInterface: XPCOMUtils.generateQI(
1122 [i for each (i in [Ci.nsIBadCertListener2,
1123 Ci.nsISSLErrorListener,
1124 Ci.nsIInterfaceRequestor])
1130 * Coroutine interface for sending an HTTP request and waiting for the
1131 * response. (This includes so-called "AJAX" requests.)
1133 * @param lspec (required) a load_spec object or URI string (see load-spec.js)
1135 * The request URI is obtained from this argument. In addition, if the
1136 * load spec specifies post data, a POST request is made instead of a
1137 * GET request, and the post data included in the load spec is
1138 * sent. Specifically, the request_mime_type and raw_post_data
1139 * properties of the load spec are used.
1141 * @param $user (optional) HTTP user name to include in the request headers
1142 * @param $password (optional) HTTP password to include in the request headers
1144 * @param $override_mime_type (optional) Force the response to be interpreted
1145 * as having the specified MIME type. This is only
1146 * really useful for forcing the MIME type to be
1147 * text/xml or something similar, such that it is
1148 * automatically parsed into a DOM document.
1149 * @param $headers (optional) an array of [name,value] pairs (each specified as
1150 * a two-element array) specifying additional headers to add to
1153 * @returns After the request completes (either successfully or with an error),
1154 * the nsIXMLHttpRequest object is returned. Its responseText (for any
1155 * arbitrary document) or responseXML (if the response type is an XML
1156 * content type) properties can be accessed to examine the response
1159 * If an exception is thrown to the continutation (which can be obtained by the
1160 * caller by calling yield CONTINUATION prior to calling this function) while the
1161 * request is in progress (i.e. before this coroutine returns), the request will
1162 * be aborted, and the exception will be propagated to the caller.
1165 define_keywords("$user", "$password", "$override_mime_type", "$headers");
1166 function send_http_request (lspec) {
1167 // why do we get warnings in jsconsole unless we initialize the
1168 // following keywords?
1169 keywords(arguments, $user = undefined, $password = undefined,
1170 $override_mime_type = undefined, $headers = undefined);
1171 if (! (lspec instanceof load_spec))
1172 lspec = load_spec(lspec);
1173 var req = xml_http_request();
1174 var cc = yield CONTINUATION;
1175 var aborting = false;
1176 req.onreadystatechange = function send_http_request__onreadysatechange () {
1177 if (req.readyState != 4)
1184 if (arguments.$override_mime_type)
1185 req.overrideMimeType(arguments.$override_mime_type);
1187 var post_data = load_spec_raw_post_data(lspec);
1189 var method = post_data ? "POST" : "GET";
1191 req.open(method, load_spec_uri_string(lspec), true, arguments.$user, arguments.$password);
1192 req.channel.notificationCallbacks = xml_http_request_load_listener;
1194 for each (let [name,value] in arguments.$headers) {
1195 req.setRequestHeader(name, value);
1199 req.setRequestHeader("Content-Type", load_spec_request_mime_type(lspec));
1200 req.send(post_data);
1212 // Let the caller access the status and reponse data
1213 yield co_return(req);
1218 * scroll_selection_into_view takes an editable element, and scrolls it so
1219 * that the selection (or insertion point) are visible.
1221 function scroll_selection_into_view (field) {
1222 if (field.namespaceURI == XUL_NS)
1223 field = field.inputField;
1225 field.QueryInterface(Ci.nsIDOMNSEditableElement)
1227 .selectionController
1228 .scrollSelectionIntoView(
1229 Ci.nsISelectionController.SELECTION_NORMAL,
1230 Ci.nsISelectionController.SELECTION_FOCUS_REGION,
1233 // we'll get here for richedit fields
1239 * build_url_regex builds a regular expression to match URLs for a given
1242 * Both the $domain and $path arguments can be either regexes, in
1243 * which case they will be matched as is, or strings, in which case
1244 * they will be matched literally.
1246 * $tlds specifies a list of valid top-level-domains to match, and
1247 * defaults to .com. Useful for when e.g. foo.org and foo.com are the
1250 * If $allow_www is true, www.domain.tld will also be allowed.
1252 define_keywords("$domain", "$path", "$tlds", "$allow_www");
1253 function build_url_regex () {
1254 function regex_to_string (obj) {
1255 if (obj instanceof RegExp)
1257 return quotemeta(obj);
1260 keywords(arguments, $path = "", $tlds = ["com"], $allow_www = false);
1261 var domain = regex_to_string(arguments.$domain);
1262 if(arguments.$allow_www) {
1263 domain = "(?:www\.)?" + domain;
1265 var path = regex_to_string(arguments.$path);
1266 var tlds = arguments.$tlds;
1267 var regex = "^https?://" + domain + "\\." + choice_regex(tlds) + "/" + path;
1268 return new RegExp(regex);
1272 * splice_ranges: Given an ordered array of non-overlapping ranges,
1273 * represented as elements of [start, end], insert a new range into the
1274 * array, extending, replacing, or merging existing ranges as needed.
1275 * Mutates `arr' in place, but returns the reference to it.
1279 * splice_range([[1,3],[4,6], 5, 8)
1282 * splice_range([[1,3],[4,6],[7,10]], 2, 8)
1285 function splice_range (arr, start, end) {
1286 for (var i = 0; i < arr.length; ++i) {
1291 arr.splice(i, 0, [start, end]);
1299 * The range we are inserting overlaps the current
1300 * range. We need to scan right to see if it also contains any other
1301 * ranges entirely, and remove them if necessary.
1304 while (j < arr.length && end >= arr[j][0])
1307 arr[i][1] = Math.max(end, arr[j][1]);
1308 arr.splice(i + 1, j - i);
1312 if (start > arr[arr.length - 1][1])
1313 arr.push([start, end]);
1318 function compute_url_up_path (url) {
1319 var new_url = Cc["@mozilla.org/network/standard-url;1"]
1320 .createInstance (Ci.nsIURL);
1323 if (new_url.param != "" || new_url.query != "")
1324 up = new_url.filePath;
1325 else if (new_url.fileName != "")
1333 function url_path_trim (url) {
1334 var uri = make_uri(url);
1340 /* possibly_valid_url returns true if the string might be a valid
1341 * thing to pass to nsIWebNavigation.loadURI. Currently just checks
1342 * that there's no whitespace in the middle and that it's not entirely
1345 function possibly_valid_url (url) {
1346 return !(/\S\s+\S/.test(url)) && !(/^\s*$/.test(url));
1350 /* remove_duplicates_filter returns a function that can be
1351 * used in Array.filter. It removes duplicates.
1353 function remove_duplicates_filter () {
1355 return function (x) {
1356 if (acc[x]) return false;
1363 /* get_current_profile returns the name of the current profile, or
1364 * null if that information cannot be found. The result is cached for
1365 * quick repeat lookup. This is safe because xulrunner does not
1366 * support switching profiles on the fly.
1368 * Profiles don't necessarily have a name--as such this information should
1369 * not be depended on for anything important. It is mainly intended for
1370 * decoration of the window title and mode-line.
1372 let (profile_name = null) {
1373 function get_current_profile () {
1375 return profile_name;
1376 if ("@mozilla.org/profile/manager;1" in Cc) {
1377 profile_name = Cc["@mozilla.org/profile/manager;1"]
1378 .getService(Ci.nsIProfile)
1380 return profile_name;
1382 var current_profile_path = Cc["@mozilla.org/file/directory_service;1"]
1383 .getService(Ci.nsIProperties)
1384 .get("ProfD", Ci.nsIFile).path;
1385 var profile_service = Cc["@mozilla.org/toolkit/profile-service;1"]
1386 .getService(Components.interfaces.nsIToolkitProfileService);
1387 var profiles = profile_service.profiles;
1388 while (profiles.hasMoreElements()) {
1389 var p = profiles.getNext().QueryInterface(Ci.nsIToolkitProfile);
1390 if (current_profile_path == p.localDir.path ||
1391 current_profile_path == p.rootDir.path)
1393 profile_name = p.name;
1403 * Given an array, switches places on the subarrays at index i1 to i2 and j1 to
1404 * j2. Leaves the rest of the array unchanged.
1406 function switch_subarrays (arr, i1, i2, j1, j2) {
1407 return arr.slice(0, i1) +
1411 arr.slice(j2, arr.length);
1416 * Convenience function for making simple XPath lookups in a document.
1418 * @param doc The document to look in.
1419 * @param exp The XPath expression to search for.
1420 * @return The XPathResult object representing the set of found nodes.
1422 function xpath_lookup (doc, exp) {
1423 return doc.evaluate(exp, doc, null, Ci.nsIDOMXPathResult.ANY_TYPE, null);
1427 /* get_contents_synchronously returns the contents of the given
1428 * url (string or nsIURI) as a string on success, or null on failure.
1430 function get_contents_synchronously (url) {
1431 var ioService=Cc["@mozilla.org/network/io-service;1"]
1432 .getService(Ci.nsIIOService);
1433 var scriptableStream=Cc["@mozilla.org/scriptableinputstream;1"]
1434 .getService(Ci.nsIScriptableInputStream);
1438 if (url instanceof Ci.nsIURI)
1439 channel = ioService.newChannelFromURI(url);
1441 channel = ioService.newChannel(url, null, null);
1442 input=channel.open();
1446 scriptableStream.init(input);
1447 var str=scriptableStream.read(input.available());
1448 scriptableStream.close();
1455 * string_format takes a format-string containing %X style format codes,
1456 * and an object mapping the code-letters to replacement text. It
1457 * returns a string with the formatting codes replaced by the replacement
1460 function string_format (spec, substitutions) {
1461 return spec.replace(/%(.)/g, function (a,b) { return substitutions[b]; });
1466 * dom_add_class adds a css class to the given dom node.
1468 function dom_add_class (node, cssclass) {
1470 node.className += " "+cssclass;
1472 node.className = cssclass;
1476 * dom_remove_class removes the given css class from the given dom node.
1478 function dom_remove_class (node, cssclass) {
1479 if (! node.className)
1481 var classes = node.className.split(" ");
1482 classes = classes.filter(function (x) { return x != cssclass; });
1483 node.className = classes.join(" ");
1488 * dom_node_flash adds the given cssclass to the node for a brief interval.
1489 * this class can be styled, to create a flashing effect.
1491 function dom_node_flash (node, cssclass) {
1492 dom_add_class(node, cssclass);
1495 dom_remove_class(node, cssclass);
1502 * data is an an alist (array of 2 element arrays) where each pair is a key
1505 * The return type is a mime input stream that can be passed as postData to
1506 * nsIWebNavigation.loadURI. In terms of Conkeror's API, the return value
1507 * of this function is of the correct type for the `post_data' field of a
1510 function make_post_data (data) {
1511 data = [(encodeURIComponent(pair[0])+'='+encodeURIComponent(pair[1]))
1512 for each (pair in data)].join('&');
1513 data = string_input_stream(data);
1514 return mime_input_stream(
1515 data, [["Content-Type", "application/x-www-form-urlencoded"]]);
1520 * Centers the viewport around a given element.
1522 * @param win The window to scroll.
1523 * @param elem The element arund which we put the viewport.
1525 function center_in_viewport (win, elem) {
1526 let point = abs_point(elem);
1528 point.x -= win.innerWidth / 2;
1529 point.y -= win.innerHeight / 2;
1531 win.scrollTo(point.x, point.y);
1536 * Takes an interactive context and a function to call with the word
1537 * at point as its sole argument, and which returns a modified word.
1539 //XXX: this should be implemented in terms of modify_region,
1540 // in order to work in richedit fields.
1541 function modify_word_at_point (I, func) {
1542 var focused = I.buffer.focused_element;
1544 // Skip any whitespaces at point and move point to the right place.
1545 var point = focused.selectionStart;
1546 var rest = focused.value.substring(point);
1548 // Skip any whitespaces.
1549 for (var i = 0, rlen = rest.length; i < rlen; i++) {
1550 if (" \n".indexOf(rest.charAt(i)) == -1) {
1556 // Find the next whitespace, as it is the end of the word. If no next
1557 // whitespace is found, we're at the end of input. TODO: Add "\n" support.
1558 goal = focused.value.indexOf(" ", point);
1560 goal = focused.value.length;
1562 // Change the value of the text field.
1563 var input = focused.value;
1565 input.substring(0, point) +
1566 func(input.substring(point, goal)) +
1567 input.substring(goal);
1570 focused.selectionStart = goal;
1571 focused.selectionEnd = goal;
1576 * Simple predicate returns true if elem is an nsIDOMNode or
1579 function element_dom_node_or_window_p (elem) {
1580 if (elem instanceof Ci.nsIDOMNode)
1582 if (elem instanceof Ci.nsIDOMWindow)
1588 * Given a hook name, a buffer and a function, waits until the buffer document
1589 * has fully loaded, then calls the function with the buffer as its only
1592 * @param {String} The hook name.
1593 * @param {buffer} The buffer.
1594 * @param {function} The function to call with the buffer as its argument once
1595 * the buffer has loaded.
1597 function do_when (hook, buffer, fun) {
1598 if (buffer.browser.webProgress.isLoadingDocument)
1599 add_hook.call(buffer, hook, fun);
1606 * html_escape replaces characters which are special in html with character
1607 * entities, safe for inserting as text into an html document.
1609 function html_escape (str) {
1610 return str.replace(/&/g, '&')
1611 .replace(/</g, '<')
1612 .replace(/>/g, '>')
1613 .replace(/"/g, '"');