Bug 1128480 - fix nsHelperAppDlg.js to create its download dir info on show() already so it continues working if the window is gone by the time we prompt for a directory to save to (incl. test), r=felipe
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Mon, 09 Feb 2015 20:26:21 +0000
changeset 256153 688a64d4e746ca7e75b94ceee36d937d7088833c
parent 256152 6280b92abfcb47a84815605cedb595819af07d35
child 256154 33ca49e58e4b046f40302f7757e2d58ffc7600d9
push id4610
push userjlund@mozilla.com
push dateMon, 30 Mar 2015 18:32:55 +0000
treeherdermozilla-beta@4df54044d9ef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfelipe
bugs1128480
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1128480 - fix nsHelperAppDlg.js to create its download dir info on show() already so it continues working if the window is gone by the time we prompt for a directory to save to (incl. test), r=felipe
browser/base/content/test/general/browser.ini
browser/base/content/test/general/browser_save_link_when_window_navigates.js
browser/base/content/test/general/navigating_window_with_download.html
browser/base/content/test/general/unknownContentType_file.pif
browser/base/content/test/general/unknownContentType_file.pif^headers^
toolkit/mozapps/downloads/nsHelperAppDlg.js
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -56,16 +56,17 @@ support-files =
   file_double_close_tab.html
   file_favicon_change.html
   file_favicon_change_not_in_document.html
   file_fullscreen-window-open.html
   get_user_media.html
   head.js
   healthreport_testRemoteCommands.html
   moz.png
+  navigating_window_with_download.html
   offlineQuotaNotification.cacheManifest
   offlineQuotaNotification.html
   page_style_sample.html
   parsingTestHelpers.jsm
   pinning_headers.sjs
   pinning_reports.sjs
   popup_blocker.html
   print_postdata.sjs
@@ -77,16 +78,18 @@ support-files =
   test_bug435035.html
   test_bug462673.html
   test_bug628179.html
   test_bug839103.html
   test_bug959531.html
   test_process_flags_chrome.html
   test_wyciwyg_copying.html
   title_test.svg
+  unknownContentType_file.pif
+  unknownContentType_file.pif^headers^
   video.ogg
   web_video.html
   web_video1.ogv
   web_video1.ogv^headers^
   zoom_test.html
   test_no_mcb_on_http_site_img.html
   test_no_mcb_on_http_site_img.css
   test_no_mcb_on_http_site_font.html
@@ -382,16 +385,18 @@ skip-if = e10s
 [browser_sanitize-timespans.js]
 skip-if = buildapp == 'mulet'
 [browser_sanitizeDialog.js]
 skip-if = buildapp == 'mulet'
 [browser_save_link-perwindowpb.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
 [browser_save_private_link_perwindowpb.js]
 skip-if = buildapp == 'mulet' || e10s # e10s: Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
+[browser_save_link_when_window_navigates.js]
+skip-if = buildapp == 'mulet' || e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
 [browser_save_video.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1100698 - test uses synthesizeMouse and then does a load of other stuff that breaks in e10s
 [browser_save_video_frame.js]
 [browser_scope.js]
 [browser_searchSuggestionUI.js]
 skip-if = e10s
 support-files =
   searchSuggestionUI.html
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_save_link_when_window_navigates.js
@@ -0,0 +1,173 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+const SAVE_PER_SITE_PREF = "browser.download.lastDir.savePerSite";
+const ALWAYS_DOWNLOAD_DIR_PREF = "browser.download.useDownloadDir";
+const UCT_URI = "chrome://mozapps/content/downloads/unknownContentType.xul";
+
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+  .getService(Ci.mozIJSSubScriptLoader)
+  .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+                 this);
+
+function createTemporarySaveDirectory() {
+  var saveDir = Cc["@mozilla.org/file/directory_service;1"]
+                  .getService(Ci.nsIProperties)
+                  .get("TmpD", Ci.nsIFile);
+  saveDir.append("testsavedir");
+  if (!saveDir.exists()) {
+    info("create testsavedir!");
+    saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
+  }
+  info("return from createTempSaveDir: " + saveDir.path);
+  return saveDir;
+}
+
+function triggerSave(aWindow, aCallback) {
+  info("started triggerSave, persite downloads: " + (Services.prefs.getBoolPref(SAVE_PER_SITE_PREF) ? "on" : "off"));
+  var fileName;
+  let testBrowser = aWindow.gBrowser.selectedBrowser;
+  let testURI = "http://mochi.test:8888/browser/browser/base/content/test/general/navigating_window_with_download.html";
+  windowObserver.setCallback(onUCTDialog);
+  testBrowser.loadURI(testURI);
+
+  // Create the folder the link will be saved into.
+  var destDir = createTemporarySaveDirectory();
+  var destFile = destDir.clone();
+
+  MockFilePicker.displayDirectory = destDir;
+  MockFilePicker.showCallback = function(fp) {
+    info("showCallback");
+    fileName = fp.defaultString;
+    info("fileName: " + fileName);
+    destFile.append (fileName);
+    MockFilePicker.returnFiles = [destFile];
+    MockFilePicker.filterIndex = 1; // kSaveAsType_URL
+    info("done showCallback");
+  };
+
+  mockTransferCallback = function(downloadSuccess) {
+    info("mockTransferCallback");
+    onTransferComplete(aWindow, downloadSuccess, destDir);
+    destDir.remove(true);
+    ok(!destDir.exists(), "Destination dir should be removed");
+    ok(!destFile.exists(), "Destination file should be removed");
+    mockTransferCallback = null;
+    info("done mockTransferCallback");
+  }
+
+  function onUCTDialog(dialog) {
+    function doLoad() {
+      content.document.querySelector('iframe').remove();
+    }
+    testBrowser.messageManager.loadFrameScript("data:,(" + doLoad.toString() + ")()", false);
+    executeSoon(continueDownloading);
+  }
+
+  function continueDownloading() {
+    let windows = Services.wm.getEnumerator("");
+    while (windows.hasMoreElements()) {
+      let win = windows.getNext();
+      if (win.location && win.location.href == UCT_URI) {
+        win.document.documentElement._fireButtonEvent("accept");
+        win.close();
+        return;
+      }
+    }
+    ok(false, "No Unknown Content Type dialog yet?");
+  }
+
+  function onTransferComplete(aWindow, downloadSuccess, destDir) {
+    ok(downloadSuccess, "Link should have been downloaded successfully");
+    aWindow.close();
+
+    executeSoon(aCallback);
+  }
+}
+
+
+let windowObserver = {
+  setCallback: function(aCallback) {
+    if (this._callback) {
+      ok(false, "Should only be dealing with one callback at a time.");
+    }
+    this._callback = aCallback;
+  },
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic != "domwindowopened") {
+      return;
+    }
+
+    let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
+
+    win.addEventListener("load", function onLoad(event) {
+      win.removeEventListener("load", onLoad, false);
+
+      if (win.location == UCT_URI) {
+        SimpleTest.executeSoon(function() {
+          if (windowObserver._callback) {
+            windowObserver._callback(win);
+            delete windowObserver._callback;
+          } else {
+            ok(false, "Unexpected UCT dialog!");
+          }
+        });
+      }
+    }, false);
+  }
+};
+
+Services.ww.registerNotification(windowObserver);
+
+function test() {
+  waitForExplicitFinish();
+
+  function testOnWindow(options, callback) {
+    info("testOnWindow(" + options + ")");
+    var win = OpenBrowserWindow(options);
+    info("got " + win);
+    whenDelayedStartupFinished(win, () => callback(win));
+  }
+
+  function whenDelayedStartupFinished(aWindow, aCallback) {
+    info("whenDelayedStartupFinished");
+    Services.obs.addObserver(function observer(aSubject, aTopic) {
+      info("whenDelayedStartupFinished, got topic: " + aTopic + ", got subject: " + aSubject + ", waiting for " + aWindow);
+      if (aWindow == aSubject) {
+        Services.obs.removeObserver(observer, aTopic);
+        executeSoon(aCallback);
+        info("whenDelayedStartupFinished found our window");
+      }
+    }, "browser-delayed-startup-finished", false);
+  }
+
+  mockTransferRegisterer.register();
+
+  registerCleanupFunction(function () {
+    info("Running the cleanup code");
+    mockTransferRegisterer.unregister();
+    MockFilePicker.cleanup();
+    Services.ww.unregisterNotification(windowObserver);
+    Services.prefs.clearUserPref(ALWAYS_DOWNLOAD_DIR_PREF);
+    Services.prefs.clearUserPref(SAVE_PER_SITE_PREF);
+    info("Finished running the cleanup code");
+  });
+ 
+  Services.prefs.setBoolPref(ALWAYS_DOWNLOAD_DIR_PREF, false);
+  testOnWindow(undefined, function(win) {
+    let windowGonePromise = promiseWindowWillBeClosed(win);
+    Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, true);
+    triggerSave(win, function() {
+      windowGonePromise.then(function() {
+        Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, false);
+        testOnWindow(undefined, function(win) {
+          triggerSave(win, finish);
+        });
+      });
+    });
+  });
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/navigating_window_with_download.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+  <head><title>This window will navigate while you're downloading something</title></head>
+  <body>
+    <iframe src="http://mochi.test:8888/browser/browser/base/content/test/general/unknownContentType_file.pif"></iframe>
+  </body>
+</html>
copy from toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.pif
copy to browser/base/content/test/general/unknownContentType_file.pif
copy from toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.pif^headers^
copy to browser/base/content/test/general/unknownContentType_file.pif^headers^
--- a/toolkit/mozapps/downloads/nsHelperAppDlg.js
+++ b/toolkit/mozapps/downloads/nsHelperAppDlg.js
@@ -1,17 +1,18 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /*
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
 
-Components.utils.import("resource://gre/modules/Services.jsm");
+const {utils: Cu, interfaces: Ci, classes: Cc, results: Cr} = Components;
+Cu.import("resource://gre/modules/Services.jsm");
 
 ///////////////////////////////////////////////////////////////////////////////
 //// Helper Functions
 
 /**
  * Determines if a given directory is able to be used to download to.
  *
  * @param aDirectory
@@ -137,16 +138,24 @@ nsUnknownContentTypeDialog.prototype = {
   // show: Open XUL dialog using window watcher.  Since the dialog is not
   //       modal, it needs to be a top level window and the way to open
   //       one of those is via that route).
   show: function(aLauncher, aContext, aReason)  {
     this.mLauncher = aLauncher;
     this.mContext  = aContext;
     this.mReason   = aReason;
 
+    // Cache some information in case this context goes away:
+    try {
+      let parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+      this._mDownloadDir = new downloadModule.DownloadLastDir(parent);
+    } catch (ex) {
+      Cu.reportError("Missing window information when showing nsIHelperAppLauncherDialog: " + ex);
+    }
+
     const nsITimer = Components.interfaces.nsITimer;
     this._showTimer = Components.classes["@mozilla.org/timer;1"]
                                 .createInstance(nsITimer);
     this._showTimer.initWithCallback(this, 0, nsITimer.TYPE_ONE_SHOT);
   },
 
   // When opening from new tab, if tab closes while dialog is opening,
   // (which is a race condition on the XUL file being cached and the timer
@@ -201,16 +210,32 @@ nsUnknownContentTypeDialog.prototype = {
     this.mLauncher = aLauncher;
 
     let prefs = Components.classes["@mozilla.org/preferences-service;1"]
                           .getService(Components.interfaces.nsIPrefBranch);
     let bundle =
       Services.strings
               .createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");
 
+    let parent;
+    let gDownloadLastDir;
+    try {
+      parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+    } catch (ex) {}
+
+    if (parent) {
+      gDownloadLastDir = new downloadModule.DownloadLastDir(parent);
+    } else {
+      // Use the cached download info, but pick an arbitrary parent window
+      // because the original one is definitely gone (and nsIFilePicker doesn't like
+      // a null parent):
+      gDownloadLastDir = this._mDownloadDir;
+      parent = Services.wm.getMostRecentWindow("");
+    }
+
     Task.spawn(function() {
       if (!aForcePrompt) {
         // Check to see if the user wishes to auto save to the default download
         // folder without prompting. Note that preference might not be set.
         let autodownload = false;
         try {
           autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR);
         } catch (e) { }
@@ -236,22 +261,19 @@ nsUnknownContentTypeDialog.prototype = {
           }
         }
       }
 
       // Use file picker to show dialog.
       var nsIFilePicker = Components.interfaces.nsIFilePicker;
       var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
       var windowTitle = bundle.GetStringFromName("saveDialogTitle");
-      var parent = aContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow);
       picker.init(parent, windowTitle, nsIFilePicker.modeSave);
       picker.defaultString = aDefaultFile;
 
-      let gDownloadLastDir = new downloadModule.DownloadLastDir(parent);
-
       if (aSuggestedFileExtension) {
         // aSuggestedFileExtension includes the period, so strip it
         picker.defaultExtension = aSuggestedFileExtension.substring(1);
       }
       else {
         try {
           picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension;
         }