toolkit/components/jsdownloads/test/unit/common_test_Download.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ts=2 et sw=2 tw=80: */
     3 /* Any copyright is dedicated to the Public Domain.
     4  * http://creativecommons.org/publicdomain/zero/1.0/ */
     6 /**
     7  * This script is loaded by "test_DownloadCore.js" and "test_DownloadLegacy.js"
     8  * with different values of the gUseLegacySaver variable, to apply tests to both
     9  * the "copy" and "legacy" saver implementations.
    10  */
    12 "use strict";
    14 ////////////////////////////////////////////////////////////////////////////////
    15 //// Globals
    17 /**
    18  * Creates and starts a new download, using either DownloadCopySaver or
    19  * DownloadLegacySaver based on the current test run.
    20  *
    21  * @return {Promise}
    22  * @resolves The newly created Download object.  The download may be in progress
    23  *           or already finished.  The promiseDownloadStopped function can be
    24  *           used to wait for completion.
    25  * @rejects JavaScript exception.
    26  */
    27 function promiseStartDownload(aSourceUrl) {
    28   if (gUseLegacySaver) {
    29     return promiseStartLegacyDownload(aSourceUrl);
    30   }
    32   return promiseNewDownload(aSourceUrl).then(download => {
    33     download.start();
    34     return download;
    35   });
    36 }
    38 /**
    39  * Creates and starts a new download, configured to keep partial data, and
    40  * returns only when the first part of "interruptible_resumable.txt" has been
    41  * saved to disk.  You must call "continueResponses" to allow the interruptible
    42  * request to continue.
    43  *
    44  * This function uses either DownloadCopySaver or DownloadLegacySaver based on
    45  * the current test run.
    46  *
    47  * @return {Promise}
    48  * @resolves The newly created Download object, still in progress.
    49  * @rejects JavaScript exception.
    50  */
    51 function promiseStartDownload_tryToKeepPartialData() {
    52   return Task.spawn(function () {
    53     mustInterruptResponses();
    55     // Start a new download and configure it to keep partially downloaded data.
    56     let download;
    57     if (!gUseLegacySaver) {
    58       let targetFilePath = getTempFile(TEST_TARGET_FILE_NAME).path;
    59       download = yield Downloads.createDownload({
    60         source: httpUrl("interruptible_resumable.txt"),
    61         target: { path: targetFilePath,
    62                   partFilePath: targetFilePath + ".part" },
    63       });
    64       download.tryToKeepPartialData = true;
    65       download.start();
    66     } else {
    67       // Start a download using nsIExternalHelperAppService, that is configured
    68       // to keep partially downloaded data by default.
    69       download = yield promiseStartExternalHelperAppServiceDownload();
    70     }
    72     yield promiseDownloadMidway(download);
    73     yield promisePartFileReady(download);
    75     throw new Task.Result(download);
    76   });
    77 }
    79 /**
    80  * This function should be called after the progress notification for a download
    81  * is received, and waits for the worker thread of BackgroundFileSaver to
    82  * receive the data to be written to the ".part" file on disk.
    83  *
    84  * @return {Promise}
    85  * @resolves When the ".part" file has been written to disk.
    86  * @rejects JavaScript exception.
    87  */
    88 function promisePartFileReady(aDownload) {
    89   return Task.spawn(function () {
    90     // We don't have control over the file output code in BackgroundFileSaver.
    91     // After we receive the download progress notification, we may only check
    92     // that the ".part" file has been created, while its size cannot be
    93     // determined because the file is currently open.
    94     try {
    95       do {
    96         yield promiseTimeout(50);
    97       } while (!(yield OS.File.exists(aDownload.target.partFilePath)));
    98     } catch (ex if ex instanceof OS.File.Error) {
    99       // This indicates that the file has been created and cannot be accessed.
   100       // The specific error might vary with the platform.
   101       do_print("Expected exception while checking existence: " + ex.toString());
   102       // Wait some more time to allow the write to complete.
   103       yield promiseTimeout(100);
   104     }
   105   });
   106 }
   108 ////////////////////////////////////////////////////////////////////////////////
   109 //// Tests
   111 /**
   112  * Executes a download and checks its basic properties after construction.
   113  * The download is started by constructing the simplest Download object with
   114  * the "copy" saver, or using the legacy nsITransfer interface.
   115  */
   116 add_task(function test_basic()
   117 {
   118   let targetFile = getTempFile(TEST_TARGET_FILE_NAME);
   120   let download;
   121   if (!gUseLegacySaver) {
   122     // When testing DownloadCopySaver, we have control over the download, thus
   123     // we can check its basic properties before it starts.
   124     download = yield Downloads.createDownload({
   125       source: { url: httpUrl("source.txt") },
   126       target: { path: targetFile.path },
   127       saver: { type: "copy" },
   128     });
   130     do_check_eq(download.source.url, httpUrl("source.txt"));
   131     do_check_eq(download.target.path, targetFile.path);
   133     yield download.start();
   134   } else {
   135     // When testing DownloadLegacySaver, the download is already started when it
   136     // is created, thus we must check its basic properties while in progress.
   137     download = yield promiseStartLegacyDownload(null,
   138                                                 { targetFile: targetFile });
   140     do_check_eq(download.source.url, httpUrl("source.txt"));
   141     do_check_eq(download.target.path, targetFile.path);
   143     yield promiseDownloadStopped(download);
   144   }
   146   // Check additional properties on the finished download.
   147   do_check_true(download.source.referrer === null);
   149   yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
   150 });
   152 /**
   153  * Executes a download with the tryToKeepPartialData property set, and ensures
   154  * that the file is saved correctly.  When testing DownloadLegacySaver, the
   155  * download is executed using the nsIExternalHelperAppService component.
   156  */
   157 add_task(function test_basic_tryToKeepPartialData()
   158 {
   159   let download = yield promiseStartDownload_tryToKeepPartialData();
   160   continueResponses();
   161   yield promiseDownloadStopped(download);
   163   // The target file should now have been created, and the ".part" file deleted.
   164   yield promiseVerifyContents(download.target.path,
   165                               TEST_DATA_SHORT + TEST_DATA_SHORT);
   166   do_check_false(yield OS.File.exists(download.target.partFilePath));
   167   do_check_eq(32, download.saver.getSha256Hash().length);
   168 });
   170 /**
   171  * Checks the referrer for downloads.
   172  */
   173 add_task(function test_referrer()
   174 {
   175   let sourcePath = "/test_referrer.txt";
   176   let sourceUrl = httpUrl("test_referrer.txt");
   177   let targetPath = getTempFile(TEST_TARGET_FILE_NAME).path;
   179   function cleanup() {
   180     gHttpServer.registerPathHandler(sourcePath, null);
   181   }
   182   do_register_cleanup(cleanup);
   184   gHttpServer.registerPathHandler(sourcePath, function (aRequest, aResponse) {
   185     aResponse.setHeader("Content-Type", "text/plain", false);
   187     do_check_true(aRequest.hasHeader("Referer"));
   188     do_check_eq(aRequest.getHeader("Referer"), TEST_REFERRER_URL);
   189   });
   190   let download = yield Downloads.createDownload({
   191     source: { url: sourceUrl, referrer: TEST_REFERRER_URL },
   192     target: targetPath,
   193   });
   194   do_check_eq(download.source.referrer, TEST_REFERRER_URL);
   195   yield download.start();
   197   download = yield Downloads.createDownload({
   198     source: { url: sourceUrl, referrer: TEST_REFERRER_URL,
   199               isPrivate: true },
   200     target: targetPath,
   201   });
   202   do_check_eq(download.source.referrer, TEST_REFERRER_URL);
   203   yield download.start();
   205   // Test the download still works for non-HTTP channel with referrer.
   206   sourceUrl = "data:text/html,<html><body></body></html>";
   207   download = yield Downloads.createDownload({
   208     source: { url: sourceUrl, referrer: TEST_REFERRER_URL },
   209     target: targetPath,
   210   });
   211   do_check_eq(download.source.referrer, TEST_REFERRER_URL);
   212   yield download.start();
   214   cleanup();
   215 });
   217 /**
   218  * Checks initial and final state and progress for a successful download.
   219  */
   220 add_task(function test_initial_final_state()
   221 {
   222   let download;
   223   if (!gUseLegacySaver) {
   224     // When testing DownloadCopySaver, we have control over the download, thus
   225     // we can check its state before it starts.
   226     download = yield promiseNewDownload();
   228     do_check_true(download.stopped);
   229     do_check_false(download.succeeded);
   230     do_check_false(download.canceled);
   231     do_check_true(download.error === null);
   232     do_check_eq(download.progress, 0);
   233     do_check_true(download.startTime === null);
   235     yield download.start();
   236   } else {
   237     // When testing DownloadLegacySaver, the download is already started when it
   238     // is created, thus we cannot check its initial state.
   239     download = yield promiseStartLegacyDownload();
   240     yield promiseDownloadStopped(download);
   241   }
   243   do_check_true(download.stopped);
   244   do_check_true(download.succeeded);
   245   do_check_false(download.canceled);
   246   do_check_true(download.error === null);
   247   do_check_eq(download.progress, 100);
   248   do_check_true(isValidDate(download.startTime));
   249 });
   251 /**
   252  * Checks the notification of the final download state.
   253  */
   254 add_task(function test_final_state_notified()
   255 {
   256   mustInterruptResponses();
   258   let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
   260   let onchangeNotified = false;
   261   let lastNotifiedStopped;
   262   let lastNotifiedProgress;
   263   download.onchange = function () {
   264     onchangeNotified = true;
   265     lastNotifiedStopped = download.stopped;
   266     lastNotifiedProgress = download.progress;
   267   };
   269   // Allow the download to complete.
   270   let promiseAttempt = download.start();
   271   continueResponses();
   272   yield promiseAttempt;
   274   // The view should have been notified before the download completes.
   275   do_check_true(onchangeNotified);
   276   do_check_true(lastNotifiedStopped);
   277   do_check_eq(lastNotifiedProgress, 100);
   278 });
   280 /**
   281  * Checks intermediate progress for a successful download.
   282  */
   283 add_task(function test_intermediate_progress()
   284 {
   285   mustInterruptResponses();
   287   let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
   289   yield promiseDownloadMidway(download);
   291   do_check_true(download.hasProgress);
   292   do_check_eq(download.currentBytes, TEST_DATA_SHORT.length);
   293   do_check_eq(download.totalBytes, TEST_DATA_SHORT.length * 2);
   295   // Continue after the first chunk of data is fully received.
   296   continueResponses();
   297   yield promiseDownloadStopped(download);
   299   do_check_true(download.stopped);
   300   do_check_eq(download.progress, 100);
   302   yield promiseVerifyContents(download.target.path,
   303                               TEST_DATA_SHORT + TEST_DATA_SHORT);
   304 });
   306 /**
   307  * Downloads a file with a "Content-Length" of 0 and checks the progress.
   308  */
   309 add_task(function test_empty_progress()
   310 {
   311   let download = yield promiseStartDownload(httpUrl("empty.txt"));
   312   yield promiseDownloadStopped(download);
   314   do_check_true(download.stopped);
   315   do_check_true(download.hasProgress);
   316   do_check_eq(download.progress, 100);
   317   do_check_eq(download.currentBytes, 0);
   318   do_check_eq(download.totalBytes, 0);
   320   // We should have received the content type even for an empty file.
   321   do_check_eq(download.contentType, "text/plain");
   323   do_check_eq((yield OS.File.stat(download.target.path)).size, 0);
   324 });
   326 /**
   327  * Downloads a file with a "Content-Length" of 0 with the tryToKeepPartialData
   328  * property set, and ensures that the file is saved correctly.
   329  */
   330 add_task(function test_empty_progress_tryToKeepPartialData()
   331 {
   332   // Start a new download and configure it to keep partially downloaded data.
   333   let download;
   334   if (!gUseLegacySaver) {
   335     let targetFilePath = getTempFile(TEST_TARGET_FILE_NAME).path;
   336     download = yield Downloads.createDownload({
   337       source: httpUrl("empty.txt"),
   338       target: { path: targetFilePath,
   339                 partFilePath: targetFilePath + ".part" },
   340     });
   341     download.tryToKeepPartialData = true;
   342     download.start();
   343   } else {
   344     // Start a download using nsIExternalHelperAppService, that is configured
   345     // to keep partially downloaded data by default.
   346     download = yield promiseStartExternalHelperAppServiceDownload(
   347                                                          httpUrl("empty.txt"));
   348   }
   349   yield promiseDownloadStopped(download);
   351   // The target file should now have been created, and the ".part" file deleted.
   352   do_check_eq((yield OS.File.stat(download.target.path)).size, 0);
   353   do_check_false(yield OS.File.exists(download.target.partFilePath));
   354   do_check_eq(32, download.saver.getSha256Hash().length);
   355 });
   357 /**
   358  * Downloads an empty file with no "Content-Length" and checks the progress.
   359  */
   360 add_task(function test_empty_noprogress()
   361 {
   362   let sourcePath = "/test_empty_noprogress.txt";
   363   let sourceUrl = httpUrl("test_empty_noprogress.txt");
   364   let deferRequestReceived = Promise.defer();
   366   // Register an interruptible handler that notifies us when the request occurs.
   367   function cleanup() {
   368     gHttpServer.registerPathHandler(sourcePath, null);
   369   }
   370   do_register_cleanup(cleanup);
   372   registerInterruptibleHandler(sourcePath,
   373     function firstPart(aRequest, aResponse) {
   374       aResponse.setHeader("Content-Type", "text/plain", false);
   375       deferRequestReceived.resolve();
   376     }, function secondPart(aRequest, aResponse) { });
   378   // Start the download, without allowing the request to finish.
   379   mustInterruptResponses();
   380   let download;
   381   if (!gUseLegacySaver) {
   382     // When testing DownloadCopySaver, we have control over the download, thus
   383     // we can hook its onchange callback that will be notified when the
   384     // download starts.
   385     download = yield promiseNewDownload(sourceUrl);
   387     download.onchange = function () {
   388       if (!download.stopped) {
   389         do_check_false(download.hasProgress);
   390         do_check_eq(download.currentBytes, 0);
   391         do_check_eq(download.totalBytes, 0);
   392       }
   393     };
   395     download.start();
   396   } else {
   397     // When testing DownloadLegacySaver, the download is already started when it
   398     // is created, and it may have already made all needed property change
   399     // notifications, thus there is no point in checking the onchange callback.
   400     download = yield promiseStartLegacyDownload(sourceUrl);
   401   }
   403   // Wait for the request to be received by the HTTP server, but don't allow the
   404   // request to finish yet.  Before checking the download state, wait for the
   405   // events to be processed by the client.
   406   yield deferRequestReceived.promise;
   407   yield promiseExecuteSoon();
   409   // Check that this download has no progress report.
   410   do_check_false(download.stopped);
   411   do_check_false(download.hasProgress);
   412   do_check_eq(download.currentBytes, 0);
   413   do_check_eq(download.totalBytes, 0);
   415   // Now allow the response to finish.
   416   continueResponses();
   417   yield promiseDownloadStopped(download);
   419   // We should have received the content type even if no progress is reported.
   420   do_check_eq(download.contentType, "text/plain");
   422   // Verify the state of the completed download.
   423   do_check_true(download.stopped);
   424   do_check_false(download.hasProgress);
   425   do_check_eq(download.progress, 100);
   426   do_check_eq(download.currentBytes, 0);
   427   do_check_eq(download.totalBytes, 0);
   429   do_check_eq((yield OS.File.stat(download.target.path)).size, 0);
   430 });
   432 /**
   433  * Calls the "start" method two times before the download is finished.
   434  */
   435 add_task(function test_start_twice()
   436 {
   437   mustInterruptResponses();
   439   let download;
   440   if (!gUseLegacySaver) {
   441     // When testing DownloadCopySaver, we have control over the download, thus
   442     // we can start the download later during the test.
   443     download = yield promiseNewDownload(httpUrl("interruptible.txt"));
   444   } else {
   445     // When testing DownloadLegacySaver, the download is already started when it
   446     // is created.  Effectively, we are starting the download three times.
   447     download = yield promiseStartLegacyDownload(httpUrl("interruptible.txt"));
   448   }
   450   // Call the start method two times.
   451   let promiseAttempt1 = download.start();
   452   let promiseAttempt2 = download.start();
   454   // Allow the download to finish.
   455   continueResponses();
   457   // Both promises should now be resolved.
   458   yield promiseAttempt1;
   459   yield promiseAttempt2;
   461   do_check_true(download.stopped);
   462   do_check_true(download.succeeded);
   463   do_check_false(download.canceled);
   464   do_check_true(download.error === null);
   466   yield promiseVerifyContents(download.target.path,
   467                               TEST_DATA_SHORT + TEST_DATA_SHORT);
   468 });
   470 /**
   471  * Cancels a download and verifies that its state is reported correctly.
   472  */
   473 add_task(function test_cancel_midway()
   474 {
   475   mustInterruptResponses();
   477   // In this test case, we execute different checks that are only possible with
   478   // DownloadCopySaver or DownloadLegacySaver respectively.
   479   let download;
   480   let options = {};
   481   if (!gUseLegacySaver) {
   482     download = yield promiseNewDownload(httpUrl("interruptible.txt"));
   483   } else {
   484     download = yield promiseStartLegacyDownload(httpUrl("interruptible.txt"),
   485                                                 options);
   486   }
   488   // Cancel the download after receiving the first part of the response.
   489   let deferCancel = Promise.defer();
   490   let onchange = function () {
   491     if (!download.stopped && !download.canceled && download.progress == 50) {
   492       // Cancel the download immediately during the notification.
   493       deferCancel.resolve(download.cancel());
   495       // The state change happens immediately after calling "cancel", but
   496       // temporary files or part files may still exist at this point.
   497       do_check_true(download.canceled);
   498     }
   499   };
   501   // Register for the notification, but also call the function directly in
   502   // case the download already reached the expected progress.  This may happen
   503   // when using DownloadLegacySaver.
   504   download.onchange = onchange;
   505   onchange();
   507   let promiseAttempt;
   508   if (!gUseLegacySaver) {
   509     promiseAttempt = download.start();
   510   }
   512   // Wait on the promise returned by the "cancel" method to ensure that the
   513   // cancellation process finished and temporary files were removed.
   514   yield deferCancel.promise;
   516   if (gUseLegacySaver) {
   517     // The nsIWebBrowserPersist instance should have been canceled now.
   518     do_check_eq(options.outPersist.result, Cr.NS_ERROR_ABORT);
   519   }
   521   do_check_true(download.stopped);
   522   do_check_true(download.canceled);
   523   do_check_true(download.error === null);
   525   do_check_false(yield OS.File.exists(download.target.path));
   527   // Progress properties are not reset by canceling.
   528   do_check_eq(download.progress, 50);
   529   do_check_eq(download.totalBytes, TEST_DATA_SHORT.length * 2);
   530   do_check_eq(download.currentBytes, TEST_DATA_SHORT.length);
   532   if (!gUseLegacySaver) {
   533     // The promise returned by "start" should have been rejected meanwhile.
   534     try {
   535       yield promiseAttempt;
   536       do_throw("The download should have been canceled.");
   537     } catch (ex if ex instanceof Downloads.Error) {
   538       do_check_false(ex.becauseSourceFailed);
   539       do_check_false(ex.becauseTargetFailed);
   540     }
   541   }
   542 });
   544 /**
   545  * Cancels a download while keeping partially downloaded data, and verifies that
   546  * both the target file and the ".part" file are deleted.
   547  */
   548 add_task(function test_cancel_midway_tryToKeepPartialData()
   549 {
   550   let download = yield promiseStartDownload_tryToKeepPartialData();
   552   do_check_true(yield OS.File.exists(download.target.path));
   553   do_check_true(yield OS.File.exists(download.target.partFilePath));
   555   yield download.cancel();
   556   yield download.removePartialData();
   558   do_check_true(download.stopped);
   559   do_check_true(download.canceled);
   560   do_check_true(download.error === null);
   562   do_check_false(yield OS.File.exists(download.target.path));
   563   do_check_false(yield OS.File.exists(download.target.partFilePath));
   564 });
   566 /**
   567  * Cancels a download right after starting it.
   568  */
   569 add_task(function test_cancel_immediately()
   570 {
   571   mustInterruptResponses();
   573   let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
   575   let promiseAttempt = download.start();
   576   do_check_false(download.stopped);
   578   let promiseCancel = download.cancel();
   579   do_check_true(download.canceled);
   581   // At this point, we don't know whether the download has already stopped or
   582   // is still waiting for cancellation.  We can wait on the promise returned
   583   // by the "start" method to know for sure.
   584   try {
   585     yield promiseAttempt;
   586     do_throw("The download should have been canceled.");
   587   } catch (ex if ex instanceof Downloads.Error) {
   588     do_check_false(ex.becauseSourceFailed);
   589     do_check_false(ex.becauseTargetFailed);
   590   }
   592   do_check_true(download.stopped);
   593   do_check_true(download.canceled);
   594   do_check_true(download.error === null);
   596   do_check_false(yield OS.File.exists(download.target.path));
   598   // Check that the promise returned by the "cancel" method has been resolved.
   599   yield promiseCancel;
   600 });
   602 /**
   603  * Cancels and restarts a download sequentially.
   604  */
   605 add_task(function test_cancel_midway_restart()
   606 {
   607   mustInterruptResponses();
   609   let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
   611   // The first time, cancel the download midway.
   612   yield promiseDownloadMidway(download);
   613   yield download.cancel();
   615   do_check_true(download.stopped);
   617   // The second time, we'll provide the entire interruptible response.
   618   continueResponses();
   619   download.onchange = null;
   620   let promiseAttempt = download.start();
   622   // Download state should have already been reset.
   623   do_check_false(download.stopped);
   624   do_check_false(download.canceled);
   625   do_check_true(download.error === null);
   627   // For the following test, we rely on the network layer reporting its progress
   628   // asynchronously.  Otherwise, there is nothing stopping the restarted
   629   // download from reaching the same progress as the first request already.
   630   do_check_eq(download.progress, 0);
   631   do_check_eq(download.totalBytes, 0);
   632   do_check_eq(download.currentBytes, 0);
   634   yield promiseAttempt;
   636   do_check_true(download.stopped);
   637   do_check_true(download.succeeded);
   638   do_check_false(download.canceled);
   639   do_check_true(download.error === null);
   641   yield promiseVerifyContents(download.target.path,
   642                               TEST_DATA_SHORT + TEST_DATA_SHORT);
   643 });
   645 /**
   646  * Cancels a download and restarts it from where it stopped.
   647  */
   648 add_task(function test_cancel_midway_restart_tryToKeepPartialData()
   649 {
   650   let download = yield promiseStartDownload_tryToKeepPartialData();
   651   yield download.cancel();
   653   do_check_true(download.stopped);
   654   do_check_true(download.hasPartialData);
   656   // The target file should not exist, but we should have kept the partial data.
   657   do_check_false(yield OS.File.exists(download.target.path));
   658   yield promiseVerifyContents(download.target.partFilePath, TEST_DATA_SHORT);
   660   // Verify that the server sent the response from the start.
   661   do_check_eq(gMostRecentFirstBytePos, 0);
   663   // The second time, we'll request and obtain the second part of the response,
   664   // but we still stop when half of the remaining progress is reached.
   665   let deferMidway = Promise.defer();
   666   download.onchange = function () {
   667     if (!download.stopped && !download.canceled &&
   668         download.currentBytes == Math.floor(TEST_DATA_SHORT.length * 3 / 2)) {
   669       download.onchange = null;
   670       deferMidway.resolve();
   671     }
   672   };
   674   mustInterruptResponses();
   675   let promiseAttempt = download.start();
   677   // Continue when the number of bytes we received is correct, then check that
   678   // progress is at about 75 percent.  The exact figure may vary because of
   679   // rounding issues, since the total number of bytes in the response might not
   680   // be a multiple of four.
   681   yield deferMidway.promise;
   682   do_check_true(download.progress > 72 && download.progress < 78);
   684   // Now we allow the download to finish.
   685   continueResponses();
   686   yield promiseAttempt;
   688   // Check that the server now sent the second part only.
   689   do_check_eq(gMostRecentFirstBytePos, TEST_DATA_SHORT.length);
   691   // The target file should now have been created, and the ".part" file deleted.
   692   yield promiseVerifyContents(download.target.path,
   693                               TEST_DATA_SHORT + TEST_DATA_SHORT);
   694   do_check_false(yield OS.File.exists(download.target.partFilePath));
   695 });
   697 /**
   698  * Cancels a download while keeping partially downloaded data, then removes the
   699  * data and restarts the download from the beginning.
   700  */
   701 add_task(function test_cancel_midway_restart_removePartialData()
   702 {
   703   let download = yield promiseStartDownload_tryToKeepPartialData();
   704   yield download.cancel();
   706   do_check_true(download.hasPartialData);
   707   yield promiseVerifyContents(download.target.partFilePath, TEST_DATA_SHORT);
   709   yield download.removePartialData();
   711   do_check_false(download.hasPartialData);
   712   do_check_false(yield OS.File.exists(download.target.partFilePath));
   714   // The second time, we'll request and obtain the entire response again.
   715   continueResponses();
   716   yield download.start();
   718   // Verify that the server sent the response from the start.
   719   do_check_eq(gMostRecentFirstBytePos, 0);
   721   // The target file should now have been created, and the ".part" file deleted.
   722   yield promiseVerifyContents(download.target.path,
   723                               TEST_DATA_SHORT + TEST_DATA_SHORT);
   724   do_check_false(yield OS.File.exists(download.target.partFilePath));
   725 });
   727 /**
   728  * Cancels a download while keeping partially downloaded data, then removes the
   729  * data and restarts the download from the beginning without keeping the partial
   730  * data anymore.
   731  */
   732 add_task(function test_cancel_midway_restart_tryToKeepPartialData_false()
   733 {
   734   let download = yield promiseStartDownload_tryToKeepPartialData();
   735   yield download.cancel();
   737   download.tryToKeepPartialData = false;
   739   // The above property change does not affect existing partial data.
   740   do_check_true(download.hasPartialData);
   741   yield promiseVerifyContents(download.target.partFilePath, TEST_DATA_SHORT);
   743   yield download.removePartialData();
   744   do_check_false(yield OS.File.exists(download.target.partFilePath));
   746   // Restart the download from the beginning.
   747   mustInterruptResponses();
   748   download.start();
   750   yield promiseDownloadMidway(download);
   751   yield promisePartFileReady(download);
   753   // While the download is in progress, we should still have a ".part" file.
   754   do_check_false(download.hasPartialData);
   755   do_check_true(yield OS.File.exists(download.target.partFilePath));
   757   yield download.cancel();
   759   // The ".part" file should be deleted now that the download is canceled.
   760   do_check_false(download.hasPartialData);
   761   do_check_false(yield OS.File.exists(download.target.partFilePath));
   763   // The third time, we'll request and obtain the entire response again.
   764   continueResponses();
   765   yield download.start();
   767   // Verify that the server sent the response from the start.
   768   do_check_eq(gMostRecentFirstBytePos, 0);
   770   // The target file should now have been created, and the ".part" file deleted.
   771   yield promiseVerifyContents(download.target.path,
   772                               TEST_DATA_SHORT + TEST_DATA_SHORT);
   773   do_check_false(yield OS.File.exists(download.target.partFilePath));
   774 });
   776 /**
   777  * Cancels a download right after starting it, then restarts it immediately.
   778  */
   779 add_task(function test_cancel_immediately_restart_immediately()
   780 {
   781   mustInterruptResponses();
   783   let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
   784   let promiseAttempt = download.start();
   786   do_check_false(download.stopped);
   788   download.cancel();
   789   do_check_true(download.canceled);
   791   let promiseRestarted = download.start();
   792   do_check_false(download.stopped);
   793   do_check_false(download.canceled);
   794   do_check_true(download.error === null);
   796   // For the following test, we rely on the network layer reporting its progress
   797   // asynchronously.  Otherwise, there is nothing stopping the restarted
   798   // download from reaching the same progress as the first request already.
   799   do_check_eq(download.hasProgress, false);
   800   do_check_eq(download.progress, 0);
   801   do_check_eq(download.totalBytes, 0);
   802   do_check_eq(download.currentBytes, 0);
   804   // Ensure the next request is now allowed to complete, regardless of whether
   805   // the canceled request was received by the server or not.
   806   continueResponses();
   807   try {
   808     yield promiseAttempt;
   809     // If we get here, it means that the first attempt actually succeeded.  In
   810     // fact, this could be a valid outcome, because the cancellation request may
   811     // not have been processed in time before the download finished.
   812     do_print("The download should have been canceled.");
   813   } catch (ex if ex instanceof Downloads.Error) {
   814     do_check_false(ex.becauseSourceFailed);
   815     do_check_false(ex.becauseTargetFailed);
   816   }
   818   yield promiseRestarted;
   820   do_check_true(download.stopped);
   821   do_check_true(download.succeeded);
   822   do_check_false(download.canceled);
   823   do_check_true(download.error === null);
   825   yield promiseVerifyContents(download.target.path,
   826                               TEST_DATA_SHORT + TEST_DATA_SHORT);
   827 });
   829 /**
   830  * Cancels a download midway, then restarts it immediately.
   831  */
   832 add_task(function test_cancel_midway_restart_immediately()
   833 {
   834   mustInterruptResponses();
   836   let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
   837   let promiseAttempt = download.start();
   839   // The first time, cancel the download midway.
   840   yield promiseDownloadMidway(download);
   841   download.cancel();
   842   do_check_true(download.canceled);
   844   let promiseRestarted = download.start();
   845   do_check_false(download.stopped);
   846   do_check_false(download.canceled);
   847   do_check_true(download.error === null);
   849   // For the following test, we rely on the network layer reporting its progress
   850   // asynchronously.  Otherwise, there is nothing stopping the restarted
   851   // download from reaching the same progress as the first request already.
   852   do_check_eq(download.hasProgress, false);
   853   do_check_eq(download.progress, 0);
   854   do_check_eq(download.totalBytes, 0);
   855   do_check_eq(download.currentBytes, 0);
   857   // The second request is allowed to complete.
   858   continueResponses();
   859   try {
   860     yield promiseAttempt;
   861     do_throw("The download should have been canceled.");
   862   } catch (ex if ex instanceof Downloads.Error) {
   863     do_check_false(ex.becauseSourceFailed);
   864     do_check_false(ex.becauseTargetFailed);
   865   }
   867   yield promiseRestarted;
   869   do_check_true(download.stopped);
   870   do_check_true(download.succeeded);
   871   do_check_false(download.canceled);
   872   do_check_true(download.error === null);
   874   yield promiseVerifyContents(download.target.path,
   875                               TEST_DATA_SHORT + TEST_DATA_SHORT);
   876 });
   878 /**
   879  * Calls the "cancel" method on a successful download.
   880  */
   881 add_task(function test_cancel_successful()
   882 {
   883   let download = yield promiseStartDownload();
   884   yield promiseDownloadStopped(download);
   886   // The cancel method should succeed with no effect.
   887   yield download.cancel();
   889   do_check_true(download.stopped);
   890   do_check_true(download.succeeded);
   891   do_check_false(download.canceled);
   892   do_check_true(download.error === null);
   894   yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
   895 });
   897 /**
   898  * Calls the "cancel" method two times in a row.
   899  */
   900 add_task(function test_cancel_twice()
   901 {
   902   mustInterruptResponses();
   904   let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
   906   let promiseAttempt = download.start();
   907   do_check_false(download.stopped);
   909   let promiseCancel1 = download.cancel();
   910   do_check_true(download.canceled);
   911   let promiseCancel2 = download.cancel();
   913   try {
   914     yield promiseAttempt;
   915     do_throw("The download should have been canceled.");
   916   } catch (ex if ex instanceof Downloads.Error) {
   917     do_check_false(ex.becauseSourceFailed);
   918     do_check_false(ex.becauseTargetFailed);
   919   }
   921   // Both promises should now be resolved.
   922   yield promiseCancel1;
   923   yield promiseCancel2;
   925   do_check_true(download.stopped);
   926   do_check_false(download.succeeded);
   927   do_check_true(download.canceled);
   928   do_check_true(download.error === null);
   930   do_check_false(yield OS.File.exists(download.target.path));
   931 });
   933 /**
   934  * Checks that a download cannot be restarted after the "finalize" method.
   935  */
   936 add_task(function test_finalize()
   937 {
   938   mustInterruptResponses();
   940   let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
   942   let promiseFinalized = download.finalize();
   944   try {
   945     yield download.start();
   946     do_throw("It should not be possible to restart after finalization.");
   947   } catch (ex) { }
   949   yield promiseFinalized;
   951   do_check_true(download.stopped);
   952   do_check_false(download.succeeded);
   953   do_check_true(download.canceled);
   954   do_check_true(download.error === null);
   956   do_check_false(yield OS.File.exists(download.target.path));
   957 });
   959 /**
   960  * Checks that the "finalize" method can remove partially downloaded data.
   961  */
   962 add_task(function test_finalize_tryToKeepPartialData()
   963 {
   964   // Check finalization without removing partial data.
   965   let download = yield promiseStartDownload_tryToKeepPartialData();
   966   yield download.finalize();
   968   do_check_true(download.hasPartialData);
   969   do_check_true(yield OS.File.exists(download.target.partFilePath));
   971   // Clean up.
   972   yield download.removePartialData();
   974   // Check finalization while removing partial data.
   975   download = yield promiseStartDownload_tryToKeepPartialData();
   976   yield download.finalize(true);
   978   do_check_false(download.hasPartialData);
   979   do_check_false(yield OS.File.exists(download.target.partFilePath));
   980 });
   982 /**
   983  * Checks that whenSucceeded returns a promise that is resolved after a restart.
   984  */
   985 add_task(function test_whenSucceeded_after_restart()
   986 {
   987   mustInterruptResponses();
   989   let promiseSucceeded;
   991   let download;
   992   if (!gUseLegacySaver) {
   993     // When testing DownloadCopySaver, we have control over the download, thus
   994     // we can verify getting a reference before the first download attempt.
   995     download = yield promiseNewDownload(httpUrl("interruptible.txt"));
   996     promiseSucceeded = download.whenSucceeded();
   997     download.start();
   998   } else {
   999     // When testing DownloadLegacySaver, the download is already started when it
  1000     // is created, thus we cannot get the reference before the first attempt.
  1001     download = yield promiseStartLegacyDownload(httpUrl("interruptible.txt"));
  1002     promiseSucceeded = download.whenSucceeded();
  1005   // Cancel the first download attempt.
  1006   yield download.cancel();
  1008   // The second request is allowed to complete.
  1009   continueResponses();
  1010   download.start();
  1012   // Wait for the download to finish by waiting on the whenSucceeded promise.
  1013   yield promiseSucceeded;
  1015   do_check_true(download.stopped);
  1016   do_check_true(download.succeeded);
  1017   do_check_false(download.canceled);
  1018   do_check_true(download.error === null);
  1020   yield promiseVerifyContents(download.target.path,
  1021                               TEST_DATA_SHORT + TEST_DATA_SHORT);
  1022 });
  1024 /**
  1025  * Ensures download error details are reported on network failures.
  1026  */
  1027 add_task(function test_error_source()
  1029   let serverSocket = startFakeServer();
  1030   try {
  1031     let sourceUrl = "http://localhost:" + serverSocket.port + "/source.txt";
  1033     let download;
  1034     try {
  1035       if (!gUseLegacySaver) {
  1036         // When testing DownloadCopySaver, we want to check that the promise
  1037         // returned by the "start" method is rejected.
  1038         download = yield promiseNewDownload(sourceUrl);
  1040         do_check_true(download.error === null);
  1042         yield download.start();
  1043       } else {
  1044         // When testing DownloadLegacySaver, we cannot be sure whether we are
  1045         // testing the promise returned by the "start" method or we are testing
  1046         // the "error" property checked by promiseDownloadStopped.  This happens
  1047         // because we don't have control over when the download is started.
  1048         download = yield promiseStartLegacyDownload(sourceUrl);
  1049         yield promiseDownloadStopped(download);
  1051       do_throw("The download should have failed.");
  1052     } catch (ex if ex instanceof Downloads.Error && ex.becauseSourceFailed) {
  1053       // A specific error object is thrown when reading from the source fails.
  1056     // Check the properties now that the download stopped.
  1057     do_check_true(download.stopped);
  1058     do_check_false(download.canceled);
  1059     do_check_true(download.error !== null);
  1060     do_check_true(download.error.becauseSourceFailed);
  1061     do_check_false(download.error.becauseTargetFailed);
  1063     do_check_false(yield OS.File.exists(download.target.path));
  1064   } finally {
  1065     serverSocket.close();
  1067 });
  1069 /**
  1070  * Ensures download error details are reported on local writing failures.
  1071  */
  1072 add_task(function test_error_target()
  1074   // Create a file without write access permissions before downloading.
  1075   let targetFile = getTempFile(TEST_TARGET_FILE_NAME);
  1076   targetFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0);
  1077   try {
  1078     let download;
  1079     try {
  1080       if (!gUseLegacySaver) {
  1081         // When testing DownloadCopySaver, we want to check that the promise
  1082         // returned by the "start" method is rejected.
  1083         download = yield Downloads.createDownload({
  1084           source: httpUrl("source.txt"),
  1085           target: targetFile,
  1086         });
  1087         yield download.start();
  1088       } else {
  1089         // When testing DownloadLegacySaver, we cannot be sure whether we are
  1090         // testing the promise returned by the "start" method or we are testing
  1091         // the "error" property checked by promiseDownloadStopped.  This happens
  1092         // because we don't have control over when the download is started.
  1093         download = yield promiseStartLegacyDownload(null,
  1094                                                     { targetFile: targetFile });
  1095         yield promiseDownloadStopped(download);
  1097       do_throw("The download should have failed.");
  1098     } catch (ex if ex instanceof Downloads.Error && ex.becauseTargetFailed) {
  1099       // A specific error object is thrown when writing to the target fails.
  1102     // Check the properties now that the download stopped.
  1103     do_check_true(download.stopped);
  1104     do_check_false(download.canceled);
  1105     do_check_true(download.error !== null);
  1106     do_check_true(download.error.becauseTargetFailed);
  1107     do_check_false(download.error.becauseSourceFailed);
  1108   } finally {
  1109     // Restore the default permissions to allow deleting the file on Windows.
  1110     if (targetFile.exists()) {
  1111       targetFile.permissions = FileUtils.PERMS_FILE;
  1112       targetFile.remove(false);
  1115 });
  1117 /**
  1118  * Restarts a failed download.
  1119  */
  1120 add_task(function test_error_restart()
  1122   let download;
  1124   // Create a file without write access permissions before downloading.
  1125   let targetFile = getTempFile(TEST_TARGET_FILE_NAME);
  1126   targetFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0);
  1127   try {
  1128     // Use DownloadCopySaver or DownloadLegacySaver based on the test run,
  1129     // specifying the target file we created.
  1130     if (!gUseLegacySaver) {
  1131       download = yield Downloads.createDownload({
  1132         source: httpUrl("source.txt"),
  1133         target: targetFile,
  1134       });
  1135       download.start();
  1136     } else {
  1137       download = yield promiseStartLegacyDownload(null,
  1138                                                   { targetFile: targetFile });
  1140     yield promiseDownloadStopped(download);
  1141     do_throw("The download should have failed.");
  1142   } catch (ex if ex instanceof Downloads.Error && ex.becauseTargetFailed) {
  1143     // A specific error object is thrown when writing to the target fails.
  1144   } finally {
  1145     // Restore the default permissions to allow deleting the file on Windows.
  1146     if (targetFile.exists()) {
  1147       targetFile.permissions = FileUtils.PERMS_FILE;
  1149       // Also for Windows, rename the file before deleting.  This makes the
  1150       // current file name available immediately for a new file, while deleting
  1151       // in place prevents creation of a file with the same name for some time.
  1152       targetFile.moveTo(null, targetFile.leafName + ".delete.tmp");
  1153       targetFile.remove(false);
  1157   // Restart the download and wait for completion.
  1158   yield download.start();
  1160   do_check_true(download.stopped);
  1161   do_check_true(download.succeeded);
  1162   do_check_false(download.canceled);
  1163   do_check_true(download.error === null);
  1164   do_check_eq(download.progress, 100);
  1166   yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
  1167 });
  1169 /**
  1170  * Executes download in both public and private modes.
  1171  */
  1172 add_task(function test_public_and_private()
  1174   let sourcePath = "/test_public_and_private.txt";
  1175   let sourceUrl = httpUrl("test_public_and_private.txt");
  1176   let testCount = 0;
  1178   // Apply pref to allow all cookies.
  1179   Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
  1181   function cleanup() {
  1182     Services.prefs.clearUserPref("network.cookie.cookieBehavior");
  1183     Services.cookies.removeAll();
  1184     gHttpServer.registerPathHandler(sourcePath, null);
  1186   do_register_cleanup(cleanup);
  1188   gHttpServer.registerPathHandler(sourcePath, function (aRequest, aResponse) {
  1189     aResponse.setHeader("Content-Type", "text/plain", false);
  1191     if (testCount == 0) {
  1192       // No cookies should exist for first public download.
  1193       do_check_false(aRequest.hasHeader("Cookie"));
  1194       aResponse.setHeader("Set-Cookie", "foobar=1", false);
  1195       testCount++;
  1196     } else if (testCount == 1) {
  1197       // The cookie should exists for second public download.
  1198       do_check_true(aRequest.hasHeader("Cookie"));
  1199       do_check_eq(aRequest.getHeader("Cookie"), "foobar=1");
  1200       testCount++;
  1201     } else if (testCount == 2)  {
  1202       // No cookies should exist for first private download.
  1203       do_check_false(aRequest.hasHeader("Cookie"));
  1205   });
  1207   let targetFile = getTempFile(TEST_TARGET_FILE_NAME);
  1208   yield Downloads.fetch(sourceUrl, targetFile);
  1209   yield Downloads.fetch(sourceUrl, targetFile);
  1211   if (!gUseLegacySaver) {
  1212     let download = yield Downloads.createDownload({
  1213       source: { url: sourceUrl, isPrivate: true },
  1214       target: targetFile,
  1215     });
  1216     yield download.start();
  1217   } else {
  1218     let download = yield promiseStartLegacyDownload(sourceUrl,
  1219                                                     { isPrivate: true });
  1220     yield promiseDownloadStopped(download);
  1223   cleanup();
  1224 });
  1226 /**
  1227  * Checks the startTime gets updated even after a restart.
  1228  */
  1229 add_task(function test_cancel_immediately_restart_and_check_startTime()
  1231   let download = yield promiseStartDownload();
  1233   let startTime = download.startTime;
  1234   do_check_true(isValidDate(download.startTime));
  1236   yield download.cancel();
  1237   do_check_eq(download.startTime.getTime(), startTime.getTime());
  1239   // Wait for a timeout.
  1240   yield promiseTimeout(10);
  1242   yield download.start();
  1243   do_check_true(download.startTime.getTime() > startTime.getTime());
  1244 });
  1246 /**
  1247  * Executes download with content-encoding.
  1248  */
  1249 add_task(function test_with_content_encoding()
  1251   let sourcePath = "/test_with_content_encoding.txt";
  1252   let sourceUrl = httpUrl("test_with_content_encoding.txt");
  1254   function cleanup() {
  1255     gHttpServer.registerPathHandler(sourcePath, null);
  1257   do_register_cleanup(cleanup);
  1259   gHttpServer.registerPathHandler(sourcePath, function (aRequest, aResponse) {
  1260     aResponse.setHeader("Content-Type", "text/plain", false);
  1261     aResponse.setHeader("Content-Encoding", "gzip", false);
  1262     aResponse.setHeader("Content-Length",
  1263                         "" + TEST_DATA_SHORT_GZIP_ENCODED.length, false);
  1265     let bos = new BinaryOutputStream(aResponse.bodyOutputStream);
  1266     bos.writeByteArray(TEST_DATA_SHORT_GZIP_ENCODED,
  1267                        TEST_DATA_SHORT_GZIP_ENCODED.length);
  1268   });
  1270   let download = yield promiseStartDownload(sourceUrl);
  1271   yield promiseDownloadStopped(download);
  1273   do_check_eq(download.progress, 100);
  1274   do_check_eq(download.totalBytes, TEST_DATA_SHORT_GZIP_ENCODED.length);
  1276   // Ensure the content matches the decoded test data.
  1277   yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
  1279   cleanup();
  1280 });
  1282 /**
  1283  * Checks that the file is not decoded if the extension matches the encoding.
  1284  */
  1285 add_task(function test_with_content_encoding_ignore_extension()
  1287   let sourcePath = "/test_with_content_encoding_ignore_extension.gz";
  1288   let sourceUrl = httpUrl("test_with_content_encoding_ignore_extension.gz");
  1290   function cleanup() {
  1291     gHttpServer.registerPathHandler(sourcePath, null);
  1293   do_register_cleanup(cleanup);
  1295   gHttpServer.registerPathHandler(sourcePath, function (aRequest, aResponse) {
  1296     aResponse.setHeader("Content-Type", "text/plain", false);
  1297     aResponse.setHeader("Content-Encoding", "gzip", false);
  1298     aResponse.setHeader("Content-Length",
  1299                         "" + TEST_DATA_SHORT_GZIP_ENCODED.length, false);
  1301     let bos = new BinaryOutputStream(aResponse.bodyOutputStream);
  1302     bos.writeByteArray(TEST_DATA_SHORT_GZIP_ENCODED,
  1303                        TEST_DATA_SHORT_GZIP_ENCODED.length);
  1304   });
  1306   let download = yield promiseStartDownload(sourceUrl);
  1307   yield promiseDownloadStopped(download);
  1309   do_check_eq(download.progress, 100);
  1310   do_check_eq(download.totalBytes, TEST_DATA_SHORT_GZIP_ENCODED.length);
  1312   // Ensure the content matches the encoded test data.  We convert the data to a
  1313   // string before executing the content check.
  1314   yield promiseVerifyContents(download.target.path,
  1315         String.fromCharCode.apply(String, TEST_DATA_SHORT_GZIP_ENCODED));
  1317   cleanup();
  1318 });
  1320 /**
  1321  * Cancels and restarts a download sequentially with content-encoding.
  1322  */
  1323 add_task(function test_cancel_midway_restart_with_content_encoding()
  1325   mustInterruptResponses();
  1327   let download = yield promiseStartDownload(httpUrl("interruptible_gzip.txt"));
  1329   // The first time, cancel the download midway.
  1330   let deferCancel = Promise.defer();
  1331   let onchange = function () {
  1332     if (!download.stopped && !download.canceled &&
  1333         download.currentBytes == TEST_DATA_SHORT_GZIP_ENCODED_FIRST.length) {
  1334       deferCancel.resolve(download.cancel());
  1336   };
  1338   // Register for the notification, but also call the function directly in
  1339   // case the download already reached the expected progress.
  1340   download.onchange = onchange;
  1341   onchange();
  1343   yield deferCancel.promise;
  1345   do_check_true(download.stopped);
  1347   // The second time, we'll provide the entire interruptible response.
  1348   continueResponses();
  1349   download.onchange = null;
  1350   yield download.start();
  1352   do_check_eq(download.progress, 100);
  1353   do_check_eq(download.totalBytes, TEST_DATA_SHORT_GZIP_ENCODED.length);
  1355   yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
  1356 });
  1358 /**
  1359  * Download with parental controls enabled.
  1360  */
  1361 add_task(function test_blocked_parental_controls()
  1363   function cleanup() {
  1364     DownloadIntegration.shouldBlockInTest = false;
  1366   do_register_cleanup(cleanup);
  1367   DownloadIntegration.shouldBlockInTest = true;
  1369   let download;
  1370   try {
  1371     if (!gUseLegacySaver) {
  1372       // When testing DownloadCopySaver, we want to check that the promise
  1373       // returned by the "start" method is rejected.
  1374       download = yield promiseNewDownload();
  1375       yield download.start();
  1376     } else {
  1377       // When testing DownloadLegacySaver, we cannot be sure whether we are
  1378       // testing the promise returned by the "start" method or we are testing
  1379       // the "error" property checked by promiseDownloadStopped.  This happens
  1380       // because we don't have control over when the download is started.
  1381       download = yield promiseStartLegacyDownload();
  1382       yield promiseDownloadStopped(download);
  1384     do_throw("The download should have blocked.");
  1385   } catch (ex if ex instanceof Downloads.Error && ex.becauseBlocked) {
  1386     do_check_true(ex.becauseBlockedByParentalControls);
  1387     do_check_true(download.error.becauseBlockedByParentalControls);
  1390   // Now that the download stopped, the target file should not exist.
  1391   do_check_false(yield OS.File.exists(download.target.path));
  1393   cleanup();
  1394 });
  1396 /**
  1397  * Test a download that will be blocked by Windows parental controls by
  1398  * resulting in an HTTP status code of 450.
  1399  */
  1400 add_task(function test_blocked_parental_controls_httpstatus450()
  1402   let download;
  1403   try {
  1404     if (!gUseLegacySaver) {
  1405       download = yield promiseNewDownload(httpUrl("parentalblocked.zip"));
  1406       yield download.start();
  1408     else {
  1409       download = yield promiseStartLegacyDownload(httpUrl("parentalblocked.zip"));
  1410       yield promiseDownloadStopped(download);
  1412     do_throw("The download should have blocked.");
  1413   } catch (ex if ex instanceof Downloads.Error && ex.becauseBlocked) {
  1414     do_check_true(ex.becauseBlockedByParentalControls);
  1415     do_check_true(download.error.becauseBlockedByParentalControls);
  1416     do_check_true(download.stopped);
  1419   do_check_false(yield OS.File.exists(download.target.path));
  1420 });
  1422 /**
  1423  * Check that DownloadCopySaver can always retrieve the hash.
  1424  * DownloadLegacySaver can only retrieve the hash when
  1425  * nsIExternalHelperAppService is invoked.
  1426  */
  1427 add_task(function test_getSha256Hash()
  1429   if (!gUseLegacySaver) {
  1430     let download = yield promiseStartDownload(httpUrl("source.txt"));
  1431     yield promiseDownloadStopped(download);
  1432     do_check_true(download.stopped);
  1433     do_check_eq(32, download.saver.getSha256Hash().length);
  1435 });
  1437 /**
  1438  * Checks that application reputation blocks the download and the target file
  1439  * does not exist.
  1440  */
  1441 add_task(function test_blocked_applicationReputation()
  1443   function cleanup() {
  1444     DownloadIntegration.shouldBlockInTestForApplicationReputation = false;
  1446   do_register_cleanup(cleanup);
  1447   DownloadIntegration.shouldBlockInTestForApplicationReputation = true;
  1449   let download;
  1450   try {
  1451     if (!gUseLegacySaver) {
  1452       // When testing DownloadCopySaver, we want to check that the promise
  1453       // returned by the "start" method is rejected.
  1454       download = yield promiseNewDownload();
  1455       yield download.start();
  1456     } else {
  1457       // When testing DownloadLegacySaver, we cannot be sure whether we are
  1458       // testing the promise returned by the "start" method or we are testing
  1459       // the "error" property checked by promiseDownloadStopped.  This happens
  1460       // because we don't have control over when the download is started.
  1461       download = yield promiseStartLegacyDownload();
  1462       yield promiseDownloadStopped(download);
  1464     do_throw("The download should have blocked.");
  1465   } catch (ex if ex instanceof Downloads.Error && ex.becauseBlocked) {
  1466     do_check_true(ex.becauseBlockedByReputationCheck);
  1467     do_check_true(download.error.becauseBlockedByReputationCheck);
  1470   // Now that the download is blocked, the target file should not exist.
  1471   do_check_false(yield OS.File.exists(download.target.path));
  1472   cleanup();
  1473 });
  1475 /**
  1476  * download.showContainingDirectory() action
  1477  */
  1478 add_task(function test_showContainingDirectory() {
  1479   DownloadIntegration._deferTestShowDir = Promise.defer();
  1481   let targetPath = getTempFile(TEST_TARGET_FILE_NAME).path;
  1483   let download = yield Downloads.createDownload({
  1484     source: { url: httpUrl("source.txt") },
  1485     target: ""
  1486   });
  1488   try {
  1489     yield download.showContainingDirectory();
  1490     do_throw("Should have failed because of an invalid path.");
  1491   } catch (ex if ex instanceof Components.Exception) {
  1492     // Invalid paths on Windows are reported with NS_ERROR_FAILURE,
  1493     // but with NS_ERROR_FILE_UNRECOGNIZED_PATH on Mac/Linux
  1494     let validResult = ex.result == Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH ||
  1495                       ex.result == Cr.NS_ERROR_FAILURE;
  1496     do_check_true(validResult);
  1499   download = yield Downloads.createDownload({
  1500     source: { url: httpUrl("source.txt") },
  1501     target: targetPath
  1502   });
  1505   DownloadIntegration._deferTestShowDir = Promise.defer();
  1506   download.showContainingDirectory();
  1507   let result = yield DownloadIntegration._deferTestShowDir.promise;
  1508   do_check_eq(result, "success");
  1509 });
  1511 /**
  1512  * download.launch() action
  1513  */
  1514 add_task(function test_launch() {
  1515   let customLauncher = getTempFile("app-launcher");
  1517   // Test both with and without setting a custom application.
  1518   for (let launcherPath of [null, customLauncher.path]) {
  1519     let download;
  1520     if (!gUseLegacySaver) {
  1521       // When testing DownloadCopySaver, we have control over the download, thus
  1522       // we can test that file is not launched if download.succeeded is not set.
  1523       download = yield Downloads.createDownload({
  1524         source: httpUrl("source.txt"),
  1525         target: getTempFile(TEST_TARGET_FILE_NAME).path,
  1526         launcherPath: launcherPath,
  1527         launchWhenSucceeded: true
  1528       });
  1530       try {
  1531         yield download.launch();
  1532         do_throw("Can't launch download file as it has not completed yet");
  1533       } catch (ex) {
  1534         do_check_eq(ex.message,
  1535                     "launch can only be called if the download succeeded");
  1538       yield download.start();
  1539     } else {
  1540       // When testing DownloadLegacySaver, the download is already started when
  1541       // it is created, thus we don't test calling "launch" before starting.
  1542       download = yield promiseStartLegacyDownload(
  1543                                          httpUrl("source.txt"),
  1544                                          { launcherPath: launcherPath,
  1545                                            launchWhenSucceeded: true });
  1546       yield promiseDownloadStopped(download);
  1549     do_check_true(download.launchWhenSucceeded);
  1551     DownloadIntegration._deferTestOpenFile = Promise.defer();
  1552     download.launch();
  1553     let result = yield DownloadIntegration._deferTestOpenFile.promise;
  1555     // Verify that the results match the test case.
  1556     if (!launcherPath) {
  1557       // This indicates that the default handler has been chosen.
  1558       do_check_true(result === null);
  1559     } else {
  1560       // Check the nsIMIMEInfo instance that would have been used for launching.
  1561       do_check_eq(result.preferredAction, Ci.nsIMIMEInfo.useHelperApp);
  1562       do_check_true(result.preferredApplicationHandler
  1563                           .QueryInterface(Ci.nsILocalHandlerApp)
  1564                           .executable.equals(customLauncher));
  1567 });
  1569 /**
  1570  * Test passing an invalid path as the launcherPath property.
  1571  */
  1572 add_task(function test_launcherPath_invalid() {
  1573   let download = yield Downloads.createDownload({
  1574     source: { url: httpUrl("source.txt") },
  1575     target: { path: getTempFile(TEST_TARGET_FILE_NAME).path },
  1576     launcherPath: " "
  1577   });
  1579   DownloadIntegration._deferTestOpenFile = Promise.defer();
  1580   yield download.start();
  1581   try {
  1582     download.launch();
  1583     result = yield DownloadIntegration._deferTestOpenFile.promise;
  1584     do_throw("Can't launch file with invalid custom launcher")
  1585   } catch (ex if ex instanceof Components.Exception) {
  1586     // Invalid paths on Windows are reported with NS_ERROR_FAILURE,
  1587     // but with NS_ERROR_FILE_UNRECOGNIZED_PATH on Mac/Linux
  1588     let validResult = ex.result == Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH ||
  1589                       ex.result == Cr.NS_ERROR_FAILURE;
  1590     do_check_true(validResult);
  1592 });
  1594 /**
  1595  * Tests that download.launch() is automatically called after
  1596  * the download finishes if download.launchWhenSucceeded = true
  1597  */
  1598 add_task(function test_launchWhenSucceeded() {
  1599   let customLauncher = getTempFile("app-launcher");
  1601   // Test both with and without setting a custom application.
  1602   for (let launcherPath of [null, customLauncher.path]) {
  1603     DownloadIntegration._deferTestOpenFile = Promise.defer();
  1605     if (!gUseLegacySaver) {
  1606       let download = yield Downloads.createDownload({
  1607         source: httpUrl("source.txt"),
  1608         target: getTempFile(TEST_TARGET_FILE_NAME).path,
  1609         launchWhenSucceeded: true,
  1610         launcherPath: launcherPath,
  1611       });
  1612       yield download.start();
  1613     } else {
  1614       let download = yield promiseStartLegacyDownload(
  1615                                              httpUrl("source.txt"),
  1616                                              { launcherPath: launcherPath,
  1617                                                launchWhenSucceeded: true });
  1618       yield promiseDownloadStopped(download);
  1621     let result = yield DownloadIntegration._deferTestOpenFile.promise;
  1623     // Verify that the results match the test case.
  1624     if (!launcherPath) {
  1625       // This indicates that the default handler has been chosen.
  1626       do_check_true(result === null);
  1627     } else {
  1628       // Check the nsIMIMEInfo instance that would have been used for launching.
  1629       do_check_eq(result.preferredAction, Ci.nsIMIMEInfo.useHelperApp);
  1630       do_check_true(result.preferredApplicationHandler
  1631                           .QueryInterface(Ci.nsILocalHandlerApp)
  1632                           .executable.equals(customLauncher));
  1635 });
  1637 /**
  1638  * Tests that the proper content type is set for a normal download.
  1639  */
  1640 add_task(function test_contentType() {
  1641   let download = yield promiseStartDownload(httpUrl("source.txt"));
  1642   yield promiseDownloadStopped(download);
  1644   do_check_eq("text/plain", download.contentType);
  1645 });
  1647 /**
  1648  * Tests that the serialization/deserialization of the startTime Date
  1649  * object works correctly.
  1650  */
  1651 add_task(function test_toSerializable_startTime()
  1653   let download1 = yield promiseStartDownload(httpUrl("source.txt"));
  1654   yield promiseDownloadStopped(download1);
  1656   let serializable = download1.toSerializable();
  1657   let reserialized = JSON.parse(JSON.stringify(serializable));
  1659   let download2 = yield Downloads.createDownload(reserialized);
  1661   do_check_eq(download1.startTime.constructor.name, "Date");
  1662   do_check_eq(download2.startTime.constructor.name, "Date");
  1663   do_check_eq(download1.startTime.toJSON(), download2.startTime.toJSON());
  1664 });
  1666 /**
  1667  * This test will call the platform specific operations within
  1668  * DownloadPlatform::DownloadDone. While there is no test to verify the
  1669  * specific behaviours, this at least ensures that there is no error or crash.
  1670  */
  1671 add_task(function test_platform_integration()
  1673   let downloadFiles = [];
  1674   function cleanup() {
  1675     for (let file of downloadFiles) {
  1676       file.remove(true);
  1679   do_register_cleanup(cleanup);
  1681   for (let isPrivate of [false, true]) {
  1682     DownloadIntegration.downloadDoneCalled = false;
  1684     // Some platform specific operations only operate on files outside the
  1685     // temporary directory or in the Downloads directory (such as setting
  1686     // the Windows searchable attribute, and the Mac Downloads icon bouncing),
  1687     // so use the system Downloads directory for the target file.
  1688     let targetFilePath = yield DownloadIntegration.getSystemDownloadsDirectory();
  1689     targetFilePath = OS.Path.join(targetFilePath,
  1690                                   "test" + (Math.floor(Math.random() * 1000000)));
  1691     let targetFile = new FileUtils.File(targetFilePath);
  1692     downloadFiles.push(targetFile);
  1694     let download;
  1695     if (gUseLegacySaver) {
  1696       download = yield promiseStartLegacyDownload(httpUrl("source.txt"),
  1697                                                   { targetFile: targetFile });
  1699     else {
  1700       download = yield Downloads.createDownload({
  1701         source: httpUrl("source.txt"),
  1702         target: targetFile,
  1703       });
  1704       download.start();
  1707     // Wait for the whenSucceeded promise to be resolved first.
  1708     // downloadDone should be called before the whenSucceeded promise is resolved.
  1709     yield download.whenSucceeded().then(function () {
  1710       do_check_true(DownloadIntegration.downloadDoneCalled);
  1711     });
  1713     // Then, wait for the promise returned by "start" to be resolved.
  1714     yield promiseDownloadStopped(download);
  1716     yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
  1718 });
  1720 /**
  1721  * Checks that downloads are added to browsing history when they start.
  1722  */
  1723 add_task(function test_history()
  1725   mustInterruptResponses();
  1727   // We will wait for the visit to be notified during the download.
  1728   yield promiseClearHistory();
  1729   let promiseVisit = promiseWaitForVisit(httpUrl("interruptible.txt"));
  1731   // Start a download that is not allowed to finish yet.
  1732   let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
  1734   // The history notifications should be received before the download completes.
  1735   let [time, transitionType] = yield promiseVisit;
  1736   do_check_eq(time, download.startTime.getTime() * 1000);
  1737   do_check_eq(transitionType, Ci.nsINavHistoryService.TRANSITION_DOWNLOAD);
  1739   // Restart and complete the download after clearing history.
  1740   yield promiseClearHistory();
  1741   download.cancel();
  1742   continueResponses();
  1743   yield download.start();
  1745   // The restart should not have added a new history visit.
  1746   do_check_false(yield promiseIsURIVisited(httpUrl("interruptible.txt")));
  1747 });
  1749 /**
  1750  * Checks that downloads started by nsIHelperAppService are added to the
  1751  * browsing history when they start.
  1752  */
  1753 add_task(function test_history_tryToKeepPartialData()
  1755   // We will wait for the visit to be notified during the download.
  1756   yield promiseClearHistory();
  1757   let promiseVisit =
  1758       promiseWaitForVisit(httpUrl("interruptible_resumable.txt"));
  1760   // Start a download that is not allowed to finish yet.
  1761   let beforeStartTimeMs = Date.now();
  1762   let download = yield promiseStartDownload_tryToKeepPartialData();
  1764   // The history notifications should be received before the download completes.
  1765   let [time, transitionType] = yield promiseVisit;
  1766   do_check_eq(transitionType, Ci.nsINavHistoryService.TRANSITION_DOWNLOAD);
  1768   // The time set by nsIHelperAppService may be different than the start time in
  1769   // the download object, thus we only check that it is a meaningful time.  Note
  1770   // that we subtract one second from the earliest time to account for rounding.
  1771   do_check_true(time >= beforeStartTimeMs * 1000 - 1000000);
  1773   // Complete the download before finishing the test.
  1774   continueResponses();
  1775   yield promiseDownloadStopped(download);
  1776 });
  1778 /**
  1779  * Tests that the temp download files are removed on exit and exiting private
  1780  * mode after they have been launched.
  1781  */
  1782 add_task(function test_launchWhenSucceeded_deleteTempFileOnExit() {
  1783   const kDeleteTempFileOnExit = "browser.helperApps.deleteTempFileOnExit";
  1785   let customLauncherPath = getTempFile("app-launcher").path;
  1786   let autoDeleteTargetPathOne = getTempFile(TEST_TARGET_FILE_NAME).path;
  1787   let autoDeleteTargetPathTwo = getTempFile(TEST_TARGET_FILE_NAME).path;
  1788   let noAutoDeleteTargetPath = getTempFile(TEST_TARGET_FILE_NAME).path;
  1790   let autoDeleteDownloadOne = yield Downloads.createDownload({
  1791     source: { url: httpUrl("source.txt"), isPrivate: true },
  1792     target: autoDeleteTargetPathOne,
  1793     launchWhenSucceeded: true,
  1794     launcherPath: customLauncherPath,
  1795   });
  1796   yield autoDeleteDownloadOne.start();
  1798   Services.prefs.setBoolPref(kDeleteTempFileOnExit, true);
  1799   let autoDeleteDownloadTwo = yield Downloads.createDownload({
  1800     source: httpUrl("source.txt"),
  1801     target: autoDeleteTargetPathTwo,
  1802     launchWhenSucceeded: true,
  1803     launcherPath: customLauncherPath,
  1804   });
  1805   yield autoDeleteDownloadTwo.start();
  1807   Services.prefs.setBoolPref(kDeleteTempFileOnExit, false);
  1808   let noAutoDeleteDownload = yield Downloads.createDownload({
  1809     source: httpUrl("source.txt"),
  1810     target: noAutoDeleteTargetPath,
  1811     launchWhenSucceeded: true,
  1812     launcherPath: customLauncherPath,
  1813   });
  1814   yield noAutoDeleteDownload.start();
  1816   Services.prefs.clearUserPref(kDeleteTempFileOnExit);
  1818   do_check_true(yield OS.File.exists(autoDeleteTargetPathOne));
  1819   do_check_true(yield OS.File.exists(autoDeleteTargetPathTwo));
  1820   do_check_true(yield OS.File.exists(noAutoDeleteTargetPath));
  1822   // Simulate leaving private browsing mode
  1823   Services.obs.notifyObservers(null, "last-pb-context-exited", null);
  1824   do_check_false(yield OS.File.exists(autoDeleteTargetPathOne));
  1826   // Simulate browser shutdown
  1827   let expire = Cc["@mozilla.org/uriloader/external-helper-app-service;1"]
  1828                  .getService(Ci.nsIObserver);
  1829   expire.observe(null, "profile-before-change", null);
  1830   do_check_false(yield OS.File.exists(autoDeleteTargetPathTwo));
  1831   do_check_true(yield OS.File.exists(noAutoDeleteTargetPath));
  1832 });

mercurial