Bug 1418616 - Add default extension to downloads saveAs dialogs, r?rpl draft
authorTomislav Jovanovic <tomica@gmail.com>
Thu, 29 Mar 2018 20:09:45 +0200
changeset 775291 f434a3f8a4b12183a7630e82d3f3f5870d7cdd38
parent 774711 8c71359d60e21055074ae8bc3dcb796d20f0cbaf
push id104685
push userbmo:tomica@gmail.com
push dateFri, 30 Mar 2018 20:28:30 +0000
reviewersrpl
bugs1418616
milestone61.0a1
Bug 1418616 - Add default extension to downloads saveAs dialogs, r?rpl MozReview-Commit-ID: 9WOfZoc7wa6
toolkit/components/extensions/parent/ext-downloads.js
toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_saveAs.html
--- a/toolkit/components/extensions/parent/ext-downloads.js
+++ b/toolkit/components/extensions/parent/ext-downloads.js
@@ -514,23 +514,29 @@ this.downloads = class extends Extension
                   break;
               }
             }
 
             if (!saveAs) {
               return target;
             }
 
+            const window = Services.wm.getMostRecentWindow("navigator:browser");
+            const basename = OS.Path.basename(target);
+            const ext = basename.match(/\.([^.]+)$/);
+
             // Setup the file picker Save As dialog.
             const picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
-            const window = Services.wm.getMostRecentWindow("navigator:browser");
             picker.init(window, null, Ci.nsIFilePicker.modeSave);
             picker.displayDirectory = new FileUtils.File(dir);
             picker.appendFilters(Ci.nsIFilePicker.filterAll);
-            picker.defaultString = OS.Path.basename(target);
+            picker.defaultString = basename;
+
+            // Configure a default file extension, used as fallback on Windows.
+            picker.defaultExtension = ext && ext[1];
 
             // Open the dialog and resolve/reject with the result.
             return new Promise((resolve, reject) => {
               picker.open(result => {
                 if (result === Ci.nsIFilePicker.returnCancel) {
                   reject({message: "Download canceled by the user"});
                 } else {
                   resolve(picker.file.path);
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_saveAs.html
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_saveAs.html
@@ -44,28 +44,33 @@ add_task(async function setup() {
     await SpecialPowers.popPrefEnv();
     pickerDir.remove(true);
     defaultDir.remove(true);
   });
 });
 
 add_task(async function test_downloads_saveAs() {
   const pickerFile = pickerDir.clone();
-  pickerFile.append("file_download.txt");
+  pickerFile.append("file_download.nonext.txt");
 
   const defaultFile = defaultDir.clone();
-  defaultFile.append("file_download.txt");
+  defaultFile.append("file_download.nonext.txt");
 
   const {MockFilePicker} = SpecialPowers;
   MockFilePicker.init(window);
 
   MockFilePicker.showCallback = fp => {
     // On picker 'show' event, choose the filename that was set as the default
     // and append it to the picker's download directory
     let file = pickerDir.clone();
+
+    // Assert that the downloads API configures both default properties.
+    is(fp.defaultString, "file_download.nonext.txt", "Got the expected FilePicker defaultString");
+    is(fp.defaultExtension, "txt", "Got the expected FilePicker defaultExtension");
+
     file.append(fp.defaultString);
     MockFilePicker.setFiles([file]);
   };
 
   function background() {
     const url = URL.createObjectURL(new Blob(["file content"]));
     browser.test.onMessage.addListener(async (filename, saveAs) => {
       try {
@@ -96,39 +101,39 @@ add_task(async function test_downloads_s
   await extension.startup();
   await extension.awaitMessage("ready");
 
   async function testExpectFilePicker(saveAs) {
     ok(!pickerFile.exists(), "the file should have been cleaned up properly previously");
 
     MockFilePicker.returnValue = MockFilePicker.returnOK;
 
-    extension.sendMessage("file_download.txt", saveAs);
+    extension.sendMessage("file_download.nonext.txt", saveAs);
     let result = await extension.awaitMessage("done");
     ok(result.ok, `downloads.download() works with saveAs=${saveAs}`);
 
     ok(pickerFile.exists(), "the file exists.");
     is(pickerFile.fileSize, 12, "downloaded file is the correct size");
     pickerFile.remove(false);
 
     // Test the user canceling the save dialog.
     MockFilePicker.returnValue = MockFilePicker.returnCancel;
 
-    extension.sendMessage("file_download.txt", saveAs);
+    extension.sendMessage("file_download.nonext.txt", saveAs);
     result = await extension.awaitMessage("done");
 
     ok(!result.ok, "download rejected if the user cancels the dialog");
     is(result.message, "Download canceled by the user", "with the correct message");
     ok(!pickerFile.exists(), "file was not downloaded");
   }
 
   async function testNoFilePicker(saveAs) {
     ok(!defaultFile.exists(), "the file should have been cleaned up properly previously");
 
-    extension.sendMessage("file_download.txt", saveAs);
+    extension.sendMessage("file_download.nonext.txt", saveAs);
     let result = await extension.awaitMessage("done");
     ok(result.ok, `downloads.download() works with saveAs=${saveAs}`);
 
     ok(defaultFile.exists(), "the file exists.");
     is(defaultFile.fileSize, 12, "downloaded file is the correct size");
     defaultFile.remove(false);
   }