Bug 92737 - Part 8: Download multiple files when multiple items are dropped on Downloads button. r=enndeakin
authorTooru Fujisawa <arai_a@mac.com>
Sun, 21 Dec 2014 19:05:03 +0900
changeset 314460 1107b20c6c51c030d4f013361d5b3ae185691423
parent 314459 2d201f01953fe44d5b37dc1bbec8650f8352533f
child 314461 b1976ce06a096f7d4e381416148a04285d88e218
push id81898
push userarai_a@mac.com
push dateTue, 20 Sep 2016 07:58:28 +0000
treeherdermozilla-inbound@d87a29e5c993 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersenndeakin
bugs92737
milestone52.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 92737 - Part 8: Download multiple files when multiple items are dropped on Downloads button. r=enndeakin
browser/components/downloads/content/indicator.js
browser/components/downloads/test/browser/browser.ini
browser/components/downloads/test/browser/browser_indicatorDrop.js
browser/components/downloads/test/browser/head.js
--- a/browser/components/downloads/content/indicator.js
+++ b/browser/components/downloads/content/indicator.js
@@ -515,25 +515,28 @@ const DownloadsIndicatorView = {
 
   onDrop(aEvent) {
     let dt = aEvent.dataTransfer;
     // If dragged item is from our source, do not try to
     // redownload already downloaded file.
     if (dt.mozGetDataAt("application/x-moz-file", 0))
       return;
 
-    let name = {};
-    let url = browserDragAndDrop.drop(aEvent, name);
-    if (url) {
-      if (url.startsWith("about:")) {
-        return;
-      }
-
-      let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document;
-      saveURL(url, name.value, null, true, true, null, sourceDoc);
+    let links = browserDragAndDrop.dropLinks(aEvent);
+    if (!links.length)
+      return;
+    let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document;
+    let handled = false;
+    for (let link of links) {
+      if (link.url.startsWith("about:"))
+        continue;
+      saveURL(link.url, link.name, null, true, true, null, sourceDoc);
+      handled = true;
+    }
+    if (handled) {
       aEvent.preventDefault();
     }
   },
 
   _indicator: null,
   __indicatorCounter: null,
   __indicatorProgress: null,
 
--- a/browser/components/downloads/test/browser/browser.ini
+++ b/browser/components/downloads/test/browser/browser.ini
@@ -4,10 +4,11 @@ support-files = head.js
 [browser_basic_functionality.js]
 skip-if = buildapp == "mulet"
 [browser_first_download_panel.js]
 skip-if = os == "linux" # Bug 949434
 [browser_overflow_anchor.js]
 skip-if = os == "linux" # Bug 952422
 [browser_confirm_unblock_download.js]
 [browser_iframe_gone_mid_download.js]
+[browser_indicatorDrop.js]
 [browser_downloads_panel_block.js]
 [browser_downloads_panel_footer.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/test/browser/browser_indicatorDrop.js
@@ -0,0 +1,67 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
+  "resource://testing-common/httpd.js");
+
+registerCleanupFunction(function*() {
+  yield task_resetState();
+  yield task_clearHistory();
+});
+
+add_task(function* test_indicatorDrop() {
+  let downloadButton = document.getElementById("downloads-button");
+  ok(downloadButton, "download button present");
+
+  let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+      getService(Ci.mozIJSSubScriptLoader);
+  let EventUtils = {};
+  scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+  function* task_drop(urls) {
+    let dragData = [[{type: "text/plain", data: urls.join("\n")}]];
+
+    let list = yield Downloads.getList(Downloads.ALL);
+
+    let added = new Set();
+    let succeeded = new Set();
+    yield new Promise(function(resolve) {
+      let view = {
+        onDownloadAdded: function(download) {
+          added.add(download.source.url);
+        },
+        onDownloadChanged: function(download) {
+          if (!added.has(download.source.url))
+            return;
+          if (!download.succeeded)
+            return;
+          succeeded.add(download.source.url);
+          if (succeeded.size == urls.length) {
+            list.removeView(view).then(resolve);
+          }
+        }
+      };
+      list.addView(view).then(function() {
+        EventUtils.synthesizeDrop(downloadButton, downloadButton, dragData, "link", window);
+      });
+    });
+
+    for (let url of urls) {
+      ok(added.has(url), url + " is added to download");
+    }
+  }
+
+  // Ensure that state is reset in case previous tests didn't finish.
+  yield task_resetState();
+
+  yield setDownloadDir();
+
+  startServer();
+
+  yield* task_drop([httpUrl("file1.txt")]);
+  yield* task_drop([httpUrl("file1.txt"),
+                    httpUrl("file2.txt"),
+                    httpUrl("file3.txt")]);
+});
--- a/browser/components/downloads/test/browser/head.js
+++ b/browser/components/downloads/test/browser/head.js
@@ -15,16 +15,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
                                   "resource:///modules/DownloadsCommon.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+                                  "resource://gre/modules/PlacesUtils.jsm");
 const nsIDM = Ci.nsIDownloadManager;
 
 var gTestTargetFile = FileUtils.getFile("TmpD", ["dm-ui-test.file"]);
 gTestTargetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
 
 // Load mocking/stubbing library, sinon
 // docs: http://sinonjs.org/docs/
 Services.scriptloader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
@@ -187,16 +189,84 @@ function task_openPanel()
 {
   yield promiseFocus();
 
   let promise = promisePanelOpened();
   DownloadsPanel.showPanel();
   yield promise;
 }
 
+function setDownloadDir() {
+  let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
+  tmpDir.append("testsavedir");
+  if (!tmpDir.exists()) {
+    tmpDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+    registerCleanupFunction(function () {
+      try {
+        tmpDir.remove(true);
+      } catch (e) {
+        // On Windows debug build this may fail.
+      }
+    });
+  }
+
+  yield new Promise(resolve => {
+    SpecialPowers.pushPrefEnv({"set": [
+      ["browser.download.folderList", 2],
+      ["browser.download.dir", tmpDir, Ci.nsIFile],
+    ]}, resolve);
+  });
+}
+
+
+let gHttpServer = null;
+function startServer() {
+  gHttpServer = new HttpServer();
+  gHttpServer.start(-1);
+  registerCleanupFunction(function*() {
+     yield new Promise(function(resolve) {
+      gHttpServer.stop(resolve);
+    });
+  });
+
+  gHttpServer.registerPathHandler("/file1.txt", (request, response) => {
+    response.setStatusLine(null, 200, "OK");
+    response.write("file1");
+    response.processAsync();
+    response.finish();
+  });
+  gHttpServer.registerPathHandler("/file2.txt", (request, response) => {
+    response.setStatusLine(null, 200, "OK");
+    response.write("file2");
+    response.processAsync();
+    response.finish();
+  });
+  gHttpServer.registerPathHandler("/file3.txt", (request, response) => {
+    response.setStatusLine(null, 200, "OK");
+    response.write("file3");
+    response.processAsync();
+    response.finish();
+  });
+}
+
+function httpUrl(aFileName) {
+  return "http://localhost:" + gHttpServer.identity.primaryPort + "/" +
+    aFileName;
+}
+
+function task_clearHistory() {
+  return new Promise(function(resolve) {
+    Services.obs.addObserver(function observeCH(aSubject, aTopic, aData) {
+      Services.obs.removeObserver(observeCH, PlacesUtils.TOPIC_EXPIRATION_FINISHED);
+      resolve();
+    }, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
+    PlacesUtils.history.clear();
+  });
+}
+
 function promiseAlertDialogOpen(buttonAction) {
   return new Promise(resolve => {
     Services.ww.registerNotification(function onOpen(subj, topic, data) {
       if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
         // The test listens for the "load" event which guarantees that the alert
         // class has already been added (it is added when "DOMContentLoaded" is
         // fired).
         subj.addEventListener("load", function onLoad() {