diff options
Diffstat (limited to 'browser/components/screenshots/tests')
28 files changed, 5419 insertions, 0 deletions
diff --git a/browser/components/screenshots/tests/browser/browser.toml b/browser/components/screenshots/tests/browser/browser.toml new file mode 100644 index 0000000000..b363c14732 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser.toml @@ -0,0 +1,55 @@ +[DEFAULT] +support-files = [ + "head.js", + "iframe-test-page.html", + "first-iframe.html", + "second-iframe.html", + "test-page.html", + "short-test-page.html", + "large-test-page.html", + "test-page-resize.html", +] + +prefs = [ + "extensions.screenshots.disabled=false", + "screenshots.browser.component.enabled=true", +] + +["browser_iframe_test.js"] + +["browser_overlay_keyboard_test.js"] + +["browser_screenshots_drag_scroll_test.js"] + +["browser_screenshots_drag_test.js"] + +["browser_screenshots_focus_test.js"] + +["browser_screenshots_overlay_panel_sync.js"] + +["browser_screenshots_page_unload.js"] + +["browser_screenshots_short_page_test.js"] + +["browser_screenshots_telemetry_tests.js"] + +["browser_screenshots_test_downloads.js"] + +["browser_screenshots_test_escape.js"] + +["browser_screenshots_test_full_page.js"] + +["browser_screenshots_test_page_crash.js"] +skip-if = ["!crashreporter"] + +["browser_screenshots_test_screenshot_too_big.js"] + +["browser_screenshots_test_toggle_pref.js"] + +["browser_screenshots_test_toolbar_button.js"] + +["browser_screenshots_test_visible.js"] + +["browser_test_element_picker.js"] + +["browser_test_resize.js"] diff --git a/browser/components/screenshots/tests/browser/browser_iframe_test.js b/browser/components/screenshots/tests/browser/browser_iframe_test.js new file mode 100644 index 0000000000..bb853fbe28 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_iframe_test.js @@ -0,0 +1,123 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_selectingElementsInIframes() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: IFRAME_TEST_PAGE, + }, + async browser => { + let helper = new ScreenshotsHelper(browser); + helper.triggerUIFromToolbar(); + + // There are two iframes in the test page. One iframe is nested in the + // other so we SpecialPowers.spawn into the iframes to get the + // dimension/position of the elements within each iframe. + let elementDimensions = await SpecialPowers.spawn( + browser, + [], + async () => { + let divDims = content.document + .querySelector("div") + .getBoundingClientRect(); + + let iframe = content.document.querySelector("iframe"); + let iframesDivsDimArr = await SpecialPowers.spawn( + iframe, + [], + async () => { + let iframeDivDims = content.document + .querySelector("div") + .getBoundingClientRect(); + + // Element within the first iframe + iframeDivDims = { + left: iframeDivDims.left + content.window.mozInnerScreenX, + top: iframeDivDims.top + content.window.mozInnerScreenY, + width: iframeDivDims.width, + height: iframeDivDims.height, + }; + + let nestedIframe = content.document.querySelector("iframe"); + let nestedIframeDivDims = await SpecialPowers.spawn( + nestedIframe, + [], + async () => { + let secondIframeDivDims = content.document + .querySelector("div") + .getBoundingClientRect(); + + // Element within the nested iframe + secondIframeDivDims = { + left: + secondIframeDivDims.left + + content.document.defaultView.mozInnerScreenX, + top: + secondIframeDivDims.top + + content.document.defaultView.mozInnerScreenY, + width: secondIframeDivDims.width, + height: secondIframeDivDims.height, + }; + + return secondIframeDivDims; + } + ); + + return [iframeDivDims, nestedIframeDivDims]; + } + ); + + // Offset each element position for the browser window + for (let dims of iframesDivsDimArr) { + dims.left -= content.window.mozInnerScreenX; + dims.top -= content.window.mozInnerScreenY; + } + + return [divDims].concat(iframesDivsDimArr); + } + ); + + info(JSON.stringify(elementDimensions, null, 2)); + + for (let el of elementDimensions) { + let x = el.left + el.width / 2; + let y = el.top + el.height / 2; + + mouse.move(x, y); + await helper.waitForHoverElementRect(el.width, el.height); + mouse.click(x, y); + + await helper.waitForStateChange("selected"); + + let dimensions = await helper.getSelectionRegionDimensions(); + + is( + dimensions.left, + el.left, + "The region left position matches the elements left position" + ); + is( + dimensions.top, + el.top, + "The region top position matches the elements top position" + ); + is( + dimensions.width, + el.width, + "The region width matches the elements width" + ); + is( + dimensions.height, + el.height, + "The region height matches the elements height" + ); + + mouse.click(500, 500); + await helper.waitForStateChange("crosshairs"); + } + } + ); +}); diff --git a/browser/components/screenshots/tests/browser/browser_overlay_keyboard_test.js b/browser/components/screenshots/tests/browser/browser_overlay_keyboard_test.js new file mode 100644 index 0000000000..592587a67d --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_overlay_keyboard_test.js @@ -0,0 +1,748 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; +const KEY_TO_EXPECTED_POSITION_ARRAY = [ + [ + "ArrowRight", + { + top: 10, + left: 20, + bottom: 20, + right: 30, + }, + ], + [ + "ArrowDown", + { + top: 20, + left: 20, + bottom: 30, + right: 30, + }, + ], + [ + "ArrowLeft", + { + top: 20, + left: 10, + bottom: 30, + right: 20, + }, + ], + [ + "ArrowUp", + { + top: 10, + left: 10, + bottom: 20, + right: 20, + }, + ], + ["ArrowDown", { top: 20, left: 10, bottom: 30, right: 20 }], + [ + "ArrowRight", + { + top: 20, + left: 20, + bottom: 30, + right: 30, + }, + ], + [ + "ArrowUp", + { + top: 10, + left: 20, + bottom: 20, + right: 30, + }, + ], + [ + "ArrowLeft", + { + top: 10, + left: 10, + bottom: 20, + right: 20, + }, + ], +]; + +const SHIFT_PLUS_KEY_TO_EXPECTED_POSITION_ARRAY = [ + [ + "ArrowRight", + { + top: 100, + left: 200, + bottom: 200, + right: 300, + }, + ], + [ + "ArrowDown", + { + top: 200, + left: 200, + bottom: 300, + right: 300, + }, + ], + [ + "ArrowLeft", + { + top: 200, + left: 100, + bottom: 300, + right: 200, + }, + ], + [ + "ArrowUp", + { + top: 100, + left: 100, + bottom: 200, + right: 200, + }, + ], + ["ArrowDown", { top: 200, left: 100, bottom: 300, right: 200 }], + [ + "ArrowRight", + { + top: 200, + left: 200, + bottom: 300, + right: 300, + }, + ], + [ + "ArrowUp", + { + top: 100, + left: 200, + bottom: 200, + right: 300, + }, + ], + [ + "ArrowLeft", + { + top: 100, + left: 100, + bottom: 200, + right: 200, + }, + ], +]; + +/** + * + */ +add_task(async function test_moveRegionWithKeyboard() { + 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(); + + // Because the screenshots state won't go from draggingReady to + // dragging until the diagonal distance is 40px, we have to resize + // it to get the region to 10px x 10px + await helper.dragOverlay(10, 10, 100, 100); + mouse.down(100, 100); + await helper.assertStateChange("resizing"); + mouse.move(20, 20); + mouse.up(20, 20); + await helper.assertStateChange("selected"); + + await SpecialPowers.spawn( + browser, + [KEY_TO_EXPECTED_POSITION_ARRAY], + async keyToExpectedPositionArray => { + function assertSelectionRegionDimensions( + actualDimensions, + expectedDimensions + ) { + is( + actualDimensions.top, + expectedDimensions.top, + "Top dimension is correct" + ); + is( + actualDimensions.left, + expectedDimensions.left, + "Left dimension is correct" + ); + is( + actualDimensions.bottom, + expectedDimensions.bottom, + "Bottom dimension is correct" + ); + is( + actualDimensions.right, + expectedDimensions.right, + "Right dimension is correct" + ); + } + + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + + // Test moving each corner of the region + screenshotsChild.overlay.topLeftMover.focus(); + + // Check that initial position is correct + assertSelectionRegionDimensions( + screenshotsChild.overlay.selectionRegion.dimensions, + { + top: 10, + left: 10, + bottom: 20, + right: 20, + } + ); + + for (let [key, expectedDimensions] of keyToExpectedPositionArray) { + EventUtils.synthesizeKey(key, { repeat: 20 }, content); + assertSelectionRegionDimensions( + screenshotsChild.overlay.selectionRegion.dimensions, + expectedDimensions + ); + } + + // Test moving the highlight element + screenshotsChild.overlay.highlightEl.focus(); + + // Check that initial position is correct + assertSelectionRegionDimensions( + screenshotsChild.overlay.selectionRegion.dimensions, + { + top: 10, + left: 10, + bottom: 20, + right: 20, + } + ); + + for (let [key, expectedDimensions] of keyToExpectedPositionArray) { + EventUtils.synthesizeKey(key, { repeat: 10 }, content); + assertSelectionRegionDimensions( + screenshotsChild.overlay.selectionRegion.dimensions, + expectedDimensions + ); + } + } + ); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlayClosed(); + } + ); +}); + +add_task(async function test_moveRegionWithKeyboardWithShift() { + 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(100, 100, 200, 200); + + await SpecialPowers.spawn( + browser, + [SHIFT_PLUS_KEY_TO_EXPECTED_POSITION_ARRAY], + async shiftPlusKeyToExpectedPositionArray => { + function assertSelectionRegionDimensions( + actualDimensions, + expectedDimensions + ) { + is( + actualDimensions.top, + expectedDimensions.top, + "Top dimension is correct" + ); + is( + actualDimensions.left, + expectedDimensions.left, + "Left dimension is correct" + ); + is( + actualDimensions.bottom, + expectedDimensions.bottom, + "Bottom dimension is correct" + ); + is( + actualDimensions.right, + expectedDimensions.right, + "Right dimension is correct" + ); + } + + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + + // Test moving each corner of the region + screenshotsChild.overlay.topLeftMover.focus(); + + // Check that initial position is correct + assertSelectionRegionDimensions( + screenshotsChild.overlay.selectionRegion.dimensions, + { + top: 100, + left: 100, + bottom: 200, + right: 200, + } + ); + + for (let [ + key, + expectedDimensions, + ] of shiftPlusKeyToExpectedPositionArray) { + EventUtils.synthesizeKey( + key, + { repeat: 20, shiftKey: true }, + content + ); + assertSelectionRegionDimensions( + screenshotsChild.overlay.selectionRegion.dimensions, + expectedDimensions + ); + } + + // Test moving the highlight element + screenshotsChild.overlay.highlightEl.focus(); + + // Check that initial position is correct + assertSelectionRegionDimensions( + screenshotsChild.overlay.selectionRegion.dimensions, + { + top: 100, + left: 100, + bottom: 200, + right: 200, + } + ); + + for (let [ + key, + expectedDimensions, + ] of shiftPlusKeyToExpectedPositionArray) { + EventUtils.synthesizeKey( + key, + { repeat: 10, shiftKey: true }, + content + ); + assertSelectionRegionDimensions( + screenshotsChild.overlay.selectionRegion.dimensions, + expectedDimensions + ); + } + } + ); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlayClosed(); + } + ); +}); + +add_task(async function test_moveRegionWithKeyboardWithAccelKey() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + let helper = new ScreenshotsHelper(browser); + + await helper.scrollContentWindow(100, 100); + + let contentWindowDimensions = await helper.getContentDimensions(); + ok(contentWindowDimensions, "Got dimensions back from the content"); + + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + + await helper.dragOverlay(100, 100, 200, 200); + + info("Test moving the the highlight element"); + await SpecialPowers.spawn( + browser, + [contentWindowDimensions], + async contentDimensions => { + function assertSelectionRegionDimensions( + actualDimensions, + expectedDimensions + ) { + is( + actualDimensions.top, + expectedDimensions.top, + "Top dimension is correct" + ); + is( + actualDimensions.left, + expectedDimensions.left, + "Left dimension is correct" + ); + is( + actualDimensions.bottom, + expectedDimensions.bottom, + "Bottom dimension is correct" + ); + is( + actualDimensions.right, + expectedDimensions.right, + "Right dimension is correct" + ); + } + + let { scrollX, scrollY, clientHeight, clientWidth } = + contentDimensions; + + const HIGHLIGHT_CONTROL_PLUS_KEY_TO_EXPECTED_POSITION_ARRAY = [ + [ + "ArrowRight", + { + top: scrollY, + left: scrollX + clientWidth - 100, + bottom: scrollY + 100, + right: scrollX + clientWidth, + }, + ], + [ + "ArrowDown", + { + top: scrollY + clientHeight - 100, + left: scrollX + clientWidth - 100, + bottom: scrollY + clientHeight, + right: scrollX + clientWidth, + }, + ], + [ + "ArrowLeft", + { + top: scrollY + clientHeight - 100, + left: scrollX, + bottom: scrollY + clientHeight, + right: scrollX + 100, + }, + ], + [ + "ArrowUp", + { + top: scrollY, + left: scrollX, + bottom: scrollY + 100, + right: scrollX + 100, + }, + ], + [ + "ArrowDown", + { + top: scrollY + clientHeight - 100, + left: scrollX, + bottom: scrollY + clientHeight, + right: scrollX + 100, + }, + ], + [ + "ArrowRight", + { + top: scrollY + clientHeight - 100, + left: scrollX + clientWidth - 100, + bottom: scrollY + clientHeight, + right: scrollX + clientWidth, + }, + ], + [ + "ArrowUp", + { + top: scrollY, + left: scrollX + clientWidth - 100, + bottom: scrollY + 100, + right: scrollX + clientWidth, + }, + ], + [ + "ArrowLeft", + { + top: scrollY, + left: scrollX, + bottom: scrollY + 100, + right: scrollX + 100, + }, + ], + ]; + + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + screenshotsChild.overlay.highlightEl.focus(); + + // Move the region around in a clockwise direction + // Check that original position is correct + assertSelectionRegionDimensions( + screenshotsChild.overlay.selectionRegion.dimensions, + { + top: 100, + left: 100, + bottom: 200, + right: 200, + } + ); + + for (let [ + key, + expectedDimensions, + ] of HIGHLIGHT_CONTROL_PLUS_KEY_TO_EXPECTED_POSITION_ARRAY) { + EventUtils.synthesizeKey(key, { accelKey: true }, content); + assertSelectionRegionDimensions( + screenshotsChild.overlay.selectionRegion.dimensions, + expectedDimensions + ); + } + } + ); + + mouse.click(300, 300); + await helper.assertStateChange("crosshairs"); + + await helper.dragOverlay(200, 200, 300, 300); + + info("Test moving the corners clockwise"); + await SpecialPowers.spawn( + browser, + [contentWindowDimensions], + async contentDimensions => { + function assertSelectionRegionDimensions( + actualDimensions, + expectedDimensions + ) { + is( + actualDimensions.top, + expectedDimensions.top, + "Top dimension is correct" + ); + is( + actualDimensions.left, + expectedDimensions.left, + "Left dimension is correct" + ); + is( + actualDimensions.bottom, + expectedDimensions.bottom, + "Bottom dimension is correct" + ); + is( + actualDimensions.right, + expectedDimensions.right, + "Right dimension is correct" + ); + } + + let { scrollX, scrollY, clientHeight, clientWidth } = + contentDimensions; + + const CONTROL_PLUS_KEY_TO_EXPECTED_POSITION_ARRAY = [ + [ + "ArrowRight", + { + top: scrollY + 100, + left: scrollX + 100 + 100, + bottom: scrollY + 100 + 100, + right: scrollX + clientWidth, + }, + ], + [ + "ArrowDown", + { + top: scrollY + 100 + 100, + left: scrollX + 100 + 100, + bottom: scrollY + clientHeight, + right: scrollX + clientWidth, + }, + ], + [ + "ArrowLeft", + { + top: scrollY + 100 + 100, + left: scrollX, + bottom: scrollY + clientHeight, + right: scrollX + 100 + 100, + }, + ], + [ + "ArrowUp", + { + top: scrollY, + left: scrollX, + bottom: scrollY + 100 + 100, + right: scrollX + 100 + 100, + }, + ], + ]; + + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + screenshotsChild.overlay.topLeftMover.focus(); + + // Move the region around in a clockwise direction + // Check that original position is correct + assertSelectionRegionDimensions( + screenshotsChild.overlay.selectionRegion.dimensions, + { + top: 200, + left: 200, + bottom: 300, + right: 300, + } + ); + + for (let [ + key, + expectedDimensions, + ] of CONTROL_PLUS_KEY_TO_EXPECTED_POSITION_ARRAY) { + EventUtils.synthesizeKey(key, { accelKey: true }, content); + assertSelectionRegionDimensions( + screenshotsChild.overlay.selectionRegion.dimensions, + expectedDimensions + ); + } + } + ); + + mouse.click(400, 400); + await helper.assertStateChange("crosshairs"); + + await helper.dragOverlay(200, 200, 300, 300); + + info("Test moving the corners counter clockwise"); + await SpecialPowers.spawn( + browser, + [contentWindowDimensions], + async contentDimensions => { + function assertSelectionRegionDimensions( + actualDimensions, + expectedDimensions + ) { + is( + actualDimensions.top, + expectedDimensions.top, + "Top dimension is correct" + ); + is( + actualDimensions.left, + expectedDimensions.left, + "Left dimension is correct" + ); + is( + actualDimensions.bottom, + expectedDimensions.bottom, + "Bottom dimension is correct" + ); + is( + actualDimensions.right, + expectedDimensions.right, + "Right dimension is correct" + ); + } + + let { scrollX, scrollY, clientHeight, clientWidth } = + contentDimensions; + + const CONTROL_PLUS_KEY_TO_EXPECTED_POSITION_ARRAY = [ + [ + "ArrowDown", + { + top: scrollY + 100 + 100, + left: scrollX + 100, + bottom: scrollY + clientHeight, + right: scrollX + 100 + 100, + }, + ], + [ + "ArrowRight", + { + top: scrollY + 100 + 100, + left: scrollX + 100 + 100, + bottom: scrollY + clientHeight, + right: scrollX + clientWidth, + }, + ], + [ + "ArrowUp", + { + top: scrollY, + left: scrollX + 100 + 100, + bottom: scrollY + 100 + 100, + right: scrollX + clientWidth, + }, + ], + [ + "ArrowLeft", + { + top: scrollY, + left: scrollX, + bottom: scrollY + 100 + 100, + right: scrollX + 100 + 100, + }, + ], + ]; + + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + screenshotsChild.overlay.topLeftMover.focus(); + + // Move the region around in a clockwise direction + // Check that original position is correct + assertSelectionRegionDimensions( + screenshotsChild.overlay.selectionRegion.dimensions, + { + top: 200, + left: 200, + bottom: 300, + right: 300, + } + ); + + for (let [ + key, + expectedDimensions, + ] of CONTROL_PLUS_KEY_TO_EXPECTED_POSITION_ARRAY) { + EventUtils.synthesizeKey(key, { accelKey: true }, content); + assertSelectionRegionDimensions( + screenshotsChild.overlay.selectionRegion.dimensions, + expectedDimensions + ); + } + } + ); + } + ); +}); 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 new file mode 100644 index 0000000000..7fdb084ca6 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_drag_scroll_test.js @@ -0,0 +1,465 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Test that screenshots overlay covers the entire page + */ +add_task(async function test_overlayCoversEntirePage() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + let helper = new ScreenshotsHelper(browser); + let contentInfo = await helper.getContentDimensions(); + info(JSON.stringify(contentInfo, null, 2)); + ok(contentInfo, "Got dimensions back from the content"); + + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + await helper.dragOverlay(10, 10, 500, 500); + + let { scrollWidth, scrollHeight } = + await helper.getScreenshotsOverlayDimensions(); + + is( + scrollWidth, + contentInfo.scrollWidth, + "The overlay spans the entire width of the page" + ); + + is( + scrollHeight, + contentInfo.scrollHeight, + "The overlay spans the entire height of the page" + ); + } + ); +}); + +/** + * Test dragging screenshots box off top left of screen + */ +add_task(async function test_draggingBoxOffTopLeft() { + 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(); + + let startX = 10; + let startY = 10; + let endX = 500; + let endY = 500; + await helper.dragOverlay(startX, startY, endX, endY); + + mouse.down( + startX + Math.floor((endX - startX) / 2), + startY + Math.floor((endY - startY) / 2) + ); + + await helper.assertStateChange("resizing"); + + mouse.move(10, 10); + + // We moved the box to the edge of the screen so we need to wait until the box size is updated + await helper.waitForSelectionRegionSizeChange(490); + + let dimensions = await helper.getSelectionRegionDimensions(); + + is(dimensions.left, 0, "The box x1 position is now 0"); + is(dimensions.top, 0, "The box y1 position is now 0"); + is(dimensions.width, 255, "The box width is now 255"); + is(dimensions.height, 255, "The box height is now 255"); + + mouse.move( + startX + Math.floor((endX - startX) / 2), + startY + Math.floor((endY - startY) / 2) + ); + + mouse.up( + startX + Math.floor((endX - startX) / 2), + startY + Math.floor((endY - startY) / 2) + ); + + // We moved the box off the edge of the screen so we need to wait until the box size is updated + await helper.waitForSelectionRegionSizeChange(255); + + dimensions = await helper.getSelectionRegionDimensions(); + + is(dimensions.left, 10, "The box x1 position is now 10 again"); + is(dimensions.top, 10, "The box y1 position is now 10 again"); + is(dimensions.width, 490, "The box width is now 490 again"); + is(dimensions.height, 490, "The box height is now 490 again"); + } + ); +}); + +/** + * Test dragging screenshots box off bottom right of screen + */ +add_task(async function test_draggingBoxOffBottomRight() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + let helper = new ScreenshotsHelper(browser); + + let contentInfo = await helper.getContentDimensions(); + info(JSON.stringify(contentInfo)); + ok(contentInfo, "Got dimensions back from the content"); + + await helper.scrollContentWindow( + contentInfo.scrollWidth - contentInfo.clientWidth, + contentInfo.scrollHeight - contentInfo.clientHeight + ); + + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + + let startX = contentInfo.scrollWidth - 500; + let startY = contentInfo.scrollHeight - 500; + let endX = contentInfo.scrollWidth - 20; + let endY = contentInfo.scrollHeight - 20; + + await helper.dragOverlay(startX, startY, endX, endY); + + // move box off the bottom right of the screen + mouse.down( + startX + Math.floor((endX - startX) / 2), + startY + Math.floor((endY - startY) / 2) + ); + mouse.move( + startX + 50 + Math.floor((endX - startX) / 2), + startY + 50 + Math.floor((endY - startY) / 2) + ); + + await helper.assertStateChange("resizing"); + + mouse.move(endX, endY); + + // We moved the box to the edge of the screen so we need to wait until the box size is updated + await helper.waitForSelectionRegionSizeChange(480); + + let dimensions = await helper.getSelectionRegionDimensions(); + + is(dimensions.left, startX + 240, "The box x1 position is now 3748"); + is(dimensions.top, startY + 240, "The box y1 position is now 3756"); + is(dimensions.width, 260, "The box width is now 260"); + is(dimensions.height, 260, "The box height is now 260"); + + mouse.move( + startX + Math.floor((endX - startX) / 2), + startY + Math.floor((endY - startY) / 2) + ); + + mouse.up( + startX + Math.floor((endX - startX) / 2), + startY + Math.floor((endY - startY) / 2) + ); + + // We moved the box off the edge of the screen so we need to wait until the box size is updated + await helper.waitForSelectionRegionSizeChange(252); + + dimensions = await helper.getSelectionRegionDimensions(); + + is(dimensions.left, startX, "The box x1 position is now 3508 again"); + is(dimensions.top, startY, "The box y1 position is now 3516 again"); + is(dimensions.width, 480, "The box width is now 480 again"); + is(dimensions.height, 480, "The box height is now 480 again"); + } + ); +}); + +/** + * test scrolling while screenshots is open + */ +add_task(async function test_scrollingScreenshotsOpen() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + let helper = new ScreenshotsHelper(browser); + + let contentInfo = await helper.getContentDimensions(); + info(JSON.stringify(contentInfo)); + ok(contentInfo, "Got dimensions back from the content"); + + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + + let startX = 10; + let startY = 10; + let endX = 100; + let endY = 100; + + await helper.dragOverlay(startX, startY, endX, endY); + + let scrollX = 1000; + let scrollY = 1000; + + await helper.scrollContentWindow(scrollX, scrollY); + + let dimensions = await helper.getSelectionRegionDimensions(); + + is(dimensions.left, startX, "The box x1 position is 10"); + is(dimensions.top, startY, "The box y1 position is 10"); + is(dimensions.width, endX - startX, "The box width is now 90"); + is(dimensions.height, endY - startY, "The box height is now 90"); + + // reset screenshots box + await helper.escapeKeyInContent(); + await helper.assertStateChange("crosshairs"); + + await helper.dragOverlay( + scrollX + startX, + scrollY + startY, + scrollX + endX, + scrollY + endY + ); + + await helper.scrollContentWindow(0, 0); + + dimensions = await helper.getSelectionRegionDimensions(); + + is(dimensions.left, scrollX + startX, "The box x1 position is 1010"); + is(dimensions.top, scrollY + startY, "The box y1 position is 1010"); + is(dimensions.width, endX - startX, "The box width is now 90"); + is(dimensions.height, endY - startY, "The box height is now 90"); + + // reset screenshots box + await helper.escapeKeyInContent(); + await helper.assertStateChange("crosshairs"); + + await helper.dragOverlay( + startX, + startY, + contentInfo.clientWidth - 10, + contentInfo.clientHeight - 10 + ); + + await helper.scrollContentWindow( + contentInfo.clientWidth - 20, + contentInfo.clientHeight - 20 + ); + + mouse.down(contentInfo.clientWidth - 10, contentInfo.clientHeight - 10); + + await helper.assertStateChange("resizing"); + + mouse.move( + contentInfo.clientWidth * 2 - 30, + contentInfo.clientHeight * 2 - 30 + ); + + mouse.up( + contentInfo.clientWidth * 2 - 30, + contentInfo.clientHeight * 2 - 30 + ); + + await helper.assertStateChange("selected"); + + let { left, top, right, bottom, width, height } = + await helper.getSelectionRegionDimensions(); + let { scrollWidth, scrollHeight } = + await helper.getScreenshotsOverlayDimensions(); + + is(left, startX, "The box left is 10"); + is(top, startY, "The box top is 10"); + is( + right, + contentInfo.clientWidth * 2 - 30, + "The box right is 2 x clientWidth - 30" + ); + is( + bottom, + contentInfo.clientHeight * 2 - 30, + "The box right is 2 x clientHeight - 30" + ); + is( + width, + contentInfo.clientWidth * 2 - 40, + "The box right is 2 x clientWidth - 40" + ); + is( + height, + contentInfo.clientHeight * 2 - 40, + "The box right is 2 x clientHeight - 40" + ); + is( + scrollWidth, + contentInfo.scrollWidth, + "The overlay spans the entire width of the page" + ); + is( + scrollHeight, + contentInfo.scrollHeight, + "The overlay spans the entire height of the page" + ); + } + ); +}); + +/** + * test scroll if by edge + */ +add_task(async function test_scrollIfByEdge() { + 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); + + await TestUtils.waitForTick(); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + let { scrollX, scrollY } = await helper.getContentDimensions(); + + is(scrollX, windowX, "Window x position is 1000"); + is(scrollY, windowY, "Window y position is 1000"); + + let startX = 1100; + let startY = 1100; + 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"); + mouse.move(endX, endY); + mouse.up(endX, endY); + await helper.assertStateChange("selected"); + + windowX = 990; + windowY = 990; + await helper.waitForScrollTo(windowX, windowY); + + ({ scrollX, scrollY } = await helper.getContentDimensions()); + + is(scrollX, windowX, "Window x position is 990"); + is(scrollY, windowY, "Window y position is 990"); + + let contentInfo = await helper.getContentDimensions(); + + endX = windowX + contentInfo.clientWidth - 10; + endY = windowY + contentInfo.clientHeight - 10; + + info( + `starting to drag overlay to ${endX}, ${endY} in test\nclientInfo: ${JSON.stringify( + contentInfo + )}\n` + ); + await helper.dragOverlay(startX, startY, endX, endY, "selected"); + + windowX = 1000; + windowY = 1000; + 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"); + } + ); +}); + +add_task(async function test_scrollIfByEdgeWithKeyboard() { + 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 { scrollX, scrollY, clientWidth, clientHeight } = + 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.moveOverlayViaKeyboard("highlight", [ + { key: "ArrowLeft", options: { shiftKey: true } }, + { key: "ArrowLeft", options: {} }, + { key: "ArrowUp", options: { shiftKey: true } }, + { key: "ArrowUp", options: {} }, + ]); + + windowX = 989; + windowY = 989; + await helper.waitForScrollTo(windowX, windowY); + + ({ scrollX, scrollY, clientWidth, clientHeight } = + 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"); + await helper.dragOverlay( + scrollX + clientWidth - 100 - 20, + scrollY + clientHeight - 100 - 20, + scrollX + clientWidth - 20, + scrollY + clientHeight - 20 + ); + + await helper.moveOverlayViaKeyboard("highlight", [ + { key: "ArrowRight", options: { shiftKey: true } }, + { key: "ArrowRight", options: {} }, + { key: "ArrowDown", options: { shiftKey: true } }, + { key: "ArrowDown", options: {} }, + ]); + + windowX = 1000; + windowY = 1000; + 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"); + } + ); +}); diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_drag_test.js b/browser/components/screenshots/tests/browser/browser_screenshots_drag_test.js new file mode 100644 index 0000000000..605e0ae75c --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_drag_test.js @@ -0,0 +1,488 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This function drags to a 490x490 area and copies to the clipboard + */ +add_task(async function dragTest() { + 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"); + let expected = Math.floor( + 490 * (await getContentDevicePixelRatio(browser)) + ); + + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + + await helper.dragOverlay(10, 10, 500, 500); + + let clipboardChanged = helper.waitForRawClipboardChange( + expected, + expected + ); + + await helper.clickCopyButton(); + + info("Waiting for clipboard change"); + let result = await clipboardChanged; + + info("result: " + JSON.stringify(result, null, 2)); + + Assert.equal( + result.width, + expected, + `The copied image from the overlay is ${expected}px in width` + ); + Assert.equal( + result.height, + expected, + `The copied image from the overlay is ${expected}px in height` + ); + } + ); +}); + +/** + * This function drags a 1.5 zoomed browser to a 490x490 area and copies to the clipboard + */ +add_task(async function dragTest1Point5Zoom() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + const zoom = 1.5; + let helper = new ScreenshotsHelper(browser); + helper.zoomBrowser(zoom); + + let contentInfo = await helper.getContentDimensions(); + ok(contentInfo, "Got dimensions back from the content"); + let expected = Math.floor( + 50 * (await getContentDevicePixelRatio(browser)) + ); + + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + + await helper.dragOverlay(300, 100, 350, 150); + + let clipboardChanged = helper.waitForRawClipboardChange( + expected, + expected + ); + + await helper.clickCopyButton(); + + info("Waiting for clipboard change"); + let result = await clipboardChanged; + + result.zoom = zoom; + result.devicePixelRatio = window.devicePixelRatio; + result.contentDevicePixelRatio = await getContentDevicePixelRatio( + browser + ); + + info("result: " + JSON.stringify(result, null, 2)); + + Assert.equal( + result.width, + expected, + `The copied image from the overlay is ${expected}px in width` + ); + Assert.equal( + result.height, + expected, + `The copied image from the overlay is ${expected}px in height` + ); + } + ); +}); + +/** + * This function drags an area and clicks elsewhere + * on the overlay to go back to the crosshairs state + */ +add_task(async function clickOverlayResetState() { + 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, 100, 100); + + // click outside overlay + mouse.click(200, 200); + + await helper.assertStateChange("crosshairs"); + } + ); +}); + +/** + * This function drags an area and clicks the + * cancel button to restart the overlay + */ +add_task(async function overlayCancelButton() { + 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, 300, 300); + + await helper.clickCancelButton(); + + await helper.assertStateChange("crosshairs"); + } + ); +}); + +/** + * This function drags a 490x490 area and moves it along the edges + * and back to the center to confirm that the original size is preserved + */ +add_task(async function preserveBoxSizeWhenMovingOutOfWindowBounds() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: SHORT_TEST_PAGE, + }, + async browser => { + let helper = new ScreenshotsHelper(browser); + + let contentInfo = await helper.getContentDimensions(); + ok(contentInfo, "Got dimensions back from the content"); + let expected = Math.floor( + 490 * (await getContentDevicePixelRatio(browser)) + ); + + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + + await helper.dragOverlay(10, 10, 500, 500); + + let startX = 10; + let startY = 10; + let endX = 500; + let endY = 500; + + mouse.down( + Math.floor((endX - startX) / 2), + Math.floor((endY - startY) / 2) + ); + + await helper.assertStateChange("resizing"); + + mouse.move(10, 10); + + mouse.move(contentInfo.clientWidth - 10, contentInfo.clientHeight - 10); + + mouse.up( + Math.floor((endX - startX) / 2), + Math.floor((endY - startY) / 2) + ); + + await helper.assertStateChange("selected"); + + let clipboardChanged = helper.waitForRawClipboardChange( + expected, + expected + ); + + await helper.clickCopyButton(); + + info("Waiting for clipboard change"); + let result = await clipboardChanged; + + info("result: " + JSON.stringify(result, null, 2)); + + Assert.equal( + result.width, + expected, + `The copied image from the overlay is ${expected}px in width` + ); + Assert.equal( + result.height, + expected, + `The copied image from the overlay is ${expected}px in height` + ); + } + ); +}); + +/** + * This function drags a 490x490 area and resizes it to a 300x300 area + * with the 4 sides of the box + */ +add_task(async function resizeAllEdges() { + 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"); + let expected = Math.floor( + 300 * (await getContentDevicePixelRatio(browser)) + ); + + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + + await helper.dragOverlay(10, 10, 500, 500); + + let startX = 10; + let startY = 10; + let endX = 500; + let endY = 500; + + let x = Math.floor((endX - startX) / 2); + + // drag top + mouse.down(x, 10); + + await helper.assertStateChange("resizing"); + + mouse.move(x, 100); + mouse.up(x, 100); + + await helper.assertStateChange("selected"); + + // drag bottom + mouse.down(x, 500); + + await helper.assertStateChange("resizing"); + + mouse.move(x, 400); + mouse.up(x, 400); + + await helper.assertStateChange("selected"); + + // drag right + let y = Math.floor((endY - startY) / 2); + mouse.down(500, y); + + await helper.assertStateChange("resizing"); + + mouse.move(400, y); + mouse.up(400, y); + + await helper.assertStateChange("selected"); + + // drag left + mouse.down(10, y); + + await helper.assertStateChange("resizing"); + + mouse.move(100, y); + mouse.up(100, y); + + await helper.assertStateChange("selected"); + + let clipboardChanged = helper.waitForRawClipboardChange( + expected, + expected + ); + + helper.endX = 400; + helper.endY = 400; + + await helper.clickCopyButton(); + + info("Waiting for clipboard change"); + let result = await clipboardChanged; + + info("result: " + JSON.stringify(result, null, 2)); + + Assert.equal( + result.width, + expected, + `The copied image from the overlay is ${expected}px in width` + ); + Assert.equal( + result.height, + expected, + `The copied image from the overlay is ${expected}px in height` + ); + } + ); +}); + +/** + * This function drags a 490x490 area and resizes it to a 300x300 area + * with the 4 corners of the box + */ +add_task(async function resizeAllCorners() { + 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"); + let expected = Math.floor( + 300 * (await getContentDevicePixelRatio(browser)) + ); + + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + + await helper.dragOverlay(10, 10, 500, 500); + + // drag topright + mouse.down(500, 10); + + await helper.assertStateChange("resizing"); + + mouse.move(450, 50); + mouse.up(450, 50); + + await helper.assertStateChange("selected"); + + // drag bottomright + mouse.down(450, 500); + + await helper.assertStateChange("resizing"); + + mouse.move(400, 450); + mouse.up(400, 450); + + await helper.assertStateChange("selected"); + + // drag bottomleft + mouse.down(10, 450); + + await helper.assertStateChange("resizing"); + + mouse.move(50, 400); + mouse.up(50, 400); + + await helper.assertStateChange("selected"); + + // drag topleft + mouse.down(50, 50); + + await helper.assertStateChange("resizing"); + + mouse.move(100, 100); + mouse.up(100, 100); + + await helper.assertStateChange("selected"); + + let clipboardChanged = helper.waitForRawClipboardChange( + expected, + expected + ); + + helper.endX = 400; + helper.endY = 400; + + await helper.clickCopyButton(); + + info("Waiting for clipboard change"); + let result = await clipboardChanged; + + info("result: " + JSON.stringify(result, null, 2)); + + Assert.equal( + result.width, + expected, + `The copied image from the overlay is ${expected}px in width` + ); + Assert.equal( + result.height, + expected, + `The copied image from the overlay is ${expected}px in height` + ); + } + ); +}); + +/** + * This function tests clicking the overlay with the different mouse buttons + */ +add_task(async function test_otherMouseButtons() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + let helper = new ScreenshotsHelper(browser); + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + + await helper.dragOverlay(10, 10, 100, 100); + + // click outside overlay + mouse.click(200, 200, { button: 1 }); + mouse.click(200, 200, { button: 2 }); + + await TestUtils.waitForTick(); + + await helper.assertStateChange("selected"); + + mouse.click(200, 200); + + await helper.assertStateChange("crosshairs"); + + mouse.down(10, 10, { button: 1 }); + mouse.move(100, 100, { button: 1 }); + mouse.up(100, 100, { button: 1 }); + + await TestUtils.waitForTick(); + + await helper.assertStateChange("crosshairs"); + + mouse.down(10, 10, { button: 2 }); + mouse.move(100, 100, { button: 2 }); + mouse.up(100, 100, { button: 2 }); + + await TestUtils.waitForTick(); + + await helper.assertStateChange("crosshairs"); + } + ); +}); diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_focus_test.js b/browser/components/screenshots/tests/browser/browser_screenshots_focus_test.js new file mode 100644 index 0000000000..367f62205e --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_focus_test.js @@ -0,0 +1,384 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const SCREENSHOTS_EVENTS = [ + { category: "screenshots", method: "started", object: "toolbar_button" }, + { category: "screenshots", method: "canceled", object: "escape" }, +]; + +const SCREENSHOTS_LAST_SCREENSHOT_METHOD_PREF = + "screenshots.browser.component.last-screenshot-method"; +const SCREENSHOTS_LAST_SAVED_METHOD_PREF = + "screenshots.browser.component.last-saved-method"; + +async function restoreFocusOnEscape(initialFocusElem, helper) { + info( + `restoreFocusOnEscape, focusedElement: ${Services.focus.focusedElement.localName}#${Services.focus.focusedElement.id}` + ); + is( + window, + BrowserWindowTracker.getTopWindow(), + "Our window is the top window" + ); + + let gotFocus; + if (Services.focus.focusedElement !== initialFocusElem) { + gotFocus = BrowserTestUtils.waitForEvent(initialFocusElem, "focus"); + await SimpleTest.promiseFocus(initialFocusElem.ownerGlobal); + Services.focus.setFocus(initialFocusElem, Services.focus.FLAG_BYKEY); + info( + `Waiting to place focus on initialFocusElem: ${initialFocusElem.localName}#${initialFocusElem.id}` + ); + await gotFocus; + } + is( + Services.focus.focusedElement, + initialFocusElem, + `The initial element #${initialFocusElem.id} has focus` + ); + + helper.assertPanelNotVisible(); + // open Screenshots with the keyboard shortcut + info( + "Triggering screenshots UI with the ctrl+shift+s and waiting for the panel" + ); + EventUtils.synthesizeKey("s", { shiftKey: true, accelKey: true }); + + let button = await helper.getPanelButton(".visible-page"); + info("Panel is now visible, got button: " + button.className); + info( + `focusedElement: ${Services.focus.focusedElement.localName}.${Services.focus.focusedElement.className}` + ); + + await BrowserTestUtils.waitForCondition(async () => { + return button.getRootNode().activeElement === button; + }, "The first button in the panel should have focus"); + + info( + "Sending Escape to dismiss the screenshots UI and for the panel to be closed" + ); + + let exitObserved = TestUtils.topicObserved("screenshots-exit"); + EventUtils.synthesizeKey("KEY_Escape"); + await helper.waitForPanelClosed(); + await exitObserved; + info("Waiting for the initialFocusElem to be the focusedElement"); + await BrowserTestUtils.waitForCondition(async () => { + return Services.focus.focusedElement === initialFocusElem; + }, "The initially focused element should have focus"); + + info( + `Screenshots did exit, focusedElement: ${Services.focus.focusedElement.localName}#${Services.focus.focusedElement.id}` + ); + helper.assertPanelNotVisible(); +} + +add_task(async function testPanelFocused() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + await clearAllTelemetryEvents(); + let helper = new ScreenshotsHelper(browser); + + info("Opening Screenshots and waiting for the panel"); + helper.triggerUIFromToolbar(); + + let button = await helper.getPanelButton(".visible-page"); + info("Panel is now visible, got button: " + button.className); + info( + `focusedElement: ${Services.focus.focusedElement.localName}.${Services.focus.focusedElement.className}` + ); + + info("Waiting for the button to be the activeElement"); + await BrowserTestUtils.waitForCondition(() => { + return button.getRootNode().activeElement === button; + }, "The first button in the panel should have focus"); + + info("Sending Escape to close Screenshots"); + let exitObserved = TestUtils.topicObserved("screenshots-exit"); + EventUtils.synthesizeKey("KEY_Escape"); + + info("waiting for the panel to be closed"); + await helper.waitForPanelClosed(); + info("waiting for the overlay to be closed"); + await helper.waitForOverlayClosed(); + await exitObserved; + + info("Checking telemetry"); + await assertScreenshotsEvents(SCREENSHOTS_EVENTS); + helper.assertPanelNotVisible(); + } + ); +}); + +add_task(async function testRestoreFocusToChromeOnEscape() { + for (let focusSelector of [ + "#urlbar-input", // A focusable HTML chrome element + "tab[selected='true']", // The selected tab element + ]) { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + let helper = new ScreenshotsHelper(browser); + helper.assertPanelNotVisible(); + let initialFocusElem = document.querySelector(focusSelector); + await SimpleTest.promiseFocus(window); + await restoreFocusOnEscape(initialFocusElem, helper); + } + ); + } +}); + +add_task(async function testRestoreFocusToToolbarbuttonOnEscape() { + const focusId = "PanelUI-menu-button"; // a toolbarbutton + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + let helper = new ScreenshotsHelper(browser); + helper.assertPanelNotVisible(); + let initialFocusElem = document.getElementById(focusId); + await SimpleTest.promiseFocus(window); + await restoreFocusOnEscape(initialFocusElem, helper); + } + ); +}).skip(); // Bug 1867687 + +add_task(async function testRestoreFocusToContentOnEscape() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: 'data:text/html;charset=utf-8,%3Cinput type%3D"text" id%3D"field"%3E', + }, + async browser => { + let helper = new ScreenshotsHelper(browser); + await SimpleTest.promiseFocus(browser); + await BrowserTestUtils.synthesizeMouse("#field", 2, 2, {}, browser); + + let initialFocusElem = Services.focus.focusedElement; + await restoreFocusOnEscape(initialFocusElem, helper); + + is( + initialFocusElem, + document.activeElement, + "The browser element has focus" + ); + let focusId = await SpecialPowers.spawn(browser, [], () => { + return content.document.activeElement.id; + }); + is(focusId, "field", "The button in the content document has focus"); + } + ); +}); + +add_task(async function test_focusLastUsedMethod() { + await SpecialPowers.pushPrefEnv({ + set: [ + [SCREENSHOTS_LAST_SCREENSHOT_METHOD_PREF, ""], + [SCREENSHOTS_LAST_SAVED_METHOD_PREF, ""], + ["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: SHORT_TEST_PAGE, + }, + async browser => { + let helper = new ScreenshotsHelper(browser); + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + let expectedFocusedButton = await helper.getPanelButton(".visible-page"); + + await BrowserTestUtils.waitForCondition(() => { + return ( + expectedFocusedButton.getRootNode().activeElement === + expectedFocusedButton + ); + }, "The visible button in the panel should have focus"); + + is( + Services.focus.focusedElement, + expectedFocusedButton, + "The visible button in the panel should have focus" + ); + + let screenshotReady = TestUtils.topicObserved( + "screenshots-preview-ready" + ); + let fullpageButton = await helper.getPanelButton(".full-page"); + fullpageButton.click(); + await screenshotReady; + + let dialog = helper.getDialog(); + let retryButton = dialog._frame.contentDocument.getElementById("retry"); + retryButton.click(); + + await helper.waitForOverlay(); + + expectedFocusedButton = await helper.getPanelButton(".full-page"); + + await BrowserTestUtils.waitForCondition(() => { + return ( + expectedFocusedButton.getRootNode().activeElement === + expectedFocusedButton + ); + }, "The full page button in the panel should have focus"); + + is( + Services.focus.focusedElement, + expectedFocusedButton, + "The full button in the panel should have focus" + ); + + screenshotReady = TestUtils.topicObserved("screenshots-preview-ready"); + let visiblepageButton = await helper.getPanelButton(".visible-page"); + visiblepageButton.click(); + await screenshotReady; + + dialog = helper.getDialog(); + retryButton = dialog._frame.contentDocument.getElementById("retry"); + retryButton.click(); + + await helper.waitForOverlay(); + + expectedFocusedButton = await helper.getPanelButton(".visible-page"); + + await BrowserTestUtils.waitForCondition(() => { + return ( + expectedFocusedButton.getRootNode().activeElement === + expectedFocusedButton + ); + }, "The visible button in the panel should have focus"); + + is( + Services.focus.focusedElement, + expectedFocusedButton, + "The visible button in the panel should have focus" + ); + + screenshotReady = TestUtils.topicObserved("screenshots-preview-ready"); + expectedFocusedButton.click(); + await screenshotReady; + + dialog = helper.getDialog(); + + expectedFocusedButton = + dialog._frame.contentDocument.getElementById("download"); + + await BrowserTestUtils.waitForCondition(() => { + return ( + expectedFocusedButton.getRootNode().activeElement === + expectedFocusedButton + ); + }, "The download button in the preview dialog should have focus"); + + is( + Services.focus.focusedElement, + expectedFocusedButton, + "The download button in the preview dialog should have focus" + ); + + let screenshotExit = TestUtils.topicObserved("screenshots-exit"); + let copyButton = dialog._frame.contentDocument.getElementById("copy"); + copyButton.click(); + await screenshotExit; + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + let visibleButton = await helper.getPanelButton(".visible-page"); + + screenshotReady = TestUtils.topicObserved("screenshots-preview-ready"); + visibleButton.click(); + await screenshotReady; + + dialog = helper.getDialog(); + + expectedFocusedButton = + dialog._frame.contentDocument.getElementById("copy"); + + await BrowserTestUtils.waitForCondition(() => { + return ( + expectedFocusedButton.getRootNode().activeElement === + expectedFocusedButton + ); + }, "The copy button in the preview dialog should have focus"); + + is( + Services.focus.focusedElement, + expectedFocusedButton, + "The copy button in the preview dialog should have focus" + ); + + screenshotExit = TestUtils.topicObserved("screenshots-exit"); + let downloadButton = + dialog._frame.contentDocument.getElementById("download"); + downloadButton.click(); + + await Promise.all([screenshotExit, downloadFinishedPromise]); + + await publicDownloads.removeFinished(); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + visibleButton = await helper.getPanelButton(".visible-page"); + + screenshotReady = TestUtils.topicObserved("screenshots-preview-ready"); + visibleButton.click(); + await screenshotReady; + + dialog = helper.getDialog(); + + expectedFocusedButton = + dialog._frame.contentDocument.getElementById("download"); + + await BrowserTestUtils.waitForCondition(() => { + return ( + expectedFocusedButton.getRootNode().activeElement === + expectedFocusedButton + ); + }, "The download button in the preview dialog should have focus"); + + is( + Services.focus.focusedElement, + expectedFocusedButton, + "The download button in the preview dialog should have focus" + ); + + screenshotExit = TestUtils.topicObserved("screenshots-exit"); + helper.triggerUIFromToolbar(); + await screenshotExit; + } + ); + + await SpecialPowers.popPrefEnv(); +}); diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_overlay_panel_sync.js b/browser/components/screenshots/tests/browser/browser_screenshots_overlay_panel_sync.js new file mode 100644 index 0000000000..0f54255dc0 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_overlay_panel_sync.js @@ -0,0 +1,74 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +async function waitOnTabSwitch() { + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(r => setTimeout(r, 300)); +} + +add_task(async function test_overlay_and_panel_state() { + let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE); + + let screenshotsTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PAGE + ); + let browser = screenshotsTab.linkedBrowser; + + let helper = new ScreenshotsHelper(browser); + + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + + helper.assertPanelVisible(); + + await helper.dragOverlay(10, 10, 500, 500); + + await helper.assertStateChange("selected"); + + helper.assertPanelNotVisible(); + + mouse.click(600, 600); + + await helper.assertStateChange("crosshairs"); + + await helper.waitForOverlay(); + + helper.assertPanelVisible(); + + await BrowserTestUtils.switchTab(gBrowser, tab1); + await BrowserTestUtils.switchTab(gBrowser, screenshotsTab); + + await helper.waitForOverlayClosed(); + + Assert.ok(!(await helper.isOverlayInitialized()), "Overlay is closed"); + helper.assertPanelNotVisible(); + + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + + helper.assertPanelVisible(); + + await helper.dragOverlay(10, 10, 500, 500); + + await helper.assertStateChange("selected"); + + await BrowserTestUtils.switchTab(gBrowser, tab1); + await BrowserTestUtils.switchTab(gBrowser, screenshotsTab); + + Assert.ok(await helper.isOverlayInitialized(), "Overlay is open"); + helper.assertPanelNotVisible(); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlayClosed(); + + Assert.ok(!(await helper.isOverlayInitialized()), "Overlay is closed"); + helper.assertPanelNotVisible(); + + gBrowser.removeTab(tab1); + gBrowser.removeTab(screenshotsTab); +}); diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_page_unload.js b/browser/components/screenshots/tests/browser/browser_screenshots_page_unload.js new file mode 100644 index 0000000000..fbb5788a5a --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_page_unload.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const SCREENSHOTS_EVENTS = [ + { category: "screenshots", method: "started", object: "toolbar_button" }, + { category: "screenshots", method: "canceled", object: "navigation" }, +]; + +add_task(async function test() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: SHORT_TEST_PAGE, + }, + async browser => { + await clearAllTelemetryEvents(); + await SpecialPowers.spawn(browser, [SHORT_TEST_PAGE], url => { + let a = content.document.createElement("a"); + a.id = "clickMe"; + a.href = url; + a.textContent = "Click me to unload page"; + content.document.querySelector("body").appendChild(a); + }); + + let helper = new ScreenshotsHelper(browser); + + // click toolbar button so panel shows + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + await SpecialPowers.spawn(browser, [], () => { + content.document.querySelector("#clickMe").click(); + }); + + await helper.waitForOverlayClosed(); + + await assertScreenshotsEvents(SCREENSHOTS_EVENTS); + } + ); +}); diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_short_page_test.js b/browser/components/screenshots/tests/browser/browser_screenshots_short_page_test.js new file mode 100644 index 0000000000..bebc70e915 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_short_page_test.js @@ -0,0 +1,123 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This test ensures the overlay is covering the entire window event thought + * the body is smaller than the viewport + */ +add_task(async function test_overlay() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: SHORT_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 { scrollWidth, scrollHeight } = + await helper.getScreenshotsOverlayDimensions(); + Assert.equal( + scrollWidth, + contentInfo.clientWidth, + "The overlay spans the width of the window" + ); + + Assert.equal( + scrollHeight, + contentInfo.clientHeight, + "The overlay spans the height of the window" + ); + } + ); +}); + +add_task(async function test_window_resize() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: SHORT_TEST_PAGE, + }, + async browser => { + let helper = new ScreenshotsHelper(browser); + let contentInfo = await helper.getContentDimensions(); + ok(contentInfo, "Got dimensions back from the content"); + + const originalWindowWidth = window.outerWidth; + const originalWindowHeight = window.outerHeight; + + const BIG_WINDOW_SIZE = 920; + const SMALL_WINDOW_SIZE = 620; + + await helper.resizeContentWindow(SMALL_WINDOW_SIZE, SMALL_WINDOW_SIZE); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + await helper.dragOverlay(10, 10, 100, 100); + + let dimensions = await helper.getScreenshotsOverlayDimensions(); + let oldWidth = dimensions.scrollWidth; + let oldHeight = dimensions.scrollHeight; + + await helper.resizeContentWindow(BIG_WINDOW_SIZE, BIG_WINDOW_SIZE); + await helper.waitForSelectionLayerDimensionChange(oldWidth, oldHeight); + + contentInfo = await helper.getContentDimensions(); + dimensions = await helper.getScreenshotsOverlayDimensions(); + Assert.equal( + dimensions.scrollWidth, + contentInfo.clientWidth, + "The overlay spans the width of the window" + ); + Assert.equal( + dimensions.scrollHeight, + contentInfo.clientHeight, + "The overlay spans the height of the window" + ); + + oldWidth = dimensions.scrollWidth; + oldHeight = dimensions.scrollHeight; + + await helper.resizeContentWindow(SMALL_WINDOW_SIZE, SMALL_WINDOW_SIZE); + await helper.waitForSelectionLayerDimensionChange(oldWidth, oldHeight); + + contentInfo = await helper.getContentDimensions(); + dimensions = await helper.getScreenshotsOverlayDimensions(); + Assert.equal( + dimensions.scrollWidth, + contentInfo.clientWidth, + "The overlay spans the width of the window" + ); + Assert.equal( + dimensions.scrollHeight, + contentInfo.clientHeight, + "The overlay spans the height of the window" + ); + + Assert.less( + dimensions.scrollWidth, + BIG_WINDOW_SIZE, + "Screenshots overlay is smaller than the big window width" + ); + Assert.less( + dimensions.scrollHeight, + BIG_WINDOW_SIZE, + "Screenshots overlay is smaller than the big window height" + ); + + await helper.resizeContentWindow( + originalWindowWidth, + originalWindowHeight + ); + } + ); +}); diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_telemetry_tests.js b/browser/components/screenshots/tests/browser/browser_screenshots_telemetry_tests.js new file mode 100644 index 0000000000..782ffa3fd3 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_telemetry_tests.js @@ -0,0 +1,466 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const STARTED_AND_CANCELED_EVENTS = [ + { category: "screenshots", method: "started", object: "toolbar_button" }, + { category: "screenshots", method: "canceled", object: "toolbar_button" }, + { category: "screenshots", method: "started", object: "shortcut" }, + { category: "screenshots", method: "canceled", object: "shortcut" }, + { category: "screenshots", method: "started", object: "context_menu" }, + { category: "screenshots", method: "canceled", object: "context_menu" }, + { category: "screenshots", method: "started", object: "quick_actions" }, + { category: "screenshots", method: "canceled", object: "quick_actions" }, +]; + +const STARTED_RETRY_EVENTS = [ + { category: "screenshots", method: "started", object: "toolbar_button" }, + { category: "screenshots", method: "selected", object: "visible" }, + { category: "screenshots", method: "started", object: "preview_retry" }, +]; + +const CANCEL_EVENTS = [ + { category: "screenshots", method: "started", object: "toolbar_button" }, + { category: "screenshots", method: "selected", object: "full_page" }, + { category: "screenshots", method: "canceled", object: "preview_cancel" }, + { category: "screenshots", method: "started", object: "toolbar_button" }, + { category: "screenshots", method: "canceled", object: "overlay_cancel" }, +]; + +const COPY_EVENTS = [ + { category: "screenshots", method: "started", object: "toolbar_button" }, + { category: "screenshots", method: "selected", object: "visible" }, + { category: "screenshots", method: "copy", object: "preview_copy" }, + { category: "screenshots", method: "started", object: "toolbar_button" }, + { category: "screenshots", method: "copy", object: "overlay_copy" }, +]; + +const CONTENT_EVENTS = [ + { category: "screenshots", method: "selected", object: "region_selection" }, + { category: "screenshots", method: "selected", object: "region_selection" }, + { category: "screenshots", method: "started", object: "overlay_retry" }, + { category: "screenshots", method: "selected", object: "element" }, +]; + +const EXTRA_OVERLAY_EVENTS = [ + { category: "screenshots", method: "started", object: "toolbar_button" }, + { + category: "screenshots", + method: "copy", + object: "overlay_copy", + extra: { + element: "1", + region: "1", + move: "1", + resize: "1", + fullpage: "0", + visible: "0", + }, + }, +]; + +const EXTRA_EVENTS = [ + { category: "screenshots", method: "started", object: "toolbar_button" }, + { category: "screenshots", method: "selected", object: "visible" }, + { category: "screenshots", method: "started", object: "preview_retry" }, + { category: "screenshots", method: "selected", object: "full_page" }, + { + category: "screenshots", + method: "copy", + object: "preview_copy", + extra: { + element: "0", + region: "0", + move: "0", + resize: "0", + fullpage: "1", + visible: "1", + }, + }, +]; + +add_task(async function test_started_and_canceled_events() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.quickactions.enabled", true], + ["browser.urlbar.suggest.quickactions", true], + ["browser.urlbar.shortcuts.quickactions", true], + ], + }); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: SHORT_TEST_PAGE, + }, + async browser => { + await clearAllTelemetryEvents(); + let helper = new ScreenshotsHelper(browser); + let screenshotExit; + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + screenshotExit = TestUtils.topicObserved("screenshots-exit"); + helper.triggerUIFromToolbar(); + await helper.waitForOverlayClosed(); + await screenshotExit; + + EventUtils.synthesizeKey("s", { shiftKey: true, accelKey: true }); + await helper.waitForOverlay(); + + screenshotExit = TestUtils.topicObserved("screenshots-exit"); + EventUtils.synthesizeKey("s", { shiftKey: true, accelKey: true }); + await helper.waitForOverlayClosed(); + await screenshotExit; + + let contextMenu = document.getElementById("contentAreaContextMenu"); + let popupShownPromise = BrowserTestUtils.waitForEvent( + contextMenu, + "popupshown" + ); + await BrowserTestUtils.synthesizeMouseAtPoint( + 50, + 50, + { + type: "contextmenu", + button: 2, + }, + browser + ); + await popupShownPromise; + + contextMenu.activateItem( + contextMenu.querySelector("#context-take-screenshot") + ); + await helper.waitForOverlay(); + + popupShownPromise = BrowserTestUtils.waitForEvent( + contextMenu, + "popupshown" + ); + await BrowserTestUtils.synthesizeMouseAtPoint( + 50, + 50, + { + type: "contextmenu", + button: 2, + }, + browser + ); + await popupShownPromise; + + screenshotExit = TestUtils.topicObserved("screenshots-exit"); + contextMenu.activateItem( + contextMenu.querySelector("#context-take-screenshot") + ); + await helper.waitForOverlayClosed(); + await screenshotExit; + + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "screenshot", + waitForFocus: SimpleTest.waitForFocus, + }); + let { result } = await UrlbarTestUtils.getDetailsOfResultAt(window, 1); + Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.DYNAMIC); + Assert.equal(result.providerName, "quickactions"); + + info("Trigger the screenshot mode"); + EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); + EventUtils.synthesizeKey("KEY_Enter", {}, window); + await helper.waitForOverlay(); + + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "screenshot", + waitForFocus: SimpleTest.waitForFocus, + }); + ({ result } = await UrlbarTestUtils.getDetailsOfResultAt(window, 1)); + Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.DYNAMIC); + Assert.equal(result.providerName, "quickactions"); + + info("Trigger the screenshot mode"); + screenshotExit = TestUtils.topicObserved("screenshots-exit"); + EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); + EventUtils.synthesizeKey("KEY_Enter", {}, window); + await helper.waitForOverlayClosed(); + await screenshotExit; + + await assertScreenshotsEvents(STARTED_AND_CANCELED_EVENTS); + } + ); +}); + +add_task(async function test_started_retry() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + await clearAllTelemetryEvents(); + let helper = new ScreenshotsHelper(browser); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + let panel = await helper.waitForPanel(); + + let screenshotReady = TestUtils.topicObserved( + "screenshots-preview-ready" + ); + + // click the visible page button in panel + let visiblePageButton = panel + .querySelector("screenshots-buttons") + .shadowRoot.querySelector(".visible-page"); + visiblePageButton.click(); + await screenshotReady; + + let dialog = helper.getDialog(); + let retryButton = dialog._frame.contentDocument.getElementById("retry"); + ok(retryButton, "Got the retry button"); + retryButton.click(); + + await helper.waitForOverlay(); + + await assertScreenshotsEvents(STARTED_RETRY_EVENTS); + } + ); +}); + +add_task(async function test_canceled() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + await clearAllTelemetryEvents(); + let helper = new ScreenshotsHelper(browser); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + let panel = await helper.waitForPanel(); + + let screenshotReady = TestUtils.topicObserved( + "screenshots-preview-ready" + ); + + // click the full page button in panel + let fullPageButton = panel + .querySelector("screenshots-buttons") + .shadowRoot.querySelector(".full-page"); + fullPageButton.click(); + await screenshotReady; + + let dialog = helper.getDialog(); + let cancelButton = dialog._frame.contentDocument.getElementById("cancel"); + ok(cancelButton, "Got the cancel button"); + + let screenshotExit = TestUtils.topicObserved("screenshots-exit"); + cancelButton.click(); + + await helper.waitForOverlayClosed(); + await screenshotExit; + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + screenshotExit = TestUtils.topicObserved("screenshots-exit"); + await helper.clickPreviewCancelButton(); + + await helper.waitForOverlayClosed(); + await screenshotExit; + + await assertScreenshotsEvents(CANCEL_EVENTS); + } + ); +}); + +add_task(async function test_copy() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + await clearAllTelemetryEvents(); + let helper = new ScreenshotsHelper(browser); + let contentInfo = await helper.getContentDimensions(); + ok(contentInfo, "Got dimensions back from the content"); + let devicePixelRatio = await getContentDevicePixelRatio(browser); + + let expectedWidth = Math.floor( + devicePixelRatio * contentInfo.clientWidth + ); + let expectedHeight = Math.floor( + devicePixelRatio * contentInfo.clientHeight + ); + + helper.triggerUIFromToolbar(); + info("waiting for overlay"); + await helper.waitForOverlay(); + + info("waiting for panel"); + let panel = await helper.waitForPanel(); + + let screenshotReady = TestUtils.topicObserved( + "screenshots-preview-ready" + ); + + // click the visible page button in panel + let visiblePageButton = panel + .querySelector("screenshots-buttons") + .shadowRoot.querySelector(".visible-page"); + visiblePageButton.click(); + info("clicked visible page, waiting for screenshots-preview-ready"); + await screenshotReady; + + let dialog = helper.getDialog(); + let copyButton = dialog._frame.contentDocument.getElementById("copy"); + + let screenshotExit = TestUtils.topicObserved("screenshots-exit"); + let clipboardChanged = helper.waitForRawClipboardChange( + expectedWidth, + expectedHeight + ); + + // click copy button on dialog box + info("clicking the copy button"); + copyButton.click(); + + info("Waiting for clipboard change"); + await clipboardChanged; + info("waiting for screenshot exit"); + await screenshotExit; + + helper.triggerUIFromToolbar(); + info("waiting for overlay again"); + await helper.waitForOverlay(); + + await helper.dragOverlay(50, 50, 300, 300); + + clipboardChanged = helper.waitForRawClipboardChange( + devicePixelRatio * 250, + devicePixelRatio * 250 + ); + + screenshotExit = TestUtils.topicObserved("screenshots-exit"); + await helper.clickCopyButton(); + + info("Waiting for clipboard change"); + await clipboardChanged; + info("Waiting for exit again"); + await screenshotExit; + + info("Waiting for assertScreenshotsEvents"); + await assertScreenshotsEvents(COPY_EVENTS); + } + ); +}); + +add_task(async function test_content_events() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + await clearAllTelemetryEvents(); + let helper = new ScreenshotsHelper(browser); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + await helper.dragOverlay(50, 50, 300, 300); + + await helper.dragOverlay(300, 300, 333, 333, "selected"); + + await helper.dragOverlay(150, 150, 200, 200, "selected"); + + mouse.click(11, 11); + await helper.assertStateChange("crosshairs"); + + await helper.clickTestPageElement(); + + let screenshotExit = TestUtils.topicObserved("screenshots-exit"); + + await helper.clickCopyButton(); + + info("Waiting for exit"); + await screenshotExit; + + await assertScreenshotsEvents(CONTENT_EVENTS, "content", false); + await assertScreenshotsEvents(EXTRA_OVERLAY_EVENTS); + } + ); +}); + +add_task(async function test_extra_telemetry() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + await clearAllTelemetryEvents(); + let helper = new ScreenshotsHelper(browser); + let contentInfo = await helper.getContentDimensions(); + ok(contentInfo, "Got dimensions back from the content"); + + helper.triggerUIFromToolbar(); + info("waiting for overlay"); + await helper.waitForOverlay(); + + info("waiting for panel"); + let panel = await helper.waitForPanel(); + + let screenshotReady = TestUtils.topicObserved( + "screenshots-preview-ready" + ); + + // click the visible page button in panel + let visiblePageButton = panel + .querySelector("screenshots-buttons") + .shadowRoot.querySelector(".visible-page"); + visiblePageButton.click(); + info("clicked visible page, waiting for screenshots-preview-ready"); + await screenshotReady; + + let dialog = helper.getDialog(); + let retryButton = dialog._frame.contentDocument.getElementById("retry"); + retryButton.click(); + + info("waiting for panel"); + panel = await helper.waitForPanel(); + + screenshotReady = TestUtils.topicObserved("screenshots-preview-ready"); + + // click the full page button in panel + let fullPageButton = panel + .querySelector("screenshots-buttons") + .shadowRoot.querySelector(".full-page"); + fullPageButton.click(); + await screenshotReady; + + let screenshotExit = TestUtils.topicObserved("screenshots-exit"); + + dialog = helper.getDialog(); + let copyButton = dialog._frame.contentDocument.getElementById("copy"); + retryButton.click(); + // click copy button on dialog box + info("clicking the copy button"); + copyButton.click(); + + info("waiting for screenshot exit"); + await screenshotExit; + + info("Waiting for assertScreenshotsEvents"); + await assertScreenshotsEvents(EXTRA_EVENTS); + } + ); +}); diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_test_downloads.js b/browser/components/screenshots/tests/browser/browser_screenshots_test_downloads.js new file mode 100644 index 0000000000..770a7ae06b --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_test_downloads.js @@ -0,0 +1,186 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const SCREENSHOTS_EVENTS = [ + { category: "screenshots", method: "started", object: "toolbar_button" }, + { category: "screenshots", method: "download", object: "overlay_download" }, + { category: "screenshots", method: "started", object: "toolbar_button" }, + { category: "screenshots", method: "selected", object: "visible" }, + { category: "screenshots", method: "download", object: "preview_download" }, +]; + +const MockFilePicker = SpecialPowers.MockFilePicker; + +add_setup(async function () { + let tmpDir = PathUtils.join( + PathUtils.tempDir, + "testsavedir" + Math.floor(Math.random() * 2 ** 32) + ); + // Create this dir if it doesn't exist (ignores existing dirs) + await IOUtils.makeDirectory(tmpDir); + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.download.start_downloads_in_tmp_dir", true], + ["browser.helperApps.deleteTempFileOnExit", true], + ["browser.download.folderList", 2], + ["browser.download.dir", tmpDir], + ], + }); + + MockFilePicker.init(window); + MockFilePicker.useAnyFile(); + MockFilePicker.returnValue = MockFilePicker.returnOK; + + registerCleanupFunction(async () => { + Services.prefs.clearUserPref("browser.download.dir"); + Services.prefs.clearUserPref("browser.download.folderList"); + + MockFilePicker.cleanup(); + }); +}); + +function waitForFilePicker() { + return new Promise(resolve => { + MockFilePicker.showCallback = () => { + MockFilePicker.showCallback = null; + ok(true, "Saw the file picker"); + resolve(); + }; + }); +} + +add_task(async function test_download_without_filepicker() { + 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 => { + await clearAllTelemetryEvents(); + let helper = new ScreenshotsHelper(browser); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + await helper.dragOverlay(10, 10, 500, 500); + + await helper.clickDownloadButton(); + + info("wait for download to finish"); + let download = await downloadFinishedPromise; + + ok(download.succeeded, "Download should succeed"); + + await publicDownloads.removeFinished(); + + await waitForScreenshotsEventCount(2); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + let screenshotReady = TestUtils.topicObserved( + "screenshots-preview-ready" + ); + + let panel = gBrowser.selectedBrowser.ownerDocument.querySelector( + "#screenshotsPagePanel" + ); + + // click the visible page button in panel + let visiblePageButton = panel + .querySelector("screenshots-buttons") + .shadowRoot.querySelector(".visible-page"); + visiblePageButton.click(); + + let dialog = helper.getDialog(); + + await screenshotReady; + + let downloadButton = + dialog._frame.contentDocument.getElementById("download"); + ok(downloadButton, "Got the download button"); + + let screenshotExit = TestUtils.topicObserved("screenshots-exit"); + // click download button on dialog box + downloadButton.click(); + + info("wait for download to finish"); + download = await downloadFinishedPromise; + + ok(download.succeeded, "Download should succeed"); + + await publicDownloads.removeFinished(); + await screenshotExit; + await assertScreenshotsEvents(SCREENSHOTS_EVENTS); + } + ); +}); + +add_task(async function test_download_with_filepicker() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.useDownloadDir", false]], + }); + + 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 filePicker = waitForFilePicker(); + let screenshotExit = TestUtils.topicObserved("screenshots-exit"); + await helper.clickDownloadButton(); + + await filePicker; + ok(true, "Export file picker opened"); + + info("wait for download to finish"); + let download = await downloadFinishedPromise; + + ok(download.succeeded, "Download should succeed"); + await publicDownloads.removeFinished(); + await screenshotExit; + } + ); +}); diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_test_escape.js b/browser/components/screenshots/tests/browser/browser_screenshots_test_escape.js new file mode 100644 index 0000000000..73db436ff1 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_test_escape.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const SCREENSHOTS_EVENTS = [ + { category: "screenshots", method: "started", object: "toolbar_button" }, + { category: "screenshots", method: "canceled", object: "escape" }, +]; + +add_task(async function test_fullpageScreenshot() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + await clearAllTelemetryEvents(); + let helper = new ScreenshotsHelper(browser); + + // click toolbar button so panel shows + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + + EventUtils.synthesizeKey("KEY_F6", { shiftKey: true }); + + EventUtils.synthesizeKey("KEY_Escape"); + + await helper.waitForOverlayClosed(); + + await assertScreenshotsEvents(SCREENSHOTS_EVENTS); + } + ); +}); diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_test_full_page.js b/browser/components/screenshots/tests/browser/browser_screenshots_test_full_page.js new file mode 100644 index 0000000000..51cda963d9 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_test_full_page.js @@ -0,0 +1,175 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +function assertRange(lhs, rhsMin, rhsMax, msg) { + Assert.ok(lhs >= rhsMin && lhs <= rhsMax, msg); +} + +add_task(async function test_fullpageScreenshot() { + 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"); + let devicePixelRatio = await getContentDevicePixelRatio(browser); + + let expectedWidth = Math.floor( + devicePixelRatio * contentInfo.scrollWidth + ); + let expectedHeight = Math.floor( + devicePixelRatio * contentInfo.scrollHeight + ); + + // click toolbar button so panel shows + helper.triggerUIFromToolbar(); + + let panel = await helper.waitForPanel(); + + let screenshotReady = TestUtils.topicObserved( + "screenshots-preview-ready" + ); + + // click the full page button in panel + let visiblePage = panel + .querySelector("screenshots-buttons") + .shadowRoot.querySelector(".full-page"); + visiblePage.click(); + + let dialog = helper.getDialog(); + + await screenshotReady; + + let copyButton = dialog._frame.contentDocument.getElementById("copy"); + ok(copyButton, "Got the copy button"); + + let clipboardChanged = helper.waitForRawClipboardChange( + expectedWidth, + expectedHeight + ); + + // click copy button on dialog box + copyButton.click(); + + info("Waiting for clipboard change"); + let result = await clipboardChanged; + + info("result: " + JSON.stringify(result, null, 2)); + info("contentInfo: " + JSON.stringify(contentInfo, null, 2)); + + Assert.equal(result.width, expectedWidth, "Widths should be equal"); + Assert.equal(result.height, expectedHeight, "Heights should be equal"); + + // top left + assertRange(result.color.topLeft[0], 110, 111, "R color value"); + assertRange(result.color.topLeft[1], 110, 111, "G color value"); + assertRange(result.color.topLeft[2], 110, 111, "B color value"); + + // top right + assertRange(result.color.topRight[0], 55, 56, "R color value"); + assertRange(result.color.topRight[1], 155, 156, "G color value"); + assertRange(result.color.topRight[2], 155, 156, "B color value"); + + // bottom left + assertRange(result.color.bottomLeft[0], 105, 106, "R color value"); + assertRange(result.color.bottomLeft[1], 55, 56, "G color value"); + assertRange(result.color.bottomLeft[2], 105, 106, "B color value"); + + // bottom right + assertRange(result.color.bottomRight[0], 52, 53, "R color value"); + assertRange(result.color.bottomRight[1], 127, 128, "G color value"); + assertRange(result.color.bottomRight[2], 152, 153, "B color value"); + } + ); +}); + +add_task(async function test_fullpageScreenshotScrolled() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + let helper = new ScreenshotsHelper(browser); + + await SpecialPowers.spawn(browser, [], () => { + content.scrollTo(0, 2008); + }); + let contentInfo = await helper.getContentDimensions(); + ok(contentInfo, "Got dimensions back from the content"); + let devicePixelRatio = await getContentDevicePixelRatio(browser); + + let expectedWidth = Math.floor( + devicePixelRatio * contentInfo.scrollWidth + ); + let expectedHeight = Math.floor( + devicePixelRatio * contentInfo.scrollHeight + ); + + // click toolbar button so panel shows + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + let panel = await helper.waitForPanel(); + + let screenshotReady = TestUtils.topicObserved( + "screenshots-preview-ready" + ); + + // click the full page button in panel + let visiblePage = panel + .querySelector("screenshots-buttons") + .shadowRoot.querySelector(".full-page"); + visiblePage.click(); + + let dialog = helper.getDialog(); + + await screenshotReady; + + let copyButton = dialog._frame.contentDocument.getElementById("copy"); + ok(copyButton, "Got the copy button"); + + let clipboardChanged = helper.waitForRawClipboardChange( + expectedWidth, + expectedHeight + ); + + // click copy button on dialog box + copyButton.click(); + + info("Waiting for clipboard change"); + let result = await clipboardChanged; + + info("result: " + JSON.stringify(result, null, 2)); + info("contentInfo: " + JSON.stringify(contentInfo, null, 2)); + + Assert.equal(result.width, expectedWidth, "Widths should be equal"); + Assert.equal(result.height, expectedHeight, "Heights should be equal"); + + // top left + assertRange(result.color.topLeft[0], 110, 111, "R color value"); + assertRange(result.color.topLeft[1], 110, 111, "G color value"); + assertRange(result.color.topLeft[2], 110, 111, "B color value"); + + // top right + assertRange(result.color.topRight[0], 55, 56, "R color value"); + assertRange(result.color.topRight[1], 155, 156, "G color value"); + assertRange(result.color.topRight[2], 155, 156, "B color value"); + + // bottom left + assertRange(result.color.bottomLeft[0], 105, 106, "R color value"); + assertRange(result.color.bottomLeft[1], 55, 56, "G color value"); + assertRange(result.color.bottomLeft[2], 105, 106, "B color value"); + + // bottom right + assertRange(result.color.bottomRight[0], 52, 53, "R color value"); + assertRange(result.color.bottomRight[1], 127, 128, "G color value"); + assertRange(result.color.bottomRight[2], 152, 153, "B color value"); + } + ); +}); diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_test_page_crash.js b/browser/components/screenshots/tests/browser/browser_screenshots_test_page_crash.js new file mode 100644 index 0000000000..bed9d006db --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_test_page_crash.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_fullpageScreenshot() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + let helper = new ScreenshotsHelper(browser); + + // click toolbar button so UI shows + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + let panel = await helper.waitForPanel(gBrowser.selectedBrowser); + + let waitForPanelHide = BrowserTestUtils.waitForMutationCondition( + panel, + { attributes: true }, + () => { + return BrowserTestUtils.isHidden(panel); + } + ); + + await BrowserTestUtils.crashFrame(browser); + + await waitForPanelHide; + ok( + BrowserTestUtils.isHidden(panel), + "Panel buttons are hidden after page crash" + ); + + await ContentTask.spawn(browser, null, async () => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + Assert.ok(!screenshotsChild._overlay, "The overlay doesn't exist"); + }); + + let tab = gBrowser.getTabForBrowser(browser); + + SessionStore.reviveCrashedTab(tab); + + ok( + BrowserTestUtils.isHidden(panel), + "Panel buttons are hidden after page crash" + ); + } + ); +}); diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_test_screenshot_too_big.js b/browser/components/screenshots/tests/browser/browser_screenshots_test_screenshot_too_big.js new file mode 100644 index 0000000000..4e2da3d494 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_test_screenshot_too_big.js @@ -0,0 +1,90 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const SCREENSHOTS_EVENTS = [ + { category: "screenshots", method: "failed", object: "screenshot_too_large" }, + { category: "screenshots", method: "failed", object: "screenshot_too_large" }, + { category: "screenshots", method: "failed", object: "screenshot_too_large" }, + { category: "screenshots", method: "failed", object: "screenshot_too_large" }, +]; + +const { sinon } = ChromeUtils.importESModule( + "resource://testing-common/Sinon.sys.mjs" +); +ChromeUtils.defineESModuleGetters(this, { + ScreenshotsUtils: "resource:///modules/ScreenshotsUtils.sys.mjs", +}); + +add_task(async function test_screenshot_too_large_cropped() { + await clearAllTelemetryEvents(); + const screenshotsLocalization = new Localization( + ["browser/screenshots.ftl"], + true + ); + + let [errorTitle, errorMessage] = screenshotsLocalization.formatMessagesSync([ + { id: "screenshots-too-large-error-title" }, + { id: "screenshots-too-large-error-details" }, + ]); + let showAlertMessageStub = sinon + .stub(ScreenshotsUtils, "showAlertMessage") + .callsFake(function (title, message) { + is(title, errorTitle.value, "Got error title"); + is(message, errorMessage.value, "Got error message"); + }); + + let rect = { x: 0, y: 0, width: 40000, height: 40000, devicePixelRatio: 1 }; + + ScreenshotsUtils.cropScreenshotRectIfNeeded(rect); + + is( + rect.width, + MAX_CAPTURE_DIMENSION, + `The width is ${MAX_CAPTURE_DIMENSION}` + ); + is( + rect.height, + Math.floor(MAX_CAPTURE_AREA / MAX_CAPTURE_DIMENSION), + `The height is ${MAX_CAPTURE_AREA} / ${MAX_CAPTURE_DIMENSION}` + ); + + rect.width = 40000; + rect.hegith = 1; + + ScreenshotsUtils.cropScreenshotRectIfNeeded(rect); + + is( + rect.width, + MAX_CAPTURE_DIMENSION, + `The width was cropped to the max capture dimension (${MAX_CAPTURE_DIMENSION}).` + ); + + rect.width = 1; + rect.height = 40000; + + ScreenshotsUtils.cropScreenshotRectIfNeeded(rect); + + is( + rect.height, + MAX_CAPTURE_DIMENSION, + `The height was cropped to the max capture dimension (${MAX_CAPTURE_DIMENSION}).` + ); + + rect.width = 25000; + rect.height = 25000; + + ScreenshotsUtils.cropScreenshotRectIfNeeded(rect); + + is(rect.width, 25000, "The width was not cropped"); + is( + rect.height, + Math.floor(MAX_CAPTURE_AREA / 25000), + `The height was cropped to ${MAX_CAPTURE_AREA / 25000}` + ); + + showAlertMessageStub.restore(); + + await assertScreenshotsEvents(SCREENSHOTS_EVENTS); +}); 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 new file mode 100644 index 0000000000..0aafba8fb3 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_test_toggle_pref.js @@ -0,0 +1,289 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { sinon } = ChromeUtils.importESModule( + "resource://testing-common/Sinon.sys.mjs" +); + +ChromeUtils.defineESModuleGetters(this, { + ScreenshotsUtils: "resource:///modules/ScreenshotsUtils.sys.mjs", +}); +ChromeUtils.defineLazyGetter(this, "ExtensionManagement", () => { + const { Management } = ChromeUtils.importESModule( + "resource://gre/modules/Extension.sys.mjs" + ); + return Management; +}); + +add_task(async function test() { + let observerSpy = sinon.spy(); + let notifierSpy = sinon.spy(); + + let observerStub = sinon + .stub(ScreenshotsUtils, "observe") + .callsFake(observerSpy); + let notifierStub = sinon + .stub(ScreenshotsUtils, "notify") + .callsFake(function (window, type) { + notifierSpy(); + ScreenshotsUtils.notify.wrappedMethod.apply(this, arguments); + }); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: SHORT_TEST_PAGE, + }, + async browser => { + function awaitExtensionEvent(eventName, id) { + return new Promise(resolve => { + let listener = (_eventName, ...args) => { + let extension = args[0]; + if (_eventName === eventName && extension.id == id) { + ExtensionManagement.off(eventName, listener); + resolve(); + } + }; + ExtensionManagement.on(eventName, listener); + }); + } + const SCREENSHOT_EXTENSION = "screenshots@mozilla.org"; + + let helper = new ScreenshotsHelper(browser); + + ok(observerSpy.notCalled, "Observer not called"); + helper.triggerUIFromToolbar(); + Assert.equal(observerSpy.callCount, 1, "Observer function called once"); + + ok(notifierSpy.notCalled, "Notifier not called"); + EventUtils.synthesizeKey("s", { accelKey: true, shiftKey: true }); + + await TestUtils.waitForCondition(() => notifierSpy.callCount == 1); + Assert.equal(notifierSpy.callCount, 1, "Notify function called once"); + + await TestUtils.waitForCondition(() => observerSpy.callCount == 2); + Assert.equal(observerSpy.callCount, 2, "Observer function called twice"); + + let menu = document.getElementById("contentAreaContextMenu"); + let popupshown = BrowserTestUtils.waitForPopupEvent(menu, "shown"); + EventUtils.synthesizeMouseAtCenter(document.body, { + type: "contextmenu", + }); + await popupshown; + Assert.equal(menu.state, "open", "Context menu is open"); + + let popuphidden = BrowserTestUtils.waitForPopupEvent(menu, "hidden"); + menu.activateItem(menu.querySelector("#context-take-screenshot")); + await popuphidden; + + Assert.equal(observerSpy.callCount, 3, "Observer function called thrice"); + + const COMPONENT_PREF = "screenshots.browser.component.enabled"; + await SpecialPowers.pushPrefEnv({ + set: [[COMPONENT_PREF, false]], + }); + ok(!Services.prefs.getBoolPref(COMPONENT_PREF), "Extension enabled"); + await awaitExtensionEvent("ready", SCREENSHOT_EXTENSION); + + helper.triggerUIFromToolbar(); + Assert.equal( + observerSpy.callCount, + 3, + "Observer function still called thrice" + ); + + await SpecialPowers.spawn( + browser, + ["#firefox-screenshots-preselection-iframe"], + async function (iframeSelector) { + info( + `in waitForUIContent content function, iframeSelector: ${iframeSelector}` + ); + let iframe; + await ContentTaskUtils.waitForCondition(() => { + iframe = content.document.querySelector(iframeSelector); + if (!iframe || !ContentTaskUtils.isVisible(iframe)) { + info("in waitForUIContent, no visible iframe yet"); + return false; + } + return true; + }); + // wait a frame for the screenshots UI to finish any init + await new content.Promise(res => content.requestAnimationFrame(res)); + } + ); + + helper.triggerUIFromToolbar(); + await SpecialPowers.spawn( + browser, + ["#firefox-screenshots-preselection-iframe"], + async function (iframeSelector) { + info( + `in waitForUIContent content function, iframeSelector: ${iframeSelector}` + ); + let iframe; + await ContentTaskUtils.waitForCondition(() => { + iframe = content.document.querySelector(iframeSelector); + if (!iframe || !ContentTaskUtils.isVisible(iframe)) { + info("in waitForUIContent, no visible iframe yet"); + return true; + } + return false; + }); + // wait a frame for the screenshots UI to finish any init + await new content.Promise(res => content.requestAnimationFrame(res)); + } + ); + + popupshown = BrowserTestUtils.waitForPopupEvent(menu, "shown"); + EventUtils.synthesizeMouseAtCenter(document.body, { + type: "contextmenu", + }); + await popupshown; + Assert.equal(menu.state, "open", "Context menu is open"); + + popuphidden = BrowserTestUtils.waitForPopupEvent(menu, "hidden"); + menu.activateItem(menu.querySelector("#context-take-screenshot")); + await popuphidden; + + Assert.equal( + observerSpy.callCount, + 3, + "Observer function still called thrice" + ); + + await SpecialPowers.spawn( + browser, + ["#firefox-screenshots-preselection-iframe"], + async function (iframeSelector) { + info( + `in waitForUIContent content function, iframeSelector: ${iframeSelector}` + ); + let iframe; + await ContentTaskUtils.waitForCondition(() => { + iframe = content.document.querySelector(iframeSelector); + if (!iframe || !ContentTaskUtils.isVisible(iframe)) { + info("in waitForUIContent, no visible iframe yet"); + return false; + } + return true; + }); + // wait a frame for the screenshots UI to finish any init + await new content.Promise(res => content.requestAnimationFrame(res)); + } + ); + + helper.triggerUIFromToolbar(); + await SpecialPowers.spawn( + browser, + ["#firefox-screenshots-preselection-iframe"], + async function (iframeSelector) { + info( + `in waitForUIContent content function, iframeSelector: ${iframeSelector}` + ); + let iframe; + await ContentTaskUtils.waitForCondition(() => { + iframe = content.document.querySelector(iframeSelector); + if (!iframe || !ContentTaskUtils.isVisible(iframe)) { + return true; + } + info("in waitForUIContent, iframe still visible"); + info(iframe); + return false; + }); + // wait a frame for the screenshots UI to finish any init + await new content.Promise(res => content.requestAnimationFrame(res)); + } + ); + + let componentReady = TestUtils.topicObserved( + "screenshots-component-initialized" + ); + + await SpecialPowers.pushPrefEnv({ + set: [[COMPONENT_PREF, true]], + }); + ok(Services.prefs.getBoolPref(COMPONENT_PREF), "Component enabled"); + // Needed for component to initialize + await componentReady; + + helper.triggerUIFromToolbar(); + Assert.equal( + observerSpy.callCount, + 4, + "Observer function called four times" + ); + + const SCREENSHOTS_PREF = "extensions.screenshots.disabled"; + await SpecialPowers.pushPrefEnv({ + set: [[SCREENSHOTS_PREF, true]], + }); + ok(Services.prefs.getBoolPref(SCREENSHOTS_PREF), "Screenshots disabled"); + } + ); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: SHORT_TEST_PAGE, + }, + async browser => { + const SCREENSHOTS_PREF = "extensions.screenshots.disabled"; + ok(Services.prefs.getBoolPref(SCREENSHOTS_PREF), "Screenshots disabled"); + + ok( + document.getElementById("screenshot-button").disabled, + "Toolbar button disabled" + ); + + let menu = document.getElementById("contentAreaContextMenu"); + let popupshown = BrowserTestUtils.waitForPopupEvent(menu, "shown"); + EventUtils.synthesizeMouseAtCenter(document.body, { + type: "contextmenu", + }); + await popupshown; + Assert.equal(menu.state, "open", "Context menu is open"); + + ok( + menu.querySelector("#context-take-screenshot").hidden, + "Take screenshot is not in context menu" + ); + + let popuphidden = BrowserTestUtils.waitForPopupEvent(menu, "hidden"); + menu.hidePopup(); + await popuphidden; + + await SpecialPowers.pushPrefEnv({ + set: [[SCREENSHOTS_PREF, false]], + }); + ok(!Services.prefs.getBoolPref(SCREENSHOTS_PREF), "Screenshots enabled"); + } + ); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: SHORT_TEST_PAGE, + }, + async browser => { + const SCREENSHOTS_PREF = "extensions.screenshots.disabled"; + ok(!Services.prefs.getBoolPref(SCREENSHOTS_PREF), "Screenshots enabled"); + + let helper = new ScreenshotsHelper(browser); + + helper.triggerUIFromToolbar(); + Assert.equal( + observerSpy.callCount, + 5, + "Observer function called for the fifth time" + ); + } + ); + + observerStub.restore(); + notifierStub.restore(); + + await SpecialPowers.popPrefEnv(); +}); diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_test_toolbar_button.js b/browser/components/screenshots/tests/browser/browser_screenshots_test_toolbar_button.js new file mode 100644 index 0000000000..5ad7d32192 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_test_toolbar_button.js @@ -0,0 +1,26 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function testScreenshotButtonDisabled() { + info("Test the Screenshots button in the panel"); + + let screenshotBtn = document.getElementById("screenshot-button"); + Assert.ok(screenshotBtn, "The screenshots button was added to the nav bar"); + + await BrowserTestUtils.withNewTab("https://example.com/", () => { + Assert.equal( + screenshotBtn.disabled, + false, + "Screenshots button is enabled" + ); + }); + await BrowserTestUtils.withNewTab("about:home", () => { + Assert.equal( + screenshotBtn.disabled, + false, + "Screenshots button is still enabled on about pages" + ); + }); +}); diff --git a/browser/components/screenshots/tests/browser/browser_screenshots_test_visible.js b/browser/components/screenshots/tests/browser/browser_screenshots_test_visible.js new file mode 100644 index 0000000000..7b7a46f73d --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_test_visible.js @@ -0,0 +1,356 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_visibleScreenshot() { + 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"); + let devicePixelRatio = await getContentDevicePixelRatio(browser); + + let expectedWidth = Math.floor( + devicePixelRatio * contentInfo.clientWidth + ); + let expectedHeight = Math.floor( + devicePixelRatio * contentInfo.clientHeight + ); + + // click toolbar button so panel shows + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + + let screenshotReady = TestUtils.topicObserved( + "screenshots-preview-ready" + ); + + let panel = gBrowser.selectedBrowser.ownerDocument.querySelector( + "#screenshotsPagePanel" + ); + + // click the visible page button in panel + let visiblePageButton = panel + .querySelector("screenshots-buttons") + .shadowRoot.querySelector(".visible-page"); + visiblePageButton.click(); + + let dialog = helper.getDialog(); + + await screenshotReady; + + let copyButton = dialog._frame.contentDocument.getElementById("copy"); + ok(copyButton, "Got the copy button"); + + let clipboardChanged = helper.waitForRawClipboardChange( + expectedWidth, + expectedHeight + ); + + // click copy button on dialog box + copyButton.click(); + + info("Waiting for clipboard change"); + let result = await clipboardChanged; + + info("result: " + JSON.stringify(result, null, 2)); + info("contentInfo: " + JSON.stringify(contentInfo, null, 2)); + + Assert.equal(result.width, expectedWidth, "Widths should be equal"); + Assert.equal(result.height, expectedHeight, "Heights should be equal"); + + // top left + Assert.equal(111, result.color.topLeft[0], "R color value"); + Assert.equal(111, result.color.topLeft[1], "G color value"); + Assert.equal(111, result.color.topLeft[2], "B color value"); + + // top right + Assert.equal(111, result.color.topRight[0], "R color value"); + Assert.equal(111, result.color.topRight[1], "G color value"); + Assert.equal(111, result.color.topRight[2], "B color value"); + + // bottom left + Assert.equal(111, result.color.bottomLeft[0], "R color value"); + Assert.equal(111, result.color.bottomLeft[1], "G color value"); + Assert.equal(111, result.color.bottomLeft[2], "B color value"); + + // bottom right + Assert.equal(111, result.color.bottomRight[0], "R color value"); + Assert.equal(111, result.color.bottomRight[1], "G color value"); + Assert.equal(111, result.color.bottomRight[2], "B color value"); + } + ); +}); + +add_task(async function test_visibleScreenshotScrolledY() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + await SpecialPowers.spawn(browser, [], () => { + content.scrollTo(0, 2008); + }); + + let helper = new ScreenshotsHelper(browser); + let contentInfo = await helper.getContentDimensions(); + ok(contentInfo, "Got dimensions back from the content"); + let devicePixelRatio = await getContentDevicePixelRatio(browser); + + let expectedWidth = Math.floor( + devicePixelRatio * contentInfo.clientWidth + ); + let expectedHeight = Math.floor( + devicePixelRatio * contentInfo.clientHeight + ); + + // click toolbar button so panel shows + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + let panel = gBrowser.selectedBrowser.ownerDocument.querySelector( + "#screenshotsPagePanel" + ); + + let screenshotReady = TestUtils.topicObserved( + "screenshots-preview-ready" + ); + + // click the visible page button in panel + let visiblePageButton = panel + .querySelector("screenshots-buttons") + .shadowRoot.querySelector(".visible-page"); + visiblePageButton.click(); + + let dialog = helper.getDialog(); + + await screenshotReady; + + let copyButton = dialog._frame.contentDocument.getElementById("copy"); + ok(copyButton, "Got the copy button"); + + let clipboardChanged = helper.waitForRawClipboardChange( + expectedWidth, + expectedHeight + ); + + // click copy button on dialog box + copyButton.click(); + + info("Waiting for clipboard change"); + let result = await clipboardChanged; + + // let result = await helper.getImageSizeAndColorFromClipboard(); + // debugger; + + info("result: " + JSON.stringify(result, null, 2)); + info("contentInfo: " + JSON.stringify(contentInfo, null, 2)); + + Assert.equal(result.width, expectedWidth, "Widths should be equal"); + Assert.equal(result.height, expectedHeight, "Heights should be equal"); + + // top left + Assert.equal(105, result.color.topLeft[0], "R color value"); + Assert.equal(55, result.color.topLeft[1], "G color value"); + Assert.equal(105, result.color.topLeft[2], "B color value"); + + // top right + Assert.equal(105, result.color.topRight[0], "R color value"); + Assert.equal(55, result.color.topRight[1], "G color value"); + Assert.equal(105, result.color.topRight[2], "B color value"); + + // bottom left + Assert.equal(105, result.color.bottomLeft[0], "R color value"); + Assert.equal(55, result.color.bottomLeft[1], "G color value"); + Assert.equal(105, result.color.bottomLeft[2], "B color value"); + + // bottom right + Assert.equal(105, result.color.bottomRight[0], "R color value"); + Assert.equal(55, result.color.bottomRight[1], "G color value"); + Assert.equal(105, result.color.bottomRight[2], "B color value"); + } + ); +}); + +add_task(async function test_visibleScreenshotScrolledX() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + await SpecialPowers.spawn(browser, [], () => { + content.scrollTo(2004, 0); + }); + + let helper = new ScreenshotsHelper(browser); + let contentInfo = await helper.getContentDimensions(); + ok(contentInfo, "Got dimensions back from the content"); + let devicePixelRatio = await getContentDevicePixelRatio(browser); + + let expectedWidth = Math.floor( + devicePixelRatio * contentInfo.clientWidth + ); + let expectedHeight = Math.floor( + devicePixelRatio * contentInfo.clientHeight + ); + + // click toolbar button so panel shows + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + let panel = gBrowser.selectedBrowser.ownerDocument.querySelector( + "#screenshotsPagePanel" + ); + + let screenshotReady = TestUtils.topicObserved( + "screenshots-preview-ready" + ); + + // click the visible page button in panel + let visiblePageButton = panel + .querySelector("screenshots-buttons") + .shadowRoot.querySelector(".visible-page"); + visiblePageButton.click(); + + let dialog = helper.getDialog(); + + await screenshotReady; + + let copyButton = dialog._frame.contentDocument.getElementById("copy"); + ok(copyButton, "Got the copy button"); + + let clipboardChanged = helper.waitForRawClipboardChange( + expectedWidth, + expectedHeight + ); + + // click copy button on dialog box + copyButton.click(); + + info("Waiting for clipboard change"); + let result = await clipboardChanged; + + info("result: " + JSON.stringify(result, null, 2)); + info("contentInfo: " + JSON.stringify(contentInfo, null, 2)); + + Assert.equal(result.width, expectedWidth, "Widths should be equal"); + Assert.equal(result.height, expectedHeight, "Heights should be equal"); + + // top left + Assert.equal(55, result.color.topLeft[0], "R color value"); + Assert.equal(155, result.color.topLeft[1], "G color value"); + Assert.equal(155, result.color.topLeft[2], "B color value"); + + // top right + Assert.equal(55, result.color.topRight[0], "R color value"); + Assert.equal(155, result.color.topRight[1], "G color value"); + Assert.equal(155, result.color.topRight[2], "B color value"); + + // bottom left + Assert.equal(55, result.color.bottomLeft[0], "R color value"); + Assert.equal(155, result.color.bottomLeft[1], "G color value"); + Assert.equal(155, result.color.bottomLeft[2], "B color value"); + + // bottom right + Assert.equal(55, result.color.bottomRight[0], "R color value"); + Assert.equal(155, result.color.bottomRight[1], "G color value"); + Assert.equal(155, result.color.bottomRight[2], "B color value"); + } + ); +}); + +add_task(async function test_visibleScreenshotScrolledXAndY() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + await SpecialPowers.spawn(browser, [], () => { + content.scrollTo(2004, 2008); + }); + + let helper = new ScreenshotsHelper(browser); + let contentInfo = await helper.getContentDimensions(); + ok(contentInfo, "Got dimensions back from the content"); + let devicePixelRatio = await getContentDevicePixelRatio(browser); + + let expectedWidth = Math.floor( + devicePixelRatio * contentInfo.clientWidth + ); + let expectedHeight = Math.floor( + devicePixelRatio * contentInfo.clientHeight + ); + + // click toolbar button so panel shows + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + let panel = gBrowser.selectedBrowser.ownerDocument.querySelector( + "#screenshotsPagePanel" + ); + + let screenshotReady = TestUtils.topicObserved( + "screenshots-preview-ready" + ); + + // click the visible page button in panel + let visiblePageButton = panel + .querySelector("screenshots-buttons") + .shadowRoot.querySelector(".visible-page"); + visiblePageButton.click(); + + let dialog = helper.getDialog(); + + await screenshotReady; + + let copyButton = dialog._frame.contentDocument.getElementById("copy"); + ok(copyButton, "Got the copy button"); + + let clipboardChanged = helper.waitForRawClipboardChange( + expectedWidth, + expectedHeight + ); + + // click copy button on dialog box + copyButton.click(); + + info("Waiting for clipboard change"); + let result = await clipboardChanged; + + info("result: " + JSON.stringify(result, null, 2)); + info("contentInfo: " + JSON.stringify(contentInfo, null, 2)); + + Assert.equal(result.width, expectedWidth, "Widths should be equal"); + Assert.equal(result.height, expectedHeight, "Heights should be equal"); + + // top left + Assert.equal(52, result.color.topLeft[0], "R color value"); + Assert.equal(127, result.color.topLeft[1], "G color value"); + Assert.equal(152, result.color.topLeft[2], "B color value"); + + // top right + Assert.equal(52, result.color.topRight[0], "R color value"); + Assert.equal(127, result.color.topRight[1], "G color value"); + Assert.equal(152, result.color.topRight[2], "B color value"); + + // bottom left + Assert.equal(52, result.color.bottomLeft[0], "R color value"); + Assert.equal(127, result.color.bottomLeft[1], "G color value"); + Assert.equal(152, result.color.bottomLeft[2], "B color value"); + + // bottom right + Assert.equal(52, result.color.bottomRight[0], "R color value"); + Assert.equal(127, result.color.bottomRight[1], "G color value"); + Assert.equal(152, result.color.bottomRight[2], "B color value"); + } + ); +}); diff --git a/browser/components/screenshots/tests/browser/browser_test_element_picker.js b/browser/components/screenshots/tests/browser/browser_test_element_picker.js new file mode 100644 index 0000000000..17ed2a0190 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_test_element_picker.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_element_picker() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + await clearAllTelemetryEvents(); + let helper = new ScreenshotsHelper(browser); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + await helper.clickTestPageElement(); + + let rect = await helper.getTestPageElementRect(); + let region = await helper.getSelectionRegionDimensions(); + + is( + region.left, + rect.left, + "The selected region left is the same as the element left" + ); + is( + region.right, + rect.right, + "The selected region right is the same as the element right" + ); + is( + region.top, + rect.top, + "The selected region top is the same as the element top" + ); + is( + region.bottom, + rect.bottom, + "The selected region bottom is the same as the element bottom" + ); + + mouse.click(10, 10); + await helper.waitForStateChange("crosshairs"); + + let hoverElementRegionValid = await helper.isHoverElementRegionValid(); + + ok(!hoverElementRegionValid, "Hover element rect is null"); + + mouse.click(10, 10); + await helper.waitForStateChange("crosshairs"); + } + ); +}); diff --git a/browser/components/screenshots/tests/browser/browser_test_resize.js b/browser/components/screenshots/tests/browser/browser_test_resize.js new file mode 100644 index 0000000000..b249a346d6 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_test_resize.js @@ -0,0 +1,100 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const windowWidth = 768; + +add_task(async function test_window_resize() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: RESIZE_TEST_PAGE, + }, + async browser => { + let helper = new ScreenshotsHelper(browser); + await helper.resizeContentWindow(windowWidth, window.outerHeight); + const originalContentDimensions = await helper.getContentDimensions(); + info(JSON.stringify(originalContentDimensions, null, 2)); + + await helper.zoomBrowser(1.5); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + await helper.scrollContentWindow(windowWidth, window.outerHeight); + + await helper.clickTestPageElement("hello"); + + await helper.zoomBrowser(1); + + await helper.waitForOverlaySizeChangeTo( + originalContentDimensions.scrollWidth, + originalContentDimensions.scrollHeight + ); + + let contentDims = await helper.getContentDimensions(); + info(JSON.stringify(contentDims, null, 2)); + + is( + contentDims.scrollWidth, + originalContentDimensions.scrollWidth, + "Width of page is back to original" + ); + is( + contentDims.scrollHeight, + originalContentDimensions.scrollHeight, + "Height of page is back to original" + ); + } + ); +}); + +add_task(async function test_window_resize_vertical_writing_mode() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: RESIZE_TEST_PAGE, + }, + async browser => { + await SpecialPowers.spawn(browser, [], () => { + content.document.documentElement.style = "writing-mode: vertical-lr;"; + }); + + let helper = new ScreenshotsHelper(browser); + await helper.resizeContentWindow(windowWidth, window.outerHeight); + const originalContentDimensions = await helper.getContentDimensions(); + info(JSON.stringify(originalContentDimensions, null, 2)); + + await helper.zoomBrowser(1.5); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + await helper.scrollContentWindow(windowWidth, window.outerHeight); + + await helper.clickTestPageElement("hello"); + + await helper.zoomBrowser(1); + + await helper.waitForOverlaySizeChangeTo( + originalContentDimensions.scrollWidth, + originalContentDimensions.scrollHeight + ); + + let contentDims = await helper.getContentDimensions(); + info(JSON.stringify(contentDims, null, 2)); + + is( + contentDims.scrollWidth, + originalContentDimensions.scrollWidth, + "Width of page is back to original" + ); + is( + contentDims.scrollHeight, + originalContentDimensions.scrollHeight, + "Height of page is back to original" + ); + } + ); +}); diff --git a/browser/components/screenshots/tests/browser/first-iframe.html b/browser/components/screenshots/tests/browser/first-iframe.html new file mode 100644 index 0000000000..9b0c123486 --- /dev/null +++ b/browser/components/screenshots/tests/browser/first-iframe.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<html lang="en"> + <head> + <meta charset="utf-8"> + <style> + div { + font-size: 40px; + margin: 30px; + width: 234px; + height: 51px; + color: blue; + } + </style> + </head> +<body> + <div>Hello world!</div> + <iframe + width="300" + height="300" + src="https://example.org/browser/browser/components/screenshots/tests/browser/second-iframe.html" + ></iframe> +</body> +</html> diff --git a/browser/components/screenshots/tests/browser/head.js b/browser/components/screenshots/tests/browser/head.js new file mode 100644 index 0000000000..171e3b8c41 --- /dev/null +++ b/browser/components/screenshots/tests/browser/head.js @@ -0,0 +1,951 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); +const { UrlbarTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/UrlbarTestUtils.sys.mjs" +); + +const TEST_ROOT = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); + +const TEST_PAGE = TEST_ROOT + "test-page.html"; +const SHORT_TEST_PAGE = TEST_ROOT + "short-test-page.html"; +const LARGE_TEST_PAGE = TEST_ROOT + "large-test-page.html"; +const IFRAME_TEST_PAGE = TEST_ROOT + "iframe-test-page.html"; +const RESIZE_TEST_PAGE = TEST_ROOT + "test-page-resize.html"; + +const { MAX_CAPTURE_DIMENSION, MAX_CAPTURE_AREA } = ChromeUtils.importESModule( + "resource:///modules/ScreenshotsUtils.sys.mjs" +); + +const gScreenshotUISelectors = { + panel: "#screenshotsPagePanel", + fullPageButton: "button.full-page", + visiblePageButton: "button.visible-page", + copyButton: "button.#copy", +}; + +// MouseEvents is for the mouse events on the Anonymous content +const MouseEvents = { + mouse: new Proxy( + {}, + { + get: (target, name) => + async function (x, y, options = {}) { + if (name === "click") { + this.down(x, y, options); + this.up(x, y, options); + } else { + await safeSynthesizeMouseEventInContentPage(":root", x, y, { + type: "mouse" + name, + ...options, + }); + } + }, + } + ), +}; + +const { mouse } = MouseEvents; + +class ScreenshotsHelper { + constructor(browser) { + this.browser = browser; + this.selector = gScreenshotUISelectors; + } + + get toolbarButton() { + return this.browser.ownerDocument.getElementById("screenshot-button"); + } + + get panel() { + return this.browser.ownerDocument.querySelector(this.selector.panel); + } + + /** + * Click the screenshots button in the toolbar + */ + triggerUIFromToolbar() { + let button = this.toolbarButton; + ok( + BrowserTestUtils.isVisible(button), + "The screenshot toolbar button is visible" + ); + button.click(); + } + + async getPanelButton(selector) { + let panel = await this.waitForPanel(); + let screenshotsButtons = panel.querySelector("screenshots-buttons"); + ok(screenshotsButtons, "Found the screenshots-buttons"); + let button = screenshotsButtons.shadowRoot.querySelector(selector); + ok(button, `Found ${selector} button`); + return button; + } + + async waitForPanel() { + let panel = this.panel; + await BrowserTestUtils.waitForCondition(async () => { + if (!panel) { + panel = this.panel; + } + return panel && BrowserTestUtils.isVisible(panel); + }); + return panel; + } + + async waitForOverlay() { + const panel = await this.waitForPanel(); + ok(BrowserTestUtils.isVisible(panel), "Panel buttons are visible"); + + await BrowserTestUtils.waitForCondition(async () => { + let init = await this.isOverlayInitialized(); + return init; + }); + info("Overlay is visible"); + } + + async waitForPanelClosed() { + let panel = this.panel; + if (!panel) { + info("waitForPanelClosed: Panel doesnt exist"); + return; + } + if (panel.hidden) { + info("waitForPanelClosed: panel is already hidden"); + return; + } + info("waitForPanelClosed: waiting for the panel to become hidden"); + await BrowserTestUtils.waitForMutationCondition( + panel, + { attributes: true }, + () => { + return BrowserTestUtils.isHidden(panel); + } + ); + ok(BrowserTestUtils.isHidden(panel), "Panel buttons are hidden"); + info("waitForPanelClosed, panel is hidden: " + panel.hidden); + } + + async waitForOverlayClosed() { + await this.waitForPanelClosed(); + await BrowserTestUtils.waitForCondition(async () => { + let init = !(await this.isOverlayInitialized()); + info("Is overlay initialized: " + !init); + return init; + }); + info("Overlay is not visible"); + } + + async isOverlayInitialized() { + return SpecialPowers.spawn(this.browser, [], () => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + return screenshotsChild?.overlay?.initialized; + }); + } + + waitForStateChange(newState) { + return SpecialPowers.spawn(this.browser, [newState], async state => { + 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}`); + + return screenshotsChild.overlay.state; + }); + } + + async assertStateChange(newState) { + let currentState = await this.waitForStateChange(newState); + + is( + currentState, + newState, + `The current state is ${currentState}, expected ${newState}` + ); + } + + getHoverElementRect() { + return ContentTask.spawn(this.browser, null, async () => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + return screenshotsChild.overlay.hoverElementRegion.dimensions; + }); + } + + isHoverElementRegionValid() { + return ContentTask.spawn(this.browser, null, async () => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + return screenshotsChild.overlay.hoverElementRegion.isRegionValid; + }); + } + + async waitForHoverElementRect(expectedWidth, expectedHeight) { + return SpecialPowers.spawn( + this.browser, + [expectedWidth, expectedHeight], + async (width, height) => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + let dimensions; + await ContentTaskUtils.waitForCondition(() => { + dimensions = screenshotsChild.overlay.hoverElementRegion.dimensions; + return dimensions.width === width && dimensions.height === height; + }, "The hover element region is the expected width and height"); + return dimensions; + } + ); + } + + async waitForSelectionRegionSizeChange(currentWidth) { + await ContentTask.spawn( + this.browser, + [currentWidth], + async ([currWidth]) => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + + let dimensions = screenshotsChild.overlay.selectionRegion.dimensions; + await ContentTaskUtils.waitForCondition(() => { + dimensions = screenshotsChild.overlay.selectionRegion.dimensions; + return dimensions.width !== currWidth; + }, "Wait for selection box width change"); + } + ); + } + + /** + * This will drag an overlay starting at the given startX and startY coordinates and ending + * at the given endX and endY coordinates. + * + * endY should be at least 70px from the bottom of window and endX should be at least + * 265px from the left of the window. If these requirements are not met then the + * overlay buttons (cancel, copy, download) will be positioned different from the default + * and the methods to click the overlay buttons will not work unless the updated + * position coordinates are supplied. + * See https://searchfox.org/mozilla-central/rev/af78418c4b5f2c8721d1a06486cf4cf0b33e1e8d/browser/components/screenshots/ScreenshotsOverlayChild.sys.mjs#1789,1798 + * for how the overlay buttons are positioned when the overlay rect is near the bottom or + * left edge of the window. + * + * Note: The distance of the rect should be greater than 40 to enter in the "dragging" state. + * See https://searchfox.org/mozilla-central/rev/af78418c4b5f2c8721d1a06486cf4cf0b33e1e8d/browser/components/screenshots/ScreenshotsOverlayChild.sys.mjs#809 + * @param {Number} startX The starting X coordinate. The left edge of the overlay rect. + * @param {Number} startY The starting Y coordinate. The top edge of the overlay rect. + * @param {Number} endX The end X coordinate. The right edge of the overlay rect. + * @param {Number} endY The end Y coordinate. The bottom edge of the overlay rect. + */ + async dragOverlay( + startX, + startY, + endX, + endY, + expectedStartingState = "crosshairs" + ) { + await this.assertStateChange(expectedStartingState); + + mouse.down(startX, startY); + + await Promise.any([ + this.waitForStateChange("draggingReady"), + this.waitForStateChange("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"), + ]); + Assert.ok(true, "The overlay is in the dragging or resizing state"); + + mouse.up(endX, endY); + + await this.assertStateChange("selected"); + + this.endX = endX; + this.endY = endY; + } + + async moveOverlayViaKeyboard(mover, events) { + await SpecialPowers.spawn( + this.browser, + [mover, events], + async (moverToFocus, eventsArr) => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + + let overlay = screenshotsChild.overlay; + + switch (moverToFocus) { + case "highlight": + overlay.highlightEl.focus({ focusVisible: true }); + break; + case "mover-bottomLeft": + overlay.bottomLeftMover.focus({ focusVisible: true }); + break; + case "mover-bottomRight": + overlay.bottomRightMover.focus({ focusVisible: true }); + break; + case "mover-topLeft": + overlay.topLeftMover.focus({ focusVisible: true }); + break; + case "mover-topRight": + overlay.topRightMover.focus({ focusVisible: true }); + break; + } + screenshotsChild.overlay.highlightEl.focus(); + + for (let event of eventsArr) { + EventUtils.synthesizeKey( + event.key, + { type: "keydown", ...event.options }, + content + ); + + await ContentTaskUtils.waitForCondition( + () => overlay.state === "resizing", + "Wait for overlay state to be resizing" + ); + + EventUtils.synthesizeKey( + event.key, + { type: "keyup", ...event.options }, + content + ); + + await ContentTaskUtils.waitForCondition( + () => overlay.state === "selected", + "Wait for overlay state to be selected" + ); + } + } + ); + } + + async scrollContentWindow(x, y) { + let promise = BrowserTestUtils.waitForContentEvent(this.browser, "scroll"); + let contentDims = await this.getContentDimensions(); + await ContentTask.spawn( + this.browser, + [x, y, contentDims], + async ([xPos, yPos, cDims]) => { + content.window.scroll(xPos, yPos); + + info(JSON.stringify(cDims, null, 2)); + const scrollbarHeight = {}; + const scrollbarWidth = {}; + content.window.windowUtils.getScrollbarSize( + false, + scrollbarWidth, + scrollbarHeight + ); + + await ContentTaskUtils.waitForCondition(() => { + function isCloseEnough(a, b, diff) { + return Math.abs(a - b) <= diff; + } + + info( + `scrollbarWidth: ${scrollbarWidth.value}, scrollbarHeight: ${scrollbarHeight.value}` + ); + info( + `scrollX: ${content.window.scrollX}, scrollY: ${content.window.scrollY}, scrollMaxX: ${content.window.scrollMaxX}, scrollMaxY: ${content.window.scrollMaxY}` + ); + + // Sometimes (read intermittently) the scroll width/height will be + // off by the width/height of the scrollbar when we are expecting the + // page to be scrolled to the very end. To mitigate this, we check + // that the below differences are within the scrollbar width/height. + return ( + (content.window.scrollX === xPos || + isCloseEnough( + cDims.clientWidth + content.window.scrollX, + cDims.scrollWidth, + scrollbarWidth.value + 1 + )) && + (content.window.scrollY === yPos || + isCloseEnough( + cDims.clientHeight + content.window.scrollY, + cDims.scrollHeight, + scrollbarHeight.value + 1 + )) + ); + }, `Waiting for window to scroll to ${xPos}, ${yPos}`); + } + ); + await promise; + } + + async waitForScrollTo(x, y) { + await ContentTask.spawn(this.browser, [x, y], async ([xPos, yPos]) => { + await ContentTaskUtils.waitForCondition(() => { + info( + `Got scrollX: ${content.window.scrollX}. scrollY: ${content.window.scrollY}` + ); + return ( + content.window.scrollX === xPos && content.window.scrollY === yPos + ); + }, `Waiting for window to scroll to ${xPos}, ${yPos}`); + }); + } + + async resizeContentWindow(width, height) { + this.browser.ownerGlobal.resizeTo(width, height); + await TestUtils.waitForCondition( + () => window.outerHeight === height && window.outerWidth === width, + "Waiting for window to resize" + ); + } + + async clickDownloadButton() { + let { centerX: x, centerY: y } = await ContentTask.spawn( + this.browser, + null, + async () => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + let { left, top, width, height } = + screenshotsChild.overlay.downloadButton.getBoundingClientRect(); + let centerX = left + width / 2; + let centerY = top + height / 2; + return { centerX, centerY }; + } + ); + + info(`clicking download button at ${x}, ${y}`); + mouse.click(x, y); + } + + async clickCopyButton() { + let { centerX: x, centerY: y } = await ContentTask.spawn( + this.browser, + null, + async () => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + let { left, top, width, height } = + screenshotsChild.overlay.copyButton.getBoundingClientRect(); + let centerX = left + width / 2; + let centerY = top + height / 2; + return { centerX, centerY }; + } + ); + + info(`clicking copy button at ${x}, ${y}`); + mouse.click(x, y); + } + + async clickCancelButton() { + let { centerX: x, centerY: y } = await ContentTask.spawn( + this.browser, + null, + async () => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + let { left, top, width, height } = + screenshotsChild.overlay.cancelButton.getBoundingClientRect(); + let centerX = left + width / 2; + let centerY = top + height / 2; + return { centerX, centerY }; + } + ); + + info(`clicking cancel button at ${x}, ${y}`); + mouse.click(x, y); + } + + async clickPreviewCancelButton() { + let { centerX: x, centerY: y } = await ContentTask.spawn( + this.browser, + null, + async () => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + let { left, top, width, height } = + screenshotsChild.overlay.previewCancelButton.getBoundingClientRect(); + let centerX = left + width / 2; + let centerY = top + height / 2; + return { centerX, centerY }; + } + ); + + info(`clicking cancel button at ${x}, ${y}`); + mouse.click(x, y); + } + + escapeKeyInContent() { + return SpecialPowers.spawn(this.browser, [], () => { + EventUtils.synthesizeKey("KEY_Escape", {}, content); + }); + } + + getTestPageElementRect(elementId = "testPageElement") { + return ContentTask.spawn(this.browser, [elementId], async id => { + let ele = content.document.getElementById(id); + return ele.getBoundingClientRect(); + }); + } + + async clickTestPageElement(elementId = "testPageElement") { + let rect = await this.getTestPageElementRect(elementId); + let dims = await this.getContentDimensions(); + + let x = Math.floor(rect.x + dims.scrollX + rect.width / 2); + let y = Math.floor(rect.y + dims.scrollY + rect.height / 2); + + mouse.move(x, y); + await this.waitForHoverElementRect(rect.width, rect.height); + mouse.down(x, y); + await this.assertStateChange("draggingReady"); + mouse.up(x, y); + await this.assertStateChange("selected"); + } + + async zoomBrowser(zoom) { + let promise = BrowserTestUtils.waitForContentEvent(this.browser, "resize"); + await SpecialPowers.spawn(this.browser, [zoom], zoomLevel => { + const { Layout } = ChromeUtils.importESModule( + "chrome://mochitests/content/browser/accessible/tests/browser/Layout.sys.mjs" + ); + Layout.zoomDocument(content.document, zoomLevel); + }); + await promise; + } + + /** + * Gets the dialog box + * @returns The dialog box + */ + getDialog() { + let currDialogBox = this.browser.tabDialogBox; + let manager = currDialogBox.getTabDialogManager(); + let dialogs = manager.hasDialogs && manager.dialogs; + return dialogs[0]; + } + + assertPanelVisible() { + info("assertPanelVisible, panel.hidden:" + this.panel?.hidden); + Assert.ok( + BrowserTestUtils.isVisible(this.panel), + "Screenshots panel is visible" + ); + } + + assertPanelNotVisible() { + info("assertPanelNotVisible, panel.hidden:" + this.panel?.hidden); + Assert.ok( + !this.panel || BrowserTestUtils.isHidden(this.panel), + "Screenshots panel is not visible" + ); + } + + /** + * Copied from screenshots extension + * Returns a promise that resolves when the clipboard data has changed + * Otherwise rejects + */ + waitForRawClipboardChange(epectedWidth, expectedHeight) { + const initialClipboardData = Date.now().toString(); + SpecialPowers.clipboardCopyString(initialClipboardData); + + return TestUtils.waitForCondition( + async () => { + let data; + try { + data = await this.getImageSizeAndColorFromClipboard(); + } catch (e) { + console.log("Failed to get image/png clipboard data:", e); + return false; + } + if ( + data && + initialClipboardData !== data && + data.height === expectedHeight && + data.width === epectedWidth + ) { + return data; + } + return false; + }, + "Waiting for screenshot to copy to clipboard", + 200 + ); + } + + /** + * Gets the client and scroll demensions on the window + * @returns { Object } + * clientHeight The visible height + * clientWidth The visible width + * scrollHeight The scrollable height + * scrollWidth The scrollable width + * scrollX The scroll x position + * scrollY The scroll y position + */ + getContentDimensions() { + return SpecialPowers.spawn(this.browser, [], async function () { + let { + innerWidth, + innerHeight, + scrollMaxX, + scrollMaxY, + scrollX, + scrollY, + } = content.window; + let width = innerWidth + scrollMaxX; + let height = innerHeight + scrollMaxY; + + const scrollbarHeight = {}; + const scrollbarWidth = {}; + content.window.windowUtils.getScrollbarSize( + false, + scrollbarWidth, + scrollbarHeight + ); + width -= scrollbarWidth.value; + height -= scrollbarHeight.value; + innerWidth -= scrollbarWidth.value; + innerHeight -= scrollbarHeight.value; + + return { + clientHeight: innerHeight, + clientWidth: innerWidth, + scrollHeight: height, + scrollWidth: width, + scrollX, + scrollY, + }; + }); + } + + async getScreenshotsOverlayDimensions() { + return ContentTask.spawn(this.browser, null, async () => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + Assert.ok(screenshotsChild.overlay.initialized, "The overlay exists"); + + let screenshotsContainer = screenshotsChild.overlay.screenshotsContainer; + + await ContentTaskUtils.waitForCondition(() => { + return !screenshotsContainer.hasAttribute("resizing"); + }, "Waiting for overlay to be done resizing"); + + info( + `${screenshotsContainer.style.width} ${ + screenshotsContainer.style.height + } ${screenshotsContainer.hasAttribute("resizing")}` + ); + + return { + scrollWidth: screenshotsContainer.scrollWidth, + scrollHeight: screenshotsContainer.scrollHeight, + }; + }); + } + + async waitForSelectionLayerDimensionChange(oldWidth, oldHeight) { + await ContentTask.spawn( + this.browser, + [oldWidth, oldHeight], + async ([prevWidth, prevHeight]) => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + + await ContentTaskUtils.waitForCondition(() => { + let screenshotsContainer = + screenshotsChild.overlay.screenshotsContainer; + info( + `old height: ${prevHeight}. new height: ${screenshotsContainer.scrollHeight}.\nold width: ${prevWidth}. new width: ${screenshotsContainer.scrollWidth}` + ); + return ( + screenshotsContainer.scrollHeight !== prevHeight && + screenshotsContainer.scrollWidth !== prevWidth + ); + }, "Wait for selection box width change"); + } + ); + } + + waitForOverlaySizeChangeTo(width, height) { + return ContentTask.spawn( + this.browser, + [width, height], + async ([newWidth, newHeight]) => { + await ContentTaskUtils.waitForCondition(() => { + let { + innerHeight, + innerWidth, + scrollMaxY, + scrollMaxX, + scrollMinY, + scrollMinX, + } = content.window; + let scrollWidth = innerWidth + scrollMaxX - scrollMinX; + let scrollHeight = innerHeight + scrollMaxY - scrollMinY; + + const scrollbarHeight = {}; + const scrollbarWidth = {}; + content.window.windowUtils.getScrollbarSize( + false, + scrollbarWidth, + scrollbarHeight + ); + scrollWidth -= scrollbarWidth.value; + scrollHeight -= scrollbarHeight.value; + info( + `${scrollHeight}, ${newHeight}, ${scrollWidth}, ${newWidth}, ${content.window.scrollMaxX}` + ); + return scrollHeight === newHeight && scrollWidth === newWidth; + }, "Wait for document size change"); + } + ); + } + + getSelectionRegionDimensions() { + return ContentTask.spawn(this.browser, null, async () => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + Assert.ok(screenshotsChild.overlay.initialized, "The overlay exists"); + + return screenshotsChild.overlay.selectionRegion.dimensions; + }); + } + + /** + * Copied from screenshots extension + * A helper that returns the size of the image that was just put into the clipboard by the + * :screenshot command. + * @return The {width, height, color} dimension and color object. + */ + async getImageSizeAndColorFromClipboard() { + let flavor = "image/png"; + let image = getRawClipboardData(flavor); + if (!image) { + return false; + } + + // Due to the differences in how images could be stored in the clipboard the + // checks below are needed. The clipboard could already provide the image as + // byte streams or as image container. If it's not possible obtain a + // byte stream, the function throws. + + if (image instanceof Ci.imgIContainer) { + image = Cc["@mozilla.org/image/tools;1"] + .getService(Ci.imgITools) + .encodeImage(image, flavor); + } + + if (!(image instanceof Ci.nsIInputStream)) { + throw new Error("Unable to read image data"); + } + + const binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance( + Ci.nsIBinaryInputStream + ); + binaryStream.setInputStream(image); + const available = binaryStream.available(); + const buffer = new ArrayBuffer(available); + info( + `${binaryStream.readArrayBuffer( + available, + buffer + )} read, ${available} available` + ); + + // We are going to load the image in the content page to measure its size. + // We don't want to insert the image directly in the browser's document + // which could mess all sorts of things up + return SpecialPowers.spawn( + this.browser, + [buffer], + async function (_buffer) { + const img = content.document.createElement("img"); + const loaded = new Promise(r => { + img.addEventListener("load", r, { once: true }); + }); + const url = content.URL.createObjectURL( + new Blob([_buffer], { type: "image/png" }) + ); + + img.src = url; + content.document.documentElement.appendChild(img); + + info("Waiting for the clipboard image to load in the content page"); + await loaded; + + let canvas = content.document.createElementNS( + "http://www.w3.org/1999/xhtml", + "html:canvas" + ); + let context = canvas.getContext("2d"); + canvas.width = img.width; + canvas.height = img.height; + context.drawImage(img, 0, 0); + let topLeft = context.getImageData(0, 0, 1, 1); + let topRight = context.getImageData(img.width - 1, 0, 1, 1); + let bottomLeft = context.getImageData(0, img.height - 1, 1, 1); + let bottomRight = context.getImageData( + img.width - 1, + img.height - 1, + 1, + 1 + ); + + img.remove(); + content.URL.revokeObjectURL(url); + + return { + width: img.width, + height: img.height, + color: { + topLeft: topLeft.data, + topRight: topRight.data, + bottomLeft: bottomLeft.data, + bottomRight: bottomRight.data, + }, + }; + } + ); + } +} + +/** + * Get the raw clipboard data + * @param flavor Type of data to get from clipboard + * @returns The data from the clipboard + */ +function getRawClipboardData(flavor) { + const whichClipboard = Services.clipboard.kGlobalClipboard; + const xferable = Cc["@mozilla.org/widget/transferable;1"].createInstance( + Ci.nsITransferable + ); + xferable.init(null); + xferable.addDataFlavor(flavor); + Services.clipboard.getData( + xferable, + whichClipboard, + SpecialPowers.wrap(window).browsingContext.currentWindowContext + ); + let data = {}; + try { + // xferable.getTransferData(flavor, data); + xferable.getAnyTransferData({}, data); + info(JSON.stringify(data, null, 2)); + } catch (e) { + info(e); + } + data = data.value || null; + return data; +} + +/** + * Synthesize a mouse event on an element, after ensuring that it is visible + * in the viewport. + * + * @param {String} selector: The node selector to get the node target for the event. + * @param {number} x + * @param {number} y + * @param {object} options: Options that will be passed to BrowserTestUtils.synthesizeMouse + */ +async function safeSynthesizeMouseEventInContentPage( + selector, + x, + y, + options = {} +) { + let context = gBrowser.selectedBrowser.browsingContext; + BrowserTestUtils.synthesizeMouse(selector, x, y, options, context); +} + +add_setup(async () => { + CustomizableUI.addWidgetToArea( + "screenshot-button", + CustomizableUI.AREA_NAVBAR, + 0 + ); + let screenshotBtn = document.getElementById("screenshot-button"); + Assert.ok(screenshotBtn, "The screenshots button was added to the nav bar"); +}); + +function getContentDevicePixelRatio(browser) { + return SpecialPowers.spawn(browser, [], async function () { + return content.window.devicePixelRatio; + }); +} + +async function clearAllTelemetryEvents() { + // Clear everything. + info("Clearing all telemetry events"); + await TestUtils.waitForCondition(() => { + Services.telemetry.clearEvents(); + let events = Services.telemetry.snapshotEvents( + Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS, + true + ); + let content = events.content; + let parent = events.parent; + + return (!content && !parent) || (!content.length && !parent.length); + }); +} + +async function waitForScreenshotsEventCount(count, process = "parent") { + await TestUtils.waitForCondition( + () => { + let events = TelemetryTestUtils.getEvents( + { category: "screenshots" }, + { process } + ); + + info(`Got ${events?.length} event(s)`); + info(`Actual events: ${JSON.stringify(events, null, 2)}`); + return events.length === count ? events : null; + }, + `Waiting for ${count} ${process} event(s).`, + 200, + 100 + ); +} + +async function assertScreenshotsEvents( + expectedEvents, + process = "parent", + clearEvents = true +) { + info(`Expected events: ${JSON.stringify(expectedEvents, null, 2)}`); + // Make sure we have recorded the correct number of events + await waitForScreenshotsEventCount(expectedEvents.length, process); + + TelemetryTestUtils.assertEvents( + expectedEvents, + { category: "screenshots" }, + { clear: clearEvents, process } + ); +} diff --git a/browser/components/screenshots/tests/browser/iframe-test-page.html b/browser/components/screenshots/tests/browser/iframe-test-page.html new file mode 100644 index 0000000000..5439552734 --- /dev/null +++ b/browser/components/screenshots/tests/browser/iframe-test-page.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<html lang="en"> + <head> + <title>Screenshots</title> + <style> + div { + font-size: 40px; + margin: 30px; + width: 233px; + height: 50px; + color: green; + } + </style> + </head> + <body> + <div>Hello world!</div> + <iframe + width="500" + height="500" + src="https://example.com/browser/browser/components/screenshots/tests/browser/first-iframe.html" + ></iframe> + </body> +</html> diff --git a/browser/components/screenshots/tests/browser/large-test-page.html b/browser/components/screenshots/tests/browser/large-test-page.html new file mode 100644 index 0000000000..ab2eb8d601 --- /dev/null +++ b/browser/components/screenshots/tests/browser/large-test-page.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>Screenshots</title> +</head> +<body style="height:40000px; width:40000px; background-repeat: no-repeat; background-size: 40008px 40016px; background-color: rgb(111, 111, 111); background-image:linear-gradient(to right, transparent 50%, rgba(0, 200, 200, 0.5) 50%),linear-gradient(to bottom, transparent 50%, rgba(100, 0, 100, 0.5) 50%);"> +</body> +</html> diff --git a/browser/components/screenshots/tests/browser/second-iframe.html b/browser/components/screenshots/tests/browser/second-iframe.html new file mode 100644 index 0000000000..ca5de26bb9 --- /dev/null +++ b/browser/components/screenshots/tests/browser/second-iframe.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html lang="en"> + <head> + <meta charset="utf-8"> + <style> + div { + font-size: 40px; + margin: 30px; + width: 235px; + height: 52px; + color: red; + } + </style> + </head> +<body> + <div>Hello world!</div> +</body> +</html> diff --git a/browser/components/screenshots/tests/browser/short-test-page.html b/browser/components/screenshots/tests/browser/short-test-page.html new file mode 100644 index 0000000000..7718892f8c --- /dev/null +++ b/browser/components/screenshots/tests/browser/short-test-page.html @@ -0,0 +1,8 @@ +<html lang="en"> +<head> + <title>Screenshots</title> +</head> +<body> + <div style="height:500px; width:500px; background-color: blue;"></div> +</body> +</html> diff --git a/browser/components/screenshots/tests/browser/test-page-resize.html b/browser/components/screenshots/tests/browser/test-page-resize.html new file mode 100644 index 0000000000..dea57909b4 --- /dev/null +++ b/browser/components/screenshots/tests/browser/test-page-resize.html @@ -0,0 +1,25 @@ +<html> +<head> + <title>Screenshots</title> +</head> +<body> + <div style="display: flex;flex-flow: row wrap;gap: 8px;"> + <div style="height:100px; width:100px; background-color: blue;"></div> + <div style="height:100px; width:100px; background-color: blue;"></div> + <div style="height:100px; width:100px; background-color: blue;"></div> + <div style="height:100px; width:100px; background-color: blue;"></div> + <div style="height:100px; width:100px; background-color: blue;"></div> + <div style="height:100px; width:100px; background-color: blue;"></div> + </div> + <div style="display: flex;flex-direction: column;gap: 8px;"> + <div style="height:100px; width:100px; background-color: red;"></div> + <div style="height:100px; width:100px; background-color: red;"></div> + <div style="height:100px; width:100px; background-color: red;"></div> + <div style="height:100px; width:100px; background-color: red;"></div> + <div style="height:100px; width:100px; background-color: red;"></div> + <div style="height:100px; width:100px; background-color: red;"></div> + <div style="height:100px; width:100px; background-color: red;"></div> + <div id="hello" style="height:100px; width:100px; background-color: red;"></div> + </div> +</body> +</html> diff --git a/browser/components/screenshots/tests/browser/test-page.html b/browser/components/screenshots/tests/browser/test-page.html new file mode 100644 index 0000000000..5ddc1d6eb6 --- /dev/null +++ b/browser/components/screenshots/tests/browser/test-page.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>Screenshots</title> +</head> +<body style="height:4000px; width:4000px; background-repeat: no-repeat; background-size: 4008px 4016px; background-color: rgb(111, 111, 111); background-image:linear-gradient(to right, transparent 50%, rgba(0, 200, 200, 0.5) 50%),linear-gradient(to bottom, transparent 50%, rgba(100, 0, 100, 0.5) 50%);"> + <div id="testPageElement" style="position:absolute; top:91px; left:93px; width:92px; height:94px; border:solid red;"></div> + <script> + // Make sure the screenshots overlay anonymous document always receives events + // that web content would normally be able to intercept, as that could break the + // overlay + function disabledEvent(event) { + event.preventDefault(); + event.stopImmediatePropagation(); + } + + window.addEventListener("click", disabledEvent, true); + window.addEventListener("pointerdown", disabledEvent, true); + window.addEventListener("pointermove", disabledEvent, true); + window.addEventListener("mousemove", disabledEvent, true); + window.addEventListener("pointerup", disabledEvent, true); + window.addEventListener("keydown", disabledEvent, true); + window.addEventListener("keyup", disabledEvent, true); + </script> +</body> +</html> |