head.js (7744B)
1 /* -*- Mode: JavaScript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* Any copyright is dedicated to the Public Domain. 4 http://creativecommons.org/publicdomain/zero/1.0/ */ 5 6 "use strict"; 7 8 const kPasteMenuPopupId = "clipboardReadPasteMenuPopup"; 9 const kPasteMenuItemId = "clipboardReadPasteMenuItem"; 10 11 const kBaseUrlForContent = getRootDirectory(gTestPath).replace( 12 "chrome://mochitests/content", 13 "https://example.com" 14 ); 15 16 const kPasteCommandTests = [ 17 { description: "Test paste command without editing" }, 18 { 19 description: "Test paste command on <textarea>", 20 setupFn: aBrowser => { 21 return SpecialPowers.spawn(aBrowser, [], () => { 22 const textarea = content.document.createElement("textarea"); 23 content.document.body.appendChild(textarea); 24 textarea.focus(); 25 }); 26 }, 27 additionalCheckFunc: (aBrowser, aClipboardData) => { 28 return SpecialPowers.spawn(aBrowser, [aClipboardData], aClipboardData => { 29 const textarea = content.document.querySelector("textarea"); 30 is(textarea.value, aClipboardData, "check <textarea> value"); 31 }); 32 }, 33 }, 34 { 35 description: "Test paste command on <div contenteditable=true>", 36 setupFn: aBrowser => { 37 return SpecialPowers.spawn(aBrowser, [], () => { 38 const div = content.document.createElement("div"); 39 div.setAttribute("contenteditable", "true"); 40 content.document.body.appendChild(div); 41 div.focus(); 42 }); 43 }, 44 additionalCheckFunc: (aBrowser, aClipboardData) => { 45 return SpecialPowers.spawn(aBrowser, [aClipboardData], aClipboardData => { 46 const div = content.document.querySelector("div"); 47 is(div.innerText, aClipboardData, "check contenteditable innerText"); 48 }); 49 }, 50 }, 51 ]; 52 53 Services.scriptloader.loadSubScript( 54 "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js", 55 this 56 ); 57 58 function promiseWritingRandomTextToClipboard() { 59 const clipboardText = "X" + Math.random(); 60 return navigator.clipboard.writeText(clipboardText).then(() => { 61 return clipboardText; 62 }); 63 } 64 65 function promiseBrowserReflow() { 66 return new Promise(resolve => 67 requestAnimationFrame(() => requestAnimationFrame(resolve)) 68 ); 69 } 70 71 function waitForPasteMenuPopupEvent(aEventSuffix) { 72 // The element with id `kPasteMenuPopupId` is inserted dynamically, hence 73 // calling `BrowserTestUtils.waitForEvent` instead of 74 // `BrowserTestUtils.waitForPopupEvent`. 75 return BrowserTestUtils.waitForEvent( 76 document, 77 "popup" + aEventSuffix, 78 false /* capture */, 79 e => { 80 return e.target.getAttribute("id") == kPasteMenuPopupId; 81 } 82 ); 83 } 84 85 function promisePasteButtonIsShown() { 86 return waitForPasteMenuPopupEvent("shown").then(async () => { 87 ok(true, "Witnessed 'popupshown' event for 'Paste' button."); 88 89 const pasteButton = document.getElementById(kPasteMenuItemId); 90 if (Services.prefs.getIntPref("security.dialog_enable_delay") > 0) { 91 ok( 92 pasteButton.disabled, 93 "Paste button should be shown with disabled by default" 94 ); 95 } 96 await BrowserTestUtils.waitForMutationCondition( 97 pasteButton, 98 { attributeFilter: ["disabled"] }, 99 () => !pasteButton.disabled, 100 "Wait for paste button enabled" 101 ); 102 103 return promiseBrowserReflow().then(() => { 104 return coordinatesRelativeToScreen({ 105 target: pasteButton, 106 offsetX: 0, 107 offsetY: 0, 108 }); 109 }); 110 }); 111 } 112 113 function promisePasteButtonIsHidden() { 114 return waitForPasteMenuPopupEvent("hidden").then(() => { 115 ok(true, "Witnessed 'popuphidden' event for 'Paste' button."); 116 return promiseBrowserReflow(); 117 }); 118 } 119 120 function promiseClickPasteButton() { 121 const pasteButton = document.getElementById(kPasteMenuItemId); 122 const popup = document.getElementById(kPasteMenuPopupId); 123 let promise = BrowserTestUtils.waitForEvent(pasteButton, "command"); 124 popup.activateItem(pasteButton); 125 return promise; 126 } 127 128 function getMouseCoordsRelativeToScreenInDevicePixels() { 129 let mouseXInCSSPixels = {}; 130 let mouseYInCSSPixels = {}; 131 window.windowUtils.getLastOverWindowPointerLocationInCSSPixels( 132 mouseXInCSSPixels, 133 mouseYInCSSPixels 134 ); 135 136 return { 137 x: 138 (mouseXInCSSPixels.value + window.mozInnerScreenX) * 139 window.devicePixelRatio, 140 y: 141 (mouseYInCSSPixels.value + window.mozInnerScreenY) * 142 window.devicePixelRatio, 143 }; 144 } 145 146 function isCloselyLeftOnTopOf(aCoordsP1, aCoordsP2, aDelta = 10) { 147 return ( 148 Math.abs(aCoordsP2.x - aCoordsP1.x) <= aDelta && 149 Math.abs(aCoordsP2.y - aCoordsP1.y) <= aDelta 150 ); 151 } 152 153 async function promiseDismissPasteButton() { 154 // We intentionally turn off this a11y check, because the following click 155 // is send on the <body> to dismiss the pending popup using an alternative way 156 // of the popup dismissal, where the other way like `Esc` key is available, 157 // therefore this test can be ignored. 158 AccessibilityUtils.setEnv({ 159 mustHaveAccessibleRule: false, 160 }); 161 // nsXULPopupManager rollup is handled in widget code, so we have to 162 // synthesize native mouse events. 163 await EventUtils.promiseNativeMouseEvent({ 164 type: "click", 165 target: document.body, 166 // Relies on the assumption that the center of chrome document doesn't 167 // overlay with the paste button showed for clipboard readText request. 168 atCenter: true, 169 }); 170 // Move mouse away to avoid subsequence tests showing paste button in 171 // thie dismissing location. 172 await EventUtils.promiseNativeMouseEvent({ 173 type: "mousemove", 174 target: document.body, 175 offsetX: 100, 176 offsetY: 100, 177 }); 178 AccessibilityUtils.resetEnv(); 179 } 180 181 // @param aBrowser browser object of the content tab. 182 // @param aContentElementId the ID of the element to be clicked. 183 async function promiseClickContentElement(aBrowser, aContentElementId) { 184 // We intentionally turn off this a11y check, because the following click 185 // is send on an arbitrary web content that is not expected to be tested 186 // by itself with the browser mochitests, therefore this rule check shall 187 // be ignored by a11y-checks suite. 188 AccessibilityUtils.setEnv({ 189 mustHaveAccessibleRule: false, 190 }); 191 let result = await SpecialPowers.spawn( 192 aBrowser, 193 [aContentElementId], 194 async _contentElementId => { 195 const contentElement = content.document.getElementById(_contentElementId); 196 let promise = new Promise(resolve => { 197 contentElement.addEventListener( 198 "click", 199 function (e) { 200 resolve({ x: e.screenX, y: e.screenY }); 201 }, 202 { once: true } 203 ); 204 }); 205 206 EventUtils.synthesizeMouseAtCenter(contentElement, {}, content.window); 207 208 return promise; 209 } 210 ); 211 AccessibilityUtils.resetEnv(); 212 return result; 213 } 214 215 // @param aBrowser browser object of the content tab. 216 // @param aContentElementId the ID of the element to observe. 217 function promiseMutatedTextContentFromContentElement( 218 aBrowser, 219 aContentElementId 220 ) { 221 return SpecialPowers.spawn( 222 aBrowser, 223 [aContentElementId], 224 async _contentElementId => { 225 const contentElement = content.document.getElementById(_contentElementId); 226 227 const promiseTextContentResult = new Promise(resolve => { 228 const mutationObserver = new content.MutationObserver( 229 (aMutationRecord, aMutationObserver) => { 230 info("Observed mutation."); 231 aMutationObserver.disconnect(); 232 resolve(contentElement.textContent); 233 } 234 ); 235 236 mutationObserver.observe(contentElement, { 237 childList: true, 238 }); 239 }); 240 241 return await promiseTextContentResult; 242 } 243 ); 244 }