summaryrefslogtreecommitdiffstats
path: root/uriloader/exthandler/tests/mochitest/browser_save_filenames.js
diff options
context:
space:
mode:
Diffstat (limited to 'uriloader/exthandler/tests/mochitest/browser_save_filenames.js')
-rw-r--r--uriloader/exthandler/tests/mochitest/browser_save_filenames.js823
1 files changed, 823 insertions, 0 deletions
diff --git a/uriloader/exthandler/tests/mochitest/browser_save_filenames.js b/uriloader/exthandler/tests/mochitest/browser_save_filenames.js
new file mode 100644
index 0000000000..f421a7a609
--- /dev/null
+++ b/uriloader/exthandler/tests/mochitest/browser_save_filenames.js
@@ -0,0 +1,823 @@
+// There are at least seven different ways in a which a file can be saved or downloaded. This
+// test ensures that the filename is determined correctly when saving in these ways. The seven
+// ways are:
+// - save the file individually from the File menu
+// - save as complete web page (this uses a different codepath than the previous one)
+// - dragging an image to the local file system
+// - copy an image and paste it as a file to the local file system (windows only)
+// - open a link with content-disposition set to attachment
+// - open a link with the download attribute
+// - save a link or image from the context menu
+
+requestLongerTimeout(8);
+
+let types = {
+ text: "text/plain",
+ html: "text/html",
+ png: "image/png",
+ jpeg: "image/jpeg",
+ webp: "image/webp",
+ otherimage: "image/unknown",
+ // Other js types (application/javascript and text/javascript) are handled by the system
+ // inconsistently, but application/x-javascript is handled by the external helper app service,
+ // so it is used here for this test.
+ js: "application/x-javascript",
+ binary: "application/octet-stream",
+ nonsense: "application/x-nonsense",
+ zip: "application/zip",
+ json: "application/json",
+ tar: "application/x-tar",
+};
+
+const PNG_DATA = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
+ "ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII="
+);
+
+const JPEG_DATA = atob(
+ "/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4z" +
+ "NDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEB" +
+ "AxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS" +
+ "0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKz" +
+ "tLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgEC" +
+ "BAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpj" +
+ "ZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6" +
+ "/9oADAMBAAIRAxEAPwD3+iiigD//2Q=="
+);
+
+const WEBP_DATA = atob(
+ "UklGRiIAAABXRUJQVlA4TBUAAAAvY8AYAAfQ/4j+B4CE8H+/ENH/VCIA"
+);
+
+const DEFAULT_FILENAME =
+ AppConstants.platform == "win" ? "Untitled.htm" : "Untitled.html";
+
+const PROMISE_FILENAME_TYPE = "application/x-moz-file-promise-dest-filename";
+
+let MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+let expectedItems;
+let sendAsAttachment = false;
+let httpServer = null;
+
+function handleRequest(aRequest, aResponse) {
+ const queryString = new URLSearchParams(aRequest.queryString);
+ let type = queryString.get("type");
+ let filename = queryString.get("filename");
+ let dispname = queryString.get("dispname");
+
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+ if (type) {
+ aResponse.setHeader("Content-Type", types[type]);
+ }
+
+ if (dispname) {
+ let dispositionType = sendAsAttachment ? "attachment" : "inline";
+ aResponse.setHeader(
+ "Content-Disposition",
+ dispositionType + ';name="' + dispname + '"'
+ );
+ } else if (filename) {
+ let dispositionType = sendAsAttachment ? "attachment" : "inline";
+ aResponse.setHeader(
+ "Content-Disposition",
+ dispositionType + ';filename="' + filename + '"'
+ );
+ } else if (sendAsAttachment) {
+ aResponse.setHeader("Content-Disposition", "attachment");
+ }
+
+ if (type == "png") {
+ aResponse.write(PNG_DATA);
+ } else if (type == "jpeg") {
+ aResponse.write(JPEG_DATA);
+ } else if (type == "webp") {
+ aResponse.write(WEBP_DATA);
+ } else if (type == "html") {
+ aResponse.write(
+ "<html><head><title>file.inv</title></head><body>File</body></html>"
+ );
+ } else {
+ aResponse.write("// Some Text");
+ }
+}
+
+function handleBasicImageRequest(aRequest, aResponse) {
+ aResponse.setHeader("Content-Type", "image/png");
+ aResponse.write(PNG_DATA);
+}
+
+function handleRedirect(aRequest, aResponse) {
+ const queryString = new URLSearchParams(aRequest.queryString);
+ let filename = queryString.get("filename");
+
+ aResponse.setStatusLine(aRequest.httpVersion, 302);
+ aResponse.setHeader("Location", "/bell" + filename[0] + "?" + queryString);
+}
+
+function promiseDownloadFinished(list) {
+ return new Promise(resolve => {
+ list.addView({
+ onDownloadChanged(download) {
+ if (download.stopped) {
+ list.removeView(this);
+ resolve(download);
+ }
+ },
+ });
+ });
+}
+
+// nsIFile::CreateUnique crops long filenames if the path is too long, but
+// we don't know exactly how long depending on the full path length, so
+// for those save methods that use CreateUnique, instead just verify that
+// the filename starts with the right string and has the correct extension.
+function checkShortenedFilename(actual, expected) {
+ if (actual.length < expected.length) {
+ let actualDot = actual.lastIndexOf(".");
+ let actualExtension = actual.substring(actualDot);
+ let expectedExtension = expected.substring(expected.lastIndexOf("."));
+ if (
+ actualExtension == expectedExtension &&
+ expected.startsWith(actual.substring(0, actualDot))
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+add_setup(async function () {
+ const { HttpServer } = ChromeUtils.import(
+ "resource://testing-common/httpd.js"
+ );
+ httpServer = new HttpServer();
+ httpServer.start(8000);
+
+ // Need to load the page from localhost:8000 as the download attribute
+ // only applies to links from the same domain.
+ let saveFilenamesPage = FileUtils.getFile(
+ "CurWorkD",
+ "/browser/uriloader/exthandler/tests/mochitest/save_filenames.html".split(
+ "/"
+ )
+ );
+ httpServer.registerFile("/save_filenames.html", saveFilenamesPage);
+
+ // A variety of different scripts are set up to better ensure uniqueness.
+ httpServer.registerPathHandler("/save_filename.sjs", handleRequest);
+ httpServer.registerPathHandler("/save_thename.sjs", handleRequest);
+ httpServer.registerPathHandler("/getdata.png", handleRequest);
+ httpServer.registerPathHandler("/base", handleRequest);
+ httpServer.registerPathHandler("/basedata", handleRequest);
+ httpServer.registerPathHandler("/basetext", handleRequest);
+ httpServer.registerPathHandler("/text2.txt", handleRequest);
+ httpServer.registerPathHandler("/text3.gonk", handleRequest);
+ httpServer.registerPathHandler("/basic.png", handleBasicImageRequest);
+ httpServer.registerPathHandler("/aquamarine.jpeg", handleBasicImageRequest);
+ httpServer.registerPathHandler("/lazuli.exe", handleBasicImageRequest);
+ httpServer.registerPathHandler("/redir", handleRedirect);
+ httpServer.registerPathHandler("/bellr", handleRequest);
+ httpServer.registerPathHandler("/bellg", handleRequest);
+ httpServer.registerPathHandler("/bellb", handleRequest);
+ httpServer.registerPathHandler("/executable.exe", handleRequest);
+
+ await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "http://localhost:8000/save_filenames.html"
+ );
+
+ expectedItems = await getItems("items");
+});
+
+function getItems(parentid) {
+ return SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [parentid, AppConstants.platform],
+ (id, platform) => {
+ let elements = [];
+ let elem = content.document.getElementById(id).firstElementChild;
+ while (elem) {
+ let filename =
+ elem.dataset["filenamePlatform" + platform] || elem.dataset.filename;
+ let url = elem.getAttribute("src");
+ let draggable =
+ elem.localName == "img" && elem.dataset.nodrag != "true";
+ let unknown = elem.dataset.unknown;
+ let noattach = elem.dataset.noattach;
+ let savepagename = elem.dataset.savepagename;
+ elements.push({
+ draggable,
+ unknown,
+ filename,
+ url,
+ noattach,
+ savepagename,
+ });
+ elem = elem.nextElementSibling;
+ }
+ return elements;
+ }
+ );
+}
+
+function getDirectoryEntries(dir) {
+ let files = [];
+ let entries = dir.directoryEntries;
+ while (true) {
+ let file = entries.nextFile;
+ if (!file) {
+ break;
+ }
+ files.push(file.leafName);
+ }
+ entries.close();
+ return files;
+}
+
+// This test saves the document as a complete web page and verifies
+// that the resources are saved with the correct filename.
+add_task(async function save_document() {
+ let browser = gBrowser.selectedBrowser;
+
+ let tmp = SpecialPowers.Services.dirsvc.get("TmpD", Ci.nsIFile);
+ const baseFilename = "test_save_filenames_" + Date.now();
+
+ let tmpFile = tmp.clone();
+ tmpFile.append(baseFilename + "_document.html");
+ let tmpDir = tmp.clone();
+ tmpDir.append(baseFilename + "_document_files");
+
+ MockFilePicker.displayDirectory = tmpDir;
+ MockFilePicker.showCallback = function (fp) {
+ MockFilePicker.setFiles([tmpFile]);
+ MockFilePicker.filterIndex = 0; // kSaveAsType_Complete
+ };
+
+ let downloadsList = await Downloads.getList(Downloads.PUBLIC);
+ let savePromise = new Promise((resolve, reject) => {
+ downloadsList.addView({
+ onDownloadChanged(download) {
+ if (download.succeeded) {
+ downloadsList.removeView(this);
+ downloadsList.removeFinished();
+ resolve();
+ }
+ },
+ });
+ });
+ saveBrowser(browser);
+ await savePromise;
+
+ let filesSaved = getDirectoryEntries(tmpDir);
+
+ for (let idx = 0; idx < expectedItems.length; idx++) {
+ let filename = expectedItems[idx].filename;
+ if (idx == 66 && AppConstants.platform == "win") {
+ // This is special-cased on Windows. The default filename will be used, since
+ // the filename is invalid, but since the previous test file has the same issue,
+ // this second file will be saved with a number suffix added to it.
+ filename = "Untitled_002";
+ }
+
+ let file = tmpDir.clone();
+ file.append(filename);
+
+ let fileIdx = -1;
+ // Use checkShortenedFilename to check long filenames.
+ if (filename.length > 240) {
+ for (let t = 0; t < filesSaved.length; t++) {
+ if (
+ filesSaved[t].length > 60 &&
+ checkShortenedFilename(filesSaved[t], filename)
+ ) {
+ fileIdx = t;
+ break;
+ }
+ }
+ } else {
+ fileIdx = filesSaved.indexOf(filename);
+ }
+
+ ok(
+ fileIdx >= 0,
+ "file i" +
+ idx +
+ " " +
+ filename +
+ " was saved with the correct name using saveDocument"
+ );
+ if (fileIdx >= 0) {
+ // If found, remove it from the list. At end of the test, the
+ // list should be empty.
+ filesSaved.splice(fileIdx, 1);
+ }
+ }
+
+ is(filesSaved.length, 0, "all files accounted for");
+ tmpDir.remove(true);
+ tmpFile.remove(false);
+});
+
+// This test simulates dragging the images in the document and ensuring that
+// the correct filename is used for each one.
+// On Mac, the data is added in the parent process instead, so we cannot
+// test dragging directly.
+if (AppConstants.platform != "macosx") {
+ add_task(async function drag_files() {
+ let browser = gBrowser.selectedBrowser;
+
+ await SpecialPowers.spawn(browser, [PROMISE_FILENAME_TYPE], type => {
+ content.addEventListener("dragstart", event => {
+ content.draggedFile = event.dataTransfer.getData(type);
+ event.preventDefault();
+ });
+ });
+
+ for (let idx = 0; idx < expectedItems.length; idx++) {
+ if (!expectedItems[idx].draggable) {
+ // You can't drag non-images and invalid images.
+ continue;
+ }
+
+ await BrowserTestUtils.synthesizeMouse(
+ "#i" + idx,
+ 1,
+ 1,
+ { type: "mousedown" },
+ browser
+ );
+ await BrowserTestUtils.synthesizeMouse(
+ "#i" + idx,
+ 11,
+ 11,
+ { type: "mousemove" },
+ browser
+ );
+ await BrowserTestUtils.synthesizeMouse(
+ "#i" + idx,
+ 20,
+ 20,
+ { type: "mousemove" },
+ browser
+ );
+ await BrowserTestUtils.synthesizeMouse(
+ "#i" + idx,
+ 20,
+ 20,
+ { type: "mouseup" },
+ browser
+ );
+
+ let draggedFile = await SpecialPowers.spawn(browser, [], () => {
+ let file = content.draggedFile;
+ content.draggedFile = null;
+ return file;
+ });
+
+ is(
+ draggedFile,
+ expectedItems[idx].filename,
+ "i" +
+ idx +
+ " " +
+ expectedItems[idx].filename +
+ " was saved with the correct name when dragging"
+ );
+ }
+ });
+}
+
+// This test checks that copying an image provides the right filename
+// for pasting to the local file system. This is only implemented on Windows.
+if (AppConstants.platform == "win") {
+ add_task(async function copy_image() {
+ for (let idx = 0; idx < expectedItems.length; idx++) {
+ if (!expectedItems[idx].draggable) {
+ // You can't context-click on non-images.
+ continue;
+ }
+
+ let data = await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [idx, PROMISE_FILENAME_TYPE],
+ (imagenum, type) => {
+ // No need to wait for the data to be really on the clipboard, we only
+ // need the promise data added when the command is performed.
+ SpecialPowers.setCommandNode(
+ content,
+ content.document.getElementById("i" + imagenum)
+ );
+ SpecialPowers.doCommand(content, "cmd_copyImageContents");
+
+ return SpecialPowers.getClipboardData(type);
+ }
+ );
+
+ is(
+ data,
+ expectedItems[idx].filename,
+ "i" +
+ idx +
+ " " +
+ expectedItems[idx].filename +
+ " was saved with the correct name when copying"
+ );
+ }
+ });
+}
+
+// This test checks the default filename selected when selecting to save
+// a file from either the context menu or what would happen when save page
+// as was selected from the file menu. Note that this tests a filename assigned
+// when using content-disposition: inline.
+add_task(async function saveas_files() {
+ // Iterate over each item and try saving them from the context menu,
+ // and the Save Page As command on the File menu.
+ for (let testname of ["context menu", "save page as"]) {
+ for (let idx = 0; idx < expectedItems.length; idx++) {
+ let menu;
+ if (testname == "context menu") {
+ if (!expectedItems[idx].draggable) {
+ // You can't context-click on non-images.
+ continue;
+ }
+
+ menu = document.getElementById("contentAreaContextMenu");
+ let popupShown = BrowserTestUtils.waitForEvent(menu, "popupshown");
+ BrowserTestUtils.synthesizeMouse(
+ "#i" + idx,
+ 5,
+ 5,
+ { type: "contextmenu", button: 2 },
+ gBrowser.selectedBrowser
+ );
+ await popupShown;
+ } else {
+ if (expectedItems[idx].unknown == "typeonly") {
+ // Items marked with unknown="typeonly" have unknown content types and
+ // will be downloaded instead of opened in a tab.
+ let list = await Downloads.getList(Downloads.PUBLIC);
+ let downloadFinishedPromise = promiseDownloadFinished(list);
+
+ await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: expectedItems[idx].url,
+ waitForLoad: false,
+ waitForStateStop: true,
+ });
+
+ let download = await downloadFinishedPromise;
+
+ let filename = PathUtils.filename(download.target.path);
+
+ let expectedFilename = expectedItems[idx].filename;
+ if (expectedFilename.length > 240) {
+ ok(
+ checkShortenedFilename(filename, expectedFilename),
+ "open link" +
+ idx +
+ " " +
+ expectedFilename +
+ " was downloaded with the correct name when opened as a url (with long name)"
+ );
+ } else {
+ is(
+ filename,
+ expectedFilename,
+ "open link" +
+ idx +
+ " " +
+ expectedFilename +
+ " was downloaded with the correct name when opened as a url"
+ );
+ }
+
+ try {
+ await IOUtils.remove(download.target.path);
+ } catch (ex) {}
+
+ await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ continue;
+ }
+
+ await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: expectedItems[idx].url,
+ waitForLoad: false,
+ waitForStateStop: true,
+ });
+ }
+
+ let filename = await new Promise(resolve => {
+ MockFilePicker.showCallback = function (fp) {
+ setTimeout(() => {
+ resolve(fp.defaultString);
+ }, 0);
+ return Ci.nsIFilePicker.returnCancel;
+ };
+
+ if (testname == "context menu") {
+ let menuitem = document.getElementById("context-saveimage");
+ menu.activateItem(menuitem);
+ } else if (testname == "save page as") {
+ document.getElementById("Browser:SavePage").doCommand();
+ }
+ });
+
+ // Trying to open an unknown or binary type will just open a blank
+ // page, so trying to save will just save the blank page with the
+ // filename Untitled.html.
+ let expectedFilename = expectedItems[idx].unknown
+ ? DEFAULT_FILENAME
+ : expectedItems[idx].savepagename || expectedItems[idx].filename;
+
+ // When saving via contentAreaUtils.js, the content disposition name
+ // field is used as an alternate.
+ if (expectedFilename == "save_thename.png") {
+ expectedFilename = "withname.png";
+ }
+
+ is(
+ filename,
+ expectedFilename,
+ "i" +
+ idx +
+ " " +
+ expectedFilename +
+ " was saved with the correct name " +
+ testname
+ );
+
+ if (testname == "save page as") {
+ await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+ }
+ }
+});
+
+// This test checks that links that result in files with
+// content-disposition: attachment are saved with the right filenames.
+add_task(async function save_links() {
+ sendAsAttachment = true;
+
+ // Create some links based on each image and insert them into the document.
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
+ let doc = content.document;
+ let insertPos = doc.getElementById("attachment-links");
+
+ let idx = 0;
+ let elem = doc.getElementById("items").firstElementChild;
+ while (elem) {
+ let attachmentlink = doc.createElement("a");
+ attachmentlink.id = "attachmentlink" + idx;
+ attachmentlink.href = elem.localName == "object" ? elem.data : elem.src;
+ attachmentlink.textContent = elem.dataset.filename;
+ insertPos.appendChild(attachmentlink);
+ insertPos.appendChild(doc.createTextNode(" "));
+
+ elem = elem.nextElementSibling;
+ idx++;
+ }
+ });
+
+ let list = await Downloads.getList(Downloads.PUBLIC);
+
+ for (let idx = 0; idx < expectedItems.length; idx++) {
+ // Skip the items that won't have a content-disposition.
+ if (expectedItems[idx].noattach) {
+ continue;
+ }
+
+ let downloadFinishedPromise = promiseDownloadFinished(list);
+
+ BrowserTestUtils.synthesizeMouse(
+ "#attachmentlink" + idx,
+ 5,
+ 5,
+ {},
+ gBrowser.selectedBrowser
+ );
+
+ let download = await downloadFinishedPromise;
+
+ let filename = PathUtils.filename(download.target.path);
+
+ let expectedFilename = expectedItems[idx].filename;
+ // Use checkShortenedFilename to check long filenames.
+ if (expectedItems[idx].filename.length > 240) {
+ ok(
+ checkShortenedFilename(filename, expectedFilename),
+ "attachmentlink" +
+ idx +
+ " " +
+ expectedFilename +
+ " was saved with the correct name when opened as attachment (with long name)"
+ );
+ } else {
+ is(
+ filename,
+ expectedFilename,
+ "attachmentlink" +
+ idx +
+ " " +
+ expectedFilename +
+ " was saved with the correct name when opened as attachment"
+ );
+ }
+
+ try {
+ await IOUtils.remove(download.target.path);
+ } catch (ex) {}
+ }
+
+ sendAsAttachment = false;
+});
+
+// This test checks some cases where links to images are saved using Save Link As,
+// and when opening them in a new tab and then using Save Page As.
+add_task(async function saveas_image_links() {
+ let links = await getItems("links");
+
+ // Iterate over each link and try saving the links from the context menu,
+ // and then after opening a new tab for that link and then selecting
+ // the Save Page As command on the File menu.
+ for (let testname of ["save link as", "save link then save page as"]) {
+ for (let idx = 0; idx < links.length; idx++) {
+ let menu = document.getElementById("contentAreaContextMenu");
+ let popupShown = BrowserTestUtils.waitForEvent(menu, "popupshown");
+ BrowserTestUtils.synthesizeMouse(
+ "#link" + idx,
+ 5,
+ 5,
+ { type: "contextmenu", button: 2 },
+ gBrowser.selectedBrowser
+ );
+ await popupShown;
+
+ let promptPromise = new Promise(resolve => {
+ MockFilePicker.showCallback = function (fp) {
+ setTimeout(() => {
+ resolve(fp.defaultString);
+ }, 0);
+ return Ci.nsIFilePicker.returnCancel;
+ };
+ });
+
+ if (testname == "save link as") {
+ let menuitem = document.getElementById("context-savelink");
+ menu.activateItem(menuitem);
+ } else {
+ let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
+
+ let menuitem = document.getElementById("context-openlinkintab");
+ menu.activateItem(menuitem);
+
+ let tab = await newTabPromise;
+ await BrowserTestUtils.switchTab(gBrowser, tab);
+
+ document.getElementById("Browser:SavePage").doCommand();
+ }
+
+ let filename = await promptPromise;
+
+ let expectedFilename = links[idx].filename;
+ // Only codepaths that go through contentAreaUtils.js use the
+ // name from the content disposition.
+ if (testname == "save link as" && expectedFilename == "four.png") {
+ expectedFilename = "save_filename.png";
+ }
+
+ is(
+ filename,
+ expectedFilename,
+ "i" +
+ idx +
+ " " +
+ expectedFilename +
+ " link was saved with the correct name " +
+ testname
+ );
+
+ if (testname == "save link then save page as") {
+ await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+ }
+ }
+});
+
+// This test checks that links that with a download attribute
+// are saved with the right filenames.
+add_task(async function save_download_links() {
+ let downloads = await getItems("downloads");
+
+ let list = await Downloads.getList(Downloads.PUBLIC);
+ for (let idx = 0; idx < downloads.length; idx++) {
+ let downloadFinishedPromise = promiseDownloadFinished(list);
+
+ BrowserTestUtils.synthesizeMouse(
+ "#download" + idx,
+ 2,
+ 2,
+ {},
+ gBrowser.selectedBrowser
+ );
+
+ let download = await downloadFinishedPromise;
+
+ let filename = PathUtils.filename(download.target.path);
+
+ if (downloads[idx].filename.length > 240) {
+ ok(
+ checkShortenedFilename(filename, downloads[idx].filename),
+ "download" +
+ idx +
+ " " +
+ downloads[idx].filename +
+ " was saved with the correct name when link has download attribute"
+ );
+ } else {
+ if (idx == 66 && filename == "Untitled(1)") {
+ // Sometimes, the previous test's file still exists or wasn't created in time
+ // and a non-duplicated name is created. Allow this rather than figuring out
+ // how to avoid it since it doesn't affect what is being tested here.
+ filename = "Untitled";
+ }
+
+ is(
+ filename,
+ downloads[idx].filename,
+ "download" +
+ idx +
+ " " +
+ downloads[idx].filename +
+ " was saved with the correct name when link has download attribute"
+ );
+ }
+
+ try {
+ await IOUtils.remove(download.target.path);
+ } catch (ex) {}
+ }
+});
+
+// This test verifies that invalid extensions are not removed when they
+// have been entered in the file picker.
+add_task(async function save_page_with_invalid_after_filepicker() {
+ await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "http://localhost:8000/save_filename.sjs?type=html&filename=invfile.lnk"
+ );
+
+ let filename = await new Promise(resolve => {
+ MockFilePicker.showCallback = function (fp) {
+ let expectedFilename =
+ AppConstants.platform == "win" ? "invfile.lnk.htm" : "invfile.lnk.html";
+ is(fp.defaultString, expectedFilename, "supplied filename is correct");
+ setTimeout(() => {
+ resolve("otherfile.local");
+ }, 0);
+ return Ci.nsIFilePicker.returnCancel;
+ };
+
+ document.getElementById("Browser:SavePage").doCommand();
+ });
+
+ is(filename, "otherfile.local", "lnk extension has been preserved");
+
+ await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function save_page_with_invalid_extension() {
+ await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "http://localhost:8000/save_filename.sjs?type=html"
+ );
+
+ let filename = await new Promise(resolve => {
+ MockFilePicker.showCallback = function (fp) {
+ setTimeout(() => {
+ resolve(fp.defaultString);
+ }, 0);
+ return Ci.nsIFilePicker.returnCancel;
+ };
+
+ document.getElementById("Browser:SavePage").doCommand();
+ });
+
+ is(
+ filename,
+ AppConstants.platform == "win" ? "file.inv.htm" : "file.inv.html",
+ "html extension has been added"
+ );
+
+ await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async () => {
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ MockFilePicker.cleanup();
+ await new Promise(resolve => httpServer.stop(resolve));
+});