From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../browser_downloads_panel_context_menu.js | 421 +++++++++++++++++++++ 1 file changed, 421 insertions(+) create mode 100644 browser/components/downloads/test/browser/browser_downloads_panel_context_menu.js (limited to 'browser/components/downloads/test/browser/browser_downloads_panel_context_menu.js') diff --git a/browser/components/downloads/test/browser/browser_downloads_panel_context_menu.js b/browser/components/downloads/test/browser/browser_downloads_panel_context_menu.js new file mode 100644 index 0000000000..6a85ba570b --- /dev/null +++ b/browser/components/downloads/test/browser/browser_downloads_panel_context_menu.js @@ -0,0 +1,421 @@ +/* + Coverage for context menu state for downloads in the Downloads Panel +*/ + +let gDownloadDir; +const TestFiles = {}; + +let ReferrerInfo = Components.Constructor( + "@mozilla.org/referrer-info;1", + "nsIReferrerInfo", + "init" +); + +// Load a new URI with a specific referrer. +let exampleRefInfo = new ReferrerInfo( + Ci.nsIReferrerInfo.EMPTY, + true, + Services.io.newURI("https://example.org") +); + +const MENU_ITEMS = { + pause: ".downloadPauseMenuItem", + resume: ".downloadResumeMenuItem", + unblock: '[command="downloadsCmd_unblock"]', + openInSystemViewer: '[command="downloadsCmd_openInSystemViewer"]', + alwaysOpenInSystemViewer: '[command="downloadsCmd_alwaysOpenInSystemViewer"]', + alwaysOpenSimilarFiles: '[command="downloadsCmd_alwaysOpenSimilarFiles"]', + show: '[command="downloadsCmd_show"]', + commandsSeparator: "menuseparator,.downloadCommandsSeparator", + openReferrer: ".downloadOpenReferrerMenuItem", + copyLocation: ".downloadCopyLocationMenuItem", + separator: "menuseparator", + deleteFile: ".downloadDeleteFileMenuItem", + delete: '[command="cmd_delete"]', + clearList: '[command="downloadsCmd_clearList"]', + clearDownloads: '[command="downloadsCmd_clearDownloads"]', +}; + +const TestCasesNewMimetypes = [ + { + name: "Completed txt download", + downloads: [ + { + state: DownloadsCommon.DOWNLOAD_FINISHED, + contentType: "text/plain", + target: {}, + source: { + referrerInfo: exampleRefInfo, + }, + }, + ], + expected: { + menu: [ + MENU_ITEMS.alwaysOpenSimilarFiles, + MENU_ITEMS.show, + MENU_ITEMS.commandsSeparator, + MENU_ITEMS.openReferrer, + MENU_ITEMS.copyLocation, + MENU_ITEMS.separator, + MENU_ITEMS.deleteFile, + MENU_ITEMS.delete, + MENU_ITEMS.clearList, + ], + }, + }, + { + name: "Canceled txt download", + downloads: [ + { + state: DownloadsCommon.DOWNLOAD_CANCELED, + contentType: "text/plain", + target: {}, + source: { + referrerInfo: exampleRefInfo, + }, + }, + ], + expected: { + menu: [ + MENU_ITEMS.openReferrer, + MENU_ITEMS.copyLocation, + MENU_ITEMS.separator, + MENU_ITEMS.delete, + MENU_ITEMS.clearList, + ], + }, + }, + { + name: "Completed unknown ext download with application/octet-stream", + overrideExtension: "unknownExtension", + downloads: [ + { + state: DownloadsCommon.DOWNLOAD_FINISHED, + contentType: "application/octet-stream", + target: {}, + source: { + referrerInfo: exampleRefInfo, + }, + }, + ], + expected: { + menu: [ + MENU_ITEMS.show, + MENU_ITEMS.commandsSeparator, + MENU_ITEMS.openReferrer, + MENU_ITEMS.copyLocation, + MENU_ITEMS.separator, + MENU_ITEMS.deleteFile, + MENU_ITEMS.delete, + MENU_ITEMS.clearList, + ], + }, + }, + { + name: "Completed txt download with application/octet-stream", + overrideExtension: "txt", + downloads: [ + { + state: DownloadsCommon.DOWNLOAD_FINISHED, + contentType: "application/octet-stream", + target: {}, + source: { + referrerInfo: exampleRefInfo, + }, + }, + ], + expected: { + menu: [ + // Despite application/octet-stream content type, ensure + // alwaysOpenSimilarFiles still appears since txt files + // are supported file types. + MENU_ITEMS.alwaysOpenSimilarFiles, + MENU_ITEMS.show, + MENU_ITEMS.commandsSeparator, + MENU_ITEMS.openReferrer, + MENU_ITEMS.copyLocation, + MENU_ITEMS.separator, + MENU_ITEMS.deleteFile, + MENU_ITEMS.delete, + MENU_ITEMS.clearList, + ], + }, + }, +]; + +const TestCasesDeletedFile = [ + { + name: "Download with file deleted", + downloads: [ + { + state: DownloadsCommon.DOWNLOAD_FINISHED, + contentType: "text/plain", + target: {}, + source: { + referrerInfo: exampleRefInfo, + }, + deleted: true, + }, + ], + expected: { + menu: [ + MENU_ITEMS.alwaysOpenSimilarFiles, + MENU_ITEMS.openReferrer, + MENU_ITEMS.copyLocation, + MENU_ITEMS.separator, + MENU_ITEMS.delete, + MENU_ITEMS.clearList, + ], + }, + }, +]; + +const TestCasesMultipleFiles = [ + { + name: "Multiple files", + downloads: [ + { + state: DownloadsCommon.DOWNLOAD_FINISHED, + contentType: "text/plain", + target: {}, + source: { + referrerInfo: exampleRefInfo, + }, + }, + { + state: DownloadsCommon.DOWNLOAD_FINISHED, + contentType: "text/plain", + target: {}, + source: { + referrerInfo: exampleRefInfo, + }, + deleted: true, + }, + ], + expected: { + menu: [ + MENU_ITEMS.alwaysOpenSimilarFiles, + MENU_ITEMS.openReferrer, + MENU_ITEMS.copyLocation, + MENU_ITEMS.separator, + MENU_ITEMS.delete, + MENU_ITEMS.clearList, + ], + }, + itemIndex: 1, + }, +]; + +add_setup(async function () { + // 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); + + // create the downloaded files we'll need + TestFiles.pdf = await createDownloadedFile( + PathUtils.join(gDownloadDir, "downloaded.pdf"), + DATA_PDF + ); + info("Created downloaded PDF file at:" + TestFiles.pdf.path); + TestFiles.txt = await createDownloadedFile( + PathUtils.join(gDownloadDir, "downloaded.txt"), + "Test file" + ); + info("Created downloaded text file at:" + TestFiles.txt.path); + TestFiles.unknownExtension = await createDownloadedFile( + PathUtils.join(gDownloadDir, "downloaded.unknownExtension"), + "Test file" + ); + info( + "Created downloaded unknownExtension file at:" + + TestFiles.unknownExtension.path + ); + TestFiles.nonexistentFile = new FileUtils.File( + PathUtils.join(gDownloadDir, "nonexistent") + ); + info( + "Created nonexistent downloaded file at:" + TestFiles.nonexistentFile.path + ); +}); + +// non default mimetypes +for (let testData of TestCasesNewMimetypes) { + 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 testDownloadContextMenu(testData); + }, + }; + add_task(tmp[testData.name]); +} + +for (let testData of TestCasesDeletedFile) { + 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 testDownloadContextMenu(testData); + }, + }; + add_task(tmp[testData.name]); +} + +for (let testData of TestCasesMultipleFiles) { + 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 testDownloadContextMenu(testData); + }, + }; + add_task(tmp[testData.name]); +} + +async function testDownloadContextMenu({ + overrideExtension = null, + downloads = [], + expected, + itemIndex = 0, +}) { + // prepare downloads + await prepareDownloads(downloads, overrideExtension); + let downloadList = await Downloads.getList(Downloads.PUBLIC); + let download = (await downloadList.getAll())[itemIndex]; + info("Download succeeded? " + download.succeeded); + info("Download target exists? " + download.target.exists); + + // open panel + await task_openPanel(); + await TestUtils.waitForCondition(() => { + let downloadsListBox = document.getElementById("downloadsListBox"); + downloadsListBox.removeAttribute("disabled"); + return downloadsListBox.childElementCount == downloads.length; + }); + + let itemTarget = document + .querySelectorAll("#downloadsListBox richlistitem") + [itemIndex].querySelector(".downloadMainArea"); + EventUtils.synthesizeMouse(itemTarget, 1, 1, { type: "mousemove" }); + is( + DownloadsView.richListBox.selectedIndex, + 0, + "moving the mouse resets the richlistbox's selected index" + ); + + info("trigger the context menu"); + let contextMenu = await openContextMenu(itemTarget); + + // FIXME: This works in practice, but simulating the context menu opening + // doesn't seem to automatically set the selected index. + DownloadsView.richListBox.selectedIndex = itemIndex; + EventUtils.synthesizeMouse(itemTarget, 1, 1, { type: "mousemove" }); + is( + DownloadsView.richListBox.selectedIndex, + itemIndex, + "selected index after opening the context menu and moving the mouse" + ); + + info("context menu should be open, verify its menu items"); + let result = verifyContextMenu(contextMenu, expected.menu); + + // close menus + contextMenu.hidePopup(); + let hiddenPromise = BrowserTestUtils.waitForEvent( + DownloadsPanel.panel, + "popuphidden" + ); + DownloadsPanel.hidePanel(); + await hiddenPromise; + + ok(!result, "Expected no errors verifying context menu items"); + + // clean up downloads + await downloadList.removeFinished(); +} + +// ---------------------------------------------------------------------------- +// Helpers + +function verifyContextMenu(contextMenu, itemSelectors) { + // Ignore hidden nodes + let items = Array.from(contextMenu.children).filter(n => + BrowserTestUtils.is_visible(n) + ); + let menuAsText = items + .map(n => { + return n.nodeName == "menuseparator" + ? "---" + : `${n.label} (${n.command})`; + }) + .join("\n"); + info("Got actual context menu items: \n" + menuAsText); + + try { + is( + items.length, + itemSelectors.length, + "Context menu has the expected number of items" + ); + for (let i = 0; i < items.length; i++) { + let selector = itemSelectors[i]; + ok( + items[i].matches(selector), + `Item at ${i} matches expected selector: ${selector}` + ); + } + } catch (ex) { + return ex; + } + return null; +} + +async function prepareDownloads(downloads, overrideExtension = null) { + for (let props of downloads) { + info(JSON.stringify(props)); + if (props.state !== DownloadsCommon.DOWNLOAD_FINISHED) { + continue; + } + if (props.deleted) { + props.target = TestFiles.nonexistentFile; + continue; + } + switch (props.contentType) { + case "application/pdf": + props.target = TestFiles.pdf; + break; + case "text/plain": + props.target = TestFiles.txt; + break; + case "application/octet-stream": + props.target = TestFiles[overrideExtension]; + break; + } + ok(props.target instanceof Ci.nsIFile, "download target is a nsIFile"); + } + // If we'd just insert downloads as defined in the test case, they would + // appear reversed in the panel, because they will be in descending insertion + // order (newest at the top). The problem is we define an itemIndex based on + // the downloads array, and it would be weird to define it based on a + // reversed order. Short, we just reverse the array to preserve the order. + await task_addDownloads(downloads.reverse()); +} -- cgit v1.2.3