browser_editabletext.js (9345B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 /* import-globals-from ../../mochitest/states.js */ 8 loadScripts({ name: "states.js", dir: MOCHITESTS_DIR }); 9 10 async function testEditable(browser, acc, aBefore = "", aAfter = "") { 11 async function resetInput() { 12 if (acc.childCount <= 1) { 13 return; 14 } 15 16 let emptyInputEvent = waitForEvent(EVENT_TEXT_VALUE_CHANGE, "input"); 17 await invokeContentTask(browser, [], async () => { 18 content.document.getElementById("input").innerHTML = ""; 19 }); 20 21 await emptyInputEvent; 22 } 23 24 // //////////////////////////////////////////////////////////////////////// 25 // insertText 26 await testInsertText(acc, "hello", 0, aBefore.length); 27 await isFinalValueCorrect(browser, acc, [aBefore, "hello", aAfter]); 28 await testInsertText(acc, "ma ", 0, aBefore.length); 29 await isFinalValueCorrect(browser, acc, [aBefore, "ma hello", aAfter]); 30 await testInsertText(acc, "ma", 2, aBefore.length); 31 await isFinalValueCorrect(browser, acc, [aBefore, "mama hello", aAfter]); 32 await testInsertText(acc, " hello", 10, aBefore.length); 33 await isFinalValueCorrect(browser, acc, [ 34 aBefore, 35 "mama hello hello", 36 aAfter, 37 ]); 38 39 // //////////////////////////////////////////////////////////////////////// 40 // deleteText 41 await testDeleteText(acc, 0, 5, aBefore.length); 42 await isFinalValueCorrect(browser, acc, [aBefore, "hello hello", aAfter]); 43 await testDeleteText(acc, 5, 6, aBefore.length); 44 await isFinalValueCorrect(browser, acc, [aBefore, "hellohello", aAfter]); 45 await testDeleteText(acc, 5, 10, aBefore.length); 46 await isFinalValueCorrect(browser, acc, [aBefore, "hello", aAfter]); 47 await testDeleteText(acc, 0, 5, aBefore.length); 48 await isFinalValueCorrect(browser, acc, [aBefore, "", aAfter]); 49 50 // XXX: clipboard operation tests don't work well with editable documents. 51 if (acc.role == ROLE_DOCUMENT) { 52 return; 53 } 54 55 await resetInput(); 56 57 // copyText and pasteText 58 await testInsertText(acc, "hello", 0, aBefore.length); 59 await isFinalValueCorrect(browser, acc, [aBefore, "hello", aAfter]); 60 61 await testCopyText(acc, 0, 1, aBefore.length, browser, "h"); 62 await testPasteText(acc, 1, aBefore.length); 63 await isFinalValueCorrect(browser, acc, [aBefore, "hhello", aAfter]); 64 65 await testCopyText(acc, 5, 6, aBefore.length, browser, "o"); 66 await testPasteText(acc, 6, aBefore.length); 67 await isFinalValueCorrect(browser, acc, [aBefore, "hhelloo", aAfter]); 68 69 await testCopyText(acc, 2, 3, aBefore.length, browser, "e"); 70 await testPasteText(acc, 1, aBefore.length); 71 await isFinalValueCorrect(browser, acc, [aBefore, "hehelloo", aAfter]); 72 73 // cut & paste 74 await testCutText(acc, 0, 1, aBefore.length); 75 await isFinalValueCorrect(browser, acc, [aBefore, "ehelloo", aAfter]); 76 await testPasteText(acc, 2, aBefore.length); 77 await isFinalValueCorrect(browser, acc, [aBefore, "ehhelloo", aAfter]); 78 79 await testCutText(acc, 3, 4, aBefore.length); 80 await isFinalValueCorrect(browser, acc, [aBefore, "ehhlloo", aAfter]); 81 await testPasteText(acc, 6, aBefore.length); 82 await isFinalValueCorrect(browser, acc, [aBefore, "ehhlloeo", aAfter]); 83 84 await testCutText(acc, 0, 8, aBefore.length); 85 await isFinalValueCorrect(browser, acc, [aBefore, "", aAfter]); 86 87 await resetInput(); 88 89 // //////////////////////////////////////////////////////////////////////// 90 // setTextContents 91 await testSetTextContents(acc, "hello", aBefore.length, [ 92 EVENT_TEXT_INSERTED, 93 ]); 94 await isFinalValueCorrect(browser, acc, [aBefore, "hello", aAfter]); 95 await testSetTextContents(acc, "katze", aBefore.length, [ 96 EVENT_TEXT_REMOVED, 97 EVENT_TEXT_INSERTED, 98 ]); 99 await isFinalValueCorrect(browser, acc, [aBefore, "katze", aAfter]); 100 } 101 102 addAccessibleTask( 103 `<input id="input"/>`, 104 async function (browser, docAcc) { 105 await testEditable(browser, findAccessibleChildByID(docAcc, "input")); 106 }, 107 { chrome: true, topLevel: true } 108 ); 109 110 addAccessibleTask( 111 `<div id="input" contenteditable="true" role="textbox"></div>`, 112 async function (browser, docAcc) { 113 await testEditable( 114 browser, 115 findAccessibleChildByID(docAcc, "input"), 116 "", 117 "" 118 ); 119 }, 120 { chrome: true, topLevel: false /* bug 1834129 */ } 121 ); 122 123 addAccessibleTask( 124 `<style> 125 #input::after { 126 content: "pseudo element"; 127 } 128 </style> 129 <div id="input" contenteditable="true" role="textbox"></div>`, 130 async function (browser, docAcc) { 131 await testEditable( 132 browser, 133 findAccessibleChildByID(docAcc, "input"), 134 "", 135 "pseudo element" 136 ); 137 }, 138 { chrome: true, topLevel: false /* bug 1834129 */ } 139 ); 140 141 addAccessibleTask( 142 `<style> 143 #input::before { 144 content: "pseudo element"; 145 } 146 </style> 147 <div id="input" contenteditable="true" role="textbox"></div>`, 148 async function (browser, docAcc) { 149 await testEditable( 150 browser, 151 findAccessibleChildByID(docAcc, "input"), 152 "pseudo element" 153 ); 154 }, 155 { chrome: true, topLevel: false /* bug 1834129 */ } 156 ); 157 158 addAccessibleTask( 159 `<style> 160 #input::before { 161 content: "before"; 162 } 163 #input::after { 164 content: "after"; 165 } 166 </style> 167 <div id="input" contenteditable="true" role="textbox"></div>`, 168 async function (browser, docAcc) { 169 await testEditable( 170 browser, 171 findAccessibleChildByID(docAcc, "input"), 172 "before", 173 "after" 174 ); 175 }, 176 { chrome: true, topLevel: false /* bug 1834129 */ } 177 ); 178 179 addAccessibleTask( 180 `<style> 181 br { 182 position: fixed; 183 } 184 </style> 185 <div id="input" contenteditable="true" role="textbox"></div>`, 186 async function (browser, docAcc) { 187 document.execCommand("insertText", false, "a"); 188 document.execCommand("delete"); 189 await testEditable(browser, findAccessibleChildByID(docAcc, "input")); 190 }, 191 { chrome: true, topLevel: false /* bug 1834129 */ } 192 ); 193 194 addAccessibleTask( 195 `<style> 196 #input { 197 white-space: pre; 198 } 199 #input::before { 200 content: "before"; 201 } 202 #input::after { 203 content: "after"; 204 } 205 </style> 206 <div id="input" contenteditable="plaintext-only" role="textbox"></div>`, 207 async function (browser, docAcc) { 208 await testEditable( 209 browser, 210 findAccessibleChildByID(docAcc, "input"), 211 "before", 212 "after" 213 ); 214 }, 215 { chrome: true, topLevel: false /* bug 1834129 */ } 216 ); 217 218 addAccessibleTask( 219 ``, 220 async function (browser, docAcc) { 221 await testEditable(browser, docAcc); 222 }, 223 { 224 chrome: true, 225 topLevel: true, 226 contentDocBodyAttrs: { contentEditable: "true" }, 227 } 228 ); 229 230 /** 231 * Test PasteText replacement of selected text. 232 */ 233 addAccessibleTask( 234 `<input id="input" value="abcdef">`, 235 async function testPasteTextReplace(browser, docAcc) { 236 const input = findAccessibleChildByID(docAcc, "input"); 237 let focused = waitForEvent(EVENT_FOCUS, input); 238 info("Focusing input"); 239 input.takeFocus(); 240 await focused; 241 info("Copying ef"); 242 input.QueryInterface(nsIAccessibleEditableText); 243 let selected = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, input); 244 input.copyText(4, 6); 245 await selected; 246 info("Selecting bc"); 247 selected = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, input); 248 await invokeContentTask(browser, [], () => { 249 const inputDom = content.document.getElementById("input"); 250 inputDom.selectionStart = 1; 251 inputDom.selectionEnd = 3; 252 }); 253 await selected; 254 info("Pasting at caret"); 255 let changed = waitForEvents([ 256 [EVENT_TEXT_REMOVED, input], 257 [EVENT_TEXT_INSERTED, input], 258 [EVENT_TEXT_VALUE_CHANGE, input], 259 ]); 260 input.pasteText(nsIAccessibleText.TEXT_OFFSET_CARET); 261 await changed; 262 is(input.value, "aefdef", "input value correct after pasting"); 263 } 264 ); 265 266 addAccessibleTask( 267 `<div id="editable" contenteditable="true"><p id="p">one</p></div>`, 268 async function testNoRoleEditable(browser, docAcc) { 269 const editable = findAccessibleChildByID(docAcc, "editable"); 270 is(editable.value, "one", "initial value correct"); 271 ok(true, "Set initial text"); 272 await invokeContentTask(browser, [], () => { 273 content.document.getElementById("p").firstChild.data = "two"; 274 }); 275 await untilCacheIs(() => editable.value, "two", "value changed correctly"); 276 277 function isMultiline() { 278 let extState = {}; 279 editable.getState({}, extState); 280 return ( 281 !!(extState.value & EXT_STATE_MULTI_LINE) && 282 !(extState.value & EXT_STATE_SINGLE_LINE) 283 ); 284 } 285 286 ok(isMultiline(), "Editable is in multiline state"); 287 await invokeSetAttribute(browser, "editable", "aria-multiline", "false"); 288 await untilCacheOk(() => !isMultiline(), "editable is in singleline state"); 289 290 await invokeSetAttribute(browser, "editable", "aria-multiline"); 291 await untilCacheOk(() => isMultiline(), "editable is in multi-line again"); 292 293 await invokeSetAttribute(browser, "editable", "contenteditable"); 294 await untilCacheOk(() => { 295 let extState = {}; 296 editable.getState({}, extState); 297 return ( 298 !(extState.value & EXT_STATE_MULTI_LINE) && 299 !(extState.value & EXT_STATE_SINGLE_LINE) 300 ); 301 }, "editable should have neither multi-line nor single-line state"); 302 }, 303 { 304 chrome: true, 305 topLevel: true, 306 } 307 );