2 * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
3 * (C) Copyright 2008-2010 John J. Foerch
5 * Use, modification, and distribution are subject to the terms specified in the
11 require("content-buffer.js");
14 // note: The apparent misspellings here are not a bug.
15 // see https://developer.mozilla.org/en/XPath/Functions/translate
18 "browser_form_field_xpath_expression",
20 // "translate(@type,'RADIO','radio')!='radio' and " +
21 // "translate(@type,'CHECKBOX','checkbox')!='checkbox' and " +
22 "translate(@type,'HIDEN','hiden')!='hidden'"
23 // "translate(@type,'SUBMIT','submit')!='submit' and " +
24 // "translate(@type,'REST','rest')!='reset'"
27 // "translate(@type,'RADIO','radio')!='radio' and " +
28 // "translate(@type,'CHECKBOX','checkbox')!='checkbox' and " +
29 "translate(@type,'HIDEN','hiden')!='hidden'"
30 // "translate(@type,'SUBMIT','submit')!='submit' and " +
31 // "translate(@type,'REST','rest')!='reset'"
33 "//select | //xhtml:select | " +
34 "//textarea | //xhtml:textarea | " +
35 "//textbox | //xul:textbox",
36 "XPath expression matching elements to be selected by `browser-focus-next-form-field' " +
37 "and `browser-focus-previous-form-field.'");
39 function focus_next (buffer, count, xpath_expr, name) {
40 var focused_elem = buffer.focused_element;
42 return; // invalid count
44 function helper (win, skip_win) {
47 var doc = win.document;
48 var res = doc.evaluate(xpath_expr, doc, xpath_lookup_namespace,
49 Ci.nsIDOMXPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
50 null /* existing results */);
51 var length = res.snapshotLength;
54 for (let i = 0; i < length; ++i) {
55 let elem = res.snapshotItem(i);
56 if (elem.offsetWidth == 0 ||
57 elem.offsetHeight == 0)
59 let style = win.getComputedStyle(elem, "");
60 if (style.display == "none" || style.visibility == "hidden")
62 valid_nodes.push(elem);
65 if (valid_nodes.length > 0) {
67 if (focused_elem != null)
68 index = valid_nodes.indexOf(focused_elem);
76 index = index + count;
77 index = index % valid_nodes.length;
79 index += valid_nodes.length;
81 return valid_nodes[index];
84 // Recurse on sub-frames
85 for (var i = 0, nframes = win.frames.length; i < nframes; ++i) {
86 var elem = helper(win.frames[i], skip_win);
93 var focused_win = buffer.focused_frame;
94 var elem = helper(focused_win, null);
96 // if focused_frame is top_frame, we're doing twice as much
98 elem = helper(buffer.top_frame, focused_win);
100 browser_element_focus(buffer, elem);
102 throw interactive_error("No "+name+" found");
105 interactive("browser-focus-next-form-field",
106 "Focus the next element matching "+
107 "`browser_form_field_xpath_expression'.",
109 focus_next(I.buffer, I.p,
110 browser_form_field_xpath_expression,
114 interactive("browser-focus-previous-form-field",
115 "Focus the previous element matching "+
116 "`browser_form_field_xpath_expression'.",
118 focus_next(I.buffer, -I.p,
119 browser_form_field_xpath_expression,
124 define_variable("links_xpath_expression",
125 "//*[@onclick or @onmouseover or @onmousedown or "+
126 "@onmouseup or @oncommand or @role='link'] | " +
127 "//input[not(@type='hidden')] | //a | //area | "+
128 "//iframe | //textarea | //button | //select",
129 "XPath expression matching elements to be selected by "+
130 "`focus-next-link' and `focus-previous-link.'");
132 interactive("focus-next-link",
133 "Focus the next element matching `links_xpath_expression'.",
135 focus_next(I.buffer, I.p,
136 links_xpath_expression,
140 interactive("focus-previous-link",
141 "Focus the previous element matching `links_xpath_expression'.",
143 focus_next(I.buffer, -I.p,
144 links_xpath_expression,
149 define_mime_type_table("external_editor_extension_overrides",
150 { text: { plain: "txt" } },
151 "Mime-type table for overriding file name extensions for the "+
152 "temporary file used by edit-current-field-in-external-editor.");
156 * external_editor_make_base_filename is called by
157 * edit_field_in_external_editor to generate a filename _without
158 * extension_ for the temporary file involved in external editing.
160 function external_editor_make_base_filename (elem, top_doc) {
161 var name = top_doc.URL
163 + ( elem.getAttribute("name")
164 || elem.getAttribute("id")
165 || elem.tagName.toLowerCase() );
167 // get rid filesystem unfriendly chars
168 name = name.replace(top_doc.location.protocol, "")
169 .replace(/[^a-zA-Z0-9]+/g, "-")
170 .replace(/(^-+|-+$)/g, "");
176 function edit_field_in_external_editor (buffer, elem, doc) {
178 if (elem instanceof Ci.nsIDOMHTMLInputElement) {
179 var type = (elem.getAttribute("type") || "").toLowerCase();
180 if (type == "hidden" || type == "checkbox" || type == "radio")
181 throw interactive_error("Element is not a text field.");
182 } else if (!(elem instanceof Ci.nsIDOMHTMLTextAreaElement))
183 throw interactive_error("Element is not a text field.");
186 var mime_type = doc ? doc.contentType : "text/plain";
187 var ext = external_editor_extension_overrides.get(mime_type);
189 ext = mime_service.getPrimaryExtension(mime_type, null);
191 var name = external_editor_make_base_filename(elem, buffer.document);
194 var file = get_temporary_file(name);
196 if (elem instanceof Ci.nsIDOMHTMLInputElement ||
197 elem instanceof Ci.nsIDOMHTMLTextAreaElement)
199 var content = elem.value;
201 content = elem.innerHTML;
206 write_text_file(file, content);
212 // FIXME: decide if we should do this
213 var old_class = elem.className;
214 elem.className = "__conkeror_textbox_edited_externally " + old_class;
217 yield open_file_with_external_editor(file);
218 content = read_text_file(file);
219 if (elem instanceof Ci.nsIDOMHTMLInputElement ||
220 elem instanceof Ci.nsIDOMHTMLTextAreaElement)
222 elem.value = content;
224 elem.innerHTML = content;
227 elem.className = old_class;
233 interactive("edit-current-field-in-external-editor",
234 "Edit the contents of the currently-focused text field in an external editor.",
237 var e = b.focused_element;
238 var frame = b.focused_frame;
241 if (e.contentEditable == 'true')
242 doc = e.ownerDocument;
243 } else if (frame && frame.document.designMode &&
244 frame.document.designMode == "on") {
245 doc = frame.document;
246 e = frame.document.body;
248 yield edit_field_in_external_editor(b, e, doc);
253 define_variable("kill_whole_line", false,
254 "If true, `kill-line' with no arg at beg of line kills the whole line.");
256 function cut_to_end_of_line (field, window) {
258 var st = field.selectionStart;
259 var en = field.selectionEnd;
261 // there is no selection. set one up.
262 var eol = field.value.indexOf("\n", en);
264 field.selectionEnd = field.textLength;
266 field.selectionEnd = eol + 1;
267 else if (kill_whole_line &&
268 (st == 0 || field.value[st - 1] == "\n"))
269 field.selectionEnd = eol + 1;
271 field.selectionEnd = eol;
273 call_builtin_command(window, 'cmd_cut');
275 /* FIXME: Make this work for richedit mode as well */
278 interactive("cut-to-end-of-line",
281 call_on_focused_field(I, function (field) {
282 cut_to_end_of_line(field, I.window);
287 interactive("downcase-word",
288 "Convert following word to lower case, moving over.",
290 call_on_focused_field(I, function (field) {
291 modify_word_at_point(field, function (word) {
292 return word.toLocaleLowerCase();
298 interactive("upcase-word",
299 "Convert following word to upper case, moving over.",
301 call_on_focused_field(I, function (field) {
302 modify_word_at_point(field, function (word) {
303 return word.toLocaleUpperCase();
309 interactive("capitalize-word",
310 "Capitalize the following word (or arg words), moving over.",
312 call_on_focused_field(I, function (field) {
313 modify_word_at_point(field, function (word) {
315 return word[0].toLocaleUpperCase() + word.substring(1);
321 provide("content-buffer-input");