browser_beforeinput_by_execCommand_in_contentscript.js (5700B)
1 "use strict"; 2 3 async function installAndStartExtension() { 4 function contentScript() { 5 window.addEventListener("keydown", aEvent => { 6 console.log("keydown event is fired"); 7 if (aEvent.defaultPrevented) { 8 return; 9 } 10 let selection = window.getSelection(); 11 if (selection.isCollapsed) { 12 return; 13 } 14 if (aEvent.ctrlKey && aEvent.key === "k") { 15 document.execCommand("createLink", false, "http://example.com/"); 16 aEvent.preventDefault(); 17 } 18 }); 19 } 20 21 let extension = ExtensionTestUtils.loadExtension({ 22 manifest: { 23 content_scripts: [ 24 { 25 js: ["content_script.js"], 26 matches: ["<all_urls>"], 27 run_at: "document_start", 28 }, 29 ], 30 }, 31 files: { 32 "content_script.js": contentScript, 33 }, 34 }); 35 36 await extension.startup(); 37 38 return extension; 39 } 40 41 add_task(async function () { 42 const extension = await installAndStartExtension(); 43 const tab = await BrowserTestUtils.openNewForegroundTab( 44 gBrowser, 45 "http://example.com/browser/dom/events/test/file_beforeinput_by_execCommand_in_contentscript.html", 46 true 47 ); 48 49 /** 50 * Document.execCommand() shouldn't cause `beforeinput`, but it may be used 51 * by addons for emulating user input and make the input undoable on builtin 52 * editors. Therefore, if and only if it's called by addons, `beforeinput` 53 * should be fired. 54 */ 55 function runTest() { 56 const editor = content.document.querySelector("[contenteditable]"); 57 editor.focus(); 58 content.document.getSelection().selectAllChildren(editor); 59 let beforeinput; 60 editor.addEventListener("beforeinput", aEvent => { 61 beforeinput = aEvent; 62 }); 63 const description = 'Test execCommand("createLink")'; 64 editor.addEventListener("input", aEvent => { 65 if (!beforeinput) { 66 sendAsyncMessage("Test:BeforeInputInContentEditable", { 67 succeeded: false, 68 message: `${description}: No beforeinput event is fired`, 69 }); 70 return; 71 } 72 sendAsyncMessage("Test:BeforeInputInContentEditable", { 73 succeeded: 74 editor.innerHTML === '<a href="http://example.com/">abcdef</a>', 75 message: `${description}: editor.innerHTML=${editor.innerHTML}`, 76 }); 77 }); 78 } 79 80 try { 81 tab.linkedBrowser.messageManager.loadFrameScript( 82 "data:,(" + runTest.toString() + ")();", 83 false 84 ); 85 86 let testResult = new Promise(resolve => { 87 let mm = tab.linkedBrowser.messageManager; 88 mm.addMessageListener( 89 "Test:BeforeInputInContentEditable", 90 function onFinish(aMsg) { 91 mm.removeMessageListener( 92 "Test:BeforeInputInContentEditable", 93 onFinish 94 ); 95 is(aMsg.data.succeeded, true, aMsg.data.message); 96 resolve(); 97 } 98 ); 99 }); 100 info("Sending Ctrl+K..."); 101 await BrowserTestUtils.synthesizeKey( 102 "k", 103 { ctrlKey: true }, 104 tab.linkedBrowser 105 ); 106 info("Waiting test result..."); 107 await testResult; 108 } finally { 109 BrowserTestUtils.removeTab(tab); 110 await extension.unload(); 111 } 112 }); 113 114 add_task(async function () { 115 const extension = await installAndStartExtension(); 116 const tab = await BrowserTestUtils.openNewForegroundTab( 117 gBrowser, 118 "http://example.com/browser/dom/events/test/file_beforeinput_by_execCommand_in_contentscript.html", 119 true 120 ); 121 122 /** 123 * Document.execCommand() from addons should be treated as a user input. 124 * Therefore, it should not block first nested Document.execCommand() call 125 * in a "beforeinput" event listener in the web app. 126 */ 127 function runTest() { 128 const editor = content.document.querySelectorAll("[contenteditable]")[1]; 129 editor.focus(); 130 content.document.getSelection().selectAllChildren(editor); 131 const beforeInputs = []; 132 editor.parentNode.addEventListener( 133 "beforeinput", 134 aEvent => { 135 beforeInputs.push(aEvent); 136 }, 137 { capture: true } 138 ); 139 const description = 140 'Test web app calls execCommand("insertText") on "beforeinput"'; 141 editor.addEventListener("input", aEvent => { 142 if (!beforeInputs.length) { 143 sendAsyncMessage("Test:BeforeInputInContentEditable", { 144 succeeded: false, 145 message: `${description}: No beforeinput event is fired`, 146 }); 147 return; 148 } 149 if (beforeInputs.length > 1) { 150 sendAsyncMessage("Test:BeforeInputInContentEditable", { 151 succeeded: false, 152 message: `${description}: Too many beforeinput events are fired`, 153 }); 154 return; 155 } 156 sendAsyncMessage("Test:BeforeInputInContentEditable", { 157 succeeded: editor.innerHTML.replace("<br>", "") === "ABCDEF", 158 message: `${description}: editor.innerHTML=${editor.innerHTML}`, 159 }); 160 }); 161 } 162 163 try { 164 tab.linkedBrowser.messageManager.loadFrameScript( 165 "data:,(" + runTest.toString() + ")();", 166 false 167 ); 168 169 let testResult = new Promise(resolve => { 170 let mm = tab.linkedBrowser.messageManager; 171 mm.addMessageListener( 172 "Test:BeforeInputInContentEditable", 173 function onFinish(aMsg) { 174 mm.removeMessageListener( 175 "Test:BeforeInputInContentEditable", 176 onFinish 177 ); 178 is(aMsg.data.succeeded, true, aMsg.data.message); 179 resolve(); 180 } 181 ); 182 }); 183 info("Sending Ctrl+K..."); 184 await BrowserTestUtils.synthesizeKey( 185 "k", 186 { ctrlKey: true }, 187 tab.linkedBrowser 188 ); 189 info("Waiting test result..."); 190 await testResult; 191 } finally { 192 BrowserTestUtils.removeTab(tab); 193 await extension.unload(); 194 } 195 });