/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ let gDownloadDir; // The test is long, and it's not worth splitting it since all the tests share // the same boilerplate code. requestLongerTimeout(4); SimpleTest.requestFlakyTimeout( "Giving a chance for possible last-pb-context-exited to occur (Bug 1329912)" ); /* Coverage for opening downloaded PDFs from download views */ const TestCases = [ { name: "Download panel, default click behavior", whichUI: "downloadPanel", itemSelector: "#downloadsListBox richlistitem .downloadMainArea", async userEvents(itemTarget, win) { EventUtils.synthesizeMouseAtCenter(itemTarget, {}, win); }, expected: { downloadCount: 1, newWindow: false, opensTab: true, tabSelected: true, }, }, { name: "Download panel, system viewer menu items prefd off", whichUI: "downloadPanel", itemSelector: "#downloadsListBox richlistitem .downloadMainArea", async userEvents(itemTarget, win) { EventUtils.synthesizeMouseAtCenter(itemTarget, {}, win); }, prefs: [ ["browser.download.openInSystemViewerContextMenuItem", false], ["browser.download.alwaysOpenInSystemViewerContextMenuItem", false], ], expected: { downloadCount: 1, newWindow: false, opensTab: true, tabSelected: true, useSystemMenuItemDisabled: true, alwaysMenuItemDisabled: true, }, }, { name: "Download panel, open from keyboard", whichUI: "downloadPanel", itemSelector: "#downloadsListBox richlistitem .downloadMainArea", async userEvents(itemTarget, win) { itemTarget.focus(); EventUtils.synthesizeKey("VK_RETURN", {}, win); }, expected: { downloadCount: 1, newWindow: false, opensTab: true, tabSelected: true, }, }, { name: "Download panel, open in new window", whichUI: "downloadPanel", itemSelector: "#downloadsListBox richlistitem .downloadMainArea", async userEvents(itemTarget, win) { EventUtils.synthesizeMouseAtCenter(itemTarget, { shiftKey: true }, win); }, expected: { downloadCount: 1, newWindow: true, opensTab: false, tabSelected: true, }, }, { name: "Download panel, open foreground tab", // duplicates the default behavior whichUI: "downloadPanel", itemSelector: "#downloadsListBox richlistitem .downloadMainArea", async userEvents(itemTarget, win) { EventUtils.synthesizeMouseAtCenter( itemTarget, { ctrlKey: true, metaKey: true }, win ); }, expected: { downloadCount: 1, newWindow: false, opensTab: true, tabSelected: true, }, }, { name: "Download panel, open background tab", whichUI: "downloadPanel", itemSelector: "#downloadsListBox richlistitem .downloadMainArea", async userEvents(itemTarget, win) { EventUtils.synthesizeMouseAtCenter( itemTarget, { ctrlKey: true, metaKey: true, shiftKey: true }, win ); }, expected: { downloadCount: 1, newWindow: false, opensTab: true, tabSelected: false, }, }, { name: "Library all downloads dialog, default click behavior", whichUI: "allDownloads", async userEvents(itemTarget, win) { // double click await triggerDblclickOn(itemTarget, {}, win); }, expected: { downloadCount: 1, newWindow: false, opensTab: true, tabSelected: true, }, }, { name: "Library all downloads dialog, system viewer menu items prefd off", whichUI: "allDownloads", async userEvents(itemTarget, win) { // double click await triggerDblclickOn(itemTarget, {}, win); }, prefs: [ ["browser.download.openInSystemViewerContextMenuItem", false], ["browser.download.alwaysOpenInSystemViewerContextMenuItem", false], ], expected: { downloadCount: 1, newWindow: false, opensTab: true, tabSelected: true, useSystemMenuItemDisabled: true, alwaysMenuItemDisabled: true, }, }, { name: "Library all downloads dialog, open from keyboard", whichUI: "allDownloads", async userEvents(itemTarget, win) { itemTarget.focus(); EventUtils.synthesizeKey("VK_RETURN", {}, win); }, expected: { downloadCount: 1, newWindow: false, opensTab: true, tabSelected: true, }, }, { name: "Library all downloads dialog, open in new window", whichUI: "allDownloads", async userEvents(itemTarget, win) { // double click await triggerDblclickOn(itemTarget, { shiftKey: true }, win); }, expected: { downloadCount: 1, newWindow: true, opensTab: false, tabSelected: true, }, }, { name: "Library all downloads dialog, open foreground tab", // duplicates default behavior whichUI: "allDownloads", async userEvents(itemTarget, win) { // double click await triggerDblclickOn( itemTarget, { ctrlKey: true, metaKey: true }, win ); }, expected: { downloadCount: 1, newWindow: false, opensTab: true, tabSelected: true, }, }, { name: "Library all downloads dialog, open background tab", whichUI: "allDownloads", async userEvents(itemTarget, win) { // double click await triggerDblclickOn( itemTarget, { ctrlKey: true, metaKey: true, shiftKey: true }, win ); }, expected: { downloadCount: 1, newWindow: false, opensTab: true, tabSelected: false, }, }, { name: "about:downloads, default click behavior", whichUI: "aboutDownloads", itemSelector: "#downloadsListBox richlistitem .downloadContainer", async userEvents(itemSelector, win) { let browser = win.gBrowser.selectedBrowser; is(browser.currentURI.spec, "about:downloads"); await contentTriggerDblclickOn(itemSelector, {}, browser); }, expected: { downloadCount: 1, newWindow: false, opensTab: true, tabSelected: true, }, }, { name: "about:downloads, system viewer menu items prefd off", whichUI: "aboutDownloads", itemSelector: "#downloadsListBox richlistitem .downloadContainer", async userEvents(itemSelector, win) { let browser = win.gBrowser.selectedBrowser; is(browser.currentURI.spec, "about:downloads"); await contentTriggerDblclickOn(itemSelector, {}, browser); }, prefs: [ ["browser.download.openInSystemViewerContextMenuItem", false], ["browser.download.alwaysOpenInSystemViewerContextMenuItem", false], ], expected: { downloadCount: 1, newWindow: false, opensTab: true, tabSelected: true, useSystemMenuItemDisabled: true, alwaysMenuItemDisabled: true, }, }, { name: "about:downloads, open in new window", whichUI: "aboutDownloads", itemSelector: "#downloadsListBox richlistitem .downloadContainer", async userEvents(itemSelector, win) { let browser = win.gBrowser.selectedBrowser; is(browser.currentURI.spec, "about:downloads"); await contentTriggerDblclickOn(itemSelector, { shiftKey: true }, browser); }, expected: { downloadCount: 1, newWindow: true, opensTab: false, tabSelected: true, }, }, { name: "about:downloads, open in foreground tab", whichUI: "aboutDownloads", itemSelector: "#downloadsListBox richlistitem .downloadContainer", async userEvents(itemSelector, win) { let browser = win.gBrowser.selectedBrowser; is(browser.currentURI.spec, "about:downloads"); await contentTriggerDblclickOn( itemSelector, { ctrlKey: true, metaKey: true }, browser ); }, expected: { downloadCount: 1, newWindow: false, opensTab: true, tabSelected: true, }, }, { name: "about:downloads, open in background tab", whichUI: "aboutDownloads", itemSelector: "#downloadsListBox richlistitem .downloadContainer", async userEvents(itemSelector, win) { let browser = win.gBrowser.selectedBrowser; is(browser.currentURI.spec, "about:downloads"); await contentTriggerDblclickOn( itemSelector, { ctrlKey: true, metaKey: true, shiftKey: true }, browser ); }, expected: { downloadCount: 1, newWindow: false, opensTab: true, tabSelected: false, }, }, { name: "Private download in about:downloads, opens in new private window", skip: true, // Bug 1641770 whichUI: "aboutDownloads", itemSelector: "#downloadsListBox richlistitem .downloadContainer", async userEvents(itemSelector, win) { let browser = win.gBrowser.selectedBrowser; is(browser.currentURI.spec, "about:downloads"); await contentTriggerDblclickOn(itemSelector, { shiftKey: true }, browser); }, isPrivate: true, expected: { downloadCount: 1, newWindow: true, opensTab: false, tabSelected: true, }, }, ]; function triggerDblclickOn(target, modifiers = {}, win) { let promise = BrowserTestUtils.waitForEvent(target, "dblclick"); EventUtils.synthesizeMouseAtCenter( target, Object.assign({ clickCount: 1 }, modifiers), win ); EventUtils.synthesizeMouseAtCenter( target, Object.assign({ clickCount: 2 }, modifiers), win ); return promise; } function contentTriggerDblclickOn(selector, eventModifiers = {}, browser) { return SpecialPowers.spawn( browser, [selector, eventModifiers], async function (itemSelector, modifiers) { const EventUtils = ContentTaskUtils.getEventUtils(content); let itemTarget = content.document.querySelector(itemSelector); ok(itemTarget, "Download item target exists"); let doubleClicked = ContentTaskUtils.waitForEvent(itemTarget, "dblclick"); // NOTE: we are using sendMouseEvent instead of synthesizeMouseAtCenter // here to prevent an unexpected timeout failure in devedition builds // due to the ContentTaskUtils.waitForEvent promise never been resolved. EventUtils.sendMouseEvent( { type: "dblclick", ...modifiers }, itemTarget, content ); info("Waiting for double-click content task"); await doubleClicked; } ); } async function verifyContextMenu(contextMenu, expected = {}) { info("verifyContextMenu with expected: " + JSON.stringify(expected, null, 2)); let alwaysMenuItem = contextMenu.querySelector( ".downloadAlwaysUseSystemDefaultMenuItem" ); let useSystemMenuItem = contextMenu.querySelector( ".downloadUseSystemDefaultMenuItem" ); info("Waiting for the context menu to show up"); await TestUtils.waitForCondition( () => BrowserTestUtils.is_visible(contextMenu), "The context menu is visible" ); await TestUtils.waitForTick(); info("Checking visibility of the system viewer menu items"); is( BrowserTestUtils.is_hidden(useSystemMenuItem), expected.useSystemMenuItemDisabled, `The 'Use system viewer' menu item was ${ expected.useSystemMenuItemDisabled ? "hidden" : "visible" }` ); is( BrowserTestUtils.is_hidden(alwaysMenuItem), expected.alwaysMenuItemDisabled, `The 'Use system viewer' menu item was ${ expected.alwaysMenuItemDisabled ? "hidden" : "visible" }` ); if (!expected.useSystemMenuItemDisabled && expected.alwaysChecked) { is( alwaysMenuItem.getAttribute("checked"), "true", "The 'Always...' menu item is checked" ); } else if (!expected.useSystemMenuItemDisabled) { ok( !alwaysMenuItem.hasAttribute("checked"), "The 'Always...' menu item not checked" ); } } async function addPDFDownload(itemData) { let startTimeMs = Date.now(); info("addPDFDownload with itemData: " + JSON.stringify(itemData, null, 2)); let downloadPathname = PathUtils.join(gDownloadDir, itemData.targetFilename); delete itemData.targetFilename; info("Creating saved download file at:" + downloadPathname); let pdfFile = await createDownloadedFile(downloadPathname, DATA_PDF); info("Created file at:" + pdfFile.path); let downloadList = await Downloads.getList( itemData.isPrivate ? Downloads.PRIVATE : Downloads.PUBLIC ); let download = { source: { url: "https://example.com/some.pdf", isPrivate: itemData.isPrivate, }, target: { path: pdfFile.path, }, succeeded: DownloadsCommon.DOWNLOAD_FINISHED, canceled: false, error: null, hasPartialData: false, hasBlockedData: itemData.hasBlockedData || false, startTime: new Date(startTimeMs++), ...itemData, }; if (itemData.errorObj) { download.errorObj = itemData.errorObj; } await downloadList.add(await Downloads.createDownload(download)); return download; } async function testSetup() { // remove download files, empty out collections let downloadList = await Downloads.getList(Downloads.ALL); let downloadCount = (await downloadList.getAll()).length; is(downloadCount, 0, "At the start of the test, there should be 0 downloads"); await task_resetState(); if (!gDownloadDir) { gDownloadDir = await setDownloadDir(); } info("Created download directory: " + gDownloadDir); } async function openDownloadPanel(expectedItemCount) { // Open the user interface and wait for data to be fully loaded. let richlistbox = document.getElementById("downloadsListBox"); await task_openPanel(); await TestUtils.waitForCondition( () => richlistbox.childElementCount == expectedItemCount && !richlistbox.getAttribute("disabled") ); } async function testOpenPDFPreview({ name, whichUI, downloadProperties, itemSelector, expected, prefs = [], userEvents, isPrivate, }) { info("Test case: " + name); // Wait for focus first await promiseFocus(); await testSetup(); if (prefs.length) { await SpecialPowers.pushPrefEnv({ set: prefs, }); } // Populate downloads database with the data required by this test. info("Adding download objects"); if (!downloadProperties) { downloadProperties = { targetFilename: "downloaded.pdf", }; } let download = await addPDFDownload({ ...downloadProperties, isPrivate, }); info("Got download pathname:" + download.target.path); is( !!download.source.isPrivate, !!isPrivate, `Added download is ${isPrivate ? "private" : "not private"} as expected` ); let downloadList = await Downloads.getList( isPrivate ? Downloads.PRIVATE : Downloads.PUBLIC ); let downloads = await downloadList.getAll(); is( downloads.length, expected.downloadCount, `${isPrivate ? "Private" : "Public"} list has expected ${ downloads.length } downloads` ); let pdfFileURI = NetUtil.newURI(new FileUtils.File(download.target.path)); info("pdfFileURI:" + pdfFileURI.spec); let uiWindow = window; let previewWindow = window; // we never want to unload the test browser by loading the file: URI into it await BrowserTestUtils.withNewTab("about:blank", async initialBrowser => { let previewTab; let previewHappened; if (expected.newWindow) { info( "previewHappened will wait for new browser window with url: " + pdfFileURI.spec ); // wait for a new browser window previewHappened = BrowserTestUtils.waitForNewWindow({ anyWindow: true, url: pdfFileURI.spec, }); } else if (expected.opensTab) { // wait for a tab to be opened info("previewHappened will wait for tab with URI:" + pdfFileURI.spec); previewHappened = BrowserTestUtils.waitForNewTab( gBrowser, pdfFileURI.spec, false, // dont wait for load true // any tab, not just the next one ); } else { info( "previewHappened will wait to load " + pdfFileURI.spec + " into the current tab" ); previewHappened = BrowserTestUtils.browserLoaded( initialBrowser, false, pdfFileURI.spec ); } let itemTarget; let contextMenu; switch (whichUI) { case "downloadPanel": info("Opening download panel"); await openDownloadPanel(expected.downloadCount); info("/Opening download panel"); itemTarget = document.querySelector(itemSelector); contextMenu = uiWindow.document.querySelector("#downloadsContextMenu"); break; case "allDownloads": // we'll be interacting with the library dialog uiWindow = await openLibrary("Downloads"); let listbox = uiWindow.document.getElementById("downloadsListBox"); ok(listbox, "download list box present"); // wait for the expected number of items in the view, // and for the first item to be visible && clickable await TestUtils.waitForCondition(() => { return ( listbox.itemChildren.length == expected.downloadCount && BrowserTestUtils.is_visible(listbox.itemChildren[0]) ); }); itemTarget = listbox.itemChildren[0]; contextMenu = uiWindow.document.querySelector("#downloadsContextMenu"); break; case "aboutDownloads": info("Preparing about:downloads browser window"); // Because of bug 1329912, we sometimes get a bogus last-pb-context-exited notification // which removes all the private downloads and about:downloads renders a empty list // we'll allow time for that to happen before loading about:downloads let pbExitedOrTimeout = isPrivate ? new Promise(resolve => { const topic = "last-pb-context-exited"; const ENOUGH_TIME = 1000; function observer() { info(`Bogus ${topic} observed`); done(); } function done() { clearTimeout(timerId); Services.obs.removeObserver(observer, topic); resolve(); } /* eslint-disable mozilla/no-arbitrary-setTimeout */ const timerId = setTimeout(done, ENOUGH_TIME); Services.obs.addObserver(observer, "last-pb-context-exited"); }) : Promise.resolve(); if (isPrivate) { uiWindow = await BrowserTestUtils.openNewBrowserWindow({ private: true, }); } info( "in aboutDownloads, initially there are tabs: " + uiWindow.gBrowser.tabs.length ); let browser = uiWindow.gBrowser.selectedBrowser; await pbExitedOrTimeout; info("Loading about:downloads"); let downloadsLoaded = BrowserTestUtils.waitForEvent( browser, "InitialDownloadsLoaded", true ); BrowserTestUtils.loadURIString(browser, "about:downloads"); await BrowserTestUtils.browserLoaded(browser); info("waiting for downloadsLoaded"); await downloadsLoaded; await ContentTask.spawn( browser, [expected.downloadCount], async function awaitListItems(expectedCount) { await ContentTaskUtils.waitForCondition( () => content.document.getElementById("downloadsListBox") .childElementCount == expectedCount, `Await ${expectedCount} download list items` ); } ); break; } if (contextMenu) { info("trigger the contextmenu"); await openContextMenu(itemTarget || itemSelector, uiWindow); info("context menu should be open, verify its menu items"); let expectedValues = { useSystemMenuItemDisabled: false, alwaysMenuItemDisabled: false, ...expected, }; await verifyContextMenu(contextMenu, expectedValues); contextMenu.hidePopup(); } else { todo(contextMenu, "No context menu checks for test: " + name); } info("Executing user events"); await userEvents(itemTarget || itemSelector, uiWindow); info("Waiting for previewHappened"); let results = await previewHappened; if (expected.newWindow) { previewWindow = results; info("New window expected, got previewWindow? " + previewWindow); } previewTab = previewWindow.gBrowser.tabs[previewWindow.gBrowser.tabs.length - 1]; ok(previewTab, "Got preview tab"); let isSelected = previewWindow.gBrowser.selectedTab == previewTab; if (expected.tabSelected) { ok(isSelected, "The preview tab was selected"); } else { ok(!isSelected, "The preview tab was opened in the background"); } is( previewTab.linkedBrowser.currentURI.spec, pdfFileURI.spec, "previewTab has the expected currentURI" ); is( PrivateBrowsingUtils.isBrowserPrivate(previewTab.linkedBrowser), !!isPrivate, `The preview tab was ${isPrivate ? "private" : "not private"} as expected` ); info("cleaning up"); if (whichUI == "downloadPanel") { DownloadsPanel.hidePanel(); } let lastPBContextExitedPromise = isPrivate ? TestUtils.topicObserved("last-pb-context-exited").then(() => TestUtils.waitForTick() ) : Promise.resolve(); info("Test opened a new UI window? " + (uiWindow !== window)); if (uiWindow !== window) { info("Closing uiWindow"); await BrowserTestUtils.closeWindow(uiWindow); } if (expected.newWindow) { // will also close the previewTab await BrowserTestUtils.closeWindow(previewWindow); } else { await BrowserTestUtils.removeTab(previewTab); } info("Waiting for lastPBContextExitedPromise"); await lastPBContextExitedPromise; }); await downloadList.removeFinished(); if (prefs.length) { await SpecialPowers.popPrefEnv(); } } // register the tests for (let testData of TestCases) { if (testData.skip) { info("Skipping test:" + testData.name); continue; } // use the 'name' property of each test case as the test function name // so we get useful logs let tmp = { async [testData.name]() { await testOpenPDFPreview(testData); }, }; add_task(tmp[testData.name]); }