2 * (C) Copyright 2004-2007 Shawn Betts
3 * (C) Copyright 2007-2008 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() {
45 string_hashmap.prototype = {
46 constructor : string_hashmap,
48 put : function(s,value) {
49 this["-" + s] = value;
52 contains : function(s) {
53 return (("-" + s) in this);
56 get : function(s, default_value) {
62 get_put_default : function(s, default_value) {
65 return (this["-" + s] = default_value);
68 remove : function (s) {
72 for_each : function (f) {
75 f(i.slice(1), this[i]);
79 for_each_value : function (f) {
86 iterator: function (only_keys) {
88 for (let k in Iterator(this, true)) {
93 for (let [k,v] in Iterator(this, false)) {
102 // Put the string on the clipboard
103 function writeToClipboard(str)
105 var gClipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"]
106 .getService(Ci.nsIClipboardHelper);
107 gClipboardHelper.copyString(str);
111 function makeURLAbsolute (base, url)
114 var ioService = Components.classes["@mozilla.org/network/io-service;1"]
115 .getService(Components.interfaces.nsIIOService);
116 var baseURI = ioService.newURI(base, null, null);
118 return ioService.newURI (baseURI.resolve (url), null, null).spec;
122 function get_link_location (element)
124 if (element && element.getAttribute("href")) {
125 var loc = element.getAttribute("href");
126 return makeURLAbsolute(element.baseURI, loc);
132 function make_file(path) {
133 var f = Cc["@mozilla.org/file/local;1"]
134 .createInstance(Ci.nsILocalFile);
135 f.initWithPath(path);
139 var io_service = Cc["@mozilla.org/network/io-service;1"]
140 .getService(Ci.nsIIOService2);
142 function make_uri(uri, charset, base_uri) {
143 if (uri instanceof Ci.nsIURI)
145 if (uri instanceof Ci.nsIFile)
146 return io_service.newFileURI(uri);
147 return io_service.newURI(uri, charset, base_uri);
151 function get_document_content_disposition (document_o)
153 var content_disposition = null;
155 content_disposition =
156 document_o.defaultView
157 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
158 .getInterface(Components.interfaces.nsIDOMWindowUtils)
159 .getDocumentMetadata("content-disposition");
161 return content_disposition;
165 function set_focus_no_scroll(window, element)
167 window.document.commandDispatcher.suppressFocusScroll = true;
169 window.document.commandDispatcher.suppressFocusScroll = false;
172 function do_repeatedly_positive(func, n) {
173 var args = Array.prototype.slice.call(arguments, 2);
175 func.apply(null, args);
178 function do_repeatedly(func, n, positive_args, negative_args) {
180 do func.apply(null, negative_args); while (++n < 0);
182 while (n-- > 0) func.apply(null, positive_args);
185 // remove whitespace from the beginning and end
186 function trim_whitespace (str)
188 var tmp = new String (str);
189 return tmp.replace (/^\s+/, "").replace (/\s+$/, "");
193 * Given a node, returns its position relative to the document.
195 * @param node The node to get the position of.
196 * @return An object with properties "x" and "y" representing its offset from
197 * the left and top of the document, respectively.
199 function abs_point (node)
204 pt.x = node.offsetLeft;
205 pt.y = node.offsetTop;
206 // find imagemap's coordinates
207 if (node.tagName == "AREA") {
208 var coords = node.getAttribute("coords").split(",");
209 pt.x += Number(coords[0]);
210 pt.y += Number(coords[1]);
213 node = node.offsetParent;
214 // Sometimes this fails, so just return what we got.
216 while (node.tagName != "BODY") {
217 pt.x += node.offsetLeft;
218 pt.y += node.offsetTop;
219 node = node.offsetParent;
223 // while (node.tagName != "BODY") {
224 // alert("okay: " + node + " " + node.tagName + " " + pt.x + " " + pt.y);
225 // node = node.offsetParent;
231 var xul_app_info = Cc["@mozilla.org/xre/app-info;1"]
232 .getService(Ci.nsIXULAppInfo);
233 var xul_runtime = Cc['@mozilla.org/xre/app-info;1']
234 .getService(Ci.nsIXULRuntime);
239 // possible return values: 'Darwin', 'Linux', 'WINNT', ...
240 return xul_runtime.OS;
243 var default_directory = null;
245 var env = Cc['@mozilla.org/process/environment;1'].getService(Ci.nsIEnvironment);
246 function getenv (variable) {
247 if (env.exists (variable))
248 return env.get(variable);
252 function get_home_directory () {
253 var dir = Cc["@mozilla.org/file/local;1"]
254 .createInstance(Ci.nsILocalFile);
255 if (get_os() == "WINNT")
256 dir.initWithPath(getenv('USERPROFILE') ||
257 getenv('HOMEDRIVE') + getenv('HOMEPATH'));
259 dir.initWithPath(getenv('HOME'));
263 function set_default_directory (directory) {
265 if (directory instanceof Ci.nsILocalFile)
266 default_directory = directory.clone();
268 default_directory = Cc["@mozilla.org/file/local;1"]
269 .createInstance(Ci.nsILocalFile);
270 default_directory.initWithPath(directory);
273 default_directory = get_home_directory();
277 set_default_directory();
279 const XHTML_NS = "http://www.w3.org/1999/xhtml";
280 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
281 const MATHML_NS = "http://www.w3.org/1998/Math/MathML";
282 const XLINK_NS = "http://www.w3.org/1999/xlink";
284 function create_XUL(window, tag_name)
286 return window.document.createElementNS(XUL_NS, tag_name);
290 /* Used in calls to XPath evaluate */
291 function xpath_lookup_namespace(prefix) {
292 if (prefix == "xhtml")
301 function method_caller(obj, func) {
303 func.apply(obj, arguments);
307 function shell_quote(str) {
308 var s = str.replace("\"", "\\\"", "g");
309 s = s.replace("$", "\$", "g");
313 /* Like perl's quotemeta. Backslash all non-alphanumerics. */
314 function quotemeta(str) {
315 return str.replace(/([^a-zA-Z0-9])/g, "\\$1");
318 /* Given a list of choices (strings), return a regex which matches any
320 function choice_regex(choices) {
321 var regex = "(?:" + choices.map(quotemeta).join("|") + ")";
325 function get_window_from_frame(frame) {
327 var window = frame.QueryInterface(Ci.nsIInterfaceRequestor)
328 .getInterface(Ci.nsIWebNavigation)
329 .QueryInterface(Ci.nsIDocShellTreeItem)
331 .QueryInterface(Ci.nsIInterfaceRequestor)
332 .getInterface(Ci.nsIDOMWindow).wrappedJSObject;
333 /* window is now an XPCSafeJSObjectWrapper */
334 window.escape_wrapper(function (w) { window = w; });
335 /* window is now completely unwrapped */
342 function get_buffer_from_frame(window, frame) {
343 var count = window.buffers.count;
344 for (var i = 0; i < count; ++i) {
345 var b = window.buffers.get_buffer(i);
346 if (b.top_frame == frame)
352 var file_locator = Cc["@mozilla.org/file/directory_service;1"]
353 .getService(Ci.nsIProperties);
355 function get_shortdoc_string(doc) {
358 var idx = doc.indexOf("\n");
360 shortdoc = doc.substring(0,idx);
367 var conkeror_source_code_path = null;
369 function source_code_reference(uri, line_number) {
371 this.line_number = line_number;
373 source_code_reference.prototype = {
375 if (this.uri.indexOf(module_uri_prefix) == 0)
376 return this.uri.substring(module_uri_prefix.length);
381 var file_uri_prefix = "file://";
382 if (this.uri.indexOf(file_uri_prefix) == 0)
383 return this.uri.substring(file_uri_prefix.length);
388 if (conkeror_source_code_path != null) {
389 var module_name = this.module_name;
390 if (module_name != null)
391 return "file://" + conkeror_source_code_path + "/modules/" + module_name;
396 open_in_editor : function() {
397 yield open_with_external_editor(this.best_uri, $line = this.line_number);
401 var get_caller_source_code_reference_ignored_functions = {};
403 function get_caller_source_code_reference(extra_frames_back) {
404 /* Skip at least this function itself and whoever called it (and
405 * more if the caller wants to be skipped). */
406 var frames_to_skip = 2;
407 if (extra_frames_back != null)
408 frames_to_skip += extra_frames_back;
410 for (let f = Components.stack; f != null; f = f.caller) {
411 if (frames_to_skip > 0) {
415 if (get_caller_source_code_reference_ignored_functions[f.name])
417 return new source_code_reference(f.filename, f.lineNumber);
423 function ignore_function_for_get_caller_source_code_reference(func_name) {
424 get_caller_source_code_reference_ignored_functions[func_name] = 1;
427 require_later("external-editor.js");
429 function dom_generator(document, ns) {
430 this.document = document;
433 dom_generator.prototype = {
434 element : function(tag, parent) {
435 var node = this.document.createElementNS(this.ns, tag);
437 if (parent != null && (parent instanceof Ci.nsIDOMNode)) {
438 parent.appendChild(node);
441 for (; i < arguments.length; i += 2)
442 node.setAttribute(arguments[i], arguments[i+1]);
446 text : function(str, parent) {
447 var node = this.document.createTextNode(str);
449 parent.appendChild(node);
454 stylesheet_link : function(href, parent) {
455 var node = this.element("link");
456 node.setAttribute("rel", "stylesheet");
457 node.setAttribute("type", "text/css");
458 node.setAttribute("href", href);
460 parent.appendChild(node);
465 add_stylesheet : function (url) {
466 var head = this.document.documentElement.firstChild;
467 this.stylesheet_link(url, head);
472 * Generates a QueryInterface function suitable for an implemenation
473 * of an XPCOM interface. Unlike XPCOMUtils, this uses the Function
474 * constructor to generate a slightly more efficient version. The
475 * arguments can be either Strings or elements of
476 * Components.interfaces.
478 function generate_QI() {
479 var args = Array.prototype.slice.call(arguments).map(String).concat(["nsISupports"]);
481 Array.prototype.map.call(args,
483 "iid.equals(Components.interfaces." + x + ")")
485 ") return this; throw Components.results.NS_ERROR_NO_INTERFACE;";
486 return new Function("iid", fstr);
489 function set_branch_pref(branch, name, value) {
490 if (typeof(value) == "string") {
491 branch.setCharPref(name, value);
492 } else if (typeof(value) == "number") {
493 branch.setIntPref(name, value);
494 } else if (typeof(value) == "boolean") {
495 branch.setBoolPref(name, value);
499 function default_pref(name, value) {
500 var branch = preferences.getDefaultBranch(null);
501 set_branch_pref(branch, name, value);
504 function user_pref(name, value) {
505 var branch = preferences.getBranch(null);
506 set_branch_pref(branch, name, value);
509 function get_branch_pref(branch, name) {
510 switch (branch.getPrefType(name)) {
511 case branch.PREF_STRING:
512 return branch.getCharPref(name);
513 case branch.PREF_INT:
514 return branch.getIntPref(name);
515 case branch.PREF_BOOL:
516 return branch.getBoolPref(name);
522 function get_localized_pref(name) {
524 return preferences.getBranch(null).getComplexValue(name, Ci.nsIPrefLocalizedString).data;
530 function get_pref(name) {
531 var branch = preferences.getBranch(null);
532 return get_branch_pref(branch, name);
535 function get_default_pref(name) {
536 var branch = preferences.getDefaultBranch(null);
537 return get_branch_pref(branch, name);
540 function clear_pref(name) {
541 var branch = preferences.getBranch(null);
542 return branch.clearUserPref(name);
545 function pref_has_user_value(name) {
546 var branch = preferences.getBranch(null);
547 return branch.prefHasUserValue(name);
550 function pref_has_default_value(name) {
551 var branch = preferences.getDefaultBranch(null);
552 return branch.prefHasUserValue(name);
555 function session_pref (name, value) {
556 try { clear_pref (name); }
558 return default_pref (name, value);
561 function watch_pref(pref, hook) {
562 /* Extract pref into branch.pref */
563 let match = pref.match(/^(.*[.])?([^.]*)$/);
566 let branch = preferences.getBranch(br).QueryInterface(Ci.nsIPrefBranch2);
568 observe: function (subject, topic, data) {
569 if (topic == "nsPref:changed" && data == key) {
575 branch.addObserver("", observer, false);
578 const LOCALE_PREF = "general.useragent.locale";
580 function get_locale() {
581 return get_localized_pref(LOCALE_PREF) || get_pref(LOCALE_PREF);
584 const USER_AGENT_OVERRIDE_PREF = "general.useragent.override";
586 function set_user_agent(str) {
587 session_pref(USER_AGENT_OVERRIDE_PREF, str);
590 function define_builtin_commands(prefix, do_command_function, toggle_mark, mark_active_predicate, mode) {
592 // Specify a docstring
593 function D(cmd, docstring) {
594 var o = new String(cmd);
599 // Specify a forward/reverse pair
602 o.is_reverse_pair = true;
606 // Specify a movement/select/scroll/move-caret command group.
607 function S(command, movement, select, scroll, caret) {
608 var o = [movement, select, scroll, caret];
610 o.is_move_select_pair = true;
614 var builtin_commands = [
617 * cmd_scrollBeginLine and cmd_scrollEndLine don't do what I
618 * want, either in or out of caret mode...
620 S(D("beginning-of-line", "Move or extend the selection to the beginning of the current line."),
621 D("cmd_beginLine", "Move point to the beginning of the current line."),
622 D("cmd_selectBeginLine", "Extend selection to the beginning of the current line."),
623 D("cmd_beginLine", "Scroll to the beginning of the line"),
624 D("cmd_beginLine", "Scroll to the beginning of the line")),
625 S(D("end-of-line", "Move or extend the selection to the end of the current line."),
626 D("cmd_endLine", "Move point to the end of the current line."),
627 D("cmd_selectEndLine", "Extend selection to the end of the current line."),
628 D("cmd_endLine", "Scroll to the end of the current line."),
629 D("cmd_endLine", "Scroll to the end of the current line.")),
630 S(D("beginning-of-first-line", "Move or extend the selection to the beginning of the first line."),
631 D("cmd_moveTop", "Move point to the beginning of the first line."),
632 D("cmd_selectTop", "Extend selection to the beginning of the first line."),
633 D("cmd_scrollTop", "Scroll to the top of the buffer"),
634 D("cmd_scrollTop", "Move point to the beginning of the first line.")),
635 S(D("end-of-last-line", "Move or extend the selection to the end of the last line."),
636 D("cmd_moveBottom", "Move point to the end of the last line."),
637 D("cmd_selectBottom", "Extend selection to the end of the last line."),
638 D("cmd_scrollBottom", "Scroll to the bottom of the buffer"),
639 D("cmd_scrollBottom", "Move point to the end of the last line.")),
641 "cmd_scrollBeginLine",
644 D("cmd_copy", "Copy the selection into the clipboard."),
645 D("cmd_cut", "Cut the selection into the clipboard."),
646 D("cmd_deleteToBeginningOfLine", "Delete to the beginning of the current line."),
647 D("cmd_deleteToEndOfLine", "Delete to the end of the current line."),
648 D("cmd_selectAll", "Select all."),
649 D("cmd_scrollTop", "Scroll to the top of the buffer."),
650 D("cmd_scrollBottom", "Scroll to the bottom of the buffer.")];
652 var builtin_commands_with_count = [
653 R(S(D("forward-char", "Move or extend the selection forward one character."),
654 D("cmd_charNext", "Move point forward one character."),
655 D("cmd_selectCharNext", "Extend selection forward one character."),
656 D("cmd_scrollRight", "Scroll to the right"),
657 D("cmd_scrollRight", "Scroll to the right")),
658 S(D("backward-char", "Move or extend the selection backward one character."),
659 D("cmd_charPrevious", "Move point backward one character."),
660 D("cmd_selectCharPrevious", "Extend selection backward one character."),
661 D("cmd_scrollLeft", "Scroll to the left."),
662 D("cmd_scrollLeft", "Scroll to the left."))),
663 R(D("cmd_deleteCharForward", "Delete the following character."),
664 D("cmd_deleteCharBackward", "Delete the previous character.")),
665 R(D("cmd_deleteWordForward", "Delete the following word."),
666 D("cmd_deleteWordBackward", "Delete the previous word.")),
667 R(S(D("forward-line", "Move or extend the selection forward one line."),
668 D("cmd_lineNext", "Move point forward one line."),
669 D("cmd_selectLineNext", "Extend selection forward one line."),
670 D("cmd_scrollLineDown", "Scroll down one line."),
671 D("cmd_scrollLineDown", "Scroll down one line.")),
672 S(D("backward-line", "Move or extend the selection backward one line."),
673 D("cmd_linePrevious", "Move point backward one line."),
674 D("cmd_selectLinePrevious", "Extend selection backward one line."),
675 D("cmd_scrollLineUp", "Scroll up one line."),
676 D("cmd_scrollLineUp", "Scroll up one line."))),
677 R(S(D("forward-page", "Move or extend the selection forward one page."),
678 D("cmd_movePageDown", "Move point forward one page."),
679 D("cmd_selectPageDown", "Extend selection forward one page."),
680 D("cmd_scrollPageDown", "Scroll forward one page."),
681 D("cmd_movePageDown", "Move point forward one page.")),
682 S(D("backward-page", "Move or extend the selection backward one page."),
683 D("cmd_movePageUp", "Move point backward one page."),
684 D("cmd_selectPageUp", "Extend selection backward one page."),
685 D("cmd_scrollPageUp", "Scroll backward one page."),
686 D("cmd_movePageUp", "Move point backward one page."))),
687 R(D("cmd_undo", "Undo last editing action."),
688 D("cmd_redo", "Redo last editing action.")),
689 R(S(D("forward-word", "Move or extend the selection forward one word."),
690 D("cmd_wordNext", "Move point forward one word."),
691 D("cmd_selectWordNext", "Extend selection forward one word."),
692 D("cmd_scrollRight", "Scroll to the right."),
693 D("cmd_wordNext", "Move point forward one word.")),
694 S(D("backward-word", "Move or extend the selection backward one word."),
695 D("cmd_wordPrevious", "Move point backward one word."),
696 D("cmd_selectWordPrevious", "Extend selection backward one word."),
697 D("cmd_scrollLeft", "Scroll to the left."),
698 D("cmd_wordPrevious", "Move point backward one word."))),
699 R(D("cmd_scrollPageUp", "Scroll up one page."),
700 D("cmd_scrollPageDown", "Scroll down one page.")),
701 R(D("cmd_scrollLineUp", "Scroll up one line."),
702 D("cmd_scrollLineDown", "Scroll down one line.")),
703 R(D("cmd_scrollLeft", "Scroll left."),
704 D("cmd_scrollRight", "Scroll right.")),
705 D("cmd_paste", "Insert the contents of the clipboard.")];
707 interactive(prefix + "set-mark",
708 "Toggle whether the mark is active.\n" +
709 "When the mark is active, movement commands affect the selection.",
712 function get_mode_idx() {
713 if (mode == 'scroll') return 2;
714 else if (mode == 'caret') return 3;
718 function get_move_select_idx(I) {
719 return mark_active_predicate(I) ? 1 : get_mode_idx();
722 function doc_for_builtin(c) {
726 return s + "Run the built-in command " + c + ".";
729 function define_simple_command(c) {
730 interactive(prefix + c, doc_for_builtin(c), function (I) { do_command_function(I, c); });
733 function get_move_select_doc_string(c) {
734 return c.command.doc +
735 "\nSpecifically, if the mark is active, runs `" + prefix + c[1] + "'. " +
736 "Otherwise, runs `" + prefix + c[get_mode_idx()] + "'\n" +
737 "To toggle whether the mark is active, use `" + prefix + "set-mark'.";
740 for each (let c_temp in builtin_commands) {
742 if (c.is_move_select_pair) {
743 interactive(prefix + c.command, get_move_select_doc_string(c), function (I) {
744 var idx = get_move_select_idx(I);
745 do_command_function(I, c[idx]);
747 define_simple_command(c[0]);
748 define_simple_command(c[1]);
751 define_simple_command(c);
754 function get_reverse_pair_doc_string(main_doc, alt_command) {
755 return main_doc + "\n" +
756 "The prefix argument specifies a repeat count for this command. " +
757 "If the count is negative, `" + prefix + alt_command + "' is performed instead with " +
758 "a corresponding positive repeat count.";
761 function define_simple_reverse_pair(a, b) {
762 interactive(prefix + a, get_reverse_pair_doc_string(doc_for_builtin(a), b),
764 do_repeatedly(do_command_function, I.p, [I, a], [I, b]);
766 interactive(prefix + b, get_reverse_pair_doc_string(doc_for_builtin(b), a),
768 do_repeatedly(do_command_function, I.p, [I, b], [I, a]);
772 for each (let c_temp in builtin_commands_with_count)
775 if (c.is_reverse_pair) {
776 if (c[0].is_move_select_pair) {
777 interactive(prefix + c[0].command, get_reverse_pair_doc_string(get_move_select_doc_string(c[0]),
780 var idx = get_move_select_idx(I);
781 do_repeatedly(do_command_function, I.p, [I, c[0][idx]], [I, c[1][idx]]);
783 interactive(prefix + c[1].command, get_reverse_pair_doc_string(get_move_select_doc_string(c[1]),
786 var idx = get_move_select_idx(I);
787 do_repeatedly(do_command_function, I.p, [I, c[1][idx]], [I, c[0][idx]]);
789 define_simple_reverse_pair(c[0][0], c[1][0]);
790 define_simple_reverse_pair(c[0][1], c[1][1]);
792 define_simple_reverse_pair(c[0], c[1]);
794 let doc = doc_for_builtin(c) +
795 "\nThe prefix argument specifies a positive repeat count for this command.";
796 interactive(prefix + c, doc, function (I) {
797 do_repeatedly_positive(do_command_function, I.p, I, c);
804 var observer_service = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
806 function abort(str) {
807 var e = new Error(str);
808 e.__proto__ = abort.prototype;
811 abort.prototype.__proto__ = Error.prototype;
814 function get_temporary_file(name) {
817 var file = file_locator.get("TmpD", Ci.nsIFile);
819 // Create the file now to ensure that no exploits are possible
820 file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
825 /* FIXME: This should be moved somewhere else, perhaps. */
826 function create_info_panel(window, panel_class, row_arr) {
827 /* Show information panel above minibuffer */
829 var g = new dom_generator(window.document, XUL_NS);
831 var p = g.element("vbox", "class", "panel " + panel_class, "flex", "0");
832 var grid = g.element("grid", p);
833 var cols = g.element("columns", grid);
834 g.element("column", cols, "flex", "0");
835 g.element("column", cols, "flex", "1");
837 var rows = g.element("rows", grid);
840 for each (let [row_class, row_label, row_value] in row_arr) {
841 row = g.element("row", rows, "class", row_class);
842 g.element("label", row,
844 "class", "panel-row-label");
845 g.element("label", row,
847 "class", "panel-row-value",
850 window.minibuffer.insert_before(p);
852 p.destroy = function () {
853 this.parentNode.removeChild(this);
861 * Paste from the X primary selection, unless the system doesn't support a
862 * primary selection, in which case fall back to the clipboard.
864 function read_from_x_primary_selection ()
867 let clipboard = Components.classes["@mozilla.org/widget/clipboard;1"]
868 .getService(Components.interfaces.nsIClipboard);
870 // Fall back to global clipboard if the system doesn't support a selection
871 let which_clipboard = clipboard.supportsSelectionClipboard() ?
872 clipboard.kSelectionClipboard : clipboard.kGlobalClipboard;
874 let flavors = ["text/unicode"];
876 // Don't barf if there's nothing on the clipboard
877 if (!clipboard.hasDataMatchingFlavors(flavors, flavors.length, which_clipboard))
880 // Create transferable that will transfer the text.
881 let trans = Components.classes["@mozilla.org/widget/transferable;1"]
882 .createInstance(Components.interfaces.nsITransferable);
884 for each (let flavor in flavors) {
885 trans.addDataFlavor(flavor);
887 clipboard.getData(trans, which_clipboard);
889 var data_flavor = {};
892 trans.getAnyTransferData(data_flavor, data, dataLen);
895 data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
896 let data_length = dataLen.value;
897 if (data_flavor.value == "text/unicode")
898 data_length = dataLen.value / 2;
899 return data.data.substring(0, data_length);
905 var user_variables = {};
907 function define_variable(name, default_value, doc) {
908 conkeror[name] = default_value;
909 user_variables[name] = {
910 default_value: default_value,
912 shortdoc: get_shortdoc_string(doc),
913 source_code_reference: get_caller_source_code_reference()
917 function define_special_variable(name, getter, setter, doc) {
918 conkeror.__defineGetter__(name, getter);
919 conkeror.__defineSetter__(name, setter);
920 user_variables[name] = {
921 default_value: undefined,
923 shortdoc: get_shortdoc_string(doc),
924 source_code_reference: get_caller_source_code_reference()
928 /* Re-define load_paths as a user variable. */
929 define_variable("load_paths", load_paths,
930 "Array of URL prefixes searched in order when loading a module.\n" +
931 "Each entry must end in a slash, and should begin with file:// or chrome://.");
936 function register_user_stylesheet (url) {
937 var uri = make_uri(url);
938 var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
939 .getService(Ci.nsIStyleSheetService);
940 sss.loadAndRegisterSheet(uri, sss.USER_SHEET);
943 function unregister_user_stylesheet (url) {
944 var uri = make_uri(url);
945 var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
946 .getService(Ci.nsIStyleSheetService);
947 if (sss.sheetRegistered(uri, sss.USER_SHEET))
948 sss.unregisterSheet(uri, sss.USER_SHEET);
951 function register_agent_stylesheet (url) {
952 var uri = make_uri(url);
953 var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
954 .getService(Ci.nsIStyleSheetService);
955 sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET);
958 function unregister_agent_stylesheet (url) {
959 var uri = make_uri(url);
960 var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
961 .getService(Ci.nsIStyleSheetService);
962 if (sss.sheetRegistered(uri, sss.AGENT_SHEET))
963 sss.unregisterSheet(uri, sss.AGENT_SHEET);
966 function agent_stylesheet_registered_p (url) {
967 var uri = make_uri(url);
968 var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
969 .getService(Ci.nsIStyleSheetService);
970 return sss.sheetRegistered(uri, sss.AGENT_SHEET);
973 function user_stylesheet_registered_p (url) {
974 var uri = make_uri(url);
975 var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
976 .getService(Ci.nsIStyleSheetService);
977 return sss.sheetRegistered(uri, sss.USER_SHEET);
980 function predicate_alist_match(alist, key) {
981 for each (let i in alist) {
989 function get_meta_title(doc) {
990 var title = doc.evaluate("//meta[@name='title']/@content", doc, xpath_lookup_namespace,
991 Ci.nsIDOMXPathResult.STRING_TYPE , null);
992 if (title && title.stringValue)
993 return title.stringValue;
997 var rdf_service = Cc["@mozilla.org/rdf/rdf-service;1"]
998 .getService(Ci.nsIRDFService);
1000 const PREFIX_ITEM_URI = "urn:mozilla:item:";
1001 const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#";
1003 var extension_manager = Cc["@mozilla.org/extensions/manager;1"]
1004 .getService(Ci.nsIExtensionManager);
1006 function get_extension_rdf_property(id, name, type) {
1007 var value = extension_manager.datasource.GetTarget(
1008 rdf_service.GetResource(PREFIX_ITEM_URI + id),
1009 rdf_service.GetResource(PREFIX_NS_EM + name),
1013 return value.QueryInterface(type || Ci.nsIRDFLiteral).Value;
1016 function get_extension_update_item(id) {
1017 return extension_manager.getItemForID(id);
1020 function extension_info(id) {
1023 extension_info.prototype = {
1024 // Returns the nsIUpdateItem object associated with this extension
1025 get update_item () { return get_extension_update_item(this.id); },
1027 get_rdf_property : function (name, type) {
1028 return get_extension_rdf_property(this.id, name, type);
1032 get isDisabled () { return this.get_rdf_property("isDisabled"); },
1033 get aboutURL () { return this.get_rdf_property("aboutURL"); },
1034 get addonID () { return this.get_rdf_property("addonID"); },
1035 get availableUpdateURL () { return this.get_rdf_property("availableUpdateURL"); },
1036 get availableUpdateVersion () { return this.get_rdf_property("availableUpdateVersion"); },
1037 get blocklisted () { return this.get_rdf_property("blocklisted"); },
1038 get compatible () { return this.get_rdf_property("compatible"); },
1039 get description () { return this.get_rdf_property("description"); },
1040 get downloadURL () { return this.get_rdf_property("downloadURL"); },
1041 get isDisabled () { return this.get_rdf_property("isDisabled"); },
1042 get hidden () { return this.get_rdf_property("hidden"); },
1043 get homepageURL () { return this.get_rdf_property("homepageURL"); },
1044 get iconURL () { return this.get_rdf_property("iconURL"); },
1045 get internalName () { return this.get_rdf_property("internalName"); },
1046 get locked () { return this.get_rdf_property("locked"); },
1047 get name () { return this.get_rdf_property("name"); },
1048 get optionsURL () { return this.get_rdf_property("optionsURL"); },
1049 get opType () { return this.get_rdf_property("opType"); },
1050 get plugin () { return this.get_rdf_property("plugin"); },
1051 get previewImage () { return this.get_rdf_property("previewImage"); },
1052 get satisfiesDependencies () { return this.get_rdf_property("satisfiesDependencies"); },
1053 get providesUpdatesSecurely () { return this.get_rdf_property("providesUpdatesSecurely"); },
1054 get type () { return this.get_rdf_property("type", Ci.nsIRDFInt); },
1055 get updateable () { return this.get_rdf_property("updateable"); },
1056 get updateURL () { return this.get_rdf_property("updateURL"); },
1057 get version () { return this.get_rdf_property("version"); }
1060 function extension_is_enabled(id) {
1061 var info = new extension_info(id);
1062 return info.update_item && (info.isDisabled == "false");
1071 return this.input.length + this.output.length;
1073 push: function (x) {
1074 this.input[this.input.length] = x;
1077 let l = this.output.length;
1079 l = this.input.length;
1082 this.output = this.input.reverse();
1084 let x = this.output[l];
1085 this.output.length--;
1091 function frame_iterator(root_frame, start_with) {
1092 var q = new queue, x;
1097 for (let i = 0; i < x.frames.length; ++i)
1098 q.push(x.frames[i]);
1099 } while ((x = q.pop()));
1103 if (x == start_with)
1106 for (let i = 0; i < x.frames.length; ++i)
1107 q.push(x.frames[i]);
1108 } while ((x = q.pop()));
1111 function xml_http_request() {
1112 return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
1113 .createInstance(Ci.nsIXMLHttpRequest)
1114 .QueryInterface(Ci.nsIJSXMLHttpRequest)
1115 .QueryInterface(Ci.nsIDOMEventTarget);
1118 var xml_http_request_load_listener = {
1119 // nsIBadCertListener2
1120 notifyCertProblem: function SSLL_certProblem(socketInfo, status, targetSite) {
1124 // nsISSLErrorListener
1125 notifySSLError: function SSLL_SSLError(socketInfo, error, targetSite) {
1129 // nsIInterfaceRequestor
1130 getInterface: function SSLL_getInterface(iid) {
1131 return this.QueryInterface(iid);
1136 // FIXME: array comprehension used here to hack around the lack of
1137 // Ci.nsISSLErrorListener in 2007 versions of xulrunner 1.9pre.
1138 // make it a simple generateQI when xulrunner is more stable.
1139 QueryInterface: XPCOMUtils.generateQI (
1140 [i for each (i in [Ci.nsIBadCertListener2,
1141 Ci.nsISSLErrorListener,
1142 Ci.nsIInterfaceRequestor])
1148 * Coroutine interface for sending an HTTP request and waiting for the
1149 * response. (This includes so-called "AJAX" requests.)
1151 * @param lspec (required) a load_spec object or URI string (see load-spec.js)
1153 * The request URI is obtained from this argument. In addition, if the
1154 * load spec specifies post data, a POST request is made instead of a
1155 * GET request, and the post data included in the load spec is
1156 * sent. Specifically, the request_mime_type and raw_post_data
1157 * properties of the load spec are used.
1159 * @param $user (optional) HTTP user name to include in the request headers
1160 * @param $password (optional) HTTP password to include in the request headers
1162 * @param $override_mime_type (optional) Force the response to be interpreted
1163 * as having the specified MIME type. This is only
1164 * really useful for forcing the MIME type to be
1165 * text/xml or something similar, such that it is
1166 * automatically parsed into a DOM document.
1167 * @param $headers (optional) an array of [name,value] pairs (each specified as
1168 * a two-element array) specifying additional headers to add to
1171 * @returns After the request completes (either successfully or with an error),
1172 * the nsIXMLHttpRequest object is returned. Its responseText (for any
1173 * arbitrary document) or responseXML (if the response type is an XML
1174 * content type) properties can be accessed to examine the response
1177 * If an exception is thrown to the continutation (which can be obtained by the
1178 * caller by calling yield CONTINUATION prior to calling this function) while the
1179 * request is in progress (i.e. before this coroutine returns), the request will
1180 * be aborted, and the exception will be propagated to the caller.
1183 define_keywords("$user", "$password", "$override_mime_type", "$headers");
1184 function send_http_request(lspec) {
1185 // why do we get warnings in jsconsole unless we initialize the
1186 // following keywords?
1187 keywords(arguments, $user = undefined, $password = undefined,
1188 $override_mime_type = undefined, $headers = undefined);
1189 if (! (lspec instanceof load_spec))
1190 lspec = load_spec(lspec);
1191 var req = xml_http_request();
1192 var cc = yield CONTINUATION;
1193 var aborting = false;
1194 req.onreadystatechange = function send_http_request__onreadysatechange() {
1195 if (req.readyState != 4)
1202 if (arguments.$override_mime_type)
1203 req.overrideMimeType(arguments.$override_mime_type);
1205 var post_data = load_spec_raw_post_data(lspec);
1207 var method = post_data ? "POST" : "GET";
1209 req.open(method, load_spec_uri_string(lspec), true, arguments.$user, arguments.$password);
1210 req.channel.notificationCallbacks = xml_http_request_load_listener;
1212 for each (let [name,value] in arguments.$headers) {
1213 req.setRequestHeader(name, value);
1217 req.setRequestHeader("Content-Type", load_spec_request_mime_type(lspec));
1218 req.send(post_data);
1230 // Let the caller access the status and reponse data
1231 yield co_return(req);
1235 var JSON = ("@mozilla.org/dom/json;1" in Cc) &&
1236 Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
1240 var console_service = Cc["@mozilla.org/consoleservice;1"]
1241 .getService(Ci.nsIConsoleService);
1243 console_service.registerListener(
1244 {observe: function (msg) {
1245 if (msg instanceof Ci.nsIScriptError) {
1246 switch (msg.category) {
1248 case "content javascript":
1251 msg.QueryInterface(Ci.nsIScriptError);
1252 dumpln("Console error: " + msg.message);
1253 dumpln(" Category: " + msg.category);
1258 // ensure_index_is_visible ensures that the given index in the given
1259 // field (an html input field for example) is visible.
1260 function ensure_index_is_visible (window, field, index) {
1261 var start = field.selectionStart;
1262 var end = field.selectionEnd;
1263 field.setSelectionRange (index, index);
1264 send_key_as_event (window, field, "left");
1265 if (field.selectionStart < index) {
1266 send_key_as_event (window, field, "right");
1268 field.setSelectionRange (start, end);
1271 function regex_to_string(obj) {
1272 if(obj instanceof RegExp) {
1275 obj = quotemeta(obj);
1281 * Build a regular expression to match URLs for a given web site.
1283 * Both the $domain and $path arguments can be either regexes, in
1284 * which case they will be matched as is, or strings, in which case
1285 * they will be matched literally.
1287 * $tlds specifies a list of valid top-level-domains to match, and
1288 * defaults to .com. Useful for when e.g. foo.org and foo.com are the
1291 * If $allow_www is true, www.domain.tld will also be allowed.
1294 define_keywords("$domain", "$path", "$tlds", "$allow_www");
1295 function build_url_regex() {
1296 keywords(arguments, $path = "", $tlds = ["com"], $allow_www = false);
1297 var domain = regex_to_string(arguments.$domain);
1298 if(arguments.$allow_www) {
1299 domain = "(?:www\.)?" + domain;
1301 var path = regex_to_string(arguments.$path);
1302 var tlds = arguments.$tlds;
1303 var regex = "^https?://" + domain + "\\." + choice_regex(tlds) + "/" + path;
1304 return new RegExp(regex);
1309 * Given an ordered array of non-overlapping ranges, represented as
1310 * elements of [start, end], insert a new range into the array,
1311 * extending, replacing, or merging existing ranges as needed. Mutates
1316 * splice_range([[1,3],[4,6], 5, 8)
1319 * splice_range([[1,3],[4,6],[7,10]], 2, 8)
1322 function splice_range(arr, start, end) {
1323 for(var i = 0; i < arr.length; ++i) {
1328 arr.splice(i, 0, [start, end]);
1337 * The range we are inserting overlaps the current
1338 * range. We need to scan right to see if it also contains any other
1339 * ranges entirely, and remove them if necessary.
1342 while(j < arr.length && end >= arr[j][0]) j++;
1344 arr[i][1] = Math.max(end, arr[j][1]);
1345 arr.splice(i + 1, j - i);
1349 if(start > arr[arr.length - 1][1]) {
1350 arr.push([start, end]);
1355 function compute_url_up_path (url)
1357 var new_url = Cc["@mozilla.org/network/standard-url;1"]
1358 .createInstance (Ci.nsIURL);
1361 if (new_url.param != "" || new_url.query != "")
1362 up = new_url.filePath;
1363 else if (new_url.fileName != "")
1371 function url_path_trim (url) {
1372 var uri = make_uri(url);
1378 /* possibly_valid_url returns true if the string might be a valid
1379 * thing to pass to nsIWebNavigation.loadURI. Currently just checks
1380 * that there's no whitespace in the middle and that it's not entirely
1383 function possibly_valid_url (url) {
1384 return !(/\S\s+\S/.test(url)) && !(/^\s*$/.test(url));
1388 /* remove_duplicates_filter returns a function that can be
1389 * used in Array.filter. It removes duplicates.
1391 function remove_duplicates_filter () {
1393 return function (x) {
1394 if (acc[x]) return false;
1401 /* get_current_profile returns the name of the current profile, or null
1402 * if that information cannot be found. The result is cached in the
1403 * variable profile_name, for quick repeat lookup. This is safe because
1404 * xulrunner does not support switching profiles on the fly.
1406 * Profiles don't necessarily have a name--as such this information should
1407 * not be depended on for anything important. It is mainly intended for
1408 * decoration of the window title and mode-line.
1411 function get_current_profile () {
1413 return profile_name;
1414 if ("@mozilla.org/profile/manager;1" in Cc) {
1415 profile_name = Cc["@mozilla.org/profile/manager;1"]
1416 .getService(Ci.nsIProfile)
1418 return profile_name;
1420 var current_profile_path = Cc["@mozilla.org/file/directory_service;1"]
1421 .getService(Ci.nsIProperties)
1422 .get("ProfD", Ci.nsIFile).path;
1423 var profile_service = Cc["@mozilla.org/toolkit/profile-service;1"]
1424 .getService(Components.interfaces.nsIToolkitProfileService);
1425 var profiles = profile_service.profiles;
1426 while (profiles.hasMoreElements()) {
1427 var p = profiles.getNext().QueryInterface(Ci.nsIToolkitProfile);
1428 if (current_profile_path == p.localDir.path ||
1429 current_profile_path == p.rootDir.path)
1431 profile_name = p.name;
1440 * Given an array, switches places on the subarrays at index i1 to i2 and j1 to
1441 * j2. Leaves the rest of the array unchanged.
1443 function switch_subarrays(arr, i1, i2, j1, j2) {
1444 return arr.slice(0, i1) +
1448 arr.slice(j2, arr.length);
1452 * Makes an AJAX request to the given URI and calls the callback function upon
1453 * retrieval. In the callback function, the XMLHttpRequest can be accessed
1454 * through, assuming that the first parameter is called "evt", evt.target.
1456 * @param uri The URI to make the request to.
1457 * @param callback The callback function.
1458 * @param s (Optional) Settings object.
1460 function ajax_request(uri, callback, s) {
1462 // Set up the default settings.
1468 // Let the user's provided settings override our defaults.
1471 method : s.method ? s.method : 'GET',
1472 data : s.data ? s.data : null
1475 var httpRequest = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
1476 .createInstance(Ci.nsIXMLHttpRequest);
1477 httpRequest.onreadystatechange = callback;
1479 // If we're POSTing something, we should make sure the headers are set
1481 if (sets.method == 'POST')
1482 httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
1484 httpRequest.open(sets.method, uri, true);
1485 httpRequest.send(sets.data);
1490 * Convenience function for making simple XPath lookups in a document.
1492 * @param doc The document to look in.
1493 * @param exp The XPath expression to search for.
1494 * @return The XPathResult object representing the set of found nodes.
1496 function xpath_lookup(doc, exp) {
1497 return doc.evaluate(exp, doc, null, Ci.nsIDOMXPathResult.ANY_TYPE, null);
1501 /* get_contents_synchronously returns the contents of the given
1502 * url (string or nsIURI) as a string on success, or null on failure.
1504 function get_contents_synchronously (url) {
1505 var ioService=Cc["@mozilla.org/network/io-service;1"]
1506 .getService(Ci.nsIIOService);
1507 var scriptableStream=Cc["@mozilla.org/scriptableinputstream;1"]
1508 .getService(Ci.nsIScriptableInputStream);
1512 if (url instanceof Ci.nsIURI)
1513 channel = ioService.newChannelFromURI(url);
1515 channel = ioService.newChannel(url, null, null);
1516 input=channel.open();
1520 scriptableStream.init(input);
1521 var str=scriptableStream.read(input.available());
1522 scriptableStream.close();
1529 * string_format takes a format-string containing %X style format codes,
1530 * and an object mapping the code-letters to replacement text. It
1531 * returns a string with the formatting codes replaced by the replacement
1534 function string_format (spec, substitutions) {
1535 return spec.replace(/%(.)/g, function (a,b) { return substitutions[b]; });
1540 * dom_add_class adds a css class to the given dom node.
1542 function dom_add_class (node, cssclass) {
1544 node.className += " "+cssclass;
1546 node.className = cssclass;
1550 * dom_remove_class removes the given css class from the given dom node.
1552 function dom_remove_class (node, cssclass) {
1553 if (! node.className)
1555 var classes = node.className.split(" ");
1556 classes = classes.filter(function (x) { return x != cssclass; });
1557 node.className = classes.join(" ");
1562 * dom_node_flash adds the given cssclass to the node for a brief interval.
1563 * this class can be styled, to create a flashing effect.
1565 function dom_node_flash (node, cssclass) {
1566 dom_add_class(node, cssclass);
1569 dom_remove_class(node, cssclass);
1576 * data is an an alist (array of 2 element arrays) where each pair is a key
1579 * The return type is a mime input stream that can be passed as postData to
1580 * nsIWebNavigation.loadURI. In terms of Conkeror's API, the return value
1581 * of this function is of the correct type for the `post_data' field of a
1584 function make_post_data (data) {
1585 data = [(encodeURIComponent(pair[0])+'='+encodeURIComponent(pair[1]))
1586 for each (pair in data)].join('&');
1587 data = string_input_stream(data);
1588 return mime_input_stream(
1589 data, [["Content-Type", "application/x-www-form-urlencoded"]]);
1594 * Centers the viewport around a given element.
1596 * @param win The window to scroll.
1597 * @param elem The element arund which we put the viewport.
1599 function center_in_viewport(win, elem) {
1600 let point = abs_point(elem);
1602 point.x -= win.innerWidth / 2;
1603 point.y -= win.innerHeight / 2;
1605 win.scrollTo(point.x, point.y);
1610 * Takes an interactive context and a function to call with the word
1611 * at point as its sole argument, and which returns a modified word.
1613 function modify_word_at_point (I, func) {
1614 var focused = I.buffer.focused_element;
1616 // Skip any whitespaces at point and move point to the right place.
1617 var point = focused.selectionStart;
1618 var rest = focused.value.substring(point);
1620 // Skip any whitespaces.
1621 for (var i = 0; i < rest.length; i++) {
1622 if (" \n".indexOf(rest.charAt(i)) == -1) {
1628 // Find the next whitespace, as it is the end of the word. If no next
1629 // whitespace is found, we're at the end of input. TODO: Add "\n" support.
1630 goal = focused.value.indexOf(" ", point);
1632 goal = focused.value.length;
1634 // Change the value of the text field.
1635 var input = focused.value;
1637 input.substring(0, point) +
1638 func(input.substring(point, goal)) +
1639 input.substring(goal);
1642 focused.selectionStart = goal;
1643 focused.selectionEnd = goal;