|
1 <html> |
|
2 <head> |
|
3 <title>Test for IME state controling and focus moving for iframes</title> |
|
4 <script type="text/javascript" |
|
5 src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> |
|
6 <link rel="stylesheet" type="text/css" |
|
7 href="chrome://mochikit/content/tests/SimpleTest/test.css" /> |
|
8 <style type="text/css"> |
|
9 iframe { |
|
10 border: none; |
|
11 height: 100px; |
|
12 } |
|
13 </style> |
|
14 </head> |
|
15 <body onunload="onUnload();"> |
|
16 <p id="display"> |
|
17 <!-- Use input[readonly] because it isn't affected by the partial focus |
|
18 movement on Mac --> |
|
19 <input id="prev" readonly><br> |
|
20 <iframe id="iframe_not_editable" |
|
21 src="data:text/html,<html><body><input id='editor'></body></html>"></iframe><br> |
|
22 |
|
23 <!-- Testing IME state and focus movement, the anchor elements cannot get focus --> |
|
24 <iframe id="iframe_html" |
|
25 src="data:text/html,<html id='editor' contenteditable='true'><body><a href='about:blank'>about:blank;</a></body></html>"></iframe><br> |
|
26 <iframe id="iframe_designMode" |
|
27 src="data:text/html,<body id='editor' onload='document.designMode="on";'><a href='about:blank'>about:blank;</a></body>"></iframe><br> |
|
28 <iframe id="iframe_body" |
|
29 src="data:text/html,<body id='editor' contenteditable='true'><a href='about:blank'>about:blank;</a></body>"></iframe><br> |
|
30 <iframe id="iframe_p" |
|
31 src="data:text/html,<body><p id='editor' contenteditable='true'><a href='about:blank'>about:blank;</a></p></body>"></iframe><br> |
|
32 |
|
33 <input id="next" readonly><br> |
|
34 </p> |
|
35 <script class="testbody" type="application/javascript"> |
|
36 |
|
37 window.opener.wrappedJSObject.SimpleTest.waitForFocus(runTests, window); |
|
38 |
|
39 function ok(aCondition, aMessage) |
|
40 { |
|
41 window.opener.wrappedJSObject.SimpleTest.ok(aCondition, aMessage); |
|
42 } |
|
43 |
|
44 function is(aLeft, aRight, aMessage) |
|
45 { |
|
46 window.opener.wrappedJSObject.SimpleTest.is(aLeft, aRight, aMessage); |
|
47 } |
|
48 |
|
49 function onUnload() |
|
50 { |
|
51 window.opener.wrappedJSObject.onFinish(); |
|
52 } |
|
53 |
|
54 var gFocusObservingElement = null; |
|
55 var gBlurObservingElement = null; |
|
56 |
|
57 function onFocus(aEvent) |
|
58 { |
|
59 if (aEvent.target != gFocusObservingElement) { |
|
60 return; |
|
61 } |
|
62 ok(gFocusObservingElement.willFocus, |
|
63 "focus event is fired on unexpected element"); |
|
64 gFocusObservingElement.willFocus = false; |
|
65 } |
|
66 |
|
67 function onBlur(aEvent) |
|
68 { |
|
69 if (aEvent.target != gBlurObservingElement) { |
|
70 return; |
|
71 } |
|
72 ok(gBlurObservingElement.willBlur, |
|
73 "blur event is fired on unexpected element"); |
|
74 gBlurObservingElement.willBlur = false; |
|
75 } |
|
76 |
|
77 function observeFocusBlur(aNextFocusedNode, aWillFireFocusEvent, |
|
78 aNextBlurredNode, aWillFireBlurEvent) |
|
79 { |
|
80 if (gFocusObservingElement) { |
|
81 if (gFocusObservingElement.willFocus) { |
|
82 ok(false, "focus event was never fired on " + gFocusObservingElement); |
|
83 } |
|
84 gFocusObservingElement.removeEventListener("focus", onFocus, true); |
|
85 gFocusObservingElement.willFocus = NaN; |
|
86 gFocusObservingElement = null; |
|
87 } |
|
88 if (gBlurObservingElement) { |
|
89 if (gBlurObservingElement.willBlur) { |
|
90 ok(false, "blur event was never fired on " + gBlurObservingElement); |
|
91 } |
|
92 gBlurObservingElement.removeEventListener("blur", onBlur, true); |
|
93 gBlurObservingElement.willBlur = NaN; |
|
94 gBlurObservingElement = null; |
|
95 } |
|
96 if (aNextFocusedNode) { |
|
97 gFocusObservingElement = aNextFocusedNode; |
|
98 gFocusObservingElement.willFocus = aWillFireFocusEvent; |
|
99 gFocusObservingElement.addEventListener("focus", onFocus, true); |
|
100 } |
|
101 if (aNextBlurredNode) { |
|
102 gBlurObservingElement = aNextBlurredNode; |
|
103 gBlurObservingElement.willBlur = aWillFireBlurEvent; |
|
104 gBlurObservingElement.addEventListener("blur", onBlur, true); |
|
105 } |
|
106 } |
|
107 |
|
108 function runTests() |
|
109 { |
|
110 var utils = |
|
111 window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) |
|
112 .getInterface(Components.interfaces.nsIDOMWindowUtils); |
|
113 var fm = |
|
114 Components.classes["@mozilla.org/focus-manager;1"] |
|
115 .getService(Components.interfaces.nsIFocusManager); |
|
116 |
|
117 var iframe, editor, root, input; |
|
118 var prev = document.getElementById("prev"); |
|
119 var next = document.getElementById("next"); |
|
120 var html = document.documentElement; |
|
121 |
|
122 function resetFocusToInput(aDescription) |
|
123 { |
|
124 observeFocusBlur(null, false, null, false); |
|
125 prev.focus(); |
|
126 is(fm.focusedElement, prev, |
|
127 "input#prev[readonly] element didn't get focus: " + aDescription); |
|
128 is(utils.IMEStatus, utils.IME_STATUS_DISABLED, |
|
129 "IME enabled on input#prev[readonly]: " + aDescription); |
|
130 } |
|
131 |
|
132 function resetFocusToParentHTML(aDescription) |
|
133 { |
|
134 observeFocusBlur(null, false, null, false); |
|
135 html.focus(); |
|
136 is(fm.focusedElement, html, |
|
137 "Parent html element didn't get focus: " + aDescription); |
|
138 is(utils.IMEStatus, utils.IME_STATUS_DISABLED, |
|
139 "IME enabled on parent html element: " + aDescription); |
|
140 } |
|
141 |
|
142 function testTabKey(aForward, |
|
143 aNextFocusedNode, aWillFireFocusEvent, |
|
144 aNextBlurredNode, aWillFireBlurEvent, |
|
145 aIMEShouldBeEnabled, aTestingCaseDescription) |
|
146 { |
|
147 observeFocusBlur(aNextFocusedNode, aWillFireFocusEvent, |
|
148 aNextBlurredNode, aWillFireBlurEvent); |
|
149 synthesizeKey("VK_TAB", { shiftKey: !aForward }); |
|
150 var description = "Tab key test: "; |
|
151 if (!aForward) { |
|
152 description = "Shift-" + description; |
|
153 } |
|
154 description += aTestingCaseDescription + ": "; |
|
155 is(fm.focusedElement, aNextFocusedNode, |
|
156 description + "didn't move focus as expected"); |
|
157 is(utils.IMEStatus, |
|
158 aIMEShouldBeEnabled ? |
|
159 utils.IME_STATUS_ENABLED : utils.IME_STATUS_DISABLED, |
|
160 description + "didn't set IME state as expected"); |
|
161 } |
|
162 |
|
163 function testMouseClick(aNextFocusedNode, aWillFireFocusEvent, |
|
164 aWillAllNodeLostFocus, |
|
165 aNextBlurredNode, aWillFireBlurEvent, |
|
166 aIMEShouldBeEnabled, aTestingCaseDescription) |
|
167 { |
|
168 observeFocusBlur(aNextFocusedNode, aWillFireFocusEvent, |
|
169 aNextBlurredNode, aWillFireBlurEvent); |
|
170 // We're relying on layout inside the iframe being up to date, so make it so |
|
171 iframe.contentDocument.documentElement.getBoundingClientRect(); |
|
172 synthesizeMouse(iframe, 10, 80, { }); |
|
173 var description = "Click test: " + aTestingCaseDescription + ": "; |
|
174 is(fm.focusedElement, !aWillAllNodeLostFocus ? aNextFocusedNode : null, |
|
175 description + "didn't move focus as expected"); |
|
176 is(utils.IMEStatus, |
|
177 aIMEShouldBeEnabled ? |
|
178 utils.IME_STATUS_ENABLED : utils.IME_STATUS_DISABLED, |
|
179 description + "didn't set IME state as expected"); |
|
180 } |
|
181 |
|
182 function testOnEditorFlagChange(aDescription, aIsInDesignMode) |
|
183 { |
|
184 const kReadonly = |
|
185 Components.interfaces.nsIPlaintextEditor.eEditorReadonlyMask; |
|
186 var description = "testOnEditorFlagChange: " + aDescription; |
|
187 resetFocusToParentHTML(description); |
|
188 var htmlEditor = |
|
189 iframe.contentWindow. |
|
190 QueryInterface(Components.interfaces.nsIInterfaceRequestor). |
|
191 getInterface(Components.interfaces.nsIWebNavigation). |
|
192 QueryInterface(Components.interfaces.nsIDocShell).editor; |
|
193 var e = aIsInDesignMode ? root : editor; |
|
194 e.focus(); |
|
195 is(fm.focusedElement, e, |
|
196 description + ": focus() of editor didn't move focus as expected"); |
|
197 is(utils.IMEStatus, utils.IME_STATUS_ENABLED, |
|
198 description + ": IME isn't enabled when the editor gets focus"); |
|
199 var flags = htmlEditor.flags; |
|
200 htmlEditor.flags |= kReadonly; |
|
201 is(fm.focusedElement, e, |
|
202 description + ": when editor becomes readonly, focus moved unexpectedly"); |
|
203 is(utils.IMEStatus, utils.IME_STATUS_DISABLED, |
|
204 description + ": when editor becomes readonly, IME is still enabled"); |
|
205 htmlEditor.flags = flags; |
|
206 is(fm.focusedElement, e, |
|
207 description + ": when editor becomes read-write, focus moved unexpectedly"); |
|
208 is(utils.IMEStatus, utils.IME_STATUS_ENABLED, |
|
209 description + ": when editor becomes read-write, IME is still disabled"); |
|
210 } |
|
211 |
|
212 // hide all iframes |
|
213 document.getElementById("iframe_not_editable").style.display = "none"; |
|
214 document.getElementById("iframe_html").style.display = "none"; |
|
215 document.getElementById("iframe_designMode").style.display = "none"; |
|
216 document.getElementById("iframe_body").style.display = "none"; |
|
217 document.getElementById("iframe_p").style.display = "none"; |
|
218 |
|
219 // non editable HTML element and input element can get focus. |
|
220 iframe = document.getElementById("iframe_not_editable"); |
|
221 iframe.style.display = "inline"; |
|
222 editor = iframe.contentDocument.getElementById("editor"); |
|
223 root = iframe.contentDocument.documentElement; |
|
224 resetFocusToInput("initializing for iframe_not_editable"); |
|
225 |
|
226 testTabKey(true, root, false, prev, true, |
|
227 false, "input#prev[readonly] -> html"); |
|
228 testTabKey(true, editor, true, root, false, |
|
229 true, "html -> input in the subdoc"); |
|
230 testTabKey(true, next, true, editor, true, |
|
231 false, "input in the subdoc -> input#next[readonly]"); |
|
232 testTabKey(false, editor, true, next, true, |
|
233 true, "input#next[readonly] -> input in the subdoc"); |
|
234 testTabKey(false, root, false, editor, true, |
|
235 false, "input in the subdoc -> html"); |
|
236 testTabKey(false, prev, true, root, false, |
|
237 false, "html -> input#next[readonly]"); |
|
238 |
|
239 iframe.style.display = "none"; |
|
240 |
|
241 // HTML element (of course, it's root) must enables IME. |
|
242 iframe = document.getElementById("iframe_html"); |
|
243 iframe.style.display = "inline"; |
|
244 editor = iframe.contentDocument.getElementById("editor"); |
|
245 root = iframe.contentDocument.documentElement; |
|
246 resetFocusToInput("initializing for iframe_html"); |
|
247 |
|
248 testTabKey(true, editor, true, prev, true, |
|
249 true, "input#prev[readonly] -> html[contentediable=true]"); |
|
250 testTabKey(true, next, true, editor, true, |
|
251 false, "html[contentediable=true] -> input#next[readonly]"); |
|
252 testTabKey(false, editor, true, next, true, |
|
253 true, "input#next[readonly] -> html[contentediable=true]"); |
|
254 testTabKey(false, prev, true, editor, true, |
|
255 false, "html[contenteditable=true] -> input[readonly]"); |
|
256 |
|
257 prev.style.display = "none"; |
|
258 resetFocusToParentHTML("testing iframe_html"); |
|
259 testTabKey(true, editor, true, html, false, |
|
260 true, "html of parent -> html[contentediable=true]"); |
|
261 testTabKey(false, html, false, editor, true, |
|
262 false, "html[contenteditable=true] -> html of parent"); |
|
263 prev.style.display = "inline"; |
|
264 resetFocusToInput("after parent html <-> html[contenteditable=true]"); |
|
265 |
|
266 testMouseClick(editor, true, false, prev, true, true, "iframe_html"); |
|
267 |
|
268 testOnEditorFlagChange("html[contentediable=true]", false); |
|
269 |
|
270 iframe.style.display = "none"; |
|
271 |
|
272 // designMode should behave like <html contenteditable="true"></html> |
|
273 // but focus/blur events shouldn't be fired on its root element because |
|
274 // any elements shouldn't be focused state in designMode. |
|
275 iframe = document.getElementById("iframe_designMode"); |
|
276 iframe.style.display = "inline"; |
|
277 iframe.contentDocument.designMode = "on"; |
|
278 editor = iframe.contentDocument.getElementById("editor"); |
|
279 root = iframe.contentDocument.documentElement; |
|
280 resetFocusToInput("initializing for iframe_designMode"); |
|
281 |
|
282 testTabKey(true, root, false, prev, true, |
|
283 true, "input#prev[readonly] -> html in designMode"); |
|
284 testTabKey(true, next, true, root, false, |
|
285 false, "html in designMode -> input#next[readonly]"); |
|
286 testTabKey(false, root, false, next, true, |
|
287 true, "input#next[readonly] -> html in designMode"); |
|
288 testTabKey(false, prev, true, root, false, |
|
289 false, "html in designMode -> input#prev[readonly]"); |
|
290 |
|
291 prev.style.display = "none"; |
|
292 resetFocusToParentHTML("testing iframe_designMode"); |
|
293 testTabKey(true, root, false, html, false, |
|
294 true, "html of parent -> html in designMode"); |
|
295 testTabKey(false, html, false, root, false, |
|
296 false, "html in designMode -> html of parent"); |
|
297 prev.style.display = "inline"; |
|
298 resetFocusToInput("after parent html <-> html in designMode"); |
|
299 |
|
300 testMouseClick(editor, false, true, prev, true, true, "iframe_designMode"); |
|
301 |
|
302 testOnEditorFlagChange("html in designMode", true); |
|
303 |
|
304 iframe.style.display = "none"; |
|
305 |
|
306 // When there is no HTML element but the BODY element is editable, |
|
307 // the body element should get focus and enables IME. |
|
308 iframe = document.getElementById("iframe_body"); |
|
309 iframe.style.display = "inline"; |
|
310 editor = iframe.contentDocument.getElementById("editor"); |
|
311 root = iframe.contentDocument.documentElement; |
|
312 resetFocusToInput("initializing for iframe_body"); |
|
313 |
|
314 testTabKey(true, editor, true, prev, true, |
|
315 true, "input#prev[readonly] -> body[contentediable=true]"); |
|
316 testTabKey(true, next, true, editor, true, |
|
317 false, "body[contentediable=true] -> input#next[readonly]"); |
|
318 testTabKey(false, editor, true, next, true, |
|
319 true, "input#next[readonly] -> body[contentediable=true]"); |
|
320 testTabKey(false, prev, true, editor, true, |
|
321 false, "body[contenteditable=true] -> input#prev[readonly]"); |
|
322 |
|
323 prev.style.display = "none"; |
|
324 resetFocusToParentHTML("testing iframe_body"); |
|
325 testTabKey(true, editor, true, html, false, |
|
326 true, "html of parent -> body[contentediable=true]"); |
|
327 testTabKey(false, html, false, editor, true, |
|
328 false, "body[contenteditable=true] -> html of parent"); |
|
329 prev.style.display = "inline"; |
|
330 resetFocusToInput("after parent html <-> body[contenteditable=true]"); |
|
331 |
|
332 testMouseClick(editor, true, false, prev, true, true, "iframe_body"); |
|
333 |
|
334 testOnEditorFlagChange("body[contentediable=true]", false); |
|
335 |
|
336 iframe.style.display = "none"; |
|
337 |
|
338 // When HTML/BODY elements are not editable, focus shouldn't be moved to |
|
339 // the editable content directly. |
|
340 iframe = document.getElementById("iframe_p"); |
|
341 iframe.style.display = "inline"; |
|
342 editor = iframe.contentDocument.getElementById("editor"); |
|
343 root = iframe.contentDocument.documentElement; |
|
344 resetFocusToInput("initializing for iframe_p"); |
|
345 |
|
346 testTabKey(true, root, false, prev, true, |
|
347 false, "input#prev[readonly] -> html (has p[contenteditable=true])"); |
|
348 testTabKey(true, editor, true, root, false, |
|
349 true, "html (has p[contenteditable=true]) -> p[contentediable=true]"); |
|
350 testTabKey(true, next, true, editor, true, |
|
351 false, "p[contentediable=true] -> input#next[readonly]"); |
|
352 testTabKey(false, editor, true, next, true, |
|
353 true, "input#next[readonly] -> p[contentediable=true]"); |
|
354 testTabKey(false, root, false, editor, true, |
|
355 false, "p[contenteditable=true] -> html (has p[contenteditable=true])"); |
|
356 testTabKey(false, prev, true, root, false, |
|
357 false, "html (has p[contenteditable=true]) -> input#prev[readonly]"); |
|
358 prev.style.display = "none"; |
|
359 |
|
360 resetFocusToParentHTML("testing iframe_p"); |
|
361 testTabKey(true, root, false, html, false, |
|
362 false, "html of parent -> html (has p[contentediable=true])"); |
|
363 testTabKey(false, html, false, root, false, |
|
364 false, "html (has p[contentediable=true]) -> html of parent"); |
|
365 prev.style.display = "inline"; |
|
366 resetFocusToInput("after parent html <-> html (has p[contentediable=true])"); |
|
367 |
|
368 testMouseClick(root, false, true, prev, true, false, "iframe_p"); |
|
369 |
|
370 testOnEditorFlagChange("p[contenteditable=true]", false); |
|
371 |
|
372 iframe.style.display = "none"; |
|
373 |
|
374 window.close(); |
|
375 } |
|
376 |
|
377 </script> |
|
378 </body> |
|
379 |
|
380 </html> |