Bug 1776680 [wpt PR 34603] - [@container] Test invalidation of font-relative units...
[gecko.git] / dom / events / test / browser_navigator_clipboard_readText.js
blobd9ac24979ac65ebff013591d330a157c65ee067c
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 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 "use strict";
9 const kBaseUrlForContent = getRootDirectory(gTestPath).replace(
10   "chrome://mochitests/content",
11   "https://example.com"
14 const kContentFileName = "simple_navigator_clipboard_readText.html";
16 const kContentFileUrl = kBaseUrlForContent + kContentFileName;
18 const kApzTestNativeEventUtilsUrl =
19   "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js";
21 Services.scriptloader.loadSubScript(kApzTestNativeEventUtilsUrl, this);
23 SpecialPowers.pushPrefEnv({
24   set: [["dom.events.asyncClipboard.readText", true]],
25 });
27 const chromeDoc = window.document;
29 const kPasteMenuPopupId = "clipboardReadTextPasteMenuPopup";
30 const kPasteMenuItemId = "clipboardReadTextPasteMenuItem";
32 function promiseClickPasteButton() {
33   const pasteButton = chromeDoc.getElementById(kPasteMenuItemId);
35   // Native mouse event to execute additional code paths which wouldn't be
36   // covered by non-native events.
37   return EventUtils.promiseNativeMouseEventAndWaitForEvent({
38     type: "click",
39     target: pasteButton,
40     atCenter: true,
41   });
44 function getMouseCoordsRelativeToScreenInDevicePixels() {
45   let mouseXInCSSPixels = {};
46   let mouseYInCSSPixels = {};
47   window.windowUtils.getLastOverWindowMouseLocationInCSSPixels(
48     mouseXInCSSPixels,
49     mouseYInCSSPixels
50   );
52   return {
53     x:
54       (mouseXInCSSPixels.value + window.mozInnerScreenX) *
55       window.devicePixelRatio,
56     y:
57       (mouseYInCSSPixels.value + window.mozInnerScreenY) *
58       window.devicePixelRatio,
59   };
62 function isCloselyLeftOnTopOf(aCoordsP1, aCoordsP2, aDelta) {
63   const kDelta = 10;
64   return (
65     aCoordsP2.x - aCoordsP1.x < kDelta && aCoordsP2.y - aCoordsP1.y < kDelta
66   );
69 function waitForPasteMenuPopupEvent(aEventSuffix) {
70   // The element with id `kPasteMenuPopupId` is inserted dynamically, hence
71   // calling `BrowserTestUtils.waitForEvent` instead of
72   // `BrowserTestUtils.waitForPopupEvent`.
73   return BrowserTestUtils.waitForEvent(
74     chromeDoc,
75     "popup" + aEventSuffix,
76     false /* capture */,
77     e => {
78       return e.target.getAttribute("id") == kPasteMenuPopupId;
79     }
80   );
83 function promisePasteButtonIsShown() {
84   return waitForPasteMenuPopupEvent("shown").then(() => {
85     ok(true, "Witnessed 'popupshown' event for 'Paste' button.");
87     const pasteButton = chromeDoc.getElementById(kPasteMenuItemId);
89     return coordinatesRelativeToScreen({
90       target: pasteButton,
91       offsetX: 0,
92       offsetY: 0,
93     });
94   });
97 function promisePasteButtonIsHidden() {
98   return waitForPasteMenuPopupEvent("hidden").then(() => {
99     ok(true, "Witnessed 'popuphidden' event for 'Paste' button.");
100   });
103 // @param aBrowser browser object of the content tab.
104 // @param aMultipleReadTextCalls if false, exactly one call is made, two
105 //                               otherwise.
106 function promiseClickContentToTriggerClipboardReadText(
107   aBrowser,
108   aMultipleReadTextCalls
109 ) {
110   const contentButtonId = aMultipleReadTextCalls
111     ? "invokeReadTextTwiceId"
112     : "invokeReadTextOnceId";
114   // `aBrowser.contentDocument` is null, therefore use `SpecialPowers.spawn`.
115   return SpecialPowers.spawn(
116     aBrowser,
117     [contentButtonId],
118     async _contentButtonId => {
119       const contentButton = content.document.getElementById(_contentButtonId);
121       // Native mouse event to execute additional code paths which wouldn't be
122       // covered by non-native events.
123       await EventUtils.promiseNativeMouseEventAndWaitForEvent({
124         type: "click",
125         target: contentButton,
126         atCenter: true,
127         win: content.window,
128       });
130       // Creating an object in this, priviliged, scope via `eval` so that
131       // `coordinatesRelativeToScreen` below can access the object's
132       // properties.
133       // Inside `eval`, parenthesis are needed to indicate to the JS
134       // parser that an expression, not a block statement, should be
135       // parsed.
136       const coordinateParams = content.window.eval(`({
137         target: window.document.getElementById("${_contentButtonId}"),
138         atCenter: true,
139       })`);
140       const coords = await content.wrappedJSObject.coordinatesRelativeToScreen(
141         coordinateParams
142       );
144       return coords;
145     }
146   );
149 // @param aBrowser browser object of the content tab.
150 function promiseMutatedReadTextResultFromContentElement(aBrowser) {
151   return SpecialPowers.spawn(aBrowser, [], async () => {
152     const readTextResultElement = content.document.getElementById(
153       "readTextResultId"
154     );
156     const promiseReadTextResult = new Promise(resolve => {
157       const mutationObserver = new content.MutationObserver(
158         (aMutationRecord, aMutationObserver) => {
159           info("Observed mutation.");
160           aMutationObserver.disconnect();
161           resolve(readTextResultElement.textContent);
162         }
163       );
165       mutationObserver.observe(readTextResultElement, {
166         childList: true,
167       });
168     });
170     return await promiseReadTextResult;
171   });
174 function promiseWritingRandomTextToClipboard() {
175   const clipboardText = "X" + Math.random();
176   return navigator.clipboard.writeText(clipboardText).then(() => {
177     return clipboardText;
178   });
181 function promiseDismissPasteButton() {
182   // Native mouse event to execute additional code paths which wouldn't be
183   // covered by non-native events.
184   return EventUtils.promiseNativeMouseEvent({
185     type: "click",
186     target: chromeDoc.body,
187     offsetX: 0,
188     offsetY: 0,
189   });
192 add_task(async function test_paste_button_position() {
193   // Ensure there's text on the clipboard.
194   await promiseWritingRandomTextToClipboard();
196   await BrowserTestUtils.withNewTab(kContentFileUrl, async function(browser) {
197     const pasteButtonIsShown = promisePasteButtonIsShown();
198     const coordsOfClickInContentRelativeToScreenInDevicePixels = await promiseClickContentToTriggerClipboardReadText(
199       browser,
200       false
201     );
202     info(
203       "coordsOfClickInContentRelativeToScreenInDevicePixels: " +
204         coordsOfClickInContentRelativeToScreenInDevicePixels.x +
205         ", " +
206         coordsOfClickInContentRelativeToScreenInDevicePixels.y
207     );
209     const pasteButtonCoordsRelativeToScreenInDevicePixels = await pasteButtonIsShown;
210     info(
211       "pasteButtonCoordsRelativeToScreenInDevicePixels: " +
212         pasteButtonCoordsRelativeToScreenInDevicePixels.x +
213         ", " +
214         pasteButtonCoordsRelativeToScreenInDevicePixels.y
215     );
217     const mouseCoordsRelativeToScreenInDevicePixels = getMouseCoordsRelativeToScreenInDevicePixels();
218     info(
219       "mouseCoordsRelativeToScreenInDevicePixels: " +
220         mouseCoordsRelativeToScreenInDevicePixels.x +
221         ", " +
222         mouseCoordsRelativeToScreenInDevicePixels.y
223     );
225     // Asserting not overlapping is important; otherwise, when the
226     // "Paste" button is shown via a `mousedown` event, the following
227     // `mouseup` event could accept the "Paste" button unnoticed by the
228     // user.
229     ok(
230       isCloselyLeftOnTopOf(
231         mouseCoordsRelativeToScreenInDevicePixels,
232         pasteButtonCoordsRelativeToScreenInDevicePixels
233       ),
234       "'Paste' button is closely left on top of the mouse pointer."
235     );
236     ok(
237       isCloselyLeftOnTopOf(
238         coordsOfClickInContentRelativeToScreenInDevicePixels,
239         pasteButtonCoordsRelativeToScreenInDevicePixels
240       ),
241       "Coords of click in content are closely left on top of the 'Paste' button."
242     );
244     // To avoid disturbing subsequent tests.
245     const pasteButtonIsHidden = promisePasteButtonIsHidden();
246     await promiseClickPasteButton();
247     await pasteButtonIsHidden;
248   });
251 add_task(async function test_accepting_paste_button() {
252   // Randomized text to avoid overlappings with other tests.
253   const clipboardText = await promiseWritingRandomTextToClipboard();
255   await BrowserTestUtils.withNewTab(kContentFileUrl, async function(browser) {
256     const pasteButtonIsShown = promisePasteButtonIsShown();
257     await promiseClickContentToTriggerClipboardReadText(browser, false);
258     await pasteButtonIsShown;
259     const pasteButtonIsHidden = promisePasteButtonIsHidden();
260     const mutatedReadTextResultFromContentElement = promiseMutatedReadTextResultFromContentElement(
261       browser
262     );
263     await promiseClickPasteButton();
264     await pasteButtonIsHidden;
265     await mutatedReadTextResultFromContentElement.then(value => {
266       is(
267         value,
268         "Resolved: " + clipboardText,
269         "Text returned from `navigator.clipboard.readText()` is as expected."
270       );
271     });
272   });
275 add_task(async function test_dismissing_paste_button() {
276   await BrowserTestUtils.withNewTab(kContentFileUrl, async function(browser) {
277     const pasteButtonIsShown = promisePasteButtonIsShown();
278     await promiseClickContentToTriggerClipboardReadText(browser, false);
279     await pasteButtonIsShown;
280     const pasteButtonIsHidden = promisePasteButtonIsHidden();
281     const mutatedReadTextResultFromContentElement = promiseMutatedReadTextResultFromContentElement(
282       browser
283     );
284     await promiseDismissPasteButton();
285     await pasteButtonIsHidden;
286     await mutatedReadTextResultFromContentElement.then(value => {
287       is(
288         value,
289         "Rejected.",
290         "`navigator.clipboard.readText()` rejected after dismissing the 'Paste' button"
291       );
292     });
293   });
296 if (AppConstants.platform != "win") {
297   // See bug 1773641.
298   add_task(
299     async function test_multiple_readText_invocations_for_same_user_activation() {
300       // Randomized text to avoid overlappings with other tests.
301       const clipboardText = await promiseWritingRandomTextToClipboard();
303       await BrowserTestUtils.withNewTab(kContentFileUrl, async function(
304         browser
305       ) {
306         await promiseClickContentToTriggerClipboardReadText(browser, true);
307         const mutatedReadTextResultFromContentElement = promiseMutatedReadTextResultFromContentElement(
308           browser
309         );
310         const pasteButtonIsHidden = promisePasteButtonIsHidden();
311         await promiseClickPasteButton();
312         await mutatedReadTextResultFromContentElement.then(value => {
313           is(
314             value,
315             "Resolved 1: " + clipboardText + "; Resolved 2: " + clipboardText,
316             "Two calls of `navigator.clipboard.read()` both resolved with the expected text."
317           );
318         });
320         // To avoid disturbing subsequent tests.
321         await pasteButtonIsHidden;
322       });
323     }
324   );
326   add_task(async function test_new_user_activation_shows_paste_button_again() {
327     await BrowserTestUtils.withNewTab(kContentFileUrl, async function(browser) {
328       // Ensure there's text on the clipboard.
329       await promiseWritingRandomTextToClipboard();
331       for (let i = 0; i < 2; ++i) {
332         const pasteButtonIsShown = promisePasteButtonIsShown();
333         // A click initiates a new user activation.
334         await promiseClickContentToTriggerClipboardReadText(browser, false);
335         await pasteButtonIsShown;
337         const pasteButtonIsHidden = promisePasteButtonIsHidden();
338         await promiseClickPasteButton();
339         await pasteButtonIsHidden;
340       }
341     });
342   });