2 * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
3 * (C) Copyright 2008-2009 John J. Foerch
5 * Use, modification, and distribution are subject to the terms specified in the
9 require("content-buffer.js");
12 // note: The apparent misspellings here are not a bug.
13 // see https://developer.mozilla.org/en/XPath/Functions/translate
16 "browser_form_field_xpath_expression",
18 // "translate(@type,'RADIO','radio')!='radio' and " +
19 // "translate(@type,'CHECKBOX','checkbox')!='checkbox' and " +
20 "translate(@type,'HIDEN','hiden')!='hidden'"
21 // "translate(@type,'SUBMIT','submit')!='submit' and " +
22 // "translate(@type,'REST','rest')!='reset'"
25 // "translate(@type,'RADIO','radio')!='radio' and " +
26 // "translate(@type,'CHECKBOX','checkbox')!='checkbox' and " +
27 "translate(@type,'HIDEN','hiden')!='hidden'"
28 // "translate(@type,'SUBMIT','submit')!='submit' and " +
29 // "translate(@type,'REST','rest')!='reset'"
31 "//select | //xhtml:select | " +
32 "//textarea | //xhtml:textarea | " +
33 "//textbox | //xul:textbox",
34 "XPath expression matching elements to be selected by `browser-focus-next-form-field' " +
35 "and `browser-focus-previous-form-field.'");
37 function focus_next (buffer, count, xpath_expr, name) {
38 var focused_elem = buffer.focused_element;
40 return; // invalid count
42 function helper (win, skip_win) {
45 var doc = win.document;
46 var res = doc.evaluate(xpath_expr, doc, xpath_lookup_namespace,
47 Ci.nsIDOMXPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
48 null /* existing results */);
49 var length = res.snapshotLength;
52 for (let i = 0; i < length; ++i) {
53 let elem = res.snapshotItem(i);
54 if (elem.offsetWidth == 0 ||
55 elem.offsetHeight == 0)
57 let style = win.getComputedStyle(elem, "");
58 if (style.display == "none" || style.visibility == "hidden")
60 valid_nodes.push(elem);
63 if (valid_nodes.length > 0) {
65 if (focused_elem != null)
66 index = valid_nodes.indexOf(focused_elem);
74 index = index + count;
75 index = index % valid_nodes.length;
77 index += valid_nodes.length;
79 return valid_nodes[index];
82 // Recurse on sub-frames
83 for (var i = 0, nframes = win.frames.length; i < nframes; ++i) {
84 var elem = helper(win.frames[i], skip_win);
91 var focused_win = buffer.focused_frame;
92 var elem = helper(focused_win, null);
94 // if focused_frame is top_frame, we're doing twice as much
96 elem = helper(buffer.top_frame, focused_win);
98 browser_element_focus(buffer, elem);
100 throw interactive_error("No "+name+" found");
103 interactive("browser-focus-next-form-field",
104 "Focus the next element matching "+
105 "`browser_form_field_xpath_expression'.",
107 focus_next(I.buffer, I.p,
108 browser_form_field_xpath_expression,
112 interactive("browser-focus-previous-form-field",
113 "Focus the previous element matching "+
114 "`browser_form_field_xpath_expression'.",
116 focus_next(I.buffer, -I.p,
117 browser_form_field_xpath_expression,
122 define_variable("links_xpath_expression",
123 "//*[@onclick or @onmouseover or @onmousedown or "+
124 "@onmouseup or @oncommand or @role='link'] | " +
125 "//input[not(@type='hidden')] | //a | //area | "+
126 "//iframe | //textarea | //button | //select",
127 "XPath expression matching elements to be selected by "+
128 "`focus-next-link' and `focus-previous-link.'");
130 interactive("focus-next-link",
131 "Focus the next element matching `links_xpath_expression'.",
133 focus_next(I.buffer, I.p,
134 links_xpath_expression,
138 interactive("focus-previous-link",
139 "Focus the previous element matching `links_xpath_expression'.",
141 focus_next(I.buffer, -I.p,
142 links_xpath_expression,
147 define_variable('edit_field_in_external_editor_extension', "txt",
148 "File extension for the temp files created by "+
149 "edit-current-field-in-external-editor.");
151 function get_filename_for_current_textfield (doc, elem) {
154 + ( elem.getAttribute("name")
155 || elem.getAttribute("id")
158 // get rid filesystem unfriendly chars
159 name = name.replace(doc.location.protocol, "")
160 .replace(/[^a-zA-Z0-9]+/g, "-")
161 .replace(/(^-+|-+$)/g, "")
162 + '.' + edit_field_in_external_editor_extension;
167 function edit_field_in_external_editor (buffer, elem) {
168 if (elem instanceof Ci.nsIDOMHTMLInputElement) {
169 var type = elem.getAttribute("type");
171 type = type.toLowerCase();
172 if (type == "hidden" || type == "checkbox" || type == "radio")
173 throw interactive_error("Element is not a text field.");
174 } else if (!(elem instanceof Ci.nsIDOMHTMLTextAreaElement))
175 throw interactive_error("Element is not a text field.");
177 var name = get_filename_for_current_textfield(buffer.document, elem);
178 var file = get_temporary_file(name);
182 write_text_file(file, elem.value);
188 // FIXME: decide if we should do this
189 var old_class = elem.className;
190 elem.className = "__conkeror_textbox_edited_externally " + old_class;
193 yield open_file_with_external_editor(file);
195 elem.value = read_text_file(file);
197 elem.className = old_class;
203 interactive("edit-current-field-in-external-editor",
204 "Edit the contents of the currently-focused text field in an external editor.",
207 var elem = buf.focused_element;
208 yield edit_field_in_external_editor(buf, elem);
212 define_variable("kill_whole_line", false,
213 "If true, `kill-line' with no arg at beg of line kills the whole line.");
215 function cut_to_end_of_line (buffer) {
216 var elem = buffer.focused_element;
218 var st = elem.selectionStart;
219 var en = elem.selectionEnd;
221 // there is no selection. set one up.
222 var eol = elem.value.indexOf("\n", en);
224 elem.selectionEnd = elem.textLength;
226 elem.selectionEnd = eol + 1;
227 else if (kill_whole_line &&
228 (st == 0 || elem.value[st - 1] == "\n"))
229 elem.selectionEnd = eol + 1;
231 elem.selectionEnd = eol;
233 buffer.do_command('cmd_cut');
235 /* FIXME: Make this work for richedit mode as well */
238 interactive("cut-to-end-of-line",
241 cut_to_end_of_line(I.buffer);
245 function downcase_word (I) {
246 modify_word_at_point(I, function (word) { return word.toLocaleLowerCase(); });
248 interactive("downcase-word",
249 "Convert following word to lower case, moving over.",
253 function upcase_word (I) {
254 modify_word_at_point(I, function (word) { return word.toLocaleUpperCase(); });
256 interactive("upcase-word",
257 "Convert following word to upper case, moving over.",
261 function capitalize_word (I) {
262 modify_word_at_point(I, function (word) {
263 if (word.length > 0) {
264 return word[0].toLocaleUpperCase() + word.substring(1);
269 interactive("capitalize-word",
270 "Capitalize the following word (or arg words), moving over.",