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");