/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; const TESTROOT = "https://example.com/browser/" + RELATIVE_DIR; const MockFilePicker = SpecialPowers.MockFilePicker; add_setup(async function () { MockFilePicker.init(window.browsingContext); MockFilePicker.returnValue = MockFilePicker.returnOK; registerCleanupFunction(function () { MockFilePicker.cleanup(); }); }); async function openContextMenuAt(browser, x, y) { const contextMenu = document.getElementById("contentAreaContextMenu"); const contextMenuShownPromise = BrowserTestUtils.waitForEvent( contextMenu, "popupshown" ); info("Opening context menu at coordinates: " + x + ", " + y); await BrowserTestUtils.synthesizeMouseAtPoint( x, y, { type: "contextmenu", button: 2 }, browser ); await contextMenuShownPromise; return contextMenu; } async function getPagesContextMenuItems( browser, box, waitForStatesChanged = false ) { info(`Opening context menu at the center of box: ${JSON.stringify(box)}`); return new Promise(resolve => { setTimeout(async () => { const { x, y, width, height } = box; const ids = [ "context-pdfjs-copy-page", "context-pdfjs-cut-page", "context-pdfjs-delete-page", "context-pdfjs-save-page", "context-sep-pdfjs-save-page", ]; let statesChangedPromise; if (waitForStatesChanged) { statesChangedPromise = BrowserTestUtils.waitForContentEvent( browser, "editingstateschanged", false, null, true ); } await openContextMenuAt(browser, x + width / 2, y + height / 2); if (waitForStatesChanged) { await statesChangedPromise; } const doc = browser.ownerDocument; const results = new Map(); for (const id of ids) { results.set(id, doc.getElementById(id) || null); } resolve(results); }, 0); }); } async function hideContextMenu(browser) { info("Hiding context menu"); await new Promise(resolve => setTimeout(async () => { const doc = browser.ownerDocument; const contextMenu = doc.getElementById("contentAreaContextMenu"); const popupHiddenPromise = BrowserTestUtils.waitForEvent( contextMenu, "popuphidden" ); contextMenu.hidePopup(); await popupHiddenPromise; resolve(); }, 0) ); } function assertMenuitems(menuitems, expected) { Assert.deepEqual( [...menuitems.values()] .filter( elmt => !elmt.id.includes("-sep-") && !elmt.hidden && [null, "false"].includes(elmt.getAttribute("disabled")) ) .map(elmt => elmt.id), expected ); } async function clickOnItem(browser, items, entry) { info(`Clicking on menu item ${entry}`); const editingPromise = BrowserTestUtils.waitForContentEvent( browser, "editingaction", false, null, true ); const contextMenu = document.getElementById("contentAreaContextMenu"); contextMenu.activateItem(items.get(entry)); await editingPromise; } async function getThumbnailBox(browser, index) { info(`Getting thumbnail box for page index ${index}`); return SpecialPowers.spawn(browser, [index], async function (idx) { const { ContentTaskUtils } = ChromeUtils.importESModule( "resource://testing-common/ContentTaskUtils.sys.mjs" ); await ContentTaskUtils.waitForCondition( () => !!content.document.querySelectorAll("#thumbnailsView .thumbnail")[idx], "Thumbnail must be present" ); const el = content.document.querySelectorAll("#thumbnailsView .thumbnail")[ idx ]; const { x, y, width, height } = el.getBoundingClientRect(); return { x, y, width, height }; }); } async function selectPage(browser, index) { info(`Selecting page index ${index}`); await SpecialPowers.spawn(browser, [index], async function (idx) { const { ContentTaskUtils } = ChromeUtils.importESModule( "resource://testing-common/ContentTaskUtils.sys.mjs" ); const { document } = content; await ContentTaskUtils.waitForCondition( () => !!document.querySelectorAll( "#thumbnailsView .thumbnail input[type=checkbox]" )[idx], "Checkbox must be present" ); const checkbox = document.querySelectorAll( "#thumbnailsView .thumbnail input[type=checkbox]" )[idx]; if (!checkbox.checked) { checkbox.click(); } await ContentTaskUtils.waitForCondition( () => checkbox.checked, "Checkbox must be checked" ); }); } add_task(async function test_pages_context_menu() { makePDFJSHandler(); await BrowserTestUtils.withNewTab( { gBrowser, url: "about:blank" }, async function (browser) { await SpecialPowers.pushPrefEnv({ set: [["pdfjs.enableSplitMerge", true]], }); await waitForPdfJSAllLayers( browser, TESTROOT + "file_pdfjs_numbered_pages.pdf", [ [ "annotationEditorLayer", "annotationLayer", "textLayer", "canvasWrapper", ], [ "annotationEditorLayer", "annotationLayer", "textLayer", "canvasWrapper", ], ] ); await waitForSelector( browser, "#viewerContainer", "Viewer container must be present" ); // Without any pages selected, no pages context menu items should be visible. const viewerBox = await SpecialPowers.spawn(browser, [], async () => { const el = content.document.querySelector("#viewerContainer"); const { x, y, width, height } = el.getBoundingClientRect(); return { x, y, width, height }; }); let menuitems = await getPagesContextMenuItems(browser, viewerBox); Assert.ok( [...menuitems.values()].every(elmt => elmt.hidden), "No visible pages menuitem when no pages are selected" ); await hideContextMenu(browser); // Open the sidebar (thumbnails view). await click(browser, "#viewsManagerToggleButton"); await waitForSelector( browser, "#thumbnailsView .thumbnail input[type=checkbox]", "Thumbnails with checkboxes must be visible" ); // Select the first page. await selectPage(browser, 0); // Pages context menu items must be visible when a page is selected. let thumbnailBox = await getThumbnailBox(browser, 0); menuitems = await getPagesContextMenuItems(browser, thumbnailBox, true); assertMenuitems(menuitems, [ "context-pdfjs-copy-page", "context-pdfjs-cut-page", "context-pdfjs-delete-page", "context-pdfjs-save-page", ]); // Copy the page: paste buttons must appear in the thumbnails view. let pagesEditedPromise = BrowserTestUtils.waitForContentEvent( browser, "pagesedited", false, null, true ); await clickOnItem(browser, menuitems, "context-pdfjs-copy-page"); await pagesEditedPromise; Assert.greater( await countElements(browser, ".thumbnailPasteButton"), 0, "Paste buttons must appear after copy" ); await clickOn(browser, "#viewsManagerStatusUndoButton"); // Select the first page again (checkboxes were cleared by the copy). await selectPage(browser, 0); // Delete the page: the thumbnail count must decrease. const thumbnailCount = await countElements( browser, "#thumbnailsView .thumbnail" ); thumbnailBox = await getThumbnailBox(browser, 0); menuitems = await getPagesContextMenuItems(browser, thumbnailBox, true); pagesEditedPromise = BrowserTestUtils.waitForContentEvent( browser, "pagesedited", false, null, true ); await clickOnItem(browser, menuitems, "context-pdfjs-delete-page"); await pagesEditedPromise; await BrowserTestUtils.waitForCondition( async () => (await countElements(browser, "#thumbnailsView .thumbnail")) === thumbnailCount - 1, "One thumbnail must have been removed after delete" ); // Select the first page and cut it: count must decrease and paste buttons appear. await selectPage(browser, 0); const countAfterDelete = await countElements( browser, "#thumbnailsView .thumbnail" ); thumbnailBox = await getThumbnailBox(browser, 0); menuitems = await getPagesContextMenuItems(browser, thumbnailBox, true); const cutEditedPromise = BrowserTestUtils.waitForContentEvent( browser, "pagesedited", false, null, true ); await clickOnItem(browser, menuitems, "context-pdfjs-cut-page"); await cutEditedPromise; await BrowserTestUtils.waitForCondition( async () => (await countElements(browser, "#thumbnailsView .thumbnail")) === countAfterDelete - 1, "One thumbnail must have been removed after cut" ); Assert.greater( await countElements(browser, ".thumbnailPasteButton"), 0, "Paste buttons must appear after cut" ); // Select the first page and save: saveextractedpages event must fire. await selectPage(browser, 0); thumbnailBox = await getThumbnailBox(browser, 0); menuitems = await getPagesContextMenuItems(browser, thumbnailBox, true); const savePromise = BrowserTestUtils.waitForContentEvent( browser, "saveextractedpages", false, null, true ); await clickOnItem(browser, menuitems, "context-pdfjs-save-page"); await savePromise; await waitForPdfJSClose(browser); await SpecialPowers.popPrefEnv(); } ); });