input-events-exec-command.html (12520B)
1 <!DOCTYPE html> 2 <meta charset="utf-8"> 3 <title>execCommand() should only trigger 'input'</title> 4 <script src="/resources/testharness.js"></script> 5 <script src="/resources/testharnessreport.js"></script> 6 <div id="txt" contenteditable></div> 7 <script> 8 (function() { 9 let lastBeforeInputType = ''; 10 let lastBeforeInputData = ''; 11 let lastBeforeInputDataTransfer = undefined; 12 let lastInputType = ''; 13 let lastInputData = ''; 14 let lastInputDataTransfer = undefined; 15 const txt = document.getElementById('txt'); 16 txt.addEventListener('beforeinput', function(event) { 17 assert_true(event instanceof InputEvent); 18 assert_false(event.isComposing); 19 lastBeforeInputType = event.inputType; 20 lastBeforeInputData = event.data; 21 lastBeforeInputDataTransfer = event.dataTransfer; 22 }); 23 txt.addEventListener('input', function(event) { 24 assert_true(event instanceof InputEvent); 25 assert_false(event.isComposing); 26 lastInputType = event.inputType; 27 lastInputData = event.data; 28 lastInputDataTransfer = event.dataTransfer; 29 }); 30 31 const NO_INPUT_EVENT_FIRED = 'NO_INPUT_EVENT_FIRED'; 32 function testExecCommandInputType(command, args, inputType, data, dataTransfer) { 33 const description = `Calling execCommand("${command}", false, ${args})`; 34 lastBeforeInputType = NO_INPUT_EVENT_FIRED; 35 lastBeforeInputData = NO_INPUT_EVENT_FIRED; 36 lastBeforeInputDataTransfer = NO_INPUT_EVENT_FIRED; 37 lastInputType = NO_INPUT_EVENT_FIRED; 38 lastInputData = NO_INPUT_EVENT_FIRED; 39 lastInputDataTransfer = NO_INPUT_EVENT_FIRED; 40 test(function() { 41 try { 42 document.execCommand(command, false, args); 43 } catch (e) { 44 assert_true(false, `execCommand shouldn't cause any exception: ${e}`); 45 } 46 }, description + " (calling execCommand)"); 47 test(function() { 48 assert_equals(lastBeforeInputType, NO_INPUT_EVENT_FIRED); 49 assert_equals(lastBeforeInputData, NO_INPUT_EVENT_FIRED); 50 assert_equals(lastBeforeInputDataTransfer, NO_INPUT_EVENT_FIRED); 51 }, description + " (shouldn't fire beforeinput)"); 52 test(function() { 53 assert_equals(lastInputType, inputType); 54 }, description + " (inputType value)"); 55 if (lastInputType === NO_INPUT_EVENT_FIRED) { 56 return; 57 } 58 test(function() { 59 assert_equals(lastInputData, data); 60 }, description + " (data value)"); 61 if (dataTransfer === null) { 62 test(function() { 63 assert_equals(lastInputDataTransfer, dataTransfer, 64 `${description} should produce dataTransfer: null`); 65 }, description + " (dataTransfer value)"); 66 } else { 67 for (let item of dataTransfer) { 68 test(function() { 69 try { 70 assert_equals(lastInputDataTransfer.getData(item.type), item.data, 71 `${description} should produce dataTransfer.getData(${item.type}): ${item.data}`); 72 } catch (e) { 73 assert_true(false, `calling dataTransfer.getData(${item.type}) caused exception: ${e}`); 74 } 75 }, `${description} (dataTransfer value, ${item.type})`); 76 test(function() { 77 try { 78 lastInputDataTransfer.clearData(item.type); 79 } catch (e) { 80 assert_true(false, `calling dataTransfer.clearData(${item.type}) caused exception: ${e}`); 81 } 82 assert_equals(lastInputDataTransfer.getData(item.type), item.data, 83 `${description} dataTransfer.clearData(${item.type}) should do nothing`); 84 }, `${description} (dataTransfer.clearData(${item.type}))`); 85 test(function() { 86 try { 87 lastInputDataTransfer.setData(item.type, "foo"); 88 } catch (e) { 89 assert_true(false, `calling dataTransfer.setData(${item.type}) caused exception: ${e}`); 90 } 91 assert_equals(lastInputDataTransfer.getData(item.type), item.data, 92 `${description} dataTransfer.setData(${item.type}, "foo") should do nothing`); 93 }, `${description} (dataTransfer.setData(${item.type}))`); 94 } 95 } 96 } 97 98 txt.focus(); 99 // InsertText 100 testExecCommandInputType('insertText', 'a', 'insertText', 'a', null); 101 testExecCommandInputType('insertText', 'bc', 'insertText', 'bc', null); 102 test(function() { 103 assert_equals(txt.innerHTML, 'abc'); 104 }, "execCommand(\"insertText\") should insert \"abc\" into the editor"); 105 testExecCommandInputType('insertOrderedList', null, 'insertOrderedList', null, null); 106 test(function() { 107 assert_equals(txt.innerHTML, '<ol><li>abc</li></ol>'); 108 }, "execCommand(\"insertOrderedList\") should make <ol> and wrap the text with it"); 109 testExecCommandInputType('insertUnorderedList', null, 'insertUnorderedList', null, null); 110 test(function() { 111 assert_equals(txt.innerHTML, '<ul><li>abc</li></ul>'); 112 }, "execCommand(\"insertUnorderedList\") should make <ul> and wrap the text with it"); 113 testExecCommandInputType('insertLineBreak', null, 'insertLineBreak', null, null); 114 testExecCommandInputType('insertParagraph', null, 'insertParagraph', null, null); 115 txt.innerHTML = ''; 116 testExecCommandInputType('insertHorizontalRule', null, 'insertHorizontalRule', null, null); 117 118 // Styling 119 txt.innerHTML = 'abc'; 120 var selection = window.getSelection(); 121 selection.collapse(txt, 0); 122 selection.extend(txt, 1); 123 testExecCommandInputType('bold', null, 'formatBold', null, null); 124 test(function() { 125 assert_equals(txt.innerHTML, '<b>abc</b>'); 126 }, "execCommand(\"bold\") should wrap selected text with <b> element"); 127 testExecCommandInputType('italic', null, 'formatItalic', null, null); 128 test(function() { 129 assert_equals(txt.innerHTML, '<b><i>abc</i></b>'); 130 }, "execCommand(\"italic\") should wrap selected text with <i> element"); 131 testExecCommandInputType('underline', null, 'formatUnderline', null, null); 132 test(function() { 133 assert_equals(txt.innerHTML, '<b><i><u>abc</u></i></b>'); 134 }, "execCommand(\"underline\") should wrap selected text with <u> element"); 135 testExecCommandInputType('strikeThrough', null, 'formatStrikeThrough', null, null); 136 test(function() { 137 assert_equals(txt.innerHTML, '<b><i><u><strike>abc</strike></u></i></b>'); 138 }, "execCommand(\"strikeThrough\") should wrap selected text with <strike> element"); 139 testExecCommandInputType('superscript', null, 'formatSuperscript', null, null); 140 test(function() { 141 assert_equals(txt.innerHTML, '<b><i><u><strike><sup>abc</sup></strike></u></i></b>'); 142 }, "execCommand(\"superscript\") should wrap selected text with <sup> element"); 143 testExecCommandInputType('subscript', null, 'formatSubscript', null, null); 144 test(function() { 145 assert_equals(txt.innerHTML, '<b><i><u><strike><sub>abc</sub></strike></u></i></b>'); 146 }, "execCommand(\"subscript\") should wrap selected text with <sub> element"); 147 txt.innerHTML = 'abc'; 148 selection.collapse(txt, 0); 149 selection.extend(txt, 1); 150 for (let test of [{command: 'backColor', inputType: 'formatBackColor'}, 151 {command: 'foreColor', inputType: 'formatFontColor'}, 152 {command: 'hiliteColor', inputType: 'formatBackColor'}]) { 153 testExecCommandInputType(test.command, '#FF0000', test.inputType, 'rgb(255, 0, 0)', null); 154 testExecCommandInputType(test.command, '#00FF00FF', test.inputType, 'rgb(0, 255, 0)', null); 155 testExecCommandInputType(test.command, '#0000FF88', test.inputType, 'rgba(0, 0, 255, 0.533)', null); 156 testExecCommandInputType(test.command, 'orange', test.inputType, 'rgb(255, 165, 0)', null); 157 testExecCommandInputType(test.command, 'Inherit', test.inputType, 'inherit', null); 158 testExecCommandInputType(test.command, 'Initial', test.inputType, 'initial', null); 159 testExecCommandInputType(test.command, 'Reset', test.inputType, 'reset', null); 160 testExecCommandInputType(test.command, 'transparent', test.inputType, 'rgba(0, 0, 0, 0)', null); 161 testExecCommandInputType(test.command, 'CurrentColor', test.inputType, 'currentcolor', null); 162 testExecCommandInputType(test.command, 'Invalid-Value', test.inputType, 'Invalid-Value', null); 163 } 164 165 testExecCommandInputType('fontName', 'monospace', 'formatFontName', 'monospace', null); 166 testExecCommandInputType('fontName', ' monospace ', 'formatFontName', ' monospace ', null); 167 testExecCommandInputType('fontName', ' monospace ', 'formatFontName', ' monospace ', null); 168 169 // Formating 170 txt.innerHTML = 'abc'; 171 testExecCommandInputType('justifyCenter', null, 'formatJustifyCenter', null, null); 172 test(function() { 173 assert_equals(txt.innerHTML, '<div style="text-align: center;">abc</div>'); 174 }, "execCommand(\"justifyCenter\") should wrap the text with <div> element whose text-align is center"); 175 testExecCommandInputType('justifyFull', null, 'formatJustifyFull', null, null); 176 test(function() { 177 assert_equals(txt.innerHTML, '<div style="text-align: justify;">abc</div>'); 178 }, "execCommand(\"justifyFull\") should wrap the text with <div> element whose text-align is justify"); 179 testExecCommandInputType('justifyRight', null, 'formatJustifyRight', null, null); 180 test(function() { 181 assert_equals(txt.innerHTML, '<div style="text-align: right;">abc</div>'); 182 }, "execCommand(\"justifyRight\") should wrap the text with <div> element whose text-align is right"); 183 testExecCommandInputType('justifyLeft', null, 'formatJustifyLeft', null, null); 184 test(function() { 185 assert_equals(txt.innerHTML, '<div style="text-align: left;">abc</div>'); 186 }, "execCommand(\"justifyLeft\") should wrap the text with <div> element whose text-align is left"); 187 selection.collapse(txt, 0); 188 selection.extend(txt, 1); 189 testExecCommandInputType('removeFormat', null, 'formatRemove', null, null); 190 test(function() { 191 assert_equals(txt.innerHTML, '<div style="">abc</div>'); 192 }, "execCommand(\"removeFormat\") should remove the style of current block"); 193 testExecCommandInputType('indent', null, 'formatIndent', null, null); 194 testExecCommandInputType('outdent', null, 'formatOutdent', null, null); 195 test(function() { 196 assert_equals(txt.innerHTML, '<div style="">abc</div>'); 197 }, "Set of execCommand(\"indent\") and execCommand(\"outdent\") should keep same DOM tree"); 198 199 // Copy shouldn't fire 'input'. 200 txt.innerHTML = 'ab<b>c</b>def'; 201 selection.collapse(txt.firstChild, 1); 202 selection.extend(txt.firstChild.nextSibling.nextSibling, 1); 203 testExecCommandInputType('copy', null, NO_INPUT_EVENT_FIRED, NO_INPUT_EVENT_FIRED, NO_INPUT_EVENT_FIRED); 204 // Cut/Paste should fire 'input'. 205 testExecCommandInputType('cut', null, 'deleteByCut', null, null); 206 // XXX How can we test 'text/html' case? The detail of copied HTML fragment depends on browser. 207 testExecCommandInputType('paste', null, 'insertFromPaste', null, [{type: 'text/plain', data: 'bcd'}]); 208 209 // Link and Unlink 210 txt.innerHTML = 'abc'; 211 selection.collapse(txt.firstChild, 1); 212 selection.extend(txt.firstChild, 2); 213 testExecCommandInputType('createLink', 'https://example.com/', 'insertLink', 'https://example.com/', null); 214 test(function() { 215 assert_equals(txt.innerHTML, 'a<a href="https://example.com/">b</a>c'); 216 }, "execCommand(\"createLink\") should create a link with absolute URL"); 217 testExecCommandInputType('unlink', null, '', null, null); 218 test(function() { 219 assert_equals(txt.innerHTML, 'abc'); 220 }, "execCommand(\"createLink\") should remove the link"); 221 222 txt.innerHTML = 'abc'; 223 selection.collapse(txt.firstChild, 1); 224 selection.extend(txt.firstChild, 2); 225 testExecCommandInputType('createLink', 'foo.html', 'insertLink', 'foo.html', null); 226 test(function() { 227 assert_equals(txt.innerHTML, 'a<a href="foo.html">b</a>c'); 228 }, "execCommand(\"createLink\") should create a link with relative URL"); 229 })(); 230 </script>