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
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_mime_type_table("external_editor_extension_overrides",
148 { text: { plain: "txt" } },
149 "Mime-type table for overriding file name extensions for the "+
150 "temporary file used by edit-current-field-in-external-editor.");
154 * external_editor_make_base_filename is called by
155 * edit_field_in_external_editor to generate a filename _without
156 * extension_ for the temporary file involved in external editing.
158 function external_editor_make_base_filename (elem, top_doc) {
159 var name = top_doc.URL
161 + ( elem.getAttribute("name")
162 || elem.getAttribute("id")
163 || elem.tagName.toLowerCase() );
165 // get rid filesystem unfriendly chars
166 name = name.replace(top_doc.location.protocol, "")
167 .replace(/[^a-zA-Z0-9]+/g, "-")
168 .replace(/(^-+|-+$)/g, "");
174 function edit_field_in_external_editor (buffer, elem, doc) {
176 if (elem instanceof Ci.nsIDOMHTMLInputElement) {
177 var type = (elem.getAttribute("type") || "").toLowerCase();
178 if (type == "hidden" || type == "checkbox" || type == "radio")
179 throw interactive_error("Element is not a text field.");
180 } else if (!(elem instanceof Ci.nsIDOMHTMLTextAreaElement))
181 throw interactive_error("Element is not a text field.");
184 var mime_type = doc ? doc.contentType : "text/plain";
185 var ext = external_editor_extension_overrides.get(mime_type);
187 ext = mime_service.getPrimaryExtension(mime_type, null);
189 var name = external_editor_make_base_filename(elem, buffer.document);
192 var file = get_temporary_file(name);
194 if (elem instanceof Ci.nsIDOMHTMLInputElement ||
195 elem instanceof Ci.nsIDOMHTMLTextAreaElement)
197 var content = elem.value;
199 content = elem.innerHTML;
204 write_text_file(file, content);
210 // FIXME: decide if we should do this
211 var old_class = elem.className;
212 elem.className = "__conkeror_textbox_edited_externally " + old_class;
215 yield open_file_with_external_editor(file);
216 content = read_text_file(file);
217 if (elem instanceof Ci.nsIDOMHTMLInputElement ||
218 elem instanceof Ci.nsIDOMHTMLTextAreaElement)
220 elem.value = content;
222 elem.innerHTML = content;
225 elem.className = old_class;
231 interactive("edit-current-field-in-external-editor",
232 "Edit the contents of the currently-focused text field in an external editor.",
235 var e = b.focused_element;
236 var frame = b.focused_frame;
239 if (e.contentEditable == 'true')
240 doc = e.ownerDocument;
241 } else if (frame && frame.document.designMode &&
242 frame.document.designMode == "on") {
243 doc = frame.document;
244 e = frame.document.body;
246 yield edit_field_in_external_editor(b, e, doc);
251 define_variable("kill_whole_line", false,
252 "If true, `kill-line' with no arg at beg of line kills the whole line.");
254 function cut_to_end_of_line (field, window) {
256 var st = field.selectionStart;
257 var en = field.selectionEnd;
259 // there is no selection. set one up.
260 var eol = field.value.indexOf("\n", en);
262 field.selectionEnd = field.textLength;
264 field.selectionEnd = eol + 1;
265 else if (kill_whole_line &&
266 (st == 0 || field.value[st - 1] == "\n"))
267 field.selectionEnd = eol + 1;
269 field.selectionEnd = eol;
271 call_builtin_command(window, 'cmd_cut');
273 /* FIXME: Make this work for richedit mode as well */
276 interactive("cut-to-end-of-line",
279 call_on_focused_field(I, function (field) {
280 cut_to_end_of_line(field, I.window);
285 interactive("downcase-word",
286 "Convert following word to lower case, moving over.",
288 call_on_focused_field(I, function (field) {
289 modify_word_at_point(field, function (word) {
290 return word.toLocaleLowerCase();
296 interactive("upcase-word",
297 "Convert following word to upper case, moving over.",
299 call_on_focused_field(I, function (field) {
300 modify_word_at_point(field, function (word) {
301 return word.toLocaleUpperCase();
307 interactive("capitalize-word",
308 "Capitalize the following word (or arg words), moving over.",
310 call_on_focused_field(I, function (field) {
311 modify_word_at_point(field, function (word) {
313 return word[0].toLocaleUpperCase() + word.substring(1);
319 provide("content-buffer-input");