diff options
Diffstat (limited to 'browser/components/screenshots')
17 files changed, 725 insertions, 142 deletions
diff --git a/browser/components/screenshots/ScreenshotsOverlayChild.sys.mjs b/browser/components/screenshots/ScreenshotsOverlayChild.sys.mjs index bcb3199902..3718b6a4e0 100644 --- a/browser/components/screenshots/ScreenshotsOverlayChild.sys.mjs +++ b/browser/components/screenshots/ScreenshotsOverlayChild.sys.mjs @@ -37,6 +37,7 @@ import { import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; +import { ShortcutUtils } from "resource://gre/modules/ShortcutUtils.sys.mjs"; const STATES = { CROSSHAIRS: "crosshairs", @@ -49,7 +50,7 @@ const STATES = { const lazy = {}; ChromeUtils.defineLazyGetter(lazy, "overlayLocalization", () => { - return new Localization(["browser/screenshotsOverlay.ftl"], true); + return new Localization(["browser/screenshots.ftl"], true); }); const SCREENSHOTS_LAST_SAVED_METHOD_PREF = @@ -79,13 +80,33 @@ export class ScreenshotsOverlay { #methodsUsed; get markup() { - let [cancel, instructions, download, copy] = - lazy.overlayLocalization.formatMessagesSync([ - { id: "screenshots-overlay-cancel-button" }, - { id: "screenshots-overlay-instructions" }, - { id: "screenshots-overlay-download-button" }, - { id: "screenshots-overlay-copy-button" }, - ]); + let accelString = ShortcutUtils.getModifierString("accel"); + let copyShorcut = accelString + this.copyKey; + let downloadShortcut = accelString + this.downloadKey; + + let [ + cancelLabel, + cancelAttributes, + instructions, + downloadLabel, + downloadAttributes, + copyLabel, + copyAttributes, + ] = lazy.overlayLocalization.formatMessagesSync([ + { id: "screenshots-cancel-button" }, + { id: "screenshots-component-cancel-button" }, + { id: "screenshots-instructions" }, + { id: "screenshots-component-download-button-label" }, + { + id: "screenshots-component-download-button", + args: { shortcut: downloadShortcut }, + }, + { id: "screenshots-component-copy-button-label" }, + { + id: "screenshots-component-copy-button", + args: { shortcut: copyShorcut }, + }, + ]); return ` <template> @@ -98,7 +119,7 @@ export class ScreenshotsOverlay { <div class="face"></div> </div> <div class="preview-instructions">${instructions.value}</div> - <button class="screenshots-button ghost-button" id="screenshots-cancel-button">${cancel.value}</button> + <button class="screenshots-button ghost-button" id="screenshots-cancel-button" title="${cancelAttributes.attributes[0].value}" aria-label="${cancelAttributes.attributes[1].value}">${cancelLabel.value}</button> </div> <div id="hover-highlight" hidden></div> <div id="selection-container" hidden> @@ -138,9 +159,9 @@ export class ScreenshotsOverlay { </div> <div id="buttons-container" hidden> <div class="buttons-wrapper"> - <button id="cancel" class="screenshots-button" title="${cancel.value}" aria-label="${cancel.value}" tabindex="0"><img/></button> - <button id="copy" class="screenshots-button" title="${copy.value}" aria-label="${copy.value}" tabindex="0"><img/>${copy.value}</button> - <button id="download" class="screenshots-button primary" title="${download.value}" aria-label="${download.value}" tabindex="0"><img/>${download.value}</button> + <button id="cancel" class="screenshots-button" title="${cancelAttributes.attributes[0].value}" aria-label="${cancelAttributes.attributes[1].value}"><img/></button> + <button id="copy" class="screenshots-button" title="${copyAttributes.attributes[0].value}" aria-label="${copyAttributes.attributes[1].value}"><img/><label>${copyLabel.value}</label></button> + <button id="download" class="screenshots-button primary" title="${downloadAttributes.attributes[0].value}" aria-label="${downloadAttributes.attributes[1].value}"><img/><label>${downloadLabel.value}</label></button> </div> </div> </div> @@ -180,6 +201,14 @@ export class ScreenshotsOverlay { this.selectionRegion = new Region(this.windowDimensions); this.hoverElementRegion = new Region(this.windowDimensions); this.resetMethodsUsed(); + + let [downloadKey, copyKey] = lazy.overlayLocalization.formatMessagesSync([ + { id: "screenshots-component-download-key" }, + { id: "screenshots-component-copy-key" }, + ]); + + this.downloadKey = downloadKey.value; + this.copyKey = copyKey.value; } get content() { @@ -204,6 +233,9 @@ export class ScreenshotsOverlay { this.#content.root.appendChild(this.fragment); this.initializeElements(); + this.screenshotsContainer.dir = Services.locale.isAppLocaleRTL + ? "rtl" + : "ltr"; await this.updateWindowDimensions(); this.#setState(STATES.CROSSHAIRS); @@ -290,10 +322,6 @@ export class ScreenshotsOverlay { } handleEvent(event) { - if (event.button > 0) { - return; - } - switch (event.type) { case "click": this.handleClick(event); @@ -316,21 +344,46 @@ export class ScreenshotsOverlay { } } + /** + * If the event came from the primary button, return false as we should not + * early return in the event handler function. + * If the event had another button, set to the crosshairs or selected state + * and return true to early return from the event handler function. + * @param {PointerEvent} event + * @returns true if the event button(s) was the non primary button + * false otherwise + */ + preEventHandler(event) { + if (event.button > 0 || event.buttons > 1) { + switch (this.#state) { + case STATES.DRAGGING_READY: + this.#setState(STATES.CROSSHAIRS); + break; + case STATES.DRAGGING: + case STATES.RESIZING: + this.#setState(STATES.SELECTED); + break; + } + return true; + } + return false; + } + handleClick(event) { + if (this.preEventHandler(event)) { + return; + } + switch (event.originalTarget.id) { case "screenshots-cancel-button": case "cancel": this.maybeCancelScreenshots(); break; case "copy": - this.#dispatchEvent("Screenshots:Copy", { - region: this.selectionRegion.dimensions, - }); + this.copySelectedRegion(); break; case "download": - this.#dispatchEvent("Screenshots:Download", { - region: this.selectionRegion.dimensions, - }); + this.downloadSelectedRegion(); break; } } @@ -351,6 +404,16 @@ export class ScreenshotsOverlay { * @param {Event} event The pointerown event */ handlePointerDown(event) { + // Early return if the event target is not within the screenshots component + // element. + if (!event.originalTarget.closest("#screenshots-component")) { + return; + } + + if (this.preEventHandler(event)) { + return; + } + if ( event.originalTarget.id === "screenshots-cancel-button" || event.originalTarget.closest("#buttons-container") === @@ -379,6 +442,10 @@ export class ScreenshotsOverlay { * @param {Event} event The pointermove event */ handlePointerMove(event) { + if (this.preEventHandler(event)) { + return; + } + const { pageX, pageY, clientX, clientY } = this.getCoordinatesFromEvent(event); @@ -450,6 +517,18 @@ export class ScreenshotsOverlay { case "Escape": this.maybeCancelScreenshots(); break; + case this.copyKey.toLowerCase(): + if (this.state === "selected" && this.getAccelKey(event)) { + event.preventDefault(); + this.copySelectedRegion(); + } + break; + case this.downloadKey.toLowerCase(): + if (this.state === "selected" && this.getAccelKey(event)) { + event.preventDefault(); + this.downloadSelectedRegion(); + } + break; } } @@ -780,9 +859,9 @@ export class ScreenshotsOverlay { */ setFocusToActionButton() { if (lazy.SCREENSHOTS_LAST_SAVED_METHOD === "copy") { - this.copyButton.focus({ focusVisible: true }); + this.copyButton.focus({ focusVisible: true, preventScroll: true }); } else { - this.downloadButton.focus({ focusVisible: true }); + this.downloadButton.focus({ focusVisible: true, preventScroll: true }); } } @@ -868,6 +947,18 @@ export class ScreenshotsOverlay { } } + copySelectedRegion() { + this.#dispatchEvent("Screenshots:Copy", { + region: this.selectionRegion.dimensions, + }); + } + + downloadSelectedRegion() { + this.#dispatchEvent("Screenshots:Download", { + region: this.selectionRegion.dimensions, + }); + } + /** * Hide hover element, selection and buttons containers. * Show the preview container and the panel. @@ -1285,17 +1376,21 @@ export class ScreenshotsOverlay { this.updateSelectionSizeText(); } + /** + * Update the size of the selected region. Use the zoom to correctly display + * the region dimensions. + */ updateSelectionSizeText() { - let dpr = this.windowDimensions.devicePixelRatio; let { width, height } = this.selectionRegion.dimensions; + let zoom = Math.round(this.window.browsingContext.fullZoom * 100) / 100; let [selectionSizeTranslation] = lazy.overlayLocalization.formatMessagesSync([ { - id: "screenshots-overlay-selection-region-size", + id: "screenshots-overlay-selection-region-size-2", args: { - width: Math.floor(width * dpr), - height: Math.floor(height * dpr), + width: Math.floor(width * zoom), + height: Math.floor(height * zoom), }, }, ]); diff --git a/browser/components/screenshots/ScreenshotsUtils.sys.mjs b/browser/components/screenshots/ScreenshotsUtils.sys.mjs index fc84facee3..9df74a4359 100644 --- a/browser/components/screenshots/ScreenshotsUtils.sys.mjs +++ b/browser/components/screenshots/ScreenshotsUtils.sys.mjs @@ -817,8 +817,9 @@ export var ScreenshotsUtils = { let dialog = await this.openPreviewDialog(browser); await dialog._dialogReady; - let screenshotsUI = - dialog._frame.contentDocument.createElement("screenshots-ui"); + let screenshotsUI = dialog._frame.contentDocument.createElement( + "screenshots-preview" + ); dialog._frame.contentDocument.body.appendChild(screenshotsUI); screenshotsUI.focusButton(lazy.SCREENSHOTS_LAST_SAVED_METHOD); diff --git a/browser/components/screenshots/content/screenshots.css b/browser/components/screenshots/content/screenshots.css index 506f3658c9..b155c294f8 100644 --- a/browser/components/screenshots/content/screenshots.css +++ b/browser/components/screenshots/content/screenshots.css @@ -30,13 +30,12 @@ body { display: flex; align-items: center; justify-content: center; + gap: var(--space-xsmall); cursor: pointer; text-align: center; user-select: none; white-space: nowrap; - min-height: 36px; - font-size: 15px; - min-width: 36px; + min-width: 32px; } .preview-button > img { @@ -44,11 +43,23 @@ body { fill: currentColor; width: 16px; height: 16px; + pointer-events: none; +} + +#retry > img { + content: url("chrome://global/skin/icons/reload.svg"); +} + +#cancel > img { + content: url("chrome://global/skin/icons/close.svg"); } -#download > img, #copy > img { - margin-inline-end: 5px; + content: url("chrome://global/skin/icons/edit-copy.svg"); +} + +#download > img { + content: url("chrome://browser/skin/downloads/downloads.svg"); } .preview-image { diff --git a/browser/components/screenshots/content/screenshots.html b/browser/components/screenshots/content/screenshots.html index 88c71fb4fe..fea032700c 100644 --- a/browser/components/screenshots/content/screenshots.html +++ b/browser/components/screenshots/content/screenshots.html @@ -31,32 +31,34 @@ <button id="retry" class="preview-button" - data-l10n-id="screenshots-retry-button-title" + data-l10n-id="screenshots-component-retry-button" > - <img src="chrome://global/skin/icons/reload.svg" /> + <img /> </button> <button id="cancel" class="preview-button" - data-l10n-id="screenshots-cancel-button-title" + data-l10n-id="screenshots-component-cancel-button" > - <img src="chrome://global/skin/icons/close.svg" /> + <img /> </button> <button id="copy" class="preview-button" - data-l10n-id="screenshots-copy-button-title" + data-l10n-id="screenshots-component-copy-button" > - <img src="chrome://global/skin/icons/edit-copy.svg" /> - <span data-l10n-id="screenshots-copy-button" /> + <img /><label + data-l10n-id="screenshots-component-copy-button-label" + ></label> </button> <button id="download" class="preview-button primary" - data-l10n-id="screenshots-download-button-title" + data-l10n-id="screenshots-component-download-button" > - <img src="chrome://browser/skin/downloads/downloads.svg" /> - <span data-l10n-id="screenshots-download-button" /> + <img /><label + data-l10n-id="screenshots-component-download-button-label" + ></label> </button> </div> <div class="preview-image"> diff --git a/browser/components/screenshots/content/screenshots.js b/browser/components/screenshots/content/screenshots.js index 9e47570e07..8159206d18 100644 --- a/browser/components/screenshots/content/screenshots.js +++ b/browser/components/screenshots/content/screenshots.js @@ -6,15 +6,35 @@ "use strict"; ChromeUtils.defineESModuleGetters(this, { + AppConstants: "resource://gre/modules/AppConstants.sys.mjs", ScreenshotsUtils: "resource:///modules/ScreenshotsUtils.sys.mjs", + ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs", }); -class ScreenshotsUI extends HTMLElement { +const lazy = {}; + +ChromeUtils.defineLazyGetter(lazy, "screenshotsLocalization", () => { + return new Localization(["browser/screenshots.ftl"], true); +}); + +class ScreenshotsPreview extends HTMLElement { constructor() { super(); // we get passed the <browser> as a param via TabDialogBox.open() this.openerBrowser = window.arguments[0]; + + window.ensureCustomElements("moz-button"); + + let [downloadKey, copyKey] = + lazy.screenshotsLocalization.formatMessagesSync([ + { id: "screenshots-component-download-key" }, + { id: "screenshots-component-copy-key" }, + ]); + + this.downloadKey = downloadKey.value; + this.copyKey = copyKey.value; } + async connectedCallback() { this.initialize(); } @@ -38,6 +58,29 @@ class ScreenshotsUI extends HTMLElement { this._copyButton.addEventListener("click", this); this._downloadButton = this.querySelector("#download"); this._downloadButton.addEventListener("click", this); + + let accelString = ShortcutUtils.getModifierString("accel"); + let copyShorcut = accelString + this.copyKey; + let downloadShortcut = accelString + this.downloadKey; + + document.l10n.setAttributes( + this._cancelButton, + "screenshots-component-cancel-button" + ); + + document.l10n.setAttributes( + this._copyButton, + "screenshots-component-copy-button", + { shortcut: copyShorcut } + ); + + document.l10n.setAttributes( + this._downloadButton, + "screenshots-component-download-button", + { shortcut: downloadShortcut } + ); + + window.addEventListener("keydown", this, true); } close() { @@ -45,31 +88,68 @@ class ScreenshotsUI extends HTMLElement { window.close(); } - async handleEvent(event) { - if (event.type == "click" && event.currentTarget == this._cancelButton) { - this.close(); - ScreenshotsUtils.recordTelemetryEvent("canceled", "preview_cancel", {}); - } else if ( - event.type == "click" && - event.currentTarget == this._copyButton - ) { - this.saveToClipboard( - this.ownerDocument.getElementById("placeholder-image").src - ); - } else if ( - event.type == "click" && - event.currentTarget == this._downloadButton - ) { - await this.saveToFile( - this.ownerDocument.getElementById("placeholder-image").src - ); - } else if ( - event.type == "click" && - event.currentTarget == this._retryButton - ) { - ScreenshotsUtils.scheduleRetry(this.openerBrowser, "preview_retry"); - this.close(); + handleEvent(event) { + switch (event.type) { + case "click": + this.handleClick(event); + break; + case "keydown": + this.handleKeydown(event); + break; + } + } + + handleClick(event) { + switch (event.target.id) { + case "retry": + ScreenshotsUtils.scheduleRetry(this.openerBrowser, "preview_retry"); + this.close(); + break; + case "cancel": + this.close(); + ScreenshotsUtils.recordTelemetryEvent("canceled", "preview_cancel", {}); + break; + case "copy": + this.saveToClipboard( + this.ownerDocument.getElementById("placeholder-image").src + ); + break; + case "download": + this.saveToFile( + this.ownerDocument.getElementById("placeholder-image").src + ); + break; + } + } + + handleKeydown(event) { + switch (event.key) { + case this.copyKey.toLowerCase(): + if (this.getAccelKey(event)) { + event.preventDefault(); + event.stopPropagation(); + this.saveToClipboard( + this.ownerDocument.getElementById("placeholder-image").src + ); + } + break; + case this.downloadKey.toLowerCase(): + if (this.getAccelKey(event)) { + event.preventDefault(); + event.stopPropagation(); + this.saveToFile( + this.ownerDocument.getElementById("placeholder-image").src + ); + } + break; + } + } + + getAccelKey(event) { + if (AppConstants.platform === "macosx") { + return event.metaKey; } + return event.ctrlKey; } async saveToFile(dataUrl) { @@ -102,4 +182,4 @@ class ScreenshotsUI extends HTMLElement { } } } -customElements.define("screenshots-ui", ScreenshotsUI); +customElements.define("screenshots-preview", ScreenshotsPreview); diff --git a/browser/components/screenshots/overlay/overlay.css b/browser/components/screenshots/overlay/overlay.css index 6eeda8b44c..b042f0b0c2 100644 --- a/browser/components/screenshots/overlay/overlay.css +++ b/browser/components/screenshots/overlay/overlay.css @@ -6,6 +6,12 @@ :host { display: contents; + + /* These z-indexes are used to correctly layer elements in the screenshots overlay */ + --screenshots-lowest-layer: 1; + --screenshots-low-layer: 2; + --screenshots-high-layer: 3; + --screenshots-highest-layer: 4; } [hidden] { @@ -57,6 +63,7 @@ position: absolute; margin: 10px 0; cursor: auto; + z-index: var(--screenshots-highest-layer); } #selection-size, @@ -77,11 +84,13 @@ .screenshots-button { display: inline-flex; align-items: center; + justify-content: center; + gap: var(--space-xsmall); cursor: pointer; text-align: center; user-select: none; white-space: nowrap; - z-index: 6; + z-index: var(--screenshots-highest-layer); min-width: 32px; margin-inline: 4px; } @@ -90,6 +99,7 @@ width: 100%; height: 100%; pointer-events: none; + z-index: var(--screenshots-lowest-layer); } #screenshots-cancel-button { @@ -123,6 +133,10 @@ pointer-events: none; } +.screenshots-button > label { + pointer-events: none; +} + #cancel > img { content: url("chrome://global/skin/icons/close.svg"); } @@ -135,15 +149,14 @@ content: url("chrome://browser/skin/downloads/downloads.svg"); } -#download > img, -#copy > img { - margin-inline-end: 5px; -} - .face-container { position: relative; width: 64px; height: 64px; + + @media (prefers-contrast) { + display: none; + } } .face { @@ -172,7 +185,7 @@ border-radius: 50%; inset-inline-start: 2px; top: 4px; - z-index: 10; + z-index: var(--screenshots-high-layer); } .left { @@ -209,7 +222,7 @@ box-sizing: border-box; pointer-events: none; position: absolute; - z-index: 11; + z-index: var(--screenshots-high-layer); } #top-background { @@ -242,7 +255,7 @@ cursor: move; position: absolute; pointer-events: auto; - z-index: 2; + z-index: var(--screenshots-lowest-layer); outline-offset: 8px; } @@ -251,7 +264,7 @@ align-items: center; justify-content: center; position: absolute; - z-index: 5; + z-index: var(--screenshots-high-layer); pointer-events: auto; outline-offset: -15px; } @@ -270,7 +283,7 @@ inset-inline-start: 0; top: -30px; width: 100%; - z-index: 4; + z-index: var(--screenshots-low-layer); } .mover-target.direction-topRight { @@ -287,7 +300,7 @@ left: -30px; top: 0; width: 60px; - z-index: 4; + z-index: var(--screenshots-low-layer); } .mover-target.direction-right { @@ -296,7 +309,7 @@ right: -30px; top: 0; width: 60px; - z-index: 4; + z-index: var(--screenshots-low-layer); } .mover-target.direction-bottomLeft { @@ -313,7 +326,7 @@ height: 60px; inset-inline-start: 0; width: 100%; - z-index: 4; + z-index: var(--screenshots-low-layer); } .mover-target.direction-bottomRight { diff --git a/browser/components/screenshots/screenshots-buttons.css b/browser/components/screenshots/screenshots-buttons.css index 82b075bccb..b63308d8b4 100644 --- a/browser/components/screenshots/screenshots-buttons.css +++ b/browser/components/screenshots/screenshots-buttons.css @@ -25,7 +25,8 @@ .full-page, .visible-page { -moz-context-properties: fill, stroke; fill: currentColor; - stroke: var(--color-accent-primary); + /* stroke is the secondary fill color used to define the viewport shape in the SVGs */ + stroke: var(--color-gray-60); background-position: center top; background-repeat: no-repeat; background-size: 46px 46px; diff --git a/browser/components/screenshots/screenshots-buttons.js b/browser/components/screenshots/screenshots-buttons.js index 864505ae2f..9ac8dab2cf 100644 --- a/browser/components/screenshots/screenshots-buttons.js +++ b/browser/components/screenshots/screenshots-buttons.js @@ -13,28 +13,40 @@ }); class ScreenshotsButtons extends MozXULElement { + static #template = null; + static get markup() { return ` - <html:link rel="stylesheet" href="chrome://global/skin/global.css"/> - <html:link rel="stylesheet" href="chrome://browser/content/screenshots/screenshots-buttons.css"/> - <html:button class="visible-page footer-button" data-l10n-id="screenshots-save-visible-button"></html:button> - <html:button class="full-page footer-button" data-l10n-id="screenshots-save-page-button"></html:button> + <html:link rel="stylesheet" href="chrome://global/skin/global.css" /> + <html:link rel="stylesheet" href="chrome://browser/content/screenshots/screenshots-buttons.css" /> + <html:moz-button-group> + <html:button class="visible-page footer-button" data-l10n-id="screenshots-save-visible-button"></html:button> + <html:button class="full-page footer-button primary" data-l10n-id="screenshots-save-page-button"></html:button> + </html:moz-button-group> `; } + static get fragment() { + if (!ScreenshotsButtons.#template) { + ScreenshotsButtons.#template = MozXULElement.parseXULToFragment( + ScreenshotsButtons.markup + ); + } + return ScreenshotsButtons.#template; + } + connectedCallback() { const shadowRoot = this.attachShadow({ mode: "open" }); document.l10n.connectRoot(shadowRoot); - let fragment = MozXULElement.parseXULToFragment(this.constructor.markup); - this.shadowRoot.append(fragment); + this.shadowRoot.append(ScreenshotsButtons.fragment); - let visibleButton = shadowRoot.querySelector(".visible-page"); + let visibleButton = this.shadowRoot.querySelector(".visible-page"); visibleButton.onclick = function () { ScreenshotsUtils.doScreenshot(gBrowser.selectedBrowser, "visible"); }; - let fullpageButton = shadowRoot.querySelector(".full-page"); + let fullpageButton = this.shadowRoot.querySelector(".full-page"); fullpageButton.onclick = function () { ScreenshotsUtils.doScreenshot(gBrowser.selectedBrowser, "full_page"); }; @@ -49,7 +61,8 @@ * This will default to the visible page button. * @param {String} buttonToFocus */ - focusButton(buttonToFocus) { + async focusButton(buttonToFocus) { + await this.shadowRoot.querySelector("moz-button-group").updateComplete; if (buttonToFocus === "fullpage") { this.shadowRoot .querySelector(".full-page") diff --git a/browser/components/screenshots/tests/browser/browser.toml b/browser/components/screenshots/tests/browser/browser.toml index b27d28c677..97e7474fa3 100644 --- a/browser/components/screenshots/tests/browser/browser.toml +++ b/browser/components/screenshots/tests/browser/browser.toml @@ -18,6 +18,8 @@ prefs = [ ["browser_iframe_test.js"] skip-if = ["os == 'linux'"] +["browser_keyboard_shortcuts.js"] + ["browser_overlay_keyboard_test.js"] ["browser_screenshots_drag_scroll_test.js"] @@ -63,3 +65,5 @@ skip-if = ["!crashreporter"] ["browser_test_moving_tab_to_new_window.js"] ["browser_test_resize.js"] + +["browser_test_selection_size_text.js"] diff --git a/browser/components/screenshots/tests/browser/browser_iframe_test.js b/browser/components/screenshots/tests/browser/browser_iframe_test.js index bb853fbe28..24f7a71dca 100644 --- a/browser/components/screenshots/tests/browser/browser_iframe_test.js +++ b/browser/components/screenshots/tests/browser/browser_iframe_test.js @@ -90,7 +90,7 @@ add_task(async function test_selectingElementsInIframes() { await helper.waitForHoverElementRect(el.width, el.height); mouse.click(x, y); - await helper.waitForStateChange("selected"); + await helper.waitForStateChange(["selected"]); let dimensions = await helper.getSelectionRegionDimensions(); @@ -116,7 +116,7 @@ add_task(async function test_selectingElementsInIframes() { ); mouse.click(500, 500); - await helper.waitForStateChange("crosshairs"); + await helper.waitForStateChange(["crosshairs"]); } } ); diff --git a/browser/components/screenshots/tests/browser/browser_keyboard_shortcuts.js b/browser/components/screenshots/tests/browser/browser_keyboard_shortcuts.js new file mode 100644 index 0000000000..bca96f333f --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_keyboard_shortcuts.js @@ -0,0 +1,128 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_download_shortcut() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.useDownloadDir", true]], + }); + + let publicDownloads = await Downloads.getList(Downloads.PUBLIC); + // First ensure we catch the download finishing. + let downloadFinishedPromise = new Promise(resolve => { + publicDownloads.addView({ + onDownloadChanged(download) { + info("Download changed!"); + if (download.succeeded || download.error) { + info("Download succeeded or errored"); + publicDownloads.removeView(this); + resolve(download); + } + }, + }); + }); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + let helper = new ScreenshotsHelper(browser); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + await helper.dragOverlay(10, 10, 500, 500); + + let screenshotExit = TestUtils.topicObserved("screenshots-exit"); + + await SpecialPowers.spawn(browser, [], async () => { + EventUtils.synthesizeKey("s", { accelKey: true }, content); + }); + + info("wait for download to finish"); + let download = await downloadFinishedPromise; + + ok(download.succeeded, "Download should succeed"); + + await publicDownloads.removeFinished(); + await screenshotExit; + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + let screenshotReady = TestUtils.topicObserved( + "screenshots-preview-ready" + ); + + let visibleButton = await helper.getPanelButton(".visible-page"); + visibleButton.click(); + + await screenshotReady; + + screenshotExit = TestUtils.topicObserved("screenshots-exit"); + + EventUtils.synthesizeKey("s", { accelKey: true }); + + info("wait for download to finish"); + download = await downloadFinishedPromise; + + ok(download.succeeded, "Download should succeed"); + + await publicDownloads.removeFinished(); + await screenshotExit; + } + ); +}); + +add_task(async function test_copy_shortcut() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + let helper = new ScreenshotsHelper(browser); + let contentInfo = await helper.getContentDimensions(); + ok(contentInfo, "Got dimensions back from the content"); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + await helper.dragOverlay(10, 10, 500, 500); + + let screenshotExit = TestUtils.topicObserved("screenshots-exit"); + let clipboardChanged = helper.waitForRawClipboardChange(490, 490); + + await SpecialPowers.spawn(browser, [], async () => { + EventUtils.synthesizeKey("c", { accelKey: true }, content); + }); + + await clipboardChanged; + await screenshotExit; + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + let screenshotReady = TestUtils.topicObserved( + "screenshots-preview-ready" + ); + + let visibleButton = await helper.getPanelButton(".visible-page"); + visibleButton.click(); + + await screenshotReady; + + clipboardChanged = helper.waitForRawClipboardChange( + contentInfo.clientWidth, + contentInfo.clientHeight + ); + screenshotExit = TestUtils.topicObserved("screenshots-exit"); + + EventUtils.synthesizeKey("c", { accelKey: true }); + + await clipboardChanged; + await screenshotExit; + } + ); +}); diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_drag_scroll_test.js b/browser/components/screenshots/tests/browser/browser_screenshots_drag_scroll_test.js index 757d721268..86940a5203 100644 --- a/browser/components/screenshots/tests/browser/browser_screenshots_drag_scroll_test.js +++ b/browser/components/screenshots/tests/browser/browser_screenshots_drag_scroll_test.js @@ -353,8 +353,6 @@ add_task(async function test_scrollIfByEdge() { await helper.scrollContentWindow(windowX, windowY); - await TestUtils.waitForTick(); - helper.triggerUIFromToolbar(); await helper.waitForOverlay(); @@ -363,17 +361,18 @@ add_task(async function test_scrollIfByEdge() { is(scrollX, windowX, "Window x position is 1000"); is(scrollY, windowY, "Window y position is 1000"); - let startX = 1100; - let startY = 1100; + let startX = 1200; + let startY = 1200; let endX = 1010; let endY = 1010; - // The window won't scroll if the state is draggingReady so we move to - // get into the dragging state and then move again to scroll the window - mouse.down(startX, startY); - await helper.assertStateChange("draggingReady"); - mouse.move(1050, 1050); - await helper.assertStateChange("dragging"); + await helper.dragOverlay(startX, startY, endX + 20, endY + 20); + await helper.scrollContentWindow(windowX, windowY); + + await TestUtils.waitForTick(); + + mouse.down(endX + 20, endY + 20); + await helper.assertStateChange("resizing"); mouse.move(endX, endY); mouse.up(endX, endY); await helper.assertStateChange("selected"); @@ -387,26 +386,64 @@ add_task(async function test_scrollIfByEdge() { is(scrollX, windowX, "Window x position is 990"); is(scrollY, windowY, "Window y position is 990"); + let screenshotExit = TestUtils.topicObserved("screenshots-exit"); + helper.triggerUIFromToolbar(); + await screenshotExit; + } + ); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + let helper = new ScreenshotsHelper(browser); + + let windowX = 1000; + let windowY = 1000; + + await helper.scrollContentWindow(windowX, windowY); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + let contentInfo = await helper.getContentDimensions(); + let { scrollX, scrollY, clientWidth, clientHeight } = contentInfo; + + let startX = windowX + clientWidth - 200; + let startY = windowX + clientHeight - 200; + let endX = windowX + clientWidth - 10; + let endY = windowY + clientHeight - 10; - endX = windowX + contentInfo.clientWidth - 10; - endY = windowY + contentInfo.clientHeight - 10; + await helper.dragOverlay(startX, startY, endX - 20, endY - 20); + await helper.scrollContentWindow(windowX, windowY); + + await TestUtils.waitForTick(); info( `starting to drag overlay to ${endX}, ${endY} in test\nclientInfo: ${JSON.stringify( contentInfo )}\n` ); - await helper.dragOverlay(startX, startY, endX, endY, "selected"); + mouse.down(endX - 20, endY - 20); + await helper.assertStateChange("resizing"); + mouse.move(endX, endY); + mouse.up(endX, endY); + await helper.assertStateChange("selected"); - windowX = 1000; - windowY = 1000; + windowX = 1010; + windowY = 1010; await helper.waitForScrollTo(windowX, windowY); ({ scrollX, scrollY } = await helper.getContentDimensions()); - is(scrollX, windowX, "Window x position is 1000"); - is(scrollY, windowY, "Window y position is 1000"); + is(scrollX, windowX, "Window x position is 1010"); + is(scrollY, windowY, "Window y position is 1010"); + + let screenshotExit = TestUtils.topicObserved("screenshots-exit"); + helper.triggerUIFromToolbar(); + await screenshotExit; } ); }); @@ -428,13 +465,19 @@ add_task(async function test_scrollIfByEdgeWithKeyboard() { helper.triggerUIFromToolbar(); await helper.waitForOverlay(); - let { scrollX, scrollY, clientWidth, clientHeight } = - await helper.getContentDimensions(); + let { scrollX, scrollY } = await helper.getContentDimensions(); is(scrollX, windowX, "Window x position is 1000"); is(scrollY, windowY, "Window y position is 1000"); - await helper.dragOverlay(1020, 1020, 1120, 1120); + await helper.dragOverlay( + scrollX + 20, + scrollY + 20, + scrollX + 120, + scrollY + 120 + ); + + await helper.scrollContentWindow(windowX, windowY); await helper.moveOverlayViaKeyboard("highlight", [ { key: "ArrowLeft", options: { shiftKey: true } }, @@ -447,14 +490,36 @@ add_task(async function test_scrollIfByEdgeWithKeyboard() { windowY = 989; await helper.waitForScrollTo(windowX, windowY); - ({ scrollX, scrollY, clientWidth, clientHeight } = - await helper.getContentDimensions()); + ({ scrollX, scrollY } = await helper.getContentDimensions()); is(scrollX, windowX, "Window x position is 989"); is(scrollY, windowY, "Window y position is 989"); - mouse.click(1200, 1200); - await helper.assertStateChange("crosshairs"); + let screenshotExit = TestUtils.topicObserved("screenshots-exit"); + helper.triggerUIFromToolbar(); + await screenshotExit; + } + ); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + let helper = new ScreenshotsHelper(browser); + + let windowX = 989; + let windowY = 989; + + await helper.scrollContentWindow(windowX, windowY); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + let { scrollX, scrollY, clientWidth, clientHeight } = + await helper.getContentDimensions(); + await helper.dragOverlay( scrollX + clientWidth - 100 - 20, scrollY + clientHeight - 100 - 20, @@ -477,6 +542,10 @@ add_task(async function test_scrollIfByEdgeWithKeyboard() { is(scrollX, windowX, "Window x position is 1000"); is(scrollY, windowY, "Window y position is 1000"); + + let screenshotExit = TestUtils.topicObserved("screenshots-exit"); + helper.triggerUIFromToolbar(); + await screenshotExit; } ); }); diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_drag_test.js b/browser/components/screenshots/tests/browser/browser_screenshots_drag_test.js index 605e0ae75c..3cef2dbd72 100644 --- a/browser/components/screenshots/tests/browser/browser_screenshots_drag_test.js +++ b/browser/components/screenshots/tests/browser/browser_screenshots_drag_test.js @@ -442,7 +442,7 @@ add_task(async function resizeAllCorners() { /** * This function tests clicking the overlay with the different mouse buttons */ -add_task(async function test_otherMouseButtons() { +add_task(async function test_clickingOtherMouseButtons() { await BrowserTestUtils.withNewTab( { gBrowser, @@ -478,6 +478,7 @@ add_task(async function test_otherMouseButtons() { mouse.down(10, 10, { button: 2 }); mouse.move(100, 100, { button: 2 }); + mouse.up(100, 100, { button: 2 }); await TestUtils.waitForTick(); @@ -486,3 +487,66 @@ add_task(async function test_otherMouseButtons() { } ); }); + +/** + * This function tests dragging the overlay with the different mouse buttons + */ +add_task(async function test_draggingOtherMouseButtons() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + let helper = new ScreenshotsHelper(browser); + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + // Click with button 1 in dragging state + mouse.down(100, 100); + await helper.assertStateChange("draggingReady"); + mouse.move(200, 200); + await helper.assertStateChange("dragging"); + mouse.click(200, 200, { button: 1 }); + await helper.assertStateChange("selected"); + + // Reset + mouse.click(10, 10); + await helper.assertStateChange("crosshairs"); + + // Mouse down with button 2 in draggingReady state + mouse.down(100, 100); + await helper.assertStateChange("draggingReady"); + mouse.down(200, 200, { button: 2 }); + await helper.assertStateChange("crosshairs"); + + await helper.dragOverlay(100, 100, 200, 200); + + // Click with button 1 in resizing state + mouse.down(200, 200); + await helper.assertStateChange("resizing"); + mouse.click(200, 200, { button: 1 }); + + // Reset + mouse.click(10, 10); + await helper.assertStateChange("crosshairs"); + + await helper.dragOverlay(100, 100, 200, 200); + + // Mouse down with button 2 in dragging state + mouse.down(200, 200); + await helper.assertStateChange("resizing"); + mouse.down(200, 200, { button: 2 }); + + // Reset + mouse.click(10, 10); + await helper.assertStateChange("crosshairs"); + + // Mouse move with button 2 in draggingReady state + mouse.down(100, 100); + await helper.assertStateChange("draggingReady"); + mouse.move(100, 100, { button: 2 }); + await helper.assertStateChange("crosshairs"); + } + ); +}); diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_test_toggle_pref.js b/browser/components/screenshots/tests/browser/browser_screenshots_test_toggle_pref.js index ad262a7e67..021a37b5c9 100644 --- a/browser/components/screenshots/tests/browser/browser_screenshots_test_toggle_pref.js +++ b/browser/components/screenshots/tests/browser/browser_screenshots_test_toggle_pref.js @@ -31,7 +31,7 @@ add_task(async function test_toggling_screenshots_pref() { .callsFake(observerSpy); let notifierStub = sinon .stub(ScreenshotsUtils, "notify") - .callsFake(function (window, type) { + .callsFake(function () { notifierSpy(); ScreenshotsUtils.notify.wrappedMethod.apply(this, arguments); }); diff --git a/browser/components/screenshots/tests/browser/browser_test_element_picker.js b/browser/components/screenshots/tests/browser/browser_test_element_picker.js index 17ed2a0190..3e2069134e 100644 --- a/browser/components/screenshots/tests/browser/browser_test_element_picker.js +++ b/browser/components/screenshots/tests/browser/browser_test_element_picker.js @@ -43,14 +43,14 @@ add_task(async function test_element_picker() { ); mouse.click(10, 10); - await helper.waitForStateChange("crosshairs"); + await helper.waitForStateChange(["crosshairs"]); let hoverElementRegionValid = await helper.isHoverElementRegionValid(); ok(!hoverElementRegionValid, "Hover element rect is null"); mouse.click(10, 10); - await helper.waitForStateChange("crosshairs"); + await helper.waitForStateChange(["crosshairs"]); } ); }); diff --git a/browser/components/screenshots/tests/browser/browser_test_selection_size_text.js b/browser/components/screenshots/tests/browser/browser_test_selection_size_text.js new file mode 100644 index 0000000000..38d1acbea9 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_test_selection_size_text.js @@ -0,0 +1,86 @@ +/* Any copyright is dedicated to the Public Domain. + https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_selectionSizeTest() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + const dpr = browser.ownerGlobal.devicePixelRatio; + let helper = new ScreenshotsHelper(browser); + + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + await helper.dragOverlay(100, 100, 500, 500); + + let actualText = await helper.getOverlaySelectionSizeText(); + + Assert.equal( + actualText, + `${400 * dpr} x ${400 * dpr}`, + "The selection size text is the same" + ); + } + ); +}); + +add_task(async function test_selectionSizeTestAt1Point5Zoom() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + const zoom = 1.5; + const dpr = browser.ownerGlobal.devicePixelRatio; + let helper = new ScreenshotsHelper(browser); + helper.zoomBrowser(zoom); + + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + await helper.dragOverlay(100, 100, 500, 500); + + let actualText = await helper.getOverlaySelectionSizeText(); + + Assert.equal( + actualText, + `${400 * dpr * zoom} x ${400 * dpr * zoom}`, + "The selection size text is the same" + ); + } + ); +}); + +add_task(async function test_selectionSizeTestAtPoint5Zoom() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + const zoom = 0.5; + const dpr = browser.ownerGlobal.devicePixelRatio; + let helper = new ScreenshotsHelper(browser); + helper.zoomBrowser(zoom); + + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + await helper.dragOverlay(100, 100, 500, 500); + + let actualText = await helper.getOverlaySelectionSizeText(); + + Assert.equal( + actualText, + `${400 * dpr * zoom} x ${400 * dpr * zoom}`, + "The selection size text is the same" + ); + } + ); +}); diff --git a/browser/components/screenshots/tests/browser/head.js b/browser/components/screenshots/tests/browser/head.js index a36e955830..762da5f866 100644 --- a/browser/components/screenshots/tests/browser/head.js +++ b/browser/components/screenshots/tests/browser/head.js @@ -159,23 +159,23 @@ class ScreenshotsHelper { }); } - waitForStateChange(newState) { - return SpecialPowers.spawn(this.browser, [newState], async state => { + waitForStateChange(newStateArr) { + return SpecialPowers.spawn(this.browser, [newStateArr], async stateArr => { let screenshotsChild = content.windowGlobalChild.getActor( "ScreenshotsComponent" ); await ContentTaskUtils.waitForCondition(() => { - info(`got ${screenshotsChild.overlay.state}. expected ${state}`); - return screenshotsChild.overlay.state === state; - }, `Wait for overlay state to be ${state}`); + info(`got ${screenshotsChild.overlay.state}. expected ${stateArr}`); + return stateArr.includes(screenshotsChild.overlay.state); + }, `Wait for overlay state to be ${stateArr}`); return screenshotsChild.overlay.state; }); } async assertStateChange(newState) { - let currentState = await this.waitForStateChange(newState); + let currentState = await this.waitForStateChange([newState]); is( currentState, @@ -269,18 +269,13 @@ class ScreenshotsHelper { mouse.down(startX, startY); - await Promise.any([ - this.waitForStateChange("draggingReady"), - this.waitForStateChange("resizing"), - ]); + await this.waitForStateChange(["draggingReady", "resizing"]); Assert.ok(true, "The overlay is in the draggingReady or resizing state"); mouse.move(endX, endY); - await Promise.any([ - this.waitForStateChange("dragging"), - this.waitForStateChange("resizing"), - ]); + await this.waitForStateChange(["dragging", "resizing"]); + Assert.ok(true, "The overlay is in the dragging or resizing state"); // We intentionally turn off this a11y check, because the following mouse // event is emitted at the end of the dragging event. Its keyboard @@ -324,7 +319,6 @@ class ScreenshotsHelper { overlay.topRightMover.focus({ focusVisible: true }); break; } - screenshotsChild.overlay.highlightEl.focus(); for (let event of eventsArr) { EventUtils.synthesizeKey( @@ -354,7 +348,6 @@ class ScreenshotsHelper { } async scrollContentWindow(x, y) { - let promise = BrowserTestUtils.waitForContentEvent(this.browser, "scroll"); let contentDims = await this.getContentDimensions(); await ContentTask.spawn( this.browser, @@ -404,7 +397,6 @@ class ScreenshotsHelper { }, `Waiting for window to scroll to ${xPos}, ${yPos}`); } ); - await promise; } async waitForScrollTo(x, y) { @@ -521,6 +513,15 @@ class ScreenshotsHelper { }); } + getOverlaySelectionSizeText(elementId = "testPageElement") { + return ContentTask.spawn(this.browser, [elementId], async () => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + return screenshotsChild.overlay.selectionSize.textContent; + }); + } + async clickTestPageElement(elementId = "testPageElement") { let rect = await this.getTestPageElementRect(elementId); let dims = await this.getContentDimensions(); @@ -909,6 +910,21 @@ add_setup(async () => { ); let screenshotBtn = document.getElementById("screenshot-button"); Assert.ok(screenshotBtn, "The screenshots button was added to the nav bar"); + + registerCleanupFunction(async () => { + info(`downloads panel should be visible: ${DownloadsPanel.isPanelShowing}`); + if (DownloadsPanel.isPanelShowing) { + let hiddenPromise = BrowserTestUtils.waitForEvent( + DownloadsPanel.panel, + "popuphidden" + ); + DownloadsPanel.hidePanel(); + await hiddenPromise; + info( + `downloads panel should not be visible: ${DownloadsPanel.isPanelShowing}` + ); + } + }); }); function getContentDevicePixelRatio(browser) { |