test_contenteditable_focus.html (13075B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title>Test for contenteditable focus</title> 6 <script src="/tests/SimpleTest/SimpleTest.js"></script> 7 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> 8 </head> 9 <body> 10 <div id="display"> 11 First text in this document.<br> 12 <input id="inputText" type="text"><br> 13 <input id="inputTextReadonly" type="text" readonly><br> 14 <input id="inputButton" type="button" value="input[type=button]"><br> 15 <button id="button">button</button><br> 16 <div id="primaryEditor" contenteditable="true"> 17 editable contents.<br> 18 <input id="inputTextInEditor" type="text"><br> 19 <input id="inputTextReadonlyInEditor" type="text" readonly><br> 20 <input id="inputButtonInEditor" type="button" value="input[type=button]"><br> 21 <button id="buttonInEditor">button</button><br> 22 <div id="noeditableInEditor" contenteditable="false"> 23 <span id="spanInNoneditableInEditor">span element in noneditable in editor</span><br> 24 <input id="inputTextInNoneditableInEditor" type="text"><br> 25 <input id="inputTextReadonlyInNoneditableInEditor" type="text" readonly><br> 26 <input id="inputButtonInNoneditableInEditor" type="button" value="input[type=button]"><br> 27 <button id="buttonInNoneditableInEditor">button</button><br> 28 </div> 29 <span id="spanInEditor">span element in editor</span><br> 30 </div> 31 <div id="otherEditor" contenteditable="true"> 32 other editor. 33 </div> 34 </div> 35 <div id="content" style="display: none"> 36 37 </div> 38 <pre id="test"> 39 </pre> 40 41 <script> 42 "use strict"; 43 44 SimpleTest.waitForExplicitFinish(); 45 SimpleTest.waitForFocus(() => { 46 function getNodeDescription(aNode) { 47 if (aNode === undefined) { 48 return "undefined"; 49 } 50 if (aNode === null) { 51 return "null"; 52 } 53 switch (aNode.nodeType) { 54 case Node.TEXT_NODE: 55 return `${aNode.nodeName}, "${aNode.data.replace(/\n/g, "\\n")}"`; 56 case Node.ELEMENT_NODE: 57 return `<${aNode.tagName.toLowerCase()}${ 58 aNode.getAttribute("id") !== null 59 ? ` id="${aNode.getAttribute("id")}"` 60 : "" 61 }${ 62 aNode.getAttribute("readonly") !== null 63 ? " readonly" 64 : "" 65 }${ 66 aNode.getAttribute("type") !== null 67 ? ` type="${aNode.getAttribute("type")}"` 68 : "" 69 }>`; 70 } 71 return aNode.nodeName; 72 } 73 74 const fm = SpecialPowers.Services.focus; 75 // XXX using selCon for checking the visibility of the caret, however, 76 // selCon is shared in document, cannot get the element of owner of the 77 // caret from javascript? 78 const selCon = SpecialPowers.wrap(window).docShell. 79 QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor). 80 getInterface(SpecialPowers.Ci.nsISelectionDisplay). 81 QueryInterface(SpecialPowers.Ci.nsISelectionController); 82 83 const primaryEditor = document.getElementById("primaryEditor"); 84 const spanInEditor = document.getElementById("spanInEditor"); 85 const otherEditor = document.getElementById("otherEditor"); 86 87 (function test_initial_state_on_load() { 88 is( 89 getSelection().rangeCount, 90 0, 91 "There should be no selection range at start" 92 ); 93 ok(!selCon.caretVisible, "The caret should not be visible in the document"); 94 // Move focus to <input type="text"> in the primary editor 95 primaryEditor.querySelector("input[type=text]").focus(); 96 is( 97 SpecialPowers.unwrap(fm.focusedElement), 98 primaryEditor.querySelector("input[type=text]"), 99 '<input type="text"> in the primary editor should get focus' 100 ); 101 todo_is( 102 getSelection().rangeCount, 103 0, 104 'There should be no selection range after calling focus() of <input type="text"> in the primary editor' 105 ); 106 ok( 107 selCon.caretVisible, 108 'The caret should not be visible in the <input type="text"> in the primary editor' 109 ); 110 })(); 111 // Move focus to the editor 112 (function test_move_focus_from_child_input_to_parent_editor() { 113 primaryEditor.focus(); 114 is( 115 SpecialPowers.unwrap(fm.focusedElement), 116 primaryEditor, 117 `The editor should steal focus from <input type="text"> in the primary editor with calling its focus() (got ${ 118 getNodeDescription(SpecialPowers.unwrap(fm.focusedElement)) 119 }` 120 ); 121 is( 122 getSelection().rangeCount, 123 1, 124 "There should be one range after focus() of the editor is called" 125 ); 126 const range = getSelection().getRangeAt(0); 127 ok( 128 range.collapsed, 129 "The selection range should be collapsed (immediately after calling focus() of the editor)" 130 ); 131 is( 132 range.startContainer, 133 primaryEditor.firstChild, 134 `The selection range should be in the first text node of the editor (immediately after calling focus() of the editor, got ${ 135 getNodeDescription(range.startContainer) 136 })` 137 ); 138 ok( 139 selCon.caretVisible, 140 "The caret should be visible in the primary editor (immediately after calling focus() of the editor)" 141 ); 142 })(); 143 // Move focus to other editor 144 (function test_move_focus_from_editor_to_the_other_editor() { 145 otherEditor.focus(); 146 is( 147 SpecialPowers.unwrap(fm.focusedElement), 148 otherEditor, 149 `The other editor should steal focus from the editor (got ${ 150 getNodeDescription(SpecialPowers.unwrap(fm.focusedElement)) 151 }` 152 ); 153 is( 154 getSelection().rangeCount, 155 1, 156 "There should be one range after focus() of the other editor is called" 157 ); 158 const range = getSelection().getRangeAt(0); 159 ok( 160 range.collapsed, 161 "The selection range should be collapsed (immediately after calling focus() of the other editor)" 162 ); 163 is( 164 range.startContainer, 165 otherEditor.firstChild, 166 `The selection range should be in the first text node of the editor (immediately after calling focus() of the other editor, got ${ 167 getNodeDescription(range.startContainer) 168 })` 169 ); 170 ok( 171 selCon.caretVisible, 172 "The caret should be visible in the primary editor (immediately after calling focus() of the other editor)" 173 ); 174 })(); 175 // Move focus to <input type="text"> in the primary editor 176 (function test_move_focus_from_the_other_editor_to_input_in_the_editor() { 177 primaryEditor.querySelector("input[type=text]").focus(); 178 is( 179 SpecialPowers.unwrap(fm.focusedElement), 180 primaryEditor.querySelector("input[type=text]"), 181 `<input type="text"> in the primary editor should steal focus from the other editor (got ${ 182 getNodeDescription(SpecialPowers.unwrap(fm.focusedElement)) 183 }`); 184 is( 185 getSelection().rangeCount, 186 1, 187 'There should be one range after focus() of the <input type="text"> in the primary editor is called' 188 ); 189 const range = getSelection().getRangeAt(0); 190 ok( 191 range.collapsed, 192 'The selection range should be collapsed (immediately after calling focus() of the <input type="text"> in the primary editor)' 193 ); 194 // XXX maybe, the caret can stay on the other editor if it's better. 195 is( 196 range.startContainer, 197 primaryEditor.firstChild, 198 `The selection range should be in the first text node of the editor (immediately after calling focus() of the <input type="text"> in the primary editor, got ${ 199 getNodeDescription(range.startContainer) 200 })` 201 ); 202 ok( 203 selCon.caretVisible, 204 'The caret should be visible in the <input type="text"> (immediately after calling focus() of the <input type="text"> in the primary editor)' 205 ); 206 })(); 207 // Move focus to the other editor again 208 (function test_move_focus_from_the_other_editor_to_the_editor_with_Selection_API() { 209 otherEditor.focus(); 210 is( 211 SpecialPowers.unwrap(fm.focusedElement), 212 otherEditor, 213 `The other editor should steal focus from the <input type="text"> in the primary editor with its focus() (got ${ 214 getNodeDescription(SpecialPowers.unwrap(fm.focusedElement)) 215 })` 216 ); 217 // Set selection to the span element in the primary editor. 218 getSelection().collapse(spanInEditor.firstChild, 5); 219 is( 220 getSelection().rangeCount, 221 1, 222 "There should be one selection range after collapsing selection into the <span> in the primary editor when the other editor has focus" 223 ); 224 is( 225 SpecialPowers.unwrap(fm.focusedElement), 226 primaryEditor, 227 `The editor should steal focus from the other editor with Selection API (got ${ 228 getNodeDescription(SpecialPowers.unwrap(fm.focusedElement)) 229 }` 230 ); 231 ok( 232 selCon.caretVisible, 233 "The caret should be visible in the primary editor (immediately after moving focus with Selection API)" 234 ); 235 })(); 236 // Move focus to the editor 237 (function test_move_focus() { 238 primaryEditor.focus(); 239 is( 240 SpecialPowers.unwrap(fm.focusedElement), 241 primaryEditor, 242 "The editor should keep having focus (immediately after calling focus() of the editor when it has focus)" 243 ); 244 is( 245 getSelection().rangeCount, 246 1, 247 "There should be one selection range in the primary editor (immediately after calling focus() of the editor when it has focus)" 248 ); 249 const range = getSelection().getRangeAt(0); 250 ok( 251 range.collapsed, 252 "The selection range should be collapsed in the primary editor (immediately after calling focus() of the editor when it has focus)" 253 ); 254 is( 255 range.startOffset, 256 5, 257 "The startOffset of the selection range shouldn't be changed (immediately after calling focus() of the editor when it has focus)" 258 ); 259 is( 260 range.startContainer.parentNode, 261 spanInEditor, 262 `The startContainer of the selection range shouldn't be changed (immediately after calling focus() of the editor when it has focus, got ${ 263 getNodeDescription(range.startContainer) 264 })`); 265 ok( 266 selCon.caretVisible, 267 "The caret should be visible in the primary editor (immediately after calling focus() of the editor when it has focus)" 268 ); 269 })(); 270 271 // Move focus to each focusable element in the primary editor. 272 function test_move_focus_from_the_editor(aTargetElement, aFocusable, aCaretVisible) { 273 primaryEditor.focus(); 274 is( 275 SpecialPowers.unwrap(fm.focusedElement), 276 primaryEditor, 277 `The editor should have focus at preparing to move focus to ${ 278 getNodeDescription(aTargetElement) 279 } (got ${ 280 getNodeDescription(SpecialPowers.unwrap(fm.focusedElement)) 281 }`); 282 aTargetElement.focus(); 283 if (aFocusable) { 284 is( 285 SpecialPowers.unwrap(fm.focusedElement), 286 aTargetElement, 287 `${ 288 getNodeDescription(aTargetElement) 289 } should get focus with calling its focus() (got ${ 290 getNodeDescription(SpecialPowers.unwrap(fm.focusedElement)) 291 }` 292 ); 293 } else { 294 is( 295 SpecialPowers.unwrap(fm.focusedElement), 296 primaryEditor, 297 `${ 298 getNodeDescription(aTargetElement) 299 } should not take focus with calling its focus() (got ${ 300 getNodeDescription(SpecialPowers.unwrap(fm.focusedElement)) 301 }` 302 ); 303 } 304 is( 305 selCon.caretVisible, 306 aCaretVisible, 307 `The caret ${ 308 aCaretVisible ? "should" : "should not" 309 } visible after calling focus() of ${ 310 getNodeDescription(aTargetElement) 311 }` 312 ); 313 } 314 test_move_focus_from_the_editor(primaryEditor.querySelector("input[type=text]"), true, true); 315 test_move_focus_from_the_editor(primaryEditor.querySelector("input[type=text][readonly]"), true, true); 316 // XXX shouldn't the caret become invisible? 317 test_move_focus_from_the_editor(primaryEditor.querySelector("input[type=button]"), true, true); 318 test_move_focus_from_the_editor(primaryEditor.querySelector("button"), true, true); 319 test_move_focus_from_the_editor(primaryEditor.querySelector("div[contenteditable=false]"), false, true); 320 test_move_focus_from_the_editor(primaryEditor.querySelector("div[contenteditable=false] > span"), false, true); 321 test_move_focus_from_the_editor(primaryEditor.querySelector("div[contenteditable=false] > input[type=text]"), true, true); 322 test_move_focus_from_the_editor(primaryEditor.querySelector("div[contenteditable=false] > input[type=text][readonly]"), true, true); 323 test_move_focus_from_the_editor(primaryEditor.querySelector("div[contenteditable=false] > input[type=button]"), true, false); 324 test_move_focus_from_the_editor(primaryEditor.querySelector("div[contenteditable=false] > button"), true, false); 325 test_move_focus_from_the_editor(spanInEditor, false, true); 326 test_move_focus_from_the_editor(document.querySelector("input[type=text]"), true, true); 327 test_move_focus_from_the_editor(document.querySelector("input[type=text][readonly]"), true, true); 328 test_move_focus_from_the_editor(document.querySelector("input[type=button]"), true, false); 329 test_move_focus_from_the_editor(document.querySelector("button"), true, false); 330 331 SimpleTest.finish(); 332 }); 333 </script> 334 </body> 335 </html>