diff options
Diffstat (limited to '')
20 files changed, 3428 insertions, 0 deletions
diff --git a/browser/components/screenshots/tests/browser/browser.ini b/browser/components/screenshots/tests/browser/browser.ini new file mode 100644 index 0000000000..c850f03a97 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser.ini @@ -0,0 +1,29 @@ +[DEFAULT] +support-files = + head.js + test-page.html + short-test-page.html + large-test-page.html + +prefs = + extensions.screenshots.disabled=false + screenshots.browser.component.enabled=true + +[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] +skip-if = + (!debug && os == 'win' && os_version == '6.1') # Bug 1746281 +[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] 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..19669b7427 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_drag_scroll_test.js @@ -0,0 +1,400 @@ +/* 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 dimensions = await helper.getSelectionLayerDimensions(); + info(JSON.stringify(dimensions)); + is( + dimensions.scrollWidth, + contentInfo.scrollWidth, + "The overlay spans the entire width of the page" + ); + + is( + dimensions.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.waitForStateChange("resizing"); + let state = await helper.getOverlayState(); + is(state, "resizing", "The overlay is in the resizing state"); + + 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.waitForSelectionBoxSizeChange(490); + + let dimensions = await helper.getSelectionBoxDimensions(); + + is(dimensions.x1, 0, "The box x1 position is now 0"); + is(dimensions.y1, 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.waitForSelectionBoxSizeChange(255); + + dimensions = await helper.getSelectionBoxDimensions(); + + is(dimensions.x1, 10, "The box x1 position is now 10 again"); + is(dimensions.y1, 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.waitForStateChange("resizing"); + let state = await helper.getOverlayState(); + is(state, "resizing", "The overlay is in the resizing state"); + + 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.waitForSelectionBoxSizeChange(480); + + let dimensions = await helper.getSelectionBoxDimensions(); + + is(dimensions.x1, startX + 240, "The box x1 position is now 3748"); + is(dimensions.y1, 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.waitForSelectionBoxSizeChange(252); + + dimensions = await helper.getSelectionBoxDimensions(); + + is(dimensions.x1, startX, "The box x1 position is now 3508 again"); + is(dimensions.y1, 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.getSelectionBoxDimensions(); + + is(dimensions.x1, startX, "The box x1 position is 10"); + is(dimensions.y1, 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 + mouse.click(scrollX + startX, scrollY + endY); + await helper.waitForStateChange("crosshairs"); + + await helper.dragOverlay( + scrollX + startX, + scrollY + startY, + scrollX + endX, + scrollY + endY + ); + + await helper.scrollContentWindow(0, 0); + + dimensions = await helper.getSelectionBoxDimensions(); + + is(dimensions.x1, scrollX + startX, "The box x1 position is 1010"); + is(dimensions.y1, 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 + mouse.click(10, 10); + await helper.waitForStateChange("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.waitForStateChange("resizing"); + + mouse.move( + contentInfo.clientWidth * 2 - 30, + contentInfo.clientHeight * 2 - 30 + ); + + mouse.up( + contentInfo.clientWidth * 2 - 30, + contentInfo.clientHeight * 2 - 30 + ); + + await helper.waitForStateChange("selected"); + + dimensions = await helper.getSelectionLayerDimensions(); + info(JSON.stringify(dimensions)); + is(dimensions.boxLeft, startX, "The box left is 10"); + is(dimensions.boxTop, startY, "The box top is 10"); + is( + dimensions.boxRight, + contentInfo.clientWidth * 2 - 30, + "The box right is 2 x clientWidth - 30" + ); + is( + dimensions.boxBottom, + contentInfo.clientHeight * 2 - 30, + "The box right is 2 x clientHeight - 30" + ); + is( + dimensions.boxWidth, + contentInfo.clientWidth * 2 - 40, + "The box right is 2 x clientWidth - 40" + ); + is( + dimensions.boxHeight, + contentInfo.clientHeight * 2 - 40, + "The box right is 2 x clientHeight - 40" + ); + is( + dimensions.scrollWidth, + contentInfo.scrollWidth, + "The overlay spans the entire width of the page" + ); + is( + dimensions.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.getWindowPosition(); + + 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.waitForStateChange("draggingReady"); + mouse.move(1050, 1050); + await helper.waitForStateChange("dragging"); + mouse.move(endX, endY); + mouse.up(endX, endY); + await helper.waitForStateChange("selected"); + + windowX = 980; + windowY = 980; + await helper.waitForScrollTo(windowX, windowY); + + ({ scrollX, scrollY } = await helper.getWindowPosition()); + + is(scrollX, windowX, "Window x position is 980"); + is(scrollY, windowY, "Window y position is 980"); + + 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` + ); + mouse.down(startX, startY); + await helper.waitForStateChange("resizing"); + mouse.move(endX, endY); + mouse.up(endX, endY); + await helper.waitForStateChange("selected"); + + windowX = 1000; + windowY = 1000; + await helper.waitForScrollTo(windowX, windowY); + + ({ scrollX, scrollY } = await helper.getWindowPosition()); + + 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..a8c05dcb49 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_drag_test.js @@ -0,0 +1,475 @@ +/* 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"); + + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + + await helper.dragOverlay(10, 10, 500, 500); + + let clipboardChanged = helper.waitForRawClipboardChange(); + + helper.clickCopyButton(); + + info("Waiting for clipboard change"); + await clipboardChanged; + + let result = await helper.getImageSizeAndColorFromClipboard(); + + info("result: " + JSON.stringify(result, null, 2)); + + let expected = Math.floor( + 490 * (await getContentDevicePixelRatio(browser)) + ); + + 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"); + + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + + await helper.dragOverlay(300, 100, 350, 150); + + let clipboardChanged = helper.waitForRawClipboardChange(); + + helper.clickCopyButton(); + + info("Waiting for clipboard change"); + await clipboardChanged; + + let result = await helper.getImageSizeAndColorFromClipboard(); + result.zoom = zoom; + result.devicePixelRatio = window.devicePixelRatio; + result.contentDevicePixelRatio = await getContentDevicePixelRatio( + browser + ); + + info("result: " + JSON.stringify(result, null, 2)); + + let expected = Math.floor( + 50 * (await getContentDevicePixelRatio(browser)) + ); + + 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.waitForStateChange("crosshairs"); + let state = await helper.getOverlayState(); + Assert.equal(state, "crosshairs", "The state is back to crosshairs"); + } + ); +}); + +/** + * This function drags an area and clicks the + * cancel button to cancel 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); + + helper.clickCancelButton(); + + await helper.waitForOverlayClosed(); + + ok(!(await helper.isOverlayInitialized()), "Overlay is not initialized"); + } + ); +}); + +/** + * 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: 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 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.waitForStateChange("resizing"); + let state = await helper.getOverlayState(); + Assert.equal(state, "resizing", "The overlay is in the resizing state"); + + mouse.move(10, 10); + + mouse.move(contentInfo.clientWidth - 10, contentInfo.clientHeight - 10); + + mouse.up( + Math.floor((endX - startX) / 2), + Math.floor((endY - startY) / 2) + ); + + let clipboardChanged = helper.waitForRawClipboardChange(); + + helper.clickCopyButton(); + + info("Waiting for clipboard change"); + await clipboardChanged; + + let result = await helper.getImageSizeAndColorFromClipboard(); + + info("result: " + JSON.stringify(result, null, 2)); + + let expected = Math.floor( + 490 * (await getContentDevicePixelRatio(browser)) + ); + + 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"); + + 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.waitForStateChange("resizing"); + let state = await helper.getOverlayState(); + Assert.equal(state, "resizing", "The overlay is in the resizing state"); + + mouse.move(x, 100); + mouse.up(x, 100); + + await helper.waitForStateChange("selected"); + state = await helper.getOverlayState(); + Assert.equal(state, "selected", "The overlay is in the selected state"); + + // drag bottom + mouse.down(x, 500); + + await helper.waitForStateChange("resizing"); + state = await helper.getOverlayState(); + Assert.equal(state, "resizing", "The overlay is in the resizing state"); + + mouse.move(x, 400); + mouse.up(x, 400); + + await helper.waitForStateChange("selected"); + state = await helper.getOverlayState(); + Assert.equal(state, "selected", "The overlay is in the selected state"); + + // drag right + let y = Math.floor((endY - startY) / 2); + mouse.down(500, y); + + await helper.waitForStateChange("resizing"); + state = await helper.getOverlayState(); + Assert.equal(state, "resizing", "The overlay is in the resizing state"); + + mouse.move(400, y); + mouse.up(400, y); + + await helper.waitForStateChange("selected"); + state = await helper.getOverlayState(); + Assert.equal(state, "selected", "The overlay is in the selected state"); + + // drag left + mouse.down(10, y); + + await helper.waitForStateChange("resizing"); + state = await helper.getOverlayState(); + Assert.equal(state, "resizing", "The overlay is in the resizing state"); + + mouse.move(100, y); + mouse.up(100, y); + + await helper.waitForStateChange("selected"); + state = await helper.getOverlayState(); + Assert.equal(state, "selected", "The overlay is in the selected state"); + + let clipboardChanged = helper.waitForRawClipboardChange(); + + helper.endX = 400; + helper.endY = 400; + + helper.clickCopyButton(); + + info("Waiting for clipboard change"); + await clipboardChanged; + + let result = await helper.getImageSizeAndColorFromClipboard(); + + info("result: " + JSON.stringify(result, null, 2)); + + let expected = Math.floor( + 300 * (await getContentDevicePixelRatio(browser)) + ); + + 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"); + + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + + await helper.dragOverlay(10, 10, 500, 500); + + // drag topright + mouse.down(500, 10); + + await helper.waitForStateChange("resizing"); + let state = await helper.getOverlayState(); + Assert.equal(state, "resizing", "The overlay is in the resizing state"); + + mouse.move(450, 50); + mouse.up(450, 50); + + await helper.waitForStateChange("selected"); + state = await helper.getOverlayState(); + Assert.equal(state, "selected", "The overlay is in the selected state"); + + // drag bottomright + mouse.down(450, 500); + + await helper.waitForStateChange("resizing"); + state = await helper.getOverlayState(); + Assert.equal(state, "resizing", "The overlay is in the resizing state"); + + mouse.move(400, 450); + mouse.up(400, 450); + + await helper.waitForStateChange("selected"); + state = await helper.getOverlayState(); + Assert.equal(state, "selected", "The overlay is in the selected state"); + + // drag bottomleft + mouse.down(10, 450); + + await helper.waitForStateChange("resizing"); + state = await helper.getOverlayState(); + Assert.equal(state, "resizing", "The overlay is in the resizing state"); + + mouse.move(50, 400); + mouse.up(50, 400); + + await helper.waitForStateChange("selected"); + state = await helper.getOverlayState(); + Assert.equal(state, "selected", "The overlay is in the selected state"); + + // drag topleft + mouse.down(50, 50); + + await helper.waitForStateChange("resizing"); + state = await helper.getOverlayState(); + Assert.equal(state, "resizing", "The overlay is in the resizing state"); + + mouse.move(100, 100); + mouse.up(100, 100); + + await helper.waitForStateChange("selected"); + state = await helper.getOverlayState(); + Assert.equal(state, "selected", "The overlay is in the selected state"); + + let clipboardChanged = helper.waitForRawClipboardChange(); + + helper.endX = 400; + helper.endY = 400; + + helper.clickCopyButton(); + + info("Waiting for clipboard change"); + await clipboardChanged; + + let result = await helper.getImageSizeAndColorFromClipboard(); + + info("result: " + JSON.stringify(result, null, 2)); + + let expected = Math.floor( + 300 * (await getContentDevicePixelRatio(browser)) + ); + + 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` + ); + } + ); +}); 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..5e9937f8e1 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_focus_test.js @@ -0,0 +1,44 @@ +/* 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 testPanelFocused() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: TEST_PAGE, + }, + async browser => { + await clearAllTelemetryEvents(); + let helper = new ScreenshotsHelper(browser); + + helper.triggerUIFromToolbar(); + + await helper.waitForOverlay(); + + let screenshotsButtons = gBrowser.selectedBrowser.ownerDocument + .querySelector("#screenshotsPagePanel") + .querySelector("screenshots-buttons").shadowRoot; + + let focusedElement = screenshotsButtons.querySelector(".visible-page"); + + is( + focusedElement, + screenshotsButtons.activeElement, + "Visible button is focused" + ); + + EventUtils.synthesizeKey("KEY_Escape"); + + await helper.waitForOverlayClosed(); + + await assertScreenshotsEvents(SCREENSHOTS_EVENTS); + } + ); +}); 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..b66a4d2b57 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_overlay_panel_sync.js @@ -0,0 +1,82 @@ +/* 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.waitForStateChange("selected"); + let state = await helper.getOverlayState(); + is(state, "selected", "The overlay is in the selected state"); + + helper.assertPanelNotVisible(); + + mouse.click(600, 600); + + await helper.waitForStateChange("crosshairs"); + state = await helper.getOverlayState(); + is(state, "crosshairs", "The overlay is in the crosshairs state"); + + 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.waitForStateChange("selected"); + state = await helper.getOverlayState(); + is(state, "selected", "The overlay is in the selected state"); + + await BrowserTestUtils.switchTab(gBrowser, tab1); + await BrowserTestUtils.switchTab(gBrowser, screenshotsTab); + + Assert.ok(await helper.isOverlayInitialized(), "Overlay is open"); + helper.assertPanelNotVisible(); + state = await helper.getOverlayState(); + is(state, "selected", "The overlay is in the selected state"); + + 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..e228e304b8 --- /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: TEST_PAGE, + }, + async browser => { + await clearAllTelemetryEvents(); + await SpecialPowers.spawn(browser, [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..f31b9cc71d --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_short_page_test.js @@ -0,0 +1,148 @@ +/* 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 dimensions = await helper.getSelectionLayerDimensions(); + 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" + ); + } + ); +}); + +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; + + window.resizeTo(SMALL_WINDOW_SIZE, SMALL_WINDOW_SIZE); + await TestUtils.waitForCondition(() => { + info( + `Got ${window.outerWidth}x${ + window.outerHeight + }. Expecting ${SMALL_WINDOW_SIZE}. ${ + window.outerHeight === SMALL_WINDOW_SIZE + } ${window.outerWidth === SMALL_WINDOW_SIZE}` + ); + return ( + window.outerHeight === SMALL_WINDOW_SIZE && + window.outerWidth === SMALL_WINDOW_SIZE + ); + }, "Waiting for window to resize"); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + await helper.dragOverlay(10, 10, 100, 100); + + let dimensions = await helper.getSelectionLayerDimensions(); + let oldWidth = dimensions.scrollWidth; + let oldHeight = dimensions.scrollHeight; + + window.resizeTo(BIG_WINDOW_SIZE, BIG_WINDOW_SIZE); + await TestUtils.waitForCondition( + () => + window.outerHeight === BIG_WINDOW_SIZE && + window.outerWidth === BIG_WINDOW_SIZE, + "Waiting for window to resize" + ); + await helper.waitForSelectionLayerDimensionChange(oldWidth, oldHeight); + + contentInfo = await helper.getContentDimensions(); + dimensions = await helper.getSelectionLayerDimensions(); + 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; + + window.resizeTo(SMALL_WINDOW_SIZE, SMALL_WINDOW_SIZE); + await TestUtils.waitForCondition( + () => + window.outerHeight === SMALL_WINDOW_SIZE && + window.outerWidth === SMALL_WINDOW_SIZE, + "Waiting for window to resize" + ); + await helper.waitForSelectionLayerDimensionChange(oldWidth, oldHeight); + + contentInfo = await helper.getContentDimensions(); + dimensions = await helper.getSelectionLayerDimensions(); + 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" + ); + + ok( + dimensions.scrollWidth < BIG_WINDOW_SIZE, + "Screenshots overlay is smaller than the big window width" + ); + ok( + dimensions.scrollHeight < BIG_WINDOW_SIZE, + "Screenshots overlay is smaller than the big window height" + ); + + window.resizeTo(originalWindowWidth, originalWindowHeight); + await TestUtils.waitForCondition( + () => + window.outerHeight === originalWindowHeight && + window.outerWidth === originalWindowWidth, + "Waiting for window to resize" + ); + } + ); +}); 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..74c2335beb --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_telemetry_tests.js @@ -0,0 +1,309 @@ +/* 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: "started", object: "overlay_retry" }, + { category: "screenshots", method: "selected", object: "element" }, +]; + +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: TEST_PAGE, + }, + async browser => { + await clearAllTelemetryEvents(); + let helper = new ScreenshotsHelper(browser); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlayClosed(); + + EventUtils.synthesizeKey("s", { shiftKey: true, accelKey: true }); + await helper.waitForOverlay(); + + EventUtils.synthesizeKey("s", { shiftKey: true, accelKey: true }); + await helper.waitForOverlayClosed(); + + 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; + + contextMenu.activateItem( + contextMenu.querySelector("#context-take-screenshot") + ); + await helper.waitForOverlayClosed(); + + 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"); + EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); + EventUtils.synthesizeKey("KEY_Enter", {}, window); + await helper.waitForOverlayClosed(); + + 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"); + cancelButton.click(); + + await helper.waitForOverlayClosed(); + + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + await helper.dragOverlay(50, 50, 300, 300); + helper.clickCancelButton(); + + await helper.waitForOverlayClosed(); + + 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); + + 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 copyButton = dialog._frame.contentDocument.getElementById("copy"); + + let clipboardChanged = helper.waitForRawClipboardChange(); + + // click copy button on dialog box + copyButton.click(); + + info("Waiting for clipboard change"); + await clipboardChanged; + helper.triggerUIFromToolbar(); + await helper.waitForOverlay(); + + await helper.dragOverlay(50, 50, 300, 300); + + clipboardChanged = helper.waitForRawClipboardChange(); + + helper.clickCopyButton(); + + info("Waiting for clipboard change"); + await clipboardChanged; + + 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); + + mouse.click(11, 11); + await helper.waitForStateChange("crosshairs"); + + await helper.clickTestPageElement(); + + await assertScreenshotsEvents(CONTENT_EVENTS, "content"); + } + ); +}); 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..3f7af492fa --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_test_downloads.js @@ -0,0 +1,185 @@ +/* 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); + + 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"); + + // 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 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(); + + 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(); + } + ); +}); 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..c66b4c77d9 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_test_full_page.js @@ -0,0 +1,173 @@ +/* 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"); + + // 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(); + + // click copy button on dialog box + copyButton.click(); + + info("Waiting for clipboard change"); + await clipboardChanged; + + let result = await helper.getImageSizeAndColorFromClipboard(); + info("result: " + JSON.stringify(result, null, 2)); + info("contentInfo: " + JSON.stringify(contentInfo, null, 2)); + + Assert.equal( + contentInfo.scrollWidth, + result.width, + "Widths should be equal" + ); + + Assert.equal( + contentInfo.scrollHeight, + result.height, + "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"); + + // 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(); + + // click copy button on dialog box + copyButton.click(); + + info("Waiting for clipboard change"); + await clipboardChanged; + + let result = await helper.getImageSizeAndColorFromClipboard(); + info("result: " + JSON.stringify(result, null, 2)); + info("contentInfo: " + JSON.stringify(contentInfo, null, 2)); + + Assert.equal( + contentInfo.scrollWidth, + result.width, + "Widths should be equal" + ); + + Assert.equal( + contentInfo.scrollHeight, + result.height, + "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..4b65110c50 --- /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.is_hidden(panel); + } + ); + + await BrowserTestUtils.crashFrame(browser); + + await waitForPanelHide; + ok( + BrowserTestUtils.is_hidden(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.is_hidden(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..1c1bac32db --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_test_screenshot_too_big.js @@ -0,0 +1,86 @@ +/* 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 32767"); + is( + rect.height, + Math.floor(MAX_CAPTURE_AREA / MAX_CAPTURE_DIMENSION), + "The height is 124925329 / 32767" + ); + + rect.width = 40000; + rect.hegith = 1; + + ScreenshotsUtils.cropScreenshotRectIfNeeded(rect); + + is( + rect.width, + MAX_CAPTURE_DIMENSION, + "The width was cropped to the max capture dimension (32767)." + ); + + rect.width = 1; + rect.height = 40000; + + ScreenshotsUtils.cropScreenshotRectIfNeeded(rect); + + is( + rect.height, + MAX_CAPTURE_DIMENSION, + "The height was cropped to the max capture dimension (32767)." + ); + + rect.width = 12345; + rect.height = 12345; + + ScreenshotsUtils.cropScreenshotRectIfNeeded(rect); + + is(rect.width, 12345, "The width was not cropped"); + is( + rect.height, + Math.floor(MAX_CAPTURE_AREA / 12345), + "The height was cropped to 10119" + ); + + 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..1bd1933799 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_test_toggle_pref.js @@ -0,0 +1,287 @@ +/* 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", +}); +XPCOMUtils.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: 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.is_visible(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.is_visible(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.is_visible(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.is_visible(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: 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: 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(); +}); 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..26d28566c8 --- /dev/null +++ b/browser/components/screenshots/tests/browser/browser_screenshots_test_visible.js @@ -0,0 +1,345 @@ +/* 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); + + // 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(); + + // click copy button on dialog box + copyButton.click(); + + info("Waiting for clipboard change"); + await clipboardChanged; + + let result = await helper.getImageSizeAndColorFromClipboard(); + info("result: " + JSON.stringify(result, null, 2)); + info("contentInfo: " + JSON.stringify(contentInfo, null, 2)); + + let expectedWidth = Math.floor( + devicePixelRatio * contentInfo.clientWidth + ); + Assert.equal(result.width, expectedWidth, "Widths should be equal"); + + let expectedHeight = Math.floor( + devicePixelRatio * contentInfo.clientHeight + ); + 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_visibleScreenshotScrolled() { + 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"); + + // 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(); + + // click copy button on dialog box + copyButton.click(); + + info("Waiting for clipboard change"); + await clipboardChanged; + + let result = await helper.getImageSizeAndColorFromClipboard(); + + info("result: " + JSON.stringify(result, null, 2)); + info("contentInfo: " + JSON.stringify(contentInfo, null, 2)); + + let expectedWidth = Math.floor( + devicePixelRatio * contentInfo.clientWidth + ); + Assert.equal(result.width, expectedWidth, "Widths should be equal"); + + let expectedHeight = Math.floor( + devicePixelRatio * contentInfo.clientHeight + ); + 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_visibleScreenshotScrolled() { + 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"); + + // 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(); + + // click copy button on dialog box + copyButton.click(); + + info("Waiting for clipboard change"); + await clipboardChanged; + + let result = await helper.getImageSizeAndColorFromClipboard(); + + info("result: " + JSON.stringify(result, null, 2)); + info("contentInfo: " + JSON.stringify(contentInfo, null, 2)); + + let expectedWidth = Math.floor( + devicePixelRatio * contentInfo.clientWidth + ); + Assert.equal(result.width, expectedWidth, "Widths should be equal"); + + let expectedHeight = Math.floor( + devicePixelRatio * contentInfo.clientHeight + ); + 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_visibleScreenshotScrolled() { + 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"); + + // 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(); + + // click copy button on dialog box + copyButton.click(); + + info("Waiting for clipboard change"); + await clipboardChanged; + + let result = await helper.getImageSizeAndColorFromClipboard(); + + info("result: " + JSON.stringify(result, null, 2)); + info("contentInfo: " + JSON.stringify(contentInfo, null, 2)); + + let expectedWidth = Math.floor( + devicePixelRatio * contentInfo.clientWidth + ); + Assert.equal(result.width, expectedWidth, "Widths should be equal"); + + let expectedHeight = Math.floor( + devicePixelRatio * contentInfo.clientHeight + ); + 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/head.js b/browser/components/screenshots/tests/browser/head.js new file mode 100644 index 0000000000..5ab054a70c --- /dev/null +++ b/browser/components/screenshots/tests/browser/head.js @@ -0,0 +1,681 @@ +/* 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", + "http://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 MAX_CAPTURE_DIMENSION = 32767; +const MAX_CAPTURE_AREA = 124925329; + +const gScreenshotUISelectors = { + panelButtons: "#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, selector = ":root") { + if (name === "click") { + this.down(x, y); + this.up(x, y); + } else { + await safeSynthesizeMouseEventInContentPage(selector, x, y, { + type: "mouse" + name, + }); + } + }, + } + ), +}; + +const { mouse } = MouseEvents; + +class ScreenshotsHelper { + constructor(browser) { + this.browser = browser; + this.selector = gScreenshotUISelectors; + } + + get toolbarButton() { + return document.getElementById("screenshot-button"); + } + + /** + * Click the screenshots button in the toolbar + */ + triggerUIFromToolbar() { + let button = this.toolbarButton; + ok( + BrowserTestUtils.is_visible(button), + "The screenshot toolbar button is visible" + ); + button.click(); + } + + async waitForPanel() { + let panel = this.browser.ownerDocument.querySelector( + "#screenshotsPagePanel" + ); + await BrowserTestUtils.waitForCondition(async () => { + if (!panel) { + panel = this.browser.ownerDocument.querySelector( + "#screenshotsPagePanel" + ); + } + return panel?.state === "open" && BrowserTestUtils.is_visible(panel); + }); + return panel; + } + + async waitForOverlay() { + const panel = await this.waitForPanel(); + ok(BrowserTestUtils.is_visible(panel), "Panel buttons are visible"); + + await BrowserTestUtils.waitForCondition(async () => { + let init = await this.isOverlayInitialized(); + return init; + }); + info("Overlay is visible"); + } + + async waitForOverlayClosed() { + let panel = this.browser.ownerDocument.querySelector( + "#screenshotsPagePanel" + ); + if (!panel) { + panel = await this.waitForPanel(); + } + await BrowserTestUtils.waitForMutationCondition( + panel, + { attributes: true }, + () => { + return BrowserTestUtils.is_hidden(panel); + } + ); + ok(BrowserTestUtils.is_hidden(panel), "Panel buttons are hidden"); + + 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; + }); + } + + async getOverlayState() { + return ContentTask.spawn(this.browser, null, async () => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + return screenshotsChild._overlay.stateHandler.getState(); + }); + } + + async waitForStateChange(newState) { + await BrowserTestUtils.waitForCondition(async () => { + let state = await this.getOverlayState(); + return state === newState; + }, `Waiting for state change to ${newState}`); + } + + async getHoverElementRect() { + return ContentTask.spawn(this.browser, null, async () => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + return screenshotsChild._overlay.stateHandler.getHoverElementBoxRect(); + }); + } + + async waitForHoverElementRect() { + return TestUtils.waitForCondition(async () => { + let rect = await this.getHoverElementRect(); + return rect; + }); + } + + async waitForSelectionBoxSizeChange(currentWidth) { + await ContentTask.spawn( + this.browser, + [currentWidth], + async ([currWidth]) => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + + let dimensions = + screenshotsChild._overlay.screenshotsContainer.getSelectionLayerDimensions(); + // return dimensions.boxWidth; + await ContentTaskUtils.waitForCondition(() => { + dimensions = + screenshotsChild._overlay.screenshotsContainer.getSelectionLayerDimensions(); + return dimensions.boxWidth !== 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) { + await this.waitForStateChange("crosshairs"); + let state = await this.getOverlayState(); + Assert.equal(state, "crosshairs", "The overlay is in the crosshairs state"); + + mouse.down(startX, startY); + + await this.waitForStateChange("draggingReady"); + state = await this.getOverlayState(); + Assert.equal( + state, + "draggingReady", + "The overlay is in the draggingReady state" + ); + + mouse.move(endX, endY); + + await this.waitForStateChange("dragging"); + state = await this.getOverlayState(); + Assert.equal(state, "dragging", "The overlay is in the dragging state"); + + mouse.up(endX, endY); + + await this.waitForStateChange("selected"); + state = await this.getOverlayState(); + Assert.equal(state, "selected", "The overlay is in the selected state"); + + this.endX = endX; + this.endY = endY; + } + + async scrollContentWindow(x, y) { + let promise = BrowserTestUtils.waitForContentEvent(this.browser, "scroll"); + await ContentTask.spawn(this.browser, [x, y], async ([xPos, yPos]) => { + content.window.scroll(xPos, yPos); + + await ContentTaskUtils.waitForCondition(() => { + return ( + content.window.scrollX === xPos && content.window.scrollY === yPos + ); + }, `Waiting for window to scroll to ${xPos}, ${yPos}`); + }); + await promise; + } + + getWindowPosition() { + return ContentTask.spawn(this.browser, [], () => { + return { + scrollX: content.window.scrollX, + scrollY: content.window.scrollY, + }; + }); + } + + 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}`); + }); + } + + clickDownloadButton() { + // Click the download button with last x and y position from dragOverlay. + // The middle of the copy button is last X - 70 and last Y + 36. + // Ex. 500, 500 would be 530, 536 + mouse.click(this.endX - 70, this.endY + 36); + } + + clickCopyButton(overrideX = null, overrideY = null) { + // Click the copy button with last x and y position from dragOverlay. + // The middle of the copy button is last X - 183 and last Y + 36. + // Ex. 500, 500 would be 317, 536 + if (overrideX && overrideY) { + mouse.click(overrideX - 183, overrideY + 36); + } else { + mouse.click(this.endX - 183, this.endY + 36); + } + } + + clickCancelButton() { + // Click the cancel button with last x and y position from dragOverlay. + // The middle of the copy button is last X - 259 and last Y + 36. + // Ex. 500, 500 would be 241, 536 + mouse.click(this.endX - 259, this.endY + 36); + } + + async clickTestPageElement() { + let rect = await ContentTask.spawn(this.browser, [], async () => { + let ele = content.document.getElementById("testPageElement"); + return ele.getBoundingClientRect(); + }); + + let x = Math.floor(rect.x + rect.width / 2); + let y = Math.floor(rect.y + rect.height / 2); + + mouse.move(x, y); + await this.waitForHoverElementRect(); + mouse.down(x, y); + await this.waitForStateChange("draggingReady"); + mouse.up(x, y); + await this.waitForStateChange("selected"); + } + + async zoomBrowser(zoom) { + 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); + }); + } + + /** + * 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() { + let panel = this.browser.ownerDocument.querySelector( + "#screenshotsPagePanel" + ); + Assert.ok( + BrowserTestUtils.is_visible(panel), + "Screenshots panel is visible" + ); + } + + assertPanelNotVisible() { + let panel = this.browser.ownerDocument.querySelector( + "#screenshotsPagePanel" + ); + Assert.ok( + BrowserTestUtils.is_hidden(panel), + "Screenshots panel is not visible" + ); + } + + /** + * Copied from screenshots extension + * Returns a promise that resolves when the clipboard data has changed + * Otherwise rejects + */ + waitForRawClipboardChange() { + const initialClipboardData = Date.now().toString(); + SpecialPowers.clipboardCopyString(initialClipboardData); + + let promiseChanged = TestUtils.waitForCondition(() => { + let data; + try { + data = getRawClipboardData("image/png"); + } catch (e) { + console.log("Failed to get image/png clipboard data:", e); + return false; + } + return data && initialClipboardData !== data; + }); + return promiseChanged; + } + + /** + * 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 + */ + getContentDimensions() { + return SpecialPowers.spawn(this.browser, [], async function () { + let { innerWidth, innerHeight, scrollMaxX, scrollMaxY } = 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, + }; + }); + } + + getSelectionLayerDimensions() { + return ContentTask.spawn(this.browser, null, async () => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + Assert.ok(screenshotsChild._overlay._initialized, "The overlay exists"); + + return screenshotsChild._overlay.screenshotsContainer.getSelectionLayerDimensions(); + }); + } + + async waitForSelectionLayerDimensionChange(oldWidth, oldHeight) { + await ContentTask.spawn( + this.browser, + [oldWidth, oldHeight], + async ([prevWidth, prevHeight]) => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + + await ContentTaskUtils.waitForCondition(() => { + let dimensions = + screenshotsChild._overlay.screenshotsContainer.getSelectionLayerDimensions(); + info( + `old height: ${prevHeight}. new height: ${dimensions.scrollHeight}.\nold width: ${prevWidth}. new width: ${dimensions.scrollWidth}` + ); + return ( + dimensions.scrollHeight !== prevHeight && + dimensions.scrollWidth !== prevWidth + ); + }, "Wait for selection box width change"); + } + ); + } + + getSelectionBoxDimensions() { + return ContentTask.spawn(this.browser, null, async () => { + let screenshotsChild = content.windowGlobalChild.getActor( + "ScreenshotsComponent" + ); + Assert.ok(screenshotsChild._overlay._initialized, "The overlay exists"); + + return screenshotsChild._overlay.screenshotsContainer.getSelectionLayerBoxDimensions(); + }); + } + + /** + * Clicks an element on the screen + * @param eleSel The selector for the element to click + */ + async clickUIElement(eleSel) { + await SpecialPowers.spawn( + this.browser, + [eleSel], + async function (eleSelector) { + info( + `in clickScreenshotsUIElement content function, eleSelector: ${eleSelector}` + ); + const EventUtils = ContentTaskUtils.getEventUtils(content); + let ele = content.document.querySelector(eleSelector); + info(`Found the thing to click: ${eleSelector}: ${!!ele}`); + + EventUtils.synthesizeMouseAtCenter(ele, {}); + // wait a frame for the screenshots UI to finish any init + await new content.Promise(res => content.requestAnimationFrame(res)); + } + ); + } + + /** + * 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); + ok(image, "screenshot data exists on the clipboard"); + + // 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); + is( + binaryStream.readArrayBuffer(available, buffer), + available, + "Read expected amount of data" + ); + + // 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); + 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 + ); + 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") { + 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: true }, + { process } + ); +} 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/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.html b/browser/components/screenshots/tests/browser/test-page.html new file mode 100644 index 0000000000..4a3f7b0304 --- /dev/null +++ b/browser/components/screenshots/tests/browser/test-page.html @@ -0,0 +1,10 @@ +<!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> +</body> +</html> |