browser_content_command_insert_text.js (8692B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const { CustomizableUITestUtils } = ChromeUtils.importESModule( 7 "resource://testing-common/CustomizableUITestUtils.sys.mjs" 8 ); 9 const { ContentTaskUtils } = ChromeUtils.importESModule( 10 "resource://testing-common/ContentTaskUtils.sys.mjs" 11 ); 12 const gCUITestUtils = new CustomizableUITestUtils(window); 13 const gDOMWindowUtils = EventUtils._getDOMWindowUtils(window); 14 let searchBar; 15 16 add_setup(async function () { 17 searchBar = await gCUITestUtils.addSearchBar(); 18 registerCleanupFunction(() => { 19 gCUITestUtils.removeSearchBar(); 20 }); 21 }); 22 23 function promiseResettingSearchBarAndFocus() { 24 const waitForFocusInSearchBar = BrowserTestUtils.waitForEvent( 25 searchBar.inputField, 26 "focus" 27 ); 28 searchBar.inputField.focus(); 29 searchBar.inputField.value = ""; 30 return Promise.all([ 31 waitForFocusInSearchBar, 32 TestUtils.waitForCondition( 33 () => 34 gDOMWindowUtils.IMEStatus === Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED && 35 gDOMWindowUtils.inputContextOrigin === 36 Ci.nsIDOMWindowUtils.INPUT_CONTEXT_ORIGIN_MAIN 37 ), 38 ]); 39 } 40 41 function promiseIMEStateEnabledByRemote() { 42 return TestUtils.waitForCondition( 43 () => 44 gDOMWindowUtils.IMEStatus === Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED && 45 gDOMWindowUtils.inputContextOrigin === 46 Ci.nsIDOMWindowUtils.INPUT_CONTEXT_ORIGIN_CONTENT 47 ); 48 } 49 50 function promiseContentTick(browser) { 51 return SpecialPowers.spawn(browser, [], async () => { 52 await new Promise(r => { 53 content.requestAnimationFrame(() => { 54 content.requestAnimationFrame(r); 55 }); 56 }); 57 }); 58 } 59 60 add_task(async function test_text_editor_in_chrome() { 61 await promiseResettingSearchBarAndFocus(); 62 63 let events = []; 64 function logEvent(event) { 65 events.push(event); 66 } 67 searchBar.inputField.addEventListener("beforeinput", logEvent); 68 gDOMWindowUtils.sendContentCommandEvent("insertText", null, "XYZ"); 69 70 is( 71 searchBar.inputField.value, 72 "XYZ", 73 "The string should be inserted into the focused search bar" 74 ); 75 is( 76 events.length, 77 1, 78 "One beforeinput event should be fired in the searchbar" 79 ); 80 is(events[0]?.inputType, "insertText", 'inputType should be "insertText"'); 81 is(events[0]?.data, "XYZ", 'inputType should be "XYZ"'); 82 is(events[0]?.cancelable, true, "beforeinput event should be cancelable"); 83 searchBar.inputField.removeEventListener("beforeinput", logEvent); 84 85 searchBar.inputField.blur(); 86 }); 87 88 add_task(async function test_text_editor_in_content() { 89 for (const test of [ 90 { 91 tag: "input", 92 target: "input", 93 nonTarget: "input + input", 94 page: 'data:text/html,<input value="abc"><input value="def">', 95 }, 96 { 97 tag: "textarea", 98 target: "textarea", 99 nonTarget: "textarea + textarea", 100 page: "data:text/html,<textarea>abc</textarea><textarea>def</textarea>", 101 }, 102 ]) { 103 // Once, move focus to chrome's searchbar. 104 await promiseResettingSearchBarAndFocus(); 105 106 await BrowserTestUtils.withNewTab(test.page, async browser => { 107 await SpecialPowers.spawn(browser, [test], async function (aTest) { 108 content.window.focus(); 109 await ContentTaskUtils.waitForCondition(() => 110 content.document.hasFocus() 111 ); 112 const target = content.document.querySelector(aTest.target); 113 target.focus(); 114 target.selectionStart = target.selectionEnd = 2; 115 content.document.documentElement.scrollTop; // Flush pending things 116 }); 117 118 await promiseIMEStateEnabledByRemote(); 119 const waitForBeforeInputEvent = SpecialPowers.spawn( 120 browser, 121 [test], 122 async function (aTest) { 123 await new Promise(resolve => { 124 content.document.querySelector(aTest.target).addEventListener( 125 "beforeinput", 126 event => { 127 is( 128 event.inputType, 129 "insertText", 130 `The inputType of beforeinput event fired on <${aTest.target}> should be "insertText"` 131 ); 132 is( 133 event.data, 134 "XYZ", 135 `The data of beforeinput event fired on <${aTest.target}> should be "XYZ"` 136 ); 137 is( 138 event.cancelable, 139 true, 140 `The beforeinput event fired on <${aTest.target}> should be cancelable` 141 ); 142 resolve(); 143 }, 144 { once: true } 145 ); 146 }); 147 } 148 ); 149 const waitForInputEvent = BrowserTestUtils.waitForContentEvent( 150 browser, 151 "input" 152 ); 153 await promiseContentTick(browser); // Ensure "input" event listener in the remote process 154 gDOMWindowUtils.sendContentCommandEvent("insertText", null, "XYZ"); 155 await waitForBeforeInputEvent; 156 await waitForInputEvent; 157 158 await SpecialPowers.spawn(browser, [test], async function (aTest) { 159 is( 160 content.document.querySelector(aTest.target).value, 161 "abXYZc", 162 `The string should be inserted into the focused <${aTest.target}> element` 163 ); 164 is( 165 content.document.querySelector(aTest.nonTarget).value, 166 "def", 167 `The string should not be inserted into the non-focused <${aTest.nonTarget}> element` 168 ); 169 }); 170 }); 171 172 is( 173 searchBar.inputField.value, 174 "", 175 "The string should not be inserted into the previously focused search bar" 176 ); 177 } 178 }); 179 180 add_task(async function test_html_editor_in_content() { 181 for (const test of [ 182 { 183 mode: "contenteditable", 184 target: "div", 185 page: "data:text/html,<div contenteditable>abc</div>", 186 }, 187 { 188 mode: "designMode", 189 target: "div", 190 page: "data:text/html,<div>abc</div>", 191 }, 192 ]) { 193 // Once, move focus to chrome's searchbar. 194 await promiseResettingSearchBarAndFocus(); 195 196 await BrowserTestUtils.withNewTab(test.page, async browser => { 197 await SpecialPowers.spawn(browser, [test], async function (aTest) { 198 content.window.focus(); 199 await ContentTaskUtils.waitForCondition(() => 200 content.document.hasFocus() 201 ); 202 const target = content.document.querySelector(aTest.target); 203 if (aTest.mode == "designMode") { 204 content.document.designMode = "on"; 205 content.window.focus(); 206 } else { 207 target.focus(); 208 } 209 content.window.getSelection().collapse(target.firstChild, 2); 210 content.document.documentElement.scrollTop; // Flush pending things 211 }); 212 213 await promiseIMEStateEnabledByRemote(); 214 const waitForBeforeInputEvent = SpecialPowers.spawn( 215 browser, 216 [test], 217 async function (aTest) { 218 await new Promise(resolve => { 219 const eventTarget = 220 aTest.mode === "designMode" 221 ? content.document 222 : content.document.querySelector(aTest.target); 223 eventTarget.addEventListener( 224 "beforeinput", 225 event => { 226 is( 227 event.inputType, 228 "insertText", 229 `The inputType of beforeinput event fired on ${aTest.mode} editor should be "insertText"` 230 ); 231 is( 232 event.data, 233 "XYZ", 234 `The data of beforeinput event fired on ${aTest.mode} editor should be "XYZ"` 235 ); 236 is( 237 event.cancelable, 238 true, 239 `The beforeinput event fired on ${aTest.mode} editor should be cancelable` 240 ); 241 resolve(); 242 }, 243 { once: true } 244 ); 245 }); 246 } 247 ); 248 const waitForInputEvent = BrowserTestUtils.waitForContentEvent( 249 browser, 250 "input" 251 ); 252 await promiseContentTick(browser); // Ensure "input" event listener in the remote process 253 gDOMWindowUtils.sendContentCommandEvent("insertText", null, "XYZ"); 254 await waitForBeforeInputEvent; 255 await waitForInputEvent; 256 257 await SpecialPowers.spawn(browser, [test], async function (aTest) { 258 is( 259 content.document.querySelector(aTest.target).innerHTML, 260 "abXYZc", 261 `The string should be inserted into the focused ${aTest.mode} editor` 262 ); 263 }); 264 }); 265 266 is( 267 searchBar.inputField.value, 268 "", 269 "The string should not be inserted into the previously focused search bar" 270 ); 271 } 272 });