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

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:02dd038d59d4
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/ */
5
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 */
11
12 "use strict";
13
14 ////////////////////////////////////////////////////////////////////////////////
15 //// Globals
16
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 }
31
32 return promiseNewDownload(aSourceUrl).then(download => {
33 download.start();
34 return download;
35 });
36 }
37
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();
54
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 }
71
72 yield promiseDownloadMidway(download);
73 yield promisePartFileReady(download);
74
75 throw new Task.Result(download);
76 });
77 }
78
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 }
107
108 ////////////////////////////////////////////////////////////////////////////////
109 //// Tests
110
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);
119
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 });
129
130 do_check_eq(download.source.url, httpUrl("source.txt"));
131 do_check_eq(download.target.path, targetFile.path);
132
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 });
139
140 do_check_eq(download.source.url, httpUrl("source.txt"));
141 do_check_eq(download.target.path, targetFile.path);
142
143 yield promiseDownloadStopped(download);
144 }
145
146 // Check additional properties on the finished download.
147 do_check_true(download.source.referrer === null);
148
149 yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
150 });
151
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);
162
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 });
169
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;
178
179 function cleanup() {
180 gHttpServer.registerPathHandler(sourcePath, null);
181 }
182 do_register_cleanup(cleanup);
183
184 gHttpServer.registerPathHandler(sourcePath, function (aRequest, aResponse) {
185 aResponse.setHeader("Content-Type", "text/plain", false);
186
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();
196
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();
204
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();
213
214 cleanup();
215 });
216
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();
227
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);
234
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 }
242
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 });
250
251 /**
252 * Checks the notification of the final download state.
253 */
254 add_task(function test_final_state_notified()
255 {
256 mustInterruptResponses();
257
258 let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
259
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 };
268
269 // Allow the download to complete.
270 let promiseAttempt = download.start();
271 continueResponses();
272 yield promiseAttempt;
273
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 });
279
280 /**
281 * Checks intermediate progress for a successful download.
282 */
283 add_task(function test_intermediate_progress()
284 {
285 mustInterruptResponses();
286
287 let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
288
289 yield promiseDownloadMidway(download);
290
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);
294
295 // Continue after the first chunk of data is fully received.
296 continueResponses();
297 yield promiseDownloadStopped(download);
298
299 do_check_true(download.stopped);
300 do_check_eq(download.progress, 100);
301
302 yield promiseVerifyContents(download.target.path,
303 TEST_DATA_SHORT + TEST_DATA_SHORT);
304 });
305
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);
313
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);
319
320 // We should have received the content type even for an empty file.
321 do_check_eq(download.contentType, "text/plain");
322
323 do_check_eq((yield OS.File.stat(download.target.path)).size, 0);
324 });
325
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);
350
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 });
356
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();
365
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);
371
372 registerInterruptibleHandler(sourcePath,
373 function firstPart(aRequest, aResponse) {
374 aResponse.setHeader("Content-Type", "text/plain", false);
375 deferRequestReceived.resolve();
376 }, function secondPart(aRequest, aResponse) { });
377
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);
386
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 };
394
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 }
402
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();
408
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);
414
415 // Now allow the response to finish.
416 continueResponses();
417 yield promiseDownloadStopped(download);
418
419 // We should have received the content type even if no progress is reported.
420 do_check_eq(download.contentType, "text/plain");
421
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);
428
429 do_check_eq((yield OS.File.stat(download.target.path)).size, 0);
430 });
431
432 /**
433 * Calls the "start" method two times before the download is finished.
434 */
435 add_task(function test_start_twice()
436 {
437 mustInterruptResponses();
438
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 }
449
450 // Call the start method two times.
451 let promiseAttempt1 = download.start();
452 let promiseAttempt2 = download.start();
453
454 // Allow the download to finish.
455 continueResponses();
456
457 // Both promises should now be resolved.
458 yield promiseAttempt1;
459 yield promiseAttempt2;
460
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);
465
466 yield promiseVerifyContents(download.target.path,
467 TEST_DATA_SHORT + TEST_DATA_SHORT);
468 });
469
470 /**
471 * Cancels a download and verifies that its state is reported correctly.
472 */
473 add_task(function test_cancel_midway()
474 {
475 mustInterruptResponses();
476
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 }
487
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());
494
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 };
500
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();
506
507 let promiseAttempt;
508 if (!gUseLegacySaver) {
509 promiseAttempt = download.start();
510 }
511
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;
515
516 if (gUseLegacySaver) {
517 // The nsIWebBrowserPersist instance should have been canceled now.
518 do_check_eq(options.outPersist.result, Cr.NS_ERROR_ABORT);
519 }
520
521 do_check_true(download.stopped);
522 do_check_true(download.canceled);
523 do_check_true(download.error === null);
524
525 do_check_false(yield OS.File.exists(download.target.path));
526
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);
531
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 });
543
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();
551
552 do_check_true(yield OS.File.exists(download.target.path));
553 do_check_true(yield OS.File.exists(download.target.partFilePath));
554
555 yield download.cancel();
556 yield download.removePartialData();
557
558 do_check_true(download.stopped);
559 do_check_true(download.canceled);
560 do_check_true(download.error === null);
561
562 do_check_false(yield OS.File.exists(download.target.path));
563 do_check_false(yield OS.File.exists(download.target.partFilePath));
564 });
565
566 /**
567 * Cancels a download right after starting it.
568 */
569 add_task(function test_cancel_immediately()
570 {
571 mustInterruptResponses();
572
573 let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
574
575 let promiseAttempt = download.start();
576 do_check_false(download.stopped);
577
578 let promiseCancel = download.cancel();
579 do_check_true(download.canceled);
580
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 }
591
592 do_check_true(download.stopped);
593 do_check_true(download.canceled);
594 do_check_true(download.error === null);
595
596 do_check_false(yield OS.File.exists(download.target.path));
597
598 // Check that the promise returned by the "cancel" method has been resolved.
599 yield promiseCancel;
600 });
601
602 /**
603 * Cancels and restarts a download sequentially.
604 */
605 add_task(function test_cancel_midway_restart()
606 {
607 mustInterruptResponses();
608
609 let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
610
611 // The first time, cancel the download midway.
612 yield promiseDownloadMidway(download);
613 yield download.cancel();
614
615 do_check_true(download.stopped);
616
617 // The second time, we'll provide the entire interruptible response.
618 continueResponses();
619 download.onchange = null;
620 let promiseAttempt = download.start();
621
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);
626
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);
633
634 yield promiseAttempt;
635
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);
640
641 yield promiseVerifyContents(download.target.path,
642 TEST_DATA_SHORT + TEST_DATA_SHORT);
643 });
644
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();
652
653 do_check_true(download.stopped);
654 do_check_true(download.hasPartialData);
655
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);
659
660 // Verify that the server sent the response from the start.
661 do_check_eq(gMostRecentFirstBytePos, 0);
662
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 };
673
674 mustInterruptResponses();
675 let promiseAttempt = download.start();
676
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);
683
684 // Now we allow the download to finish.
685 continueResponses();
686 yield promiseAttempt;
687
688 // Check that the server now sent the second part only.
689 do_check_eq(gMostRecentFirstBytePos, TEST_DATA_SHORT.length);
690
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 });
696
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();
705
706 do_check_true(download.hasPartialData);
707 yield promiseVerifyContents(download.target.partFilePath, TEST_DATA_SHORT);
708
709 yield download.removePartialData();
710
711 do_check_false(download.hasPartialData);
712 do_check_false(yield OS.File.exists(download.target.partFilePath));
713
714 // The second time, we'll request and obtain the entire response again.
715 continueResponses();
716 yield download.start();
717
718 // Verify that the server sent the response from the start.
719 do_check_eq(gMostRecentFirstBytePos, 0);
720
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 });
726
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();
736
737 download.tryToKeepPartialData = false;
738
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);
742
743 yield download.removePartialData();
744 do_check_false(yield OS.File.exists(download.target.partFilePath));
745
746 // Restart the download from the beginning.
747 mustInterruptResponses();
748 download.start();
749
750 yield promiseDownloadMidway(download);
751 yield promisePartFileReady(download);
752
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));
756
757 yield download.cancel();
758
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));
762
763 // The third time, we'll request and obtain the entire response again.
764 continueResponses();
765 yield download.start();
766
767 // Verify that the server sent the response from the start.
768 do_check_eq(gMostRecentFirstBytePos, 0);
769
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 });
775
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();
782
783 let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
784 let promiseAttempt = download.start();
785
786 do_check_false(download.stopped);
787
788 download.cancel();
789 do_check_true(download.canceled);
790
791 let promiseRestarted = download.start();
792 do_check_false(download.stopped);
793 do_check_false(download.canceled);
794 do_check_true(download.error === null);
795
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);
803
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 }
817
818 yield promiseRestarted;
819
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);
824
825 yield promiseVerifyContents(download.target.path,
826 TEST_DATA_SHORT + TEST_DATA_SHORT);
827 });
828
829 /**
830 * Cancels a download midway, then restarts it immediately.
831 */
832 add_task(function test_cancel_midway_restart_immediately()
833 {
834 mustInterruptResponses();
835
836 let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
837 let promiseAttempt = download.start();
838
839 // The first time, cancel the download midway.
840 yield promiseDownloadMidway(download);
841 download.cancel();
842 do_check_true(download.canceled);
843
844 let promiseRestarted = download.start();
845 do_check_false(download.stopped);
846 do_check_false(download.canceled);
847 do_check_true(download.error === null);
848
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);
856
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 }
866
867 yield promiseRestarted;
868
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);
873
874 yield promiseVerifyContents(download.target.path,
875 TEST_DATA_SHORT + TEST_DATA_SHORT);
876 });
877
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);
885
886 // The cancel method should succeed with no effect.
887 yield download.cancel();
888
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);
893
894 yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
895 });
896
897 /**
898 * Calls the "cancel" method two times in a row.
899 */
900 add_task(function test_cancel_twice()
901 {
902 mustInterruptResponses();
903
904 let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
905
906 let promiseAttempt = download.start();
907 do_check_false(download.stopped);
908
909 let promiseCancel1 = download.cancel();
910 do_check_true(download.canceled);
911 let promiseCancel2 = download.cancel();
912
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 }
920
921 // Both promises should now be resolved.
922 yield promiseCancel1;
923 yield promiseCancel2;
924
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);
929
930 do_check_false(yield OS.File.exists(download.target.path));
931 });
932
933 /**
934 * Checks that a download cannot be restarted after the "finalize" method.
935 */
936 add_task(function test_finalize()
937 {
938 mustInterruptResponses();
939
940 let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
941
942 let promiseFinalized = download.finalize();
943
944 try {
945 yield download.start();
946 do_throw("It should not be possible to restart after finalization.");
947 } catch (ex) { }
948
949 yield promiseFinalized;
950
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);
955
956 do_check_false(yield OS.File.exists(download.target.path));
957 });
958
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();
967
968 do_check_true(download.hasPartialData);
969 do_check_true(yield OS.File.exists(download.target.partFilePath));
970
971 // Clean up.
972 yield download.removePartialData();
973
974 // Check finalization while removing partial data.
975 download = yield promiseStartDownload_tryToKeepPartialData();
976 yield download.finalize(true);
977
978 do_check_false(download.hasPartialData);
979 do_check_false(yield OS.File.exists(download.target.partFilePath));
980 });
981
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();
988
989 let promiseSucceeded;
990
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();
1003 }
1004
1005 // Cancel the first download attempt.
1006 yield download.cancel();
1007
1008 // The second request is allowed to complete.
1009 continueResponses();
1010 download.start();
1011
1012 // Wait for the download to finish by waiting on the whenSucceeded promise.
1013 yield promiseSucceeded;
1014
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);
1019
1020 yield promiseVerifyContents(download.target.path,
1021 TEST_DATA_SHORT + TEST_DATA_SHORT);
1022 });
1023
1024 /**
1025 * Ensures download error details are reported on network failures.
1026 */
1027 add_task(function test_error_source()
1028 {
1029 let serverSocket = startFakeServer();
1030 try {
1031 let sourceUrl = "http://localhost:" + serverSocket.port + "/source.txt";
1032
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);
1039
1040 do_check_true(download.error === null);
1041
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);
1050 }
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.
1054 }
1055
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);
1062
1063 do_check_false(yield OS.File.exists(download.target.path));
1064 } finally {
1065 serverSocket.close();
1066 }
1067 });
1068
1069 /**
1070 * Ensures download error details are reported on local writing failures.
1071 */
1072 add_task(function test_error_target()
1073 {
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);
1096 }
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.
1100 }
1101
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);
1113 }
1114 }
1115 });
1116
1117 /**
1118 * Restarts a failed download.
1119 */
1120 add_task(function test_error_restart()
1121 {
1122 let download;
1123
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 });
1139 }
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;
1148
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);
1154 }
1155 }
1156
1157 // Restart the download and wait for completion.
1158 yield download.start();
1159
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);
1165
1166 yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
1167 });
1168
1169 /**
1170 * Executes download in both public and private modes.
1171 */
1172 add_task(function test_public_and_private()
1173 {
1174 let sourcePath = "/test_public_and_private.txt";
1175 let sourceUrl = httpUrl("test_public_and_private.txt");
1176 let testCount = 0;
1177
1178 // Apply pref to allow all cookies.
1179 Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
1180
1181 function cleanup() {
1182 Services.prefs.clearUserPref("network.cookie.cookieBehavior");
1183 Services.cookies.removeAll();
1184 gHttpServer.registerPathHandler(sourcePath, null);
1185 }
1186 do_register_cleanup(cleanup);
1187
1188 gHttpServer.registerPathHandler(sourcePath, function (aRequest, aResponse) {
1189 aResponse.setHeader("Content-Type", "text/plain", false);
1190
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"));
1204 }
1205 });
1206
1207 let targetFile = getTempFile(TEST_TARGET_FILE_NAME);
1208 yield Downloads.fetch(sourceUrl, targetFile);
1209 yield Downloads.fetch(sourceUrl, targetFile);
1210
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);
1221 }
1222
1223 cleanup();
1224 });
1225
1226 /**
1227 * Checks the startTime gets updated even after a restart.
1228 */
1229 add_task(function test_cancel_immediately_restart_and_check_startTime()
1230 {
1231 let download = yield promiseStartDownload();
1232
1233 let startTime = download.startTime;
1234 do_check_true(isValidDate(download.startTime));
1235
1236 yield download.cancel();
1237 do_check_eq(download.startTime.getTime(), startTime.getTime());
1238
1239 // Wait for a timeout.
1240 yield promiseTimeout(10);
1241
1242 yield download.start();
1243 do_check_true(download.startTime.getTime() > startTime.getTime());
1244 });
1245
1246 /**
1247 * Executes download with content-encoding.
1248 */
1249 add_task(function test_with_content_encoding()
1250 {
1251 let sourcePath = "/test_with_content_encoding.txt";
1252 let sourceUrl = httpUrl("test_with_content_encoding.txt");
1253
1254 function cleanup() {
1255 gHttpServer.registerPathHandler(sourcePath, null);
1256 }
1257 do_register_cleanup(cleanup);
1258
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);
1264
1265 let bos = new BinaryOutputStream(aResponse.bodyOutputStream);
1266 bos.writeByteArray(TEST_DATA_SHORT_GZIP_ENCODED,
1267 TEST_DATA_SHORT_GZIP_ENCODED.length);
1268 });
1269
1270 let download = yield promiseStartDownload(sourceUrl);
1271 yield promiseDownloadStopped(download);
1272
1273 do_check_eq(download.progress, 100);
1274 do_check_eq(download.totalBytes, TEST_DATA_SHORT_GZIP_ENCODED.length);
1275
1276 // Ensure the content matches the decoded test data.
1277 yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
1278
1279 cleanup();
1280 });
1281
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()
1286 {
1287 let sourcePath = "/test_with_content_encoding_ignore_extension.gz";
1288 let sourceUrl = httpUrl("test_with_content_encoding_ignore_extension.gz");
1289
1290 function cleanup() {
1291 gHttpServer.registerPathHandler(sourcePath, null);
1292 }
1293 do_register_cleanup(cleanup);
1294
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);
1300
1301 let bos = new BinaryOutputStream(aResponse.bodyOutputStream);
1302 bos.writeByteArray(TEST_DATA_SHORT_GZIP_ENCODED,
1303 TEST_DATA_SHORT_GZIP_ENCODED.length);
1304 });
1305
1306 let download = yield promiseStartDownload(sourceUrl);
1307 yield promiseDownloadStopped(download);
1308
1309 do_check_eq(download.progress, 100);
1310 do_check_eq(download.totalBytes, TEST_DATA_SHORT_GZIP_ENCODED.length);
1311
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));
1316
1317 cleanup();
1318 });
1319
1320 /**
1321 * Cancels and restarts a download sequentially with content-encoding.
1322 */
1323 add_task(function test_cancel_midway_restart_with_content_encoding()
1324 {
1325 mustInterruptResponses();
1326
1327 let download = yield promiseStartDownload(httpUrl("interruptible_gzip.txt"));
1328
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());
1335 }
1336 };
1337
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();
1342
1343 yield deferCancel.promise;
1344
1345 do_check_true(download.stopped);
1346
1347 // The second time, we'll provide the entire interruptible response.
1348 continueResponses();
1349 download.onchange = null;
1350 yield download.start();
1351
1352 do_check_eq(download.progress, 100);
1353 do_check_eq(download.totalBytes, TEST_DATA_SHORT_GZIP_ENCODED.length);
1354
1355 yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
1356 });
1357
1358 /**
1359 * Download with parental controls enabled.
1360 */
1361 add_task(function test_blocked_parental_controls()
1362 {
1363 function cleanup() {
1364 DownloadIntegration.shouldBlockInTest = false;
1365 }
1366 do_register_cleanup(cleanup);
1367 DownloadIntegration.shouldBlockInTest = true;
1368
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);
1383 }
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);
1388 }
1389
1390 // Now that the download stopped, the target file should not exist.
1391 do_check_false(yield OS.File.exists(download.target.path));
1392
1393 cleanup();
1394 });
1395
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()
1401 {
1402 let download;
1403 try {
1404 if (!gUseLegacySaver) {
1405 download = yield promiseNewDownload(httpUrl("parentalblocked.zip"));
1406 yield download.start();
1407 }
1408 else {
1409 download = yield promiseStartLegacyDownload(httpUrl("parentalblocked.zip"));
1410 yield promiseDownloadStopped(download);
1411 }
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);
1417 }
1418
1419 do_check_false(yield OS.File.exists(download.target.path));
1420 });
1421
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()
1428 {
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);
1434 }
1435 });
1436
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()
1442 {
1443 function cleanup() {
1444 DownloadIntegration.shouldBlockInTestForApplicationReputation = false;
1445 }
1446 do_register_cleanup(cleanup);
1447 DownloadIntegration.shouldBlockInTestForApplicationReputation = true;
1448
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);
1463 }
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);
1468 }
1469
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 });
1474
1475 /**
1476 * download.showContainingDirectory() action
1477 */
1478 add_task(function test_showContainingDirectory() {
1479 DownloadIntegration._deferTestShowDir = Promise.defer();
1480
1481 let targetPath = getTempFile(TEST_TARGET_FILE_NAME).path;
1482
1483 let download = yield Downloads.createDownload({
1484 source: { url: httpUrl("source.txt") },
1485 target: ""
1486 });
1487
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);
1497 }
1498
1499 download = yield Downloads.createDownload({
1500 source: { url: httpUrl("source.txt") },
1501 target: targetPath
1502 });
1503
1504
1505 DownloadIntegration._deferTestShowDir = Promise.defer();
1506 download.showContainingDirectory();
1507 let result = yield DownloadIntegration._deferTestShowDir.promise;
1508 do_check_eq(result, "success");
1509 });
1510
1511 /**
1512 * download.launch() action
1513 */
1514 add_task(function test_launch() {
1515 let customLauncher = getTempFile("app-launcher");
1516
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 });
1529
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");
1536 }
1537
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);
1547 }
1548
1549 do_check_true(download.launchWhenSucceeded);
1550
1551 DownloadIntegration._deferTestOpenFile = Promise.defer();
1552 download.launch();
1553 let result = yield DownloadIntegration._deferTestOpenFile.promise;
1554
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));
1565 }
1566 }
1567 });
1568
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 });
1578
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);
1591 }
1592 });
1593
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");
1600
1601 // Test both with and without setting a custom application.
1602 for (let launcherPath of [null, customLauncher.path]) {
1603 DownloadIntegration._deferTestOpenFile = Promise.defer();
1604
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);
1619 }
1620
1621 let result = yield DownloadIntegration._deferTestOpenFile.promise;
1622
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));
1633 }
1634 }
1635 });
1636
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);
1643
1644 do_check_eq("text/plain", download.contentType);
1645 });
1646
1647 /**
1648 * Tests that the serialization/deserialization of the startTime Date
1649 * object works correctly.
1650 */
1651 add_task(function test_toSerializable_startTime()
1652 {
1653 let download1 = yield promiseStartDownload(httpUrl("source.txt"));
1654 yield promiseDownloadStopped(download1);
1655
1656 let serializable = download1.toSerializable();
1657 let reserialized = JSON.parse(JSON.stringify(serializable));
1658
1659 let download2 = yield Downloads.createDownload(reserialized);
1660
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 });
1665
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()
1672 {
1673 let downloadFiles = [];
1674 function cleanup() {
1675 for (let file of downloadFiles) {
1676 file.remove(true);
1677 }
1678 }
1679 do_register_cleanup(cleanup);
1680
1681 for (let isPrivate of [false, true]) {
1682 DownloadIntegration.downloadDoneCalled = false;
1683
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);
1693
1694 let download;
1695 if (gUseLegacySaver) {
1696 download = yield promiseStartLegacyDownload(httpUrl("source.txt"),
1697 { targetFile: targetFile });
1698 }
1699 else {
1700 download = yield Downloads.createDownload({
1701 source: httpUrl("source.txt"),
1702 target: targetFile,
1703 });
1704 download.start();
1705 }
1706
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 });
1712
1713 // Then, wait for the promise returned by "start" to be resolved.
1714 yield promiseDownloadStopped(download);
1715
1716 yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
1717 }
1718 });
1719
1720 /**
1721 * Checks that downloads are added to browsing history when they start.
1722 */
1723 add_task(function test_history()
1724 {
1725 mustInterruptResponses();
1726
1727 // We will wait for the visit to be notified during the download.
1728 yield promiseClearHistory();
1729 let promiseVisit = promiseWaitForVisit(httpUrl("interruptible.txt"));
1730
1731 // Start a download that is not allowed to finish yet.
1732 let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
1733
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);
1738
1739 // Restart and complete the download after clearing history.
1740 yield promiseClearHistory();
1741 download.cancel();
1742 continueResponses();
1743 yield download.start();
1744
1745 // The restart should not have added a new history visit.
1746 do_check_false(yield promiseIsURIVisited(httpUrl("interruptible.txt")));
1747 });
1748
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()
1754 {
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"));
1759
1760 // Start a download that is not allowed to finish yet.
1761 let beforeStartTimeMs = Date.now();
1762 let download = yield promiseStartDownload_tryToKeepPartialData();
1763
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);
1767
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);
1772
1773 // Complete the download before finishing the test.
1774 continueResponses();
1775 yield promiseDownloadStopped(download);
1776 });
1777
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";
1784
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;
1789
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();
1797
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();
1806
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();
1815
1816 Services.prefs.clearUserPref(kDeleteTempFileOnExit);
1817
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));
1821
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));
1825
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