input-events-get-target-ranges-deleting-range-across-editing-host-boundaries.tentative.html (33244B)
1 <!DOCTYPE html> 2 <meta charset="utf-8"> 3 <title>InputEvent.getTargetRanges() of deleting a range across editing host boundaries</title> 4 <div contenteditable></div> 5 <script src="input-events-get-target-ranges.js"></script> 6 <script src="/resources/testharness.js"></script> 7 <script src="/resources/testharnessreport.js"></script> 8 <script src="/resources/testdriver.js"></script> 9 <script src="/resources/testdriver-vendor.js"></script> 10 <script src="/resources/testdriver-actions.js"></script> 11 <script> 12 "use strict"; 13 14 // This test just check whether the deleted content range(s) and target ranges of `beforeinput` 15 // are match or different. The behavior should be defined by editing API. 16 // https://github.com/w3c/editing/issues/283 17 18 promise_test(async () => { 19 initializeTest('<p>ab[c<span contenteditable="false">no]n-editable</span>def</p>'); 20 await sendBackspaceKey(); 21 const kNothingDeletedCase = '<p>abc<span contenteditable="false">non-editable</span>def</p>'; 22 const kOnlyEditableTextDeletedCase = '<p>ab<span contenteditable="false">non-editable</span>def</p>'; 23 const kNonEditableElementDeleteCase = '<p>abdef</p>'; 24 if (gEditor.innerHTML === kNothingDeletedCase) { 25 if (gBeforeinput.length === 0) { 26 assert_true(true, "If nothing changed, `beforeinput` event may not be fired"); 27 assert_equals(gInput.length, 0, "If nothing changed, `input` event should not be fired"); 28 return; 29 } 30 assert_equals(gBeforeinput.length, 1, 31 "If nothing changed, `beforeinput` event can be fired for web apps can handle by themselves"); 32 assert_equals(gBeforeinput[0].cachedRanges.length, 0, 33 `If nothing changed but \`beforeinput\` event is fired, its target range should be empty array (got ${ 34 getRangeDescription(gBeforeinput[0].cachedRanges[0]) 35 })`); 36 assert_equals(gBeforeinput[0].inputType, "deleteContentBackward", 37 "If nothing changed but `beforeinput` event is fired, its input type should be deleteContentBackward"); 38 assert_equals(gInput.length, 0, 39 "If nothing changed but `beforeinput` event is fired, `input` event should not be fired"); 40 return; 41 } 42 if (gEditor.innerHTML === kOnlyEditableTextDeletedCase) { 43 assert_equals(gBeforeinput.length, 1, 44 "If only editable text is deleted, `beforeinput` event should be fired"); 45 assert_equals(gBeforeinput[0].cachedRanges.length, 1, 46 "If only editable text is deleted, `beforeinput` event should have a target range"); 47 assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]), 48 getRangeDescription({ 49 startContainer: gEditor.firstChild.firstChild, 50 startOffset: 2, 51 endContainer: gEditor.firstChild.firstChild, 52 endOffset: 3, 53 }), 54 "If only editable text is deleted, its target range should be the deleted text range"); 55 assert_equals(gBeforeinput[0].inputType, "deleteContent", 56 "If only editable text is deleted, its input type should be deleteContent"); 57 assert_equals(gInput.length, 1, 58 "If only editable text is deleted, `input` event should be fired"); 59 return; 60 } 61 if (gEditor.innerHTML === kNonEditableElementDeleteCase) { 62 assert_equals(gBeforeinput.length, 1, 63 "If editable text and non-editable element are deleted, `beforeinput` event should be fired"); 64 assert_equals(gBeforeinput[0].cachedRanges.length, 1, 65 "If editable text and non-editable element are deleted, `beforeinput` event should have a target range"); 66 assert_in_array(getRangeDescription(gBeforeinput[0].cachedRanges[0]), 67 [ 68 getRangeDescription({ 69 startContainer: gEditor.firstChild.firstChild, 70 startOffset: 2, 71 endContainer: gEditor.firstChild, 72 endOffset: 2, 73 }), 74 getRangeDescription({ 75 startContainer: gEditor.firstChild.firstChild, 76 startOffset: 2, 77 endContainer: gEditor.firstChild.firstChild.nextSibling, 78 endOffset: 0, 79 }), 80 ], 81 "If editable text and non-editable element are deleted, its target range should include the deleted non-editable element"); 82 assert_equals(gBeforeinput[0].inputType, "deleteContent", 83 "If editable text and non-editable element are deleted, its input type should be deleteContent"); 84 assert_equals(gInput.length, 1, 85 "If editable text and non-editable element are deleted, `input` event should be fired"); 86 return; 87 } 88 assert_in_array(gEditor.innerHTML, 89 [ 90 kNothingDeletedCase, 91 kOnlyEditableTextDeletedCase, 92 kNonEditableElementDeleteCase, 93 ], "The result content is unexpected"); 94 }, 'Backspace at "<p>ab[c<span contenteditable="false">no]n-editable</span>def</p>"'); 95 96 promise_test(async () => { 97 initializeTest('<p>abc<span contenteditable="false">non-[editable</span>de]f</p>'); 98 await sendBackspaceKey(); 99 const kNothingDeletedCase = '<p>abc<span contenteditable="false">non-editable</span>def</p>'; 100 const kOnlyEditableTextDeletedCase = '<p>abc<span contenteditable="false">non-editable</span>f</p>'; 101 const kNonEditableElementDeletedCase = '<p>abcf</p>';; 102 if (gEditor.innerHTML === kNothingDeletedCase) { 103 if (gBeforeinput.length === 0) { 104 assert_true(true, "If nothing changed, `beforeinput` event may not be fired"); 105 assert_equals(gInput.length, 0, "If nothing changed, `input` event should not be fired"); 106 return; 107 } 108 assert_equals(gBeforeinput.length, 1, 109 "If nothing changed, `beforeinput` event can be fired for web apps can handle by themselves"); 110 assert_equals(gBeforeinput[0].cachedRanges.length, 0, 111 `If nothing changed but \`beforeinput\` event is fired, its target range should be empty array (got ${ 112 getRangeDescription(gBeforeinput[0].cachedRanges[0]) 113 })`); 114 assert_equals(gBeforeinput[0].inputType, "deleteContentBackward", 115 "If nothing changed but `beforeinput` event is fired, its input type should be deleteContentBackward"); 116 assert_equals(gInput.length, 0, 117 "If nothing changed but `beforeinput` event is fired, `input` event should not be fired"); 118 return; 119 } 120 if (gEditor.innerHTML === kOnlyEditableTextDeletedCase) { 121 assert_equals(gBeforeinput.length, 1, 122 "If only editable text is deleted, `beforeinput` event should be fired"); 123 assert_equals(gBeforeinput[0].cachedRanges.length, 1, 124 "If only editable text is deleted, `beforeinput` event should have a target range"); 125 assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]), 126 getRangeDescription({ 127 startContainer: gEditor.firstChild.lastChild, 128 startOffset: 0, 129 endContainer: gEditor.firstChild.lastChild, 130 endOffset: 2, 131 }), 132 "If only editable text is deleted, its target range should be the deleted text range"); 133 assert_equals(gBeforeinput[0].inputType, "deleteContent", 134 "If only editable text is deleted, its input type should be deleteContent"); 135 assert_equals(gInput.length, 1, 136 "If only editable text is deleted, `input` event should be fired"); 137 return; 138 } 139 if (gEditor.innerHTML === kNonEditableElementDeletedCase) { 140 assert_equals(gBeforeinput.length, 1, 141 "If editable text and non-editable element are deleted, `beforeinput` event should be fired"); 142 assert_equals(gBeforeinput[0].cachedRanges.length, 1, 143 "If editable text and non-editable element are deleted, `beforeinput` event should have a target range"); 144 assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]), 145 getRangeDescription({ 146 startContainer: gEditor.firstChild, 147 startOffset: 1, 148 endContainer: gEditor.firstChild.lastChild, 149 endOffset: 2, 150 }), 151 "If editable text and non-editable element are deleted, its target range should include the deleted non-editable element"); 152 assert_equals(gBeforeinput[0].inputType, "deleteContent", 153 "If editable text and non-editable element are deleted, its input type should be deleteContent"); 154 assert_equals(gInput.length, 1, 155 "If editable text and non-editable element are deleted, `input` event should be fired"); 156 return; 157 } 158 assert_in_array(gEditor.innerHTML, 159 [ 160 kNothingDeletedCase, 161 kOnlyEditableTextDeletedCase, 162 kNonEditableElementDeletedCase, 163 ], "The result content is unexpected"); 164 }, 'Backspace at "<p>abc<span contenteditable="false">non-[editable</span>de]f</p>"'); 165 166 167 promise_test(async () => { 168 initializeTest('<p contenteditable="false"><span contenteditable>a[bc</span>non-editable<span contenteditable>de]f</span></p>'); 169 let firstRange = gSelection.getRangeAt(0); 170 if (!firstRange || 171 firstRange.startContainer != gEditor.firstChild.firstChild.firstChild || 172 firstRange.startOffset != 1 || 173 firstRange.endContainer != gEditor.firstChild.lastChild.firstChild || 174 firstRange.endOffset != 2) { 175 assert_true(true, "Selection couldn't set across editing host boundaries"); 176 return; 177 } 178 await sendBackspaceKey(); 179 const kNothingDeletedCase = '<p contenteditable="false"><span contenteditable="">abc</span>non-editable<span contenteditable="">def</span></p>'; 180 const kOnlyEditableContentDeletedCase = '<p contenteditable="false"><span contenteditable="">a</span>non-editable<span contenteditable="">f</span></p>'; 181 const kNonEditableElementDeletedCase = '<p contenteditable="false"><span contenteditable="">af</span></p>'; 182 const kDeleteEditableContentBeforeNonEditableContentCase = '<p contenteditable="false"><span contenteditable="">a</span>non-editable<span contenteditable="">def</span></p>'; 183 const kDeleteEditableContentAfterNonEditableContentCase = '<p contenteditable="false"><span contenteditable="">abc</span>non-editable<span contenteditable="">f</span></p>'; 184 if (gEditor.innerHTML === kNothingDeletedCase) { 185 if (gBeforeinput.length === 0) { 186 assert_true(true, "If nothing changed, `beforeinput` event may not be fired"); 187 assert_equals(gInput.length, 0, "If nothing changed, `input` event should not be fired"); 188 return; 189 } 190 assert_equals(gBeforeinput.length, 1, 191 "If nothing changed, `beforeinput` event can be fired for web apps can handle by themselves"); 192 assert_equals(gBeforeinput[0].cachedRanges.length, 0, 193 `If nothing changed but \`beforeinput\` event is fired, its target range should be empty array (got ${ 194 getRangeDescription(gBeforeinput[0].cachedRanges[0]) 195 })`); 196 assert_equals(gBeforeinput[0].inputType, "deleteContentBackward", 197 "If nothing changed but `beforeinput` event is fired, its input type should be deleteContentBackward"); 198 assert_equals(gInput.length, 0, 199 "If nothing changed but `beforeinput` event is fired, `input` event should not be fired"); 200 return; 201 } 202 if (gEditor.innerHTML === kOnlyEditableContentDeletedCase) { 203 assert_equals(gBeforeinput.length, 1, 204 "If only editable text is deleted, `beforeinput` event should be fired"); 205 assert_equals(gBeforeinput[0].cachedRanges.length, 2, 206 "If only editable text is deleted, `beforeinput` event should have 2 target ranges"); 207 assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]), 208 getRangeDescription({ 209 startContainer: gEditor.firstChild.firstChild.firstChild, 210 startOffset: 1, 211 endContainer: gEditor.lastChild, 212 endOffset: 3, 213 }), 214 "If only editable text is deleted, its first target range should be the deleted text range in the first text node"); 215 assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[1]), 216 getRangeDescription({ 217 startContainer: gEditor.firstChild.last.firstChild, 218 startOffset: 0, 219 endContainer: gEditor.firstChild.last.firstChild, 220 endOffset: 2, 221 }), 222 "If only editable text is deleted, its second target range should be the deleted text range in the last text node"); 223 assert_equals(gBeforeinput[0].inputType, "deleteContent", 224 "If only editable text is deleted, its input type should be deleteContent"); 225 assert_equals(gInput.length, 1, 226 "If only editable text is deleted, `input` event should be fired"); 227 return; 228 } 229 if (gEditor.innerHTML === kNonEditableElementDeletedCase) { 230 assert_equals(gBeforeinput.length, 1, 231 "If editable text and non-editable element are deleted, `beforeinput` event should be fired"); 232 assert_equals(gBeforeinput[0].cachedRanges.length, 1, 233 "If editable text and non-editable element are deleted, `beforeinput` event should have a target range"); 234 assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]), 235 getRangeDescription({ 236 startContainer: gEditor.firstChild.firstChild.firstChild, 237 startOffset: 1, 238 endContainer: gEditor.firstChild.lastChild.firstChild, 239 endOffset: 2, 240 }), 241 "If editable text and non-editable element are deleted, its target range should include the deleted non-editable element"); 242 assert_equals(gBeforeinput[0].inputType, "deleteContent", 243 "If editable text and non-editable element are deleted, its input type should be deleteContent"); 244 assert_equals(gInput.length, 1, 245 "If editable text and non-editable element are deleted, `input` event should be fired"); 246 return; 247 } 248 if (gEditor.innerHTML === kDeleteEditableContentBeforeNonEditableContentCase) { 249 assert_equals(gBeforeinput.length, 1, 250 "If editable text before non-editable element is deleted, `beforeinput` event should be fired"); 251 assert_equals(gBeforeinput[0].cachedRanges.length, 1, 252 "If editable text before non-editable element is deleted, `beforeinput` event should have a target range"); 253 assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]), 254 getRangeDescription({ 255 startContainer: gEditor.firstChild.firstChild.firstChild, 256 startOffset: 1, 257 endContainer: gEditor.firstChild.firstChild.firstChild, 258 endOffset: 3, 259 }), 260 "If editable text before non-editable element is deleted, its target range should be only the deleted text"); 261 assert_equals(gBeforeinput[0].inputType, "deleteContent", 262 "If editable text before non-editable element is deleted, its input type should be deleteContent"); 263 assert_equals(gInput.length, 1, 264 "If editable text before non-editable element is deleted, `input` event should be fired"); 265 return; 266 } 267 if (gEditor.innerHTML === kDeleteEditableContentAfterNonEditableContentCase) { 268 assert_equals(gBeforeinput.length, 1, 269 "If editable text after non-editable element is deleted, `beforeinput` event should be fired"); 270 assert_equals(gBeforeinput[0].cachedRanges.length, 1, 271 "If editable text after non-editable element is deleted, `beforeinput` event should have a target range"); 272 assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]), 273 getRangeDescription({ 274 startContainer: gEditor.firstChild.lastChild.firstChild, 275 startOffset: 1, 276 endContainer: gEditor.firstChild.lastChild.firstChild, 277 endOffset: 3, 278 }), 279 "If editable text after non-editable element is deleted, its target range should be only the deleted text"); 280 assert_equals(gBeforeinput[0].inputType, "deleteContent", 281 "If editable text after non-editable element is deleted, its input type should be deleteContent"); 282 assert_equals(gInput.length, 1, 283 "If editable text after non-editable element is deleted, `input` event should be fired"); 284 return; 285 } 286 assert_in_array(gEditor.innerHTML, 287 [ 288 kNothingDeletedCase, 289 kOnlyEditableContentDeletedCase, 290 kNonEditableElementDeletedCase, 291 kDeleteEditableContentBeforeNonEditableContentCase, 292 kDeleteEditableContentAfterNonEditableContentCase, 293 ], "The result content is unexpected"); 294 }, 'Backspace at "<p contenteditable="false"><span contenteditable>a[bc</span>non-editable<span contenteditable>de]f</span></p>"'); 295 296 promise_test(async () => { 297 initializeTest('<p>a[bc<span contenteditable="false">non-editable<span contenteditable>de]f</span></span></p>'); 298 let firstRange = gSelection.getRangeAt(0); 299 if (!firstRange || 300 firstRange.startContainer != gEditor.firstChild.firstChild || 301 firstRange.startOffset != 1 || 302 firstRange.endContainer != gEditor.querySelector("span span").firstChild || 303 firstRange.endOffset != 2) { 304 assert_true(true, "Selection couldn't set across editing host boundaries"); 305 return; 306 } 307 await sendBackspaceKey(); 308 const kNothingDeletedCase = '<p>abc<span contenteditable="false">non-editable<span contenteditable="">def</span></span></p>'; 309 const kOnlyEditableContentDeletedCase = '<p>a<span contenteditable="false">non-editable<span contenteditable="">f</span></span></p>'; 310 const kNonEditableElementDeletedCase1 = '<p>af</p>'; 311 const kNonEditableElementDeletedCase2 = '<p>a<span contenteditable="">f</span></p>'; 312 const kDeleteEditableContentBeforeNonEditableContentCase ='<p>a<span contenteditable="false">non-editable<span contenteditable="">def</span></span></p>'; 313 const kDeleteEditableContentAfterNonEditableContentCase ='<p>abc<span contenteditable="false">non-editable<span contenteditable="">f</span></span></p>'; 314 if (gEditor.innerHTML === kNothingDeletedCase) { 315 if (gBeforeinput.length === 0) { 316 assert_true(true, "If nothing changed, `beforeinput` event may not be fired"); 317 assert_equals(gInput.length, 0, "If nothing changed, `input` event should not be fired"); 318 return; 319 } 320 assert_equals(gBeforeinput.length, 1, 321 "If nothing changed, `beforeinput` event can be fired for web apps can handle by themselves"); 322 assert_equals(gBeforeinput[0].cachedRanges.length, 0, 323 `If nothing changed but \`beforeinput\` event is fired, its target range should be empty array (got ${ 324 getRangeDescription(gBeforeinput[0].cachedRanges[0]) 325 })`); 326 assert_equals(gBeforeinput[0].inputType, "deleteContentBackward", 327 "If nothing changed but `beforeinput` event is fired, its input type should be deleteContentBackward"); 328 assert_equals(gInput.length, 0, 329 "If nothing changed but `beforeinput` event is fired, `input` event should not be fired"); 330 return; 331 } 332 if (gEditor.innerHTML === kOnlyEditableContentDeletedCase) { 333 assert_equals(gBeforeinput.length, 1, 334 "If only editable text is deleted, `beforeinput` event should be fired"); 335 assert_equals(gBeforeinput[0].cachedRanges.length, 2, 336 "If only editable text is deleted, `beforeinput` event should have 2 target ranges"); 337 assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]), 338 getRangeDescription({ 339 startContainer: gEditor.firstChild.firstChild, 340 startOffset: 1, 341 endContainer: gEditor.firstChild.firstChild, 342 endOffset: 3, 343 }), 344 "If only editable text is deleted, its first target range should be the deleted text range in the first text node"); 345 assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[1]), 346 getRangeDescription({ 347 startContainer: gEditor.querySelector("span span").firstChild, 348 startOffset: 0, 349 endContainer: gEditor.querySelector("span span").firstChild, 350 endOffset: 2, 351 }), 352 "If only editable text is deleted, its second target range should be the deleted text range in the last text node"); 353 assert_equals(gBeforeinput[0].inputType, "deleteContent", 354 "If only editable text is deleted, its input type should be deleteContent"); 355 assert_equals(gInput.length, 1, 356 "If only editable text is deleted, `input` event should be fired"); 357 return; 358 } 359 if (gEditor.innerHTML === kNonEditableElementDeletedCase1) { 360 assert_equals(gBeforeinput.length, 1, 361 "If editable text and non-editable element are deleted, `beforeinput` event should be fired"); 362 assert_equals(gBeforeinput[0].cachedRanges.length, 1, 363 "If editable text and non-editable element are deleted, `beforeinput` event should have a target range"); 364 // XXX If the text nodes are merged, we need to cache it for here. 365 assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]), 366 getRangeDescription({ 367 startContainer: gEditor.firstChild.firstChild, 368 startOffset: 1, 369 endContainer: gEditor.firstChild.lastChild, 370 endOffset: 2, 371 }), 372 "If editable text and non-editable element are deleted, its target range should include the deleted non-editable element"); 373 assert_equals(gBeforeinput[0].inputType, "deleteContent", 374 "If editable text and non-editable element are deleted, its input type should be deleteContent"); 375 assert_equals(gInput.length, 1, 376 "If editable text and non-editable element are deleted, `input` event should be fired"); 377 return; 378 } 379 if (gEditor.innerHTML === kNonEditableElementDeletedCase2) { 380 assert_equals(gBeforeinput.length, 1, 381 "If editable text and non-editable element are deleted, `beforeinput` event should be fired"); 382 assert_equals(gBeforeinput[0].cachedRanges.length, 1, 383 "If editable text and non-editable element are deleted, `beforeinput` event should have a target range"); 384 assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]), 385 getRangeDescription({ 386 startContainer: gEditor.firstChild, 387 startOffset: 1, 388 endContainer: gEditor.querySelector("span").firstChild, 389 endOffset: 2, 390 }), 391 "If editable text and non-editable element are deleted, its target range should include the deleted non-editable element"); 392 assert_equals(gBeforeinput[0].inputType, "deleteContent", 393 "If editable text and non-editable element are deleted, its input type should be deleteContent"); 394 assert_equals(gInput.length, 1, 395 "If editable text and non-editable element are deleted, `input` event should be fired"); 396 return; 397 } 398 if (gEditor.innerHTML === kDeleteEditableContentBeforeNonEditableContentCase) { 399 assert_equals(gBeforeinput.length, 1, 400 "If editable text before non-editable element is deleted, `beforeinput` event should be fired"); 401 assert_equals(gBeforeinput[0].cachedRanges.length, 1, 402 "If editable text before non-editable element is deleted, `beforeinput` event should have a target range"); 403 assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]), 404 getRangeDescription({ 405 startContainer: gEditor.firstChild.firstChild, 406 startOffset: 1, 407 endContainer: gEditor.firstChild.firstChild, 408 endOffset: 3, 409 }), 410 "If editable text before non-editable element is deleted, its target range should be only the deleted text"); 411 assert_equals(gBeforeinput[0].inputType, "deleteContent", 412 "If editable text before non-editable element is deleted, its input type should be deleteContent"); 413 assert_equals(gInput.length, 1, 414 "If editable text before non-editable element is deleted, `input` event should be fired"); 415 return; 416 } 417 if (gEditor.innerHTML === kDeleteEditableContentAfterNonEditableContentCase) { 418 assert_equals(gBeforeinput.length, 1, 419 "If editable text after non-editable element is deleted, `beforeinput` event should be fired"); 420 assert_equals(gBeforeinput[0].cachedRanges.length, 1, 421 "If editable text after non-editable element is deleted, `beforeinput` event should have a target range"); 422 assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]), 423 getRangeDescription({ 424 startContainer: gEditor.querySelector("span").firstChild, 425 startOffset: 0, 426 endContainer: gEditor.querySelector("span").firstChild, 427 endOffset: 2, 428 }), 429 "If editable text after non-editable element is deleted, its target range should be only the deleted text"); 430 assert_equals(gBeforeinput[0].inputType, "deleteContent", 431 "If editable text after non-editable element is deleted, its input type should be deleteContent"); 432 assert_equals(gInput.length, 1, 433 "If editable text after non-editable element is deleted, `input` event should be fired"); 434 return; 435 } 436 assert_in_array(gEditor.innerHTML, 437 [ 438 kNothingDeletedCase, 439 kOnlyEditableContentDeletedCase, 440 kNonEditableElementDeletedCase1, 441 kNonEditableElementDeletedCase2, 442 kDeleteEditableContentBeforeNonEditableContentCase, 443 kDeleteEditableContentAfterNonEditableContentCase, 444 ], "The result content is unexpected"); 445 }, 'Backspace at "<p>a[bc<span contenteditable="false">non-editable<span contenteditable>de]f</span></span></p>"'); 446 447 promise_test(async () => { 448 initializeTest('<p><span contenteditable="false"><span contenteditable>a[bc</span>non-editable</span>de]f</p>'); 449 let firstRange = gSelection.getRangeAt(0); 450 if (!firstRange || 451 firstRange.startContainer != gEditor.querySelector("span span").firstChild || 452 firstRange.startOffset != 1 || 453 firstRange.endContainer != gEditor.firstChild.lastChild.firstChild || 454 firstRange.endOffset != 2) { 455 assert_true(true, "Selection couldn't set across editing host boundaries"); 456 return; 457 } 458 await sendBackspaceKey(); 459 const kNothingDeletedCase = '<p><span contenteditable="false"><span contenteditable="">abc</span>non-editable</span>def</p>'; 460 const kOnlyEditableContentDeletedCase = '<p><span contenteditable="false"><span contenteditable="">a</span>non-editable</span>f</p>'; 461 const kNonEditableElementDeletedCase1 = '<p><span contenteditable="false"><span contenteditable="">af</span></span></p>'; 462 const kNonEditableElementDeletedCase2 = '<p><span contenteditable="false"><span contenteditable="">a</span></span>f</p>'; 463 const kDeleteEditableContentBeforeNonEditableContentCase = '<p><span contenteditable="false"><span contenteditable="">a</span>non-editable</span>def</p>'; 464 const kDeleteEditableContentAfterNonEditableContentCase = '<p><span contenteditable="false"><span contenteditable="">abc</span>non-editable</span>f</p>'; 465 if (gEditor.innerHTML === kNothingDeletedCase) { 466 if (gBeforeinput.length === 0) { 467 assert_true(true, "If nothing changed, `beforeinput` event may not be fired"); 468 assert_equals(gInput.length, 0, "If nothing changed, `input` event should not be fired"); 469 return; 470 } 471 assert_equals(gBeforeinput.length, 1, 472 "If nothing changed, `beforeinput` event can be fired for web apps can handle by themselves"); 473 assert_equals(gBeforeinput[0].cachedRanges.length, 0, 474 `If nothing changed but \`beforeinput\` event is fired, its target range should be empty array (got ${ 475 getRangeDescription(gBeforeinput[0].cachedRanges[0]) 476 })`); 477 assert_equals(gBeforeinput[0].inputType, "deleteContentBackward", 478 "If nothing changed but `beforeinput` event is fired, its input type should be deleteContentBackward"); 479 assert_equals(gInput.length, 0, 480 "If nothing changed but `beforeinput` event is fired, `input` event should not be fired"); 481 return; 482 } 483 if (gEditor.innerHTML === kOnlyEditableContentDeletedCase) { 484 assert_equals(gBeforeinput.length, 1, 485 "If only editable text is deleted, `beforeinput` event should be fired"); 486 assert_equals(gBeforeinput[0].cachedRanges.length, 2, 487 "If only editable text is deleted, `beforeinput` event should have 2 target ranges"); 488 assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]), 489 getRangeDescription({ 490 startContainer: gEditor.querySelector("span span").firstChild, 491 startOffset: 1, 492 endContainer: gEditor.querySelector("span span").firstChild, 493 endOffset: 3, 494 }), 495 "If only editable text is deleted, its first target range should be the deleted text range in the first text node"); 496 assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[1]), 497 getRangeDescription({ 498 startContainer: gEditor.firstChild.lastChild, 499 startOffset: 0, 500 endContainer: gEditor.firstChild.lastChild, 501 endOffset: 2, 502 }), 503 "If only editable text is deleted, its second target range should be the deleted text range in the last text node"); 504 assert_equals(gBeforeinput[0].inputType, "deleteContent", 505 "If only editable text is deleted, its input type should be deleteContent"); 506 assert_equals(gInput.length, 1, 507 "If only editable text is deleted, `input` event should be fired"); 508 return; 509 } 510 if (gEditor.innerHTML === kNonEditableElementDeletedCase1) { 511 assert_equals(gBeforeinput.length, 1, 512 "If editable text and non-editable element are deleted, `beforeinput` event should be fired"); 513 assert_equals(gBeforeinput[0].cachedRanges.length, 1, 514 "If editable text and non-editable element are deleted, `beforeinput` event should have a target range"); 515 // XXX If the text nodes are merged, we need to cache it for here. 516 assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]), 517 getRangeDescription({ 518 startContainer: gEditor.querySelector("span span").firstChild, 519 startOffset: 1, 520 endContainer: gEditor.querySelector("span span").lastChild, 521 endOffset: 2, 522 }), 523 "If editable text and non-editable element are deleted, its target range should include the deleted non-editable element"); 524 assert_equals(gBeforeinput[0].inputType, "deleteContent", 525 "If editable text and non-editable element are deleted, its input type should be deleteContent"); 526 assert_equals(gInput.length, 1, 527 "If editable text and non-editable element are deleted, `input` event should be fired"); 528 return; 529 } 530 if (gEditor.innerHTML === kNonEditableElementDeletedCase2) { 531 assert_equals(gBeforeinput.length, 1, 532 "If editable text and non-editable element are deleted, `beforeinput` event should be fired"); 533 assert_equals(gBeforeinput[0].cachedRanges.length, 1, 534 "If editable text and non-editable element are deleted, `beforeinput` event should have a target range"); 535 assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]), 536 getRangeDescription({ 537 startContainer: gEditor.querySelector("span span").firstChild, 538 startOffset: 1, 539 endContainer: gEditor.firstChild.lastChild, 540 endOffset: 2, 541 }), 542 "If editable text and non-editable element are deleted, its target range should include the deleted non-editable element"); 543 assert_equals(gBeforeinput[0].inputType, "deleteContent", 544 "If editable text and non-editable element are deleted, its input type should be deleteContent"); 545 assert_equals(gInput.length, 1, 546 "If editable text and non-editable element are deleted, `input` event should be fired"); 547 return; 548 } 549 if (gEditor.innerHTML === kDeleteEditableContentBeforeNonEditableContentCase) { 550 assert_equals(gBeforeinput.length, 1, 551 "If editable text before non-editable element is deleted, `beforeinput` event should be fired"); 552 assert_equals(gBeforeinput[0].cachedRanges.length, 1, 553 "If editable text before non-editable element is deleted, `beforeinput` event should have a target range"); 554 assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]), 555 getRangeDescription({ 556 startContainer: gEditor.querySelector("span span").firstChild, 557 startOffset: 1, 558 endContainer: gEditor.querySelector("span span").firstChild, 559 endOffset: 3, 560 }), 561 "If editable text before non-editable element is deleted, its target range should be only the deleted text"); 562 assert_equals(gBeforeinput[0].inputType, "deleteContent", 563 "If editable text before non-editable element is deleted, its input type should be deleteContent"); 564 assert_equals(gInput.length, 1, 565 "If editable text before non-editable element is deleted, `input` event should be fired"); 566 return; 567 } 568 if (gEditor.innerHTML === kDeleteEditableContentAfterNonEditableContentCase) { 569 assert_equals(gBeforeinput.length, 1, 570 "If editable text after non-editable element is deleted, `beforeinput` event should be fired"); 571 assert_equals(gBeforeinput[0].cachedRanges.length, 1, 572 "If editable text after non-editable element is deleted, `beforeinput` event should have a target range"); 573 assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]), 574 getRangeDescription({ 575 startContainer: gEditor.firstChild.lastChild, 576 startOffset: 0, 577 endContainer: gEditor.firstChild.lastChild, 578 endOffset: 2, 579 }), 580 "If editable text after non-editable element is deleted, its target range should be only the deleted text"); 581 assert_equals(gBeforeinput[0].inputType, "deleteContent", 582 "If editable text after non-editable element is deleted, its input type should be deleteContent"); 583 assert_equals(gInput.length, 1, 584 "If editable text after non-editable element is deleted, `input` event should be fired"); 585 return; 586 } 587 assert_in_array(gEditor.innerHTML, 588 [ 589 kNothingDeletedCase, 590 kOnlyEditableContentDeletedCase, 591 kNonEditableElementDeletedCase1, 592 kNonEditableElementDeletedCase2, 593 kDeleteEditableContentBeforeNonEditableContentCase, 594 kDeleteEditableContentAfterNonEditableContentCase, 595 ], "The result content is unexpected"); 596 }, 'Backspace at "<p><span contenteditable="false"><span contenteditable>a[bc</span>non-editable</span>de]f</p>"'); 597 598 </script>