1 require("content-buffer.js");
3 define_buffer_local_hook("content_buffer_input_mode_change_hook");
4 define_current_buffer_hook("current_content_buffer_input_mode_change_hook", "content_buffer_input_mode_change_hook");
6 function define_content_buffer_input_mode(base_name, keymap_name, doc) {
7 var name = "content_buffer_" + base_name + "_input_mode";
8 buffer[name + "_enabled"] = false;
9 define_buffer_local_hook(name + "_enable_hook");
10 define_buffer_local_hook(name + "_disable_hook");
11 conkeror[name] = function (buffer) {
12 if (buffer[name + "_enabled"])
14 if (buffer.current_input_mode) {
15 conkeror[buffer.current_input_mode + "_disable_hook"].run(buffer);
16 buffer[buffer.current_input_mode + "_enabled"] = false;
18 buffer.current_input_mode = name;
19 buffer[name + "_enabled"] = true;
20 buffer.keymap = conkeror[keymap_name];
21 conkeror[name + "_enable_hook"].run(buffer);
22 content_buffer_input_mode_change_hook.run(buffer);
24 var hyphen_name = name.replace("_","-","g");
25 interactive(hyphen_name, doc, function(I) {conkeror[name](check_buffer(I.buffer,content_buffer));});
28 define_content_buffer_input_mode("normal", "content_buffer_normal_keymap");
30 // For SELECT elements
31 define_content_buffer_input_mode("select", "content_buffer_select_keymap");
33 // For text INPUT and TEXTAREA elements
34 define_content_buffer_input_mode("text", "content_buffer_text_keymap");
35 define_content_buffer_input_mode("textarea", "content_buffer_textarea_keymap");
37 define_content_buffer_input_mode(
38 "quote_next", "content_buffer_quote_next_keymap",
39 "This input mode sends the next key combo to the buffer, "+
40 "bypassing Conkeror's normal key handling. The mode disengages "+
41 "after one key combo.");
42 define_content_buffer_input_mode(
43 "quote", "content_buffer_quote_keymap",
44 "This input mode sends all key combos to the buffer, "+
45 "bypassing Conkeror's normal key handling, until the "+
46 "Escape key is pressed.");
48 add_hook("content_buffer_focus_change_hook", function (buffer) {
49 var form_input_mode_enabled =
50 buffer.content_buffer_text_input_mode_enabled ||
51 buffer.content_buffer_textarea_input_mode_enabled ||
52 buffer.content_buffer_select_input_mode_enabled;
54 if (form_input_mode_enabled || buffer.content_buffer_normal_input_mode_enabled) {
55 var elem = buffer.focused_element;
58 var input_mode_function = null;
59 if (elem instanceof Ci.nsIDOMHTMLInputElement) {
60 var type = elem.getAttribute("type");
61 if (type != null) type = type.toLowerCase();
62 if (type != "radio" &&
66 input_mode_function = content_buffer_text_input_mode;
68 else if (elem instanceof Ci.nsIDOMHTMLTextAreaElement)
69 input_mode_function = content_buffer_textarea_input_mode;
71 else if (elem instanceof Ci.nsIDOMHTMLSelectElement)
72 input_mode_function = content_buffer_select_input_mode;
74 if (input_mode_function) {
75 if (browser_prevent_automatic_form_focus_mode_enabled &&
76 !form_input_mode_enabled &&
77 (buffer.last_user_input_received == null ||
78 (Date.now() - buffer.last_user_input_received)
79 > browser_automatic_form_focus_window_duration)) {
80 // Automatic focus attempt blocked
83 input_mode_function(buffer);
87 content_buffer_normal_input_mode(buffer);
91 function browser_input_minibuffer_status(window) {
93 var element = create_XUL(window, "label");
94 element.setAttribute("id", "minibuffer-input-status");
95 element.collapsed = true;
96 element.setAttribute("class", "minibuffer");
97 var insert_before = window.document.getElementById("minibuffer-prompt");
98 insert_before.parentNode.insertBefore(element, insert_before);
99 this.element = element;
100 this.select_buffer_hook_func = add_hook.call(window, "select_buffer_hook", method_caller(this, this.update));
101 this.mode_change_hook_func = add_hook.call(window, "current_content_buffer_input_mode_change_hook",
102 method_caller(this, this.update));
105 browser_input_minibuffer_status.prototype = {
106 update : function () {
107 var buf = this.window.buffers.current;
108 if (buf.content_buffer_normal_input_mode_enabled) {
109 this.element.collapsed = true;
110 this.window.minibuffer.element.className = "";
113 var className = null;
114 if (buf.content_buffer_text_input_mode_enabled) {
115 name = "[TEXT INPUT]";
117 } else if (buf.content_buffer_textarea_input_mode_enabled) {
118 name = "[TEXTAREA INPUT]";
119 className = "textarea";
120 } else if (buf.content_buffer_select_input_mode_enabled) {
121 name = "[SELECT INPUT]";
122 className = "select";
123 } else if (buf.content_buffer_quote_next_input_mode_enabled) {
124 name = "[PASS THROUGH (next)]";
125 className = "quote-next";
126 } else if (buf.content_buffer_quote_input_mode_enabled) {
127 name = "[PASS THROUGH]";
131 this.element.value = name;
132 this.element.collapsed = false;
133 this.window.minibuffer.element.className = "minibuffer-" + className + "-input-mode";
136 uninstall : function () {
137 remove_hook.call(window, "select_buffer_hook", this.select_buffer_hook_func);
138 remove_hook.call(window, "current_content_buffer_input_mode_change_hook",
139 method_caller(this, this.update), this.mode_change_hook_func);
140 this.element.parentNode.removeChild(this.element);
144 function browser_input_minibuffer_status_install(window) {
145 if (window.browser_input_minibuffer_status)
146 throw new Error("browser input minibuffer status already initialized for window");
147 window.browser_input_minibuffer_status = new browser_input_minibuffer_status(window);
150 function browser_input_minibuffer_status_uninstall(window) {
151 if (window.browser_input_minibuffer_status)
153 window.browser_input_minibuffer_status.uninstall();
154 delete window.browser_input_minibuffer_status;
157 define_global_mode("browser_input_minibuffer_status_mode",
158 function () { // enable
159 add_hook("window_initialize_hook", browser_input_minibuffer_status_install);
160 for_each_window(browser_input_minibuffer_status_install);
162 function () { // disable
163 remove_hook("window_initialize_hook", browser_input_minibuffer_status_install);
164 for_each_window(browser_input_minibuffer_status_uninstall);
166 browser_input_minibuffer_status_mode(true);
169 define_user_variable("browser_automatic_form_focus_window_duration", 20, "Time window (in milliseconds) during which a form element is allowed to gain focus following a mouse click or key press, if `browser_prevent_automatic_form_focus_mode' is enabled.");;
171 define_global_mode("browser_prevent_automatic_form_focus_mode",
172 function () {}, // enable
173 function () {} // disable
175 browser_prevent_automatic_form_focus_mode(true);
177 define_user_variable(
178 "browser_form_field_xpath_expression",
180 // "translate(@type,'RADIO','radio')!='radio' and " +
181 // "translate(@type,'CHECKBOX','checkbox')!='checkbox' and " +
182 "translate(@type,'HIDEN','hiden')!='hidden'"
183 // "translate(@type,'SUBMIT','submit')!='submit' and " +
184 // "translate(@type,'REST','rest')!='reset'"
187 // "translate(@type,'RADIO','radio')!='radio' and " +
188 // "translate(@type,'CHECKBOX','checkbox')!='checkbox' and " +
189 "translate(@type,'HIDEN','hiden')!='hidden'"
190 // "translate(@type,'SUBMIT','submit')!='submit' and " +
191 // "translate(@type,'REST','rest')!='reset'"
193 "//select | //xhtml:select | " +
194 "//textarea | //xhtml:textarea | " +
195 "//textbox | //xul:textbox",
196 "XPath expression matching elements to be selected by `browser-focus-next-form-field' " +
197 "and `browser-focus-previous-form-field.'");
199 function browser_focus_next_form_field(buffer, count, xpath_expr) {
200 var focused_elem = buffer.focused_element;
202 return; // invalid count
204 function helper(win, skip_win) {
207 var doc = win.document;
208 var res = doc.evaluate(xpath_expr, doc, xpath_lookup_namespace,
209 Ci.nsIDOMXPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
210 null /* existing results */);
211 var length = res.snapshotLength;
214 if (focused_elem != null) {
215 for (var i = 0; i < length; ++i) {
216 if (res.snapshotItem(i) == focused_elem) {
229 index = index + count;
230 index = index % length;
234 return res.snapshotItem(index);
237 // Recurse on sub-frames
238 for (var i = 0; i < win.frames.length; ++i) {
239 var elem = helper(win.frames[i], skip_win);
246 var focused_win = buffer.focused_frame;
247 var elem = helper(focused_win, null);
249 elem = helper(buffer.top_frame, focused_win);
251 browser_element_focus(buffer, elem);
253 throw interactive_error("No form field found");
256 interactive("browser-focus-next-form-field", "Focus the next element matching `browser_form_field_xpath_expression'.",
258 browser_focus_next_form_field(I.buffer, I.p, browser_form_field_xpath_expression);
261 interactive("browser-focus-previous-form-field", "Focus the previous element matching `browser_form_field_xpath_expression'.",
263 browser_focus_next_form_field(I.buffer, -I.p, browser_form_field_xpath_expression);
266 function edit_field_in_external_editor(buffer, elem) {
267 if (elem instanceof Ci.nsIDOMHTMLInputElement) {
268 var type = elem.getAttribute("type");
270 type = type.toLowerCase();
271 if (type == "hidden" || type == "checkbox" || type == "radio")
272 throw interactive_error("Element is not a text field.");
273 } else if (!(elem instanceof Ci.nsIDOMHTMLTextAreaElement))
274 throw interactive_error("Element is not a text field.");
276 var name = elem.getAttribute("name");
277 if (!name || name.length == 0)
278 name = elem.getAttribute("id");
281 name = name.replace(/[^a-zA-Z0-9\-_]/g, "");
282 if (name.length == 0)
285 var file = get_temporary_file(name);
289 write_text_file(file, elem.value);
295 var old_readonly = elem.getAttribute("readonly");
296 elem.setAttribute("readonly", "true");
298 // FIXME: decide if we should do this
299 var old_class = elem.className;
300 elem.className = "__conkeror_textbox_edited_externally " + old_class;
303 yield open_file_with_external_editor(file);
305 elem.value = read_text_file(file);
307 elem.className = old_class;
310 elem.setAttribute("readonly", old_readonly);
312 elem.removeAttribute("readonly");
316 interactive("edit-current-field-in-external-editor", "Edit the contents of the currently-focused text field in an external editor.",
319 yield edit_field_in_external_editor(buf, buf.focused_element);
323 define_user_variable("kill_whole_line", false, "If true, `kill-line' with no arg at beg of line kills the whole line.");
325 function cut_to_end_of_line (buffer) {
326 var elem = buffer.focused_element;
327 var st = elem.selectionStart;
328 var en = elem.selectionEnd;
330 // there is no selection. set one up.
331 var eol = elem.value.indexOf ("\n", en);
334 elem.selectionEnd = elem.textLength;
335 } else if (eol == st) {
336 elem.selectionEnd = eol + 1;
337 } else if (kill_whole_line &&
338 (st == 0 || elem.value[st - 1] == "\n"))
340 elem.selectionEnd = eol + 1;
342 elem.selectionEnd = eol;
345 buffer.do_command ('cmd_cut');
349 "cut-to-end-of-line",
351 cut_to_end_of_line (I.buffer);