michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: * http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: /** michael@0: * This script is loaded by "test_DownloadCore.js" and "test_DownloadLegacy.js" michael@0: * with different values of the gUseLegacySaver variable, to apply tests to both michael@0: * the "copy" and "legacy" saver implementations. michael@0: */ michael@0: michael@0: "use strict"; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Globals michael@0: michael@0: /** michael@0: * Creates and starts a new download, using either DownloadCopySaver or michael@0: * DownloadLegacySaver based on the current test run. michael@0: * michael@0: * @return {Promise} michael@0: * @resolves The newly created Download object. The download may be in progress michael@0: * or already finished. The promiseDownloadStopped function can be michael@0: * used to wait for completion. michael@0: * @rejects JavaScript exception. michael@0: */ michael@0: function promiseStartDownload(aSourceUrl) { michael@0: if (gUseLegacySaver) { michael@0: return promiseStartLegacyDownload(aSourceUrl); michael@0: } michael@0: michael@0: return promiseNewDownload(aSourceUrl).then(download => { michael@0: download.start(); michael@0: return download; michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * Creates and starts a new download, configured to keep partial data, and michael@0: * returns only when the first part of "interruptible_resumable.txt" has been michael@0: * saved to disk. You must call "continueResponses" to allow the interruptible michael@0: * request to continue. michael@0: * michael@0: * This function uses either DownloadCopySaver or DownloadLegacySaver based on michael@0: * the current test run. michael@0: * michael@0: * @return {Promise} michael@0: * @resolves The newly created Download object, still in progress. michael@0: * @rejects JavaScript exception. michael@0: */ michael@0: function promiseStartDownload_tryToKeepPartialData() { michael@0: return Task.spawn(function () { michael@0: mustInterruptResponses(); michael@0: michael@0: // Start a new download and configure it to keep partially downloaded data. michael@0: let download; michael@0: if (!gUseLegacySaver) { michael@0: let targetFilePath = getTempFile(TEST_TARGET_FILE_NAME).path; michael@0: download = yield Downloads.createDownload({ michael@0: source: httpUrl("interruptible_resumable.txt"), michael@0: target: { path: targetFilePath, michael@0: partFilePath: targetFilePath + ".part" }, michael@0: }); michael@0: download.tryToKeepPartialData = true; michael@0: download.start(); michael@0: } else { michael@0: // Start a download using nsIExternalHelperAppService, that is configured michael@0: // to keep partially downloaded data by default. michael@0: download = yield promiseStartExternalHelperAppServiceDownload(); michael@0: } michael@0: michael@0: yield promiseDownloadMidway(download); michael@0: yield promisePartFileReady(download); michael@0: michael@0: throw new Task.Result(download); michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * This function should be called after the progress notification for a download michael@0: * is received, and waits for the worker thread of BackgroundFileSaver to michael@0: * receive the data to be written to the ".part" file on disk. michael@0: * michael@0: * @return {Promise} michael@0: * @resolves When the ".part" file has been written to disk. michael@0: * @rejects JavaScript exception. michael@0: */ michael@0: function promisePartFileReady(aDownload) { michael@0: return Task.spawn(function () { michael@0: // We don't have control over the file output code in BackgroundFileSaver. michael@0: // After we receive the download progress notification, we may only check michael@0: // that the ".part" file has been created, while its size cannot be michael@0: // determined because the file is currently open. michael@0: try { michael@0: do { michael@0: yield promiseTimeout(50); michael@0: } while (!(yield OS.File.exists(aDownload.target.partFilePath))); michael@0: } catch (ex if ex instanceof OS.File.Error) { michael@0: // This indicates that the file has been created and cannot be accessed. michael@0: // The specific error might vary with the platform. michael@0: do_print("Expected exception while checking existence: " + ex.toString()); michael@0: // Wait some more time to allow the write to complete. michael@0: yield promiseTimeout(100); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Tests michael@0: michael@0: /** michael@0: * Executes a download and checks its basic properties after construction. michael@0: * The download is started by constructing the simplest Download object with michael@0: * the "copy" saver, or using the legacy nsITransfer interface. michael@0: */ michael@0: add_task(function test_basic() michael@0: { michael@0: let targetFile = getTempFile(TEST_TARGET_FILE_NAME); michael@0: michael@0: let download; michael@0: if (!gUseLegacySaver) { michael@0: // When testing DownloadCopySaver, we have control over the download, thus michael@0: // we can check its basic properties before it starts. michael@0: download = yield Downloads.createDownload({ michael@0: source: { url: httpUrl("source.txt") }, michael@0: target: { path: targetFile.path }, michael@0: saver: { type: "copy" }, michael@0: }); michael@0: michael@0: do_check_eq(download.source.url, httpUrl("source.txt")); michael@0: do_check_eq(download.target.path, targetFile.path); michael@0: michael@0: yield download.start(); michael@0: } else { michael@0: // When testing DownloadLegacySaver, the download is already started when it michael@0: // is created, thus we must check its basic properties while in progress. michael@0: download = yield promiseStartLegacyDownload(null, michael@0: { targetFile: targetFile }); michael@0: michael@0: do_check_eq(download.source.url, httpUrl("source.txt")); michael@0: do_check_eq(download.target.path, targetFile.path); michael@0: michael@0: yield promiseDownloadStopped(download); michael@0: } michael@0: michael@0: // Check additional properties on the finished download. michael@0: do_check_true(download.source.referrer === null); michael@0: michael@0: yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT); michael@0: }); michael@0: michael@0: /** michael@0: * Executes a download with the tryToKeepPartialData property set, and ensures michael@0: * that the file is saved correctly. When testing DownloadLegacySaver, the michael@0: * download is executed using the nsIExternalHelperAppService component. michael@0: */ michael@0: add_task(function test_basic_tryToKeepPartialData() michael@0: { michael@0: let download = yield promiseStartDownload_tryToKeepPartialData(); michael@0: continueResponses(); michael@0: yield promiseDownloadStopped(download); michael@0: michael@0: // The target file should now have been created, and the ".part" file deleted. michael@0: yield promiseVerifyContents(download.target.path, michael@0: TEST_DATA_SHORT + TEST_DATA_SHORT); michael@0: do_check_false(yield OS.File.exists(download.target.partFilePath)); michael@0: do_check_eq(32, download.saver.getSha256Hash().length); michael@0: }); michael@0: michael@0: /** michael@0: * Checks the referrer for downloads. michael@0: */ michael@0: add_task(function test_referrer() michael@0: { michael@0: let sourcePath = "/test_referrer.txt"; michael@0: let sourceUrl = httpUrl("test_referrer.txt"); michael@0: let targetPath = getTempFile(TEST_TARGET_FILE_NAME).path; michael@0: michael@0: function cleanup() { michael@0: gHttpServer.registerPathHandler(sourcePath, null); michael@0: } michael@0: do_register_cleanup(cleanup); michael@0: michael@0: gHttpServer.registerPathHandler(sourcePath, function (aRequest, aResponse) { michael@0: aResponse.setHeader("Content-Type", "text/plain", false); michael@0: michael@0: do_check_true(aRequest.hasHeader("Referer")); michael@0: do_check_eq(aRequest.getHeader("Referer"), TEST_REFERRER_URL); michael@0: }); michael@0: let download = yield Downloads.createDownload({ michael@0: source: { url: sourceUrl, referrer: TEST_REFERRER_URL }, michael@0: target: targetPath, michael@0: }); michael@0: do_check_eq(download.source.referrer, TEST_REFERRER_URL); michael@0: yield download.start(); michael@0: michael@0: download = yield Downloads.createDownload({ michael@0: source: { url: sourceUrl, referrer: TEST_REFERRER_URL, michael@0: isPrivate: true }, michael@0: target: targetPath, michael@0: }); michael@0: do_check_eq(download.source.referrer, TEST_REFERRER_URL); michael@0: yield download.start(); michael@0: michael@0: // Test the download still works for non-HTTP channel with referrer. michael@0: sourceUrl = "data:text/html,"; michael@0: download = yield Downloads.createDownload({ michael@0: source: { url: sourceUrl, referrer: TEST_REFERRER_URL }, michael@0: target: targetPath, michael@0: }); michael@0: do_check_eq(download.source.referrer, TEST_REFERRER_URL); michael@0: yield download.start(); michael@0: michael@0: cleanup(); michael@0: }); michael@0: michael@0: /** michael@0: * Checks initial and final state and progress for a successful download. michael@0: */ michael@0: add_task(function test_initial_final_state() michael@0: { michael@0: let download; michael@0: if (!gUseLegacySaver) { michael@0: // When testing DownloadCopySaver, we have control over the download, thus michael@0: // we can check its state before it starts. michael@0: download = yield promiseNewDownload(); michael@0: michael@0: do_check_true(download.stopped); michael@0: do_check_false(download.succeeded); michael@0: do_check_false(download.canceled); michael@0: do_check_true(download.error === null); michael@0: do_check_eq(download.progress, 0); michael@0: do_check_true(download.startTime === null); michael@0: michael@0: yield download.start(); michael@0: } else { michael@0: // When testing DownloadLegacySaver, the download is already started when it michael@0: // is created, thus we cannot check its initial state. michael@0: download = yield promiseStartLegacyDownload(); michael@0: yield promiseDownloadStopped(download); michael@0: } michael@0: michael@0: do_check_true(download.stopped); michael@0: do_check_true(download.succeeded); michael@0: do_check_false(download.canceled); michael@0: do_check_true(download.error === null); michael@0: do_check_eq(download.progress, 100); michael@0: do_check_true(isValidDate(download.startTime)); michael@0: }); michael@0: michael@0: /** michael@0: * Checks the notification of the final download state. michael@0: */ michael@0: add_task(function test_final_state_notified() michael@0: { michael@0: mustInterruptResponses(); michael@0: michael@0: let download = yield promiseStartDownload(httpUrl("interruptible.txt")); michael@0: michael@0: let onchangeNotified = false; michael@0: let lastNotifiedStopped; michael@0: let lastNotifiedProgress; michael@0: download.onchange = function () { michael@0: onchangeNotified = true; michael@0: lastNotifiedStopped = download.stopped; michael@0: lastNotifiedProgress = download.progress; michael@0: }; michael@0: michael@0: // Allow the download to complete. michael@0: let promiseAttempt = download.start(); michael@0: continueResponses(); michael@0: yield promiseAttempt; michael@0: michael@0: // The view should have been notified before the download completes. michael@0: do_check_true(onchangeNotified); michael@0: do_check_true(lastNotifiedStopped); michael@0: do_check_eq(lastNotifiedProgress, 100); michael@0: }); michael@0: michael@0: /** michael@0: * Checks intermediate progress for a successful download. michael@0: */ michael@0: add_task(function test_intermediate_progress() michael@0: { michael@0: mustInterruptResponses(); michael@0: michael@0: let download = yield promiseStartDownload(httpUrl("interruptible.txt")); michael@0: michael@0: yield promiseDownloadMidway(download); michael@0: michael@0: do_check_true(download.hasProgress); michael@0: do_check_eq(download.currentBytes, TEST_DATA_SHORT.length); michael@0: do_check_eq(download.totalBytes, TEST_DATA_SHORT.length * 2); michael@0: michael@0: // Continue after the first chunk of data is fully received. michael@0: continueResponses(); michael@0: yield promiseDownloadStopped(download); michael@0: michael@0: do_check_true(download.stopped); michael@0: do_check_eq(download.progress, 100); michael@0: michael@0: yield promiseVerifyContents(download.target.path, michael@0: TEST_DATA_SHORT + TEST_DATA_SHORT); michael@0: }); michael@0: michael@0: /** michael@0: * Downloads a file with a "Content-Length" of 0 and checks the progress. michael@0: */ michael@0: add_task(function test_empty_progress() michael@0: { michael@0: let download = yield promiseStartDownload(httpUrl("empty.txt")); michael@0: yield promiseDownloadStopped(download); michael@0: michael@0: do_check_true(download.stopped); michael@0: do_check_true(download.hasProgress); michael@0: do_check_eq(download.progress, 100); michael@0: do_check_eq(download.currentBytes, 0); michael@0: do_check_eq(download.totalBytes, 0); michael@0: michael@0: // We should have received the content type even for an empty file. michael@0: do_check_eq(download.contentType, "text/plain"); michael@0: michael@0: do_check_eq((yield OS.File.stat(download.target.path)).size, 0); michael@0: }); michael@0: michael@0: /** michael@0: * Downloads a file with a "Content-Length" of 0 with the tryToKeepPartialData michael@0: * property set, and ensures that the file is saved correctly. michael@0: */ michael@0: add_task(function test_empty_progress_tryToKeepPartialData() michael@0: { michael@0: // Start a new download and configure it to keep partially downloaded data. michael@0: let download; michael@0: if (!gUseLegacySaver) { michael@0: let targetFilePath = getTempFile(TEST_TARGET_FILE_NAME).path; michael@0: download = yield Downloads.createDownload({ michael@0: source: httpUrl("empty.txt"), michael@0: target: { path: targetFilePath, michael@0: partFilePath: targetFilePath + ".part" }, michael@0: }); michael@0: download.tryToKeepPartialData = true; michael@0: download.start(); michael@0: } else { michael@0: // Start a download using nsIExternalHelperAppService, that is configured michael@0: // to keep partially downloaded data by default. michael@0: download = yield promiseStartExternalHelperAppServiceDownload( michael@0: httpUrl("empty.txt")); michael@0: } michael@0: yield promiseDownloadStopped(download); michael@0: michael@0: // The target file should now have been created, and the ".part" file deleted. michael@0: do_check_eq((yield OS.File.stat(download.target.path)).size, 0); michael@0: do_check_false(yield OS.File.exists(download.target.partFilePath)); michael@0: do_check_eq(32, download.saver.getSha256Hash().length); michael@0: }); michael@0: michael@0: /** michael@0: * Downloads an empty file with no "Content-Length" and checks the progress. michael@0: */ michael@0: add_task(function test_empty_noprogress() michael@0: { michael@0: let sourcePath = "/test_empty_noprogress.txt"; michael@0: let sourceUrl = httpUrl("test_empty_noprogress.txt"); michael@0: let deferRequestReceived = Promise.defer(); michael@0: michael@0: // Register an interruptible handler that notifies us when the request occurs. michael@0: function cleanup() { michael@0: gHttpServer.registerPathHandler(sourcePath, null); michael@0: } michael@0: do_register_cleanup(cleanup); michael@0: michael@0: registerInterruptibleHandler(sourcePath, michael@0: function firstPart(aRequest, aResponse) { michael@0: aResponse.setHeader("Content-Type", "text/plain", false); michael@0: deferRequestReceived.resolve(); michael@0: }, function secondPart(aRequest, aResponse) { }); michael@0: michael@0: // Start the download, without allowing the request to finish. michael@0: mustInterruptResponses(); michael@0: let download; michael@0: if (!gUseLegacySaver) { michael@0: // When testing DownloadCopySaver, we have control over the download, thus michael@0: // we can hook its onchange callback that will be notified when the michael@0: // download starts. michael@0: download = yield promiseNewDownload(sourceUrl); michael@0: michael@0: download.onchange = function () { michael@0: if (!download.stopped) { michael@0: do_check_false(download.hasProgress); michael@0: do_check_eq(download.currentBytes, 0); michael@0: do_check_eq(download.totalBytes, 0); michael@0: } michael@0: }; michael@0: michael@0: download.start(); michael@0: } else { michael@0: // When testing DownloadLegacySaver, the download is already started when it michael@0: // is created, and it may have already made all needed property change michael@0: // notifications, thus there is no point in checking the onchange callback. michael@0: download = yield promiseStartLegacyDownload(sourceUrl); michael@0: } michael@0: michael@0: // Wait for the request to be received by the HTTP server, but don't allow the michael@0: // request to finish yet. Before checking the download state, wait for the michael@0: // events to be processed by the client. michael@0: yield deferRequestReceived.promise; michael@0: yield promiseExecuteSoon(); michael@0: michael@0: // Check that this download has no progress report. michael@0: do_check_false(download.stopped); michael@0: do_check_false(download.hasProgress); michael@0: do_check_eq(download.currentBytes, 0); michael@0: do_check_eq(download.totalBytes, 0); michael@0: michael@0: // Now allow the response to finish. michael@0: continueResponses(); michael@0: yield promiseDownloadStopped(download); michael@0: michael@0: // We should have received the content type even if no progress is reported. michael@0: do_check_eq(download.contentType, "text/plain"); michael@0: michael@0: // Verify the state of the completed download. michael@0: do_check_true(download.stopped); michael@0: do_check_false(download.hasProgress); michael@0: do_check_eq(download.progress, 100); michael@0: do_check_eq(download.currentBytes, 0); michael@0: do_check_eq(download.totalBytes, 0); michael@0: michael@0: do_check_eq((yield OS.File.stat(download.target.path)).size, 0); michael@0: }); michael@0: michael@0: /** michael@0: * Calls the "start" method two times before the download is finished. michael@0: */ michael@0: add_task(function test_start_twice() michael@0: { michael@0: mustInterruptResponses(); michael@0: michael@0: let download; michael@0: if (!gUseLegacySaver) { michael@0: // When testing DownloadCopySaver, we have control over the download, thus michael@0: // we can start the download later during the test. michael@0: download = yield promiseNewDownload(httpUrl("interruptible.txt")); michael@0: } else { michael@0: // When testing DownloadLegacySaver, the download is already started when it michael@0: // is created. Effectively, we are starting the download three times. michael@0: download = yield promiseStartLegacyDownload(httpUrl("interruptible.txt")); michael@0: } michael@0: michael@0: // Call the start method two times. michael@0: let promiseAttempt1 = download.start(); michael@0: let promiseAttempt2 = download.start(); michael@0: michael@0: // Allow the download to finish. michael@0: continueResponses(); michael@0: michael@0: // Both promises should now be resolved. michael@0: yield promiseAttempt1; michael@0: yield promiseAttempt2; michael@0: michael@0: do_check_true(download.stopped); michael@0: do_check_true(download.succeeded); michael@0: do_check_false(download.canceled); michael@0: do_check_true(download.error === null); michael@0: michael@0: yield promiseVerifyContents(download.target.path, michael@0: TEST_DATA_SHORT + TEST_DATA_SHORT); michael@0: }); michael@0: michael@0: /** michael@0: * Cancels a download and verifies that its state is reported correctly. michael@0: */ michael@0: add_task(function test_cancel_midway() michael@0: { michael@0: mustInterruptResponses(); michael@0: michael@0: // In this test case, we execute different checks that are only possible with michael@0: // DownloadCopySaver or DownloadLegacySaver respectively. michael@0: let download; michael@0: let options = {}; michael@0: if (!gUseLegacySaver) { michael@0: download = yield promiseNewDownload(httpUrl("interruptible.txt")); michael@0: } else { michael@0: download = yield promiseStartLegacyDownload(httpUrl("interruptible.txt"), michael@0: options); michael@0: } michael@0: michael@0: // Cancel the download after receiving the first part of the response. michael@0: let deferCancel = Promise.defer(); michael@0: let onchange = function () { michael@0: if (!download.stopped && !download.canceled && download.progress == 50) { michael@0: // Cancel the download immediately during the notification. michael@0: deferCancel.resolve(download.cancel()); michael@0: michael@0: // The state change happens immediately after calling "cancel", but michael@0: // temporary files or part files may still exist at this point. michael@0: do_check_true(download.canceled); michael@0: } michael@0: }; michael@0: michael@0: // Register for the notification, but also call the function directly in michael@0: // case the download already reached the expected progress. This may happen michael@0: // when using DownloadLegacySaver. michael@0: download.onchange = onchange; michael@0: onchange(); michael@0: michael@0: let promiseAttempt; michael@0: if (!gUseLegacySaver) { michael@0: promiseAttempt = download.start(); michael@0: } michael@0: michael@0: // Wait on the promise returned by the "cancel" method to ensure that the michael@0: // cancellation process finished and temporary files were removed. michael@0: yield deferCancel.promise; michael@0: michael@0: if (gUseLegacySaver) { michael@0: // The nsIWebBrowserPersist instance should have been canceled now. michael@0: do_check_eq(options.outPersist.result, Cr.NS_ERROR_ABORT); michael@0: } michael@0: michael@0: do_check_true(download.stopped); michael@0: do_check_true(download.canceled); michael@0: do_check_true(download.error === null); michael@0: michael@0: do_check_false(yield OS.File.exists(download.target.path)); michael@0: michael@0: // Progress properties are not reset by canceling. michael@0: do_check_eq(download.progress, 50); michael@0: do_check_eq(download.totalBytes, TEST_DATA_SHORT.length * 2); michael@0: do_check_eq(download.currentBytes, TEST_DATA_SHORT.length); michael@0: michael@0: if (!gUseLegacySaver) { michael@0: // The promise returned by "start" should have been rejected meanwhile. michael@0: try { michael@0: yield promiseAttempt; michael@0: do_throw("The download should have been canceled."); michael@0: } catch (ex if ex instanceof Downloads.Error) { michael@0: do_check_false(ex.becauseSourceFailed); michael@0: do_check_false(ex.becauseTargetFailed); michael@0: } michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * Cancels a download while keeping partially downloaded data, and verifies that michael@0: * both the target file and the ".part" file are deleted. michael@0: */ michael@0: add_task(function test_cancel_midway_tryToKeepPartialData() michael@0: { michael@0: let download = yield promiseStartDownload_tryToKeepPartialData(); michael@0: michael@0: do_check_true(yield OS.File.exists(download.target.path)); michael@0: do_check_true(yield OS.File.exists(download.target.partFilePath)); michael@0: michael@0: yield download.cancel(); michael@0: yield download.removePartialData(); michael@0: michael@0: do_check_true(download.stopped); michael@0: do_check_true(download.canceled); michael@0: do_check_true(download.error === null); michael@0: michael@0: do_check_false(yield OS.File.exists(download.target.path)); michael@0: do_check_false(yield OS.File.exists(download.target.partFilePath)); michael@0: }); michael@0: michael@0: /** michael@0: * Cancels a download right after starting it. michael@0: */ michael@0: add_task(function test_cancel_immediately() michael@0: { michael@0: mustInterruptResponses(); michael@0: michael@0: let download = yield promiseStartDownload(httpUrl("interruptible.txt")); michael@0: michael@0: let promiseAttempt = download.start(); michael@0: do_check_false(download.stopped); michael@0: michael@0: let promiseCancel = download.cancel(); michael@0: do_check_true(download.canceled); michael@0: michael@0: // At this point, we don't know whether the download has already stopped or michael@0: // is still waiting for cancellation. We can wait on the promise returned michael@0: // by the "start" method to know for sure. michael@0: try { michael@0: yield promiseAttempt; michael@0: do_throw("The download should have been canceled."); michael@0: } catch (ex if ex instanceof Downloads.Error) { michael@0: do_check_false(ex.becauseSourceFailed); michael@0: do_check_false(ex.becauseTargetFailed); michael@0: } michael@0: michael@0: do_check_true(download.stopped); michael@0: do_check_true(download.canceled); michael@0: do_check_true(download.error === null); michael@0: michael@0: do_check_false(yield OS.File.exists(download.target.path)); michael@0: michael@0: // Check that the promise returned by the "cancel" method has been resolved. michael@0: yield promiseCancel; michael@0: }); michael@0: michael@0: /** michael@0: * Cancels and restarts a download sequentially. michael@0: */ michael@0: add_task(function test_cancel_midway_restart() michael@0: { michael@0: mustInterruptResponses(); michael@0: michael@0: let download = yield promiseStartDownload(httpUrl("interruptible.txt")); michael@0: michael@0: // The first time, cancel the download midway. michael@0: yield promiseDownloadMidway(download); michael@0: yield download.cancel(); michael@0: michael@0: do_check_true(download.stopped); michael@0: michael@0: // The second time, we'll provide the entire interruptible response. michael@0: continueResponses(); michael@0: download.onchange = null; michael@0: let promiseAttempt = download.start(); michael@0: michael@0: // Download state should have already been reset. michael@0: do_check_false(download.stopped); michael@0: do_check_false(download.canceled); michael@0: do_check_true(download.error === null); michael@0: michael@0: // For the following test, we rely on the network layer reporting its progress michael@0: // asynchronously. Otherwise, there is nothing stopping the restarted michael@0: // download from reaching the same progress as the first request already. michael@0: do_check_eq(download.progress, 0); michael@0: do_check_eq(download.totalBytes, 0); michael@0: do_check_eq(download.currentBytes, 0); michael@0: michael@0: yield promiseAttempt; michael@0: michael@0: do_check_true(download.stopped); michael@0: do_check_true(download.succeeded); michael@0: do_check_false(download.canceled); michael@0: do_check_true(download.error === null); michael@0: michael@0: yield promiseVerifyContents(download.target.path, michael@0: TEST_DATA_SHORT + TEST_DATA_SHORT); michael@0: }); michael@0: michael@0: /** michael@0: * Cancels a download and restarts it from where it stopped. michael@0: */ michael@0: add_task(function test_cancel_midway_restart_tryToKeepPartialData() michael@0: { michael@0: let download = yield promiseStartDownload_tryToKeepPartialData(); michael@0: yield download.cancel(); michael@0: michael@0: do_check_true(download.stopped); michael@0: do_check_true(download.hasPartialData); michael@0: michael@0: // The target file should not exist, but we should have kept the partial data. michael@0: do_check_false(yield OS.File.exists(download.target.path)); michael@0: yield promiseVerifyContents(download.target.partFilePath, TEST_DATA_SHORT); michael@0: michael@0: // Verify that the server sent the response from the start. michael@0: do_check_eq(gMostRecentFirstBytePos, 0); michael@0: michael@0: // The second time, we'll request and obtain the second part of the response, michael@0: // but we still stop when half of the remaining progress is reached. michael@0: let deferMidway = Promise.defer(); michael@0: download.onchange = function () { michael@0: if (!download.stopped && !download.canceled && michael@0: download.currentBytes == Math.floor(TEST_DATA_SHORT.length * 3 / 2)) { michael@0: download.onchange = null; michael@0: deferMidway.resolve(); michael@0: } michael@0: }; michael@0: michael@0: mustInterruptResponses(); michael@0: let promiseAttempt = download.start(); michael@0: michael@0: // Continue when the number of bytes we received is correct, then check that michael@0: // progress is at about 75 percent. The exact figure may vary because of michael@0: // rounding issues, since the total number of bytes in the response might not michael@0: // be a multiple of four. michael@0: yield deferMidway.promise; michael@0: do_check_true(download.progress > 72 && download.progress < 78); michael@0: michael@0: // Now we allow the download to finish. michael@0: continueResponses(); michael@0: yield promiseAttempt; michael@0: michael@0: // Check that the server now sent the second part only. michael@0: do_check_eq(gMostRecentFirstBytePos, TEST_DATA_SHORT.length); michael@0: michael@0: // The target file should now have been created, and the ".part" file deleted. michael@0: yield promiseVerifyContents(download.target.path, michael@0: TEST_DATA_SHORT + TEST_DATA_SHORT); michael@0: do_check_false(yield OS.File.exists(download.target.partFilePath)); michael@0: }); michael@0: michael@0: /** michael@0: * Cancels a download while keeping partially downloaded data, then removes the michael@0: * data and restarts the download from the beginning. michael@0: */ michael@0: add_task(function test_cancel_midway_restart_removePartialData() michael@0: { michael@0: let download = yield promiseStartDownload_tryToKeepPartialData(); michael@0: yield download.cancel(); michael@0: michael@0: do_check_true(download.hasPartialData); michael@0: yield promiseVerifyContents(download.target.partFilePath, TEST_DATA_SHORT); michael@0: michael@0: yield download.removePartialData(); michael@0: michael@0: do_check_false(download.hasPartialData); michael@0: do_check_false(yield OS.File.exists(download.target.partFilePath)); michael@0: michael@0: // The second time, we'll request and obtain the entire response again. michael@0: continueResponses(); michael@0: yield download.start(); michael@0: michael@0: // Verify that the server sent the response from the start. michael@0: do_check_eq(gMostRecentFirstBytePos, 0); michael@0: michael@0: // The target file should now have been created, and the ".part" file deleted. michael@0: yield promiseVerifyContents(download.target.path, michael@0: TEST_DATA_SHORT + TEST_DATA_SHORT); michael@0: do_check_false(yield OS.File.exists(download.target.partFilePath)); michael@0: }); michael@0: michael@0: /** michael@0: * Cancels a download while keeping partially downloaded data, then removes the michael@0: * data and restarts the download from the beginning without keeping the partial michael@0: * data anymore. michael@0: */ michael@0: add_task(function test_cancel_midway_restart_tryToKeepPartialData_false() michael@0: { michael@0: let download = yield promiseStartDownload_tryToKeepPartialData(); michael@0: yield download.cancel(); michael@0: michael@0: download.tryToKeepPartialData = false; michael@0: michael@0: // The above property change does not affect existing partial data. michael@0: do_check_true(download.hasPartialData); michael@0: yield promiseVerifyContents(download.target.partFilePath, TEST_DATA_SHORT); michael@0: michael@0: yield download.removePartialData(); michael@0: do_check_false(yield OS.File.exists(download.target.partFilePath)); michael@0: michael@0: // Restart the download from the beginning. michael@0: mustInterruptResponses(); michael@0: download.start(); michael@0: michael@0: yield promiseDownloadMidway(download); michael@0: yield promisePartFileReady(download); michael@0: michael@0: // While the download is in progress, we should still have a ".part" file. michael@0: do_check_false(download.hasPartialData); michael@0: do_check_true(yield OS.File.exists(download.target.partFilePath)); michael@0: michael@0: yield download.cancel(); michael@0: michael@0: // The ".part" file should be deleted now that the download is canceled. michael@0: do_check_false(download.hasPartialData); michael@0: do_check_false(yield OS.File.exists(download.target.partFilePath)); michael@0: michael@0: // The third time, we'll request and obtain the entire response again. michael@0: continueResponses(); michael@0: yield download.start(); michael@0: michael@0: // Verify that the server sent the response from the start. michael@0: do_check_eq(gMostRecentFirstBytePos, 0); michael@0: michael@0: // The target file should now have been created, and the ".part" file deleted. michael@0: yield promiseVerifyContents(download.target.path, michael@0: TEST_DATA_SHORT + TEST_DATA_SHORT); michael@0: do_check_false(yield OS.File.exists(download.target.partFilePath)); michael@0: }); michael@0: michael@0: /** michael@0: * Cancels a download right after starting it, then restarts it immediately. michael@0: */ michael@0: add_task(function test_cancel_immediately_restart_immediately() michael@0: { michael@0: mustInterruptResponses(); michael@0: michael@0: let download = yield promiseStartDownload(httpUrl("interruptible.txt")); michael@0: let promiseAttempt = download.start(); michael@0: michael@0: do_check_false(download.stopped); michael@0: michael@0: download.cancel(); michael@0: do_check_true(download.canceled); michael@0: michael@0: let promiseRestarted = download.start(); michael@0: do_check_false(download.stopped); michael@0: do_check_false(download.canceled); michael@0: do_check_true(download.error === null); michael@0: michael@0: // For the following test, we rely on the network layer reporting its progress michael@0: // asynchronously. Otherwise, there is nothing stopping the restarted michael@0: // download from reaching the same progress as the first request already. michael@0: do_check_eq(download.hasProgress, false); michael@0: do_check_eq(download.progress, 0); michael@0: do_check_eq(download.totalBytes, 0); michael@0: do_check_eq(download.currentBytes, 0); michael@0: michael@0: // Ensure the next request is now allowed to complete, regardless of whether michael@0: // the canceled request was received by the server or not. michael@0: continueResponses(); michael@0: try { michael@0: yield promiseAttempt; michael@0: // If we get here, it means that the first attempt actually succeeded. In michael@0: // fact, this could be a valid outcome, because the cancellation request may michael@0: // not have been processed in time before the download finished. michael@0: do_print("The download should have been canceled."); michael@0: } catch (ex if ex instanceof Downloads.Error) { michael@0: do_check_false(ex.becauseSourceFailed); michael@0: do_check_false(ex.becauseTargetFailed); michael@0: } michael@0: michael@0: yield promiseRestarted; michael@0: michael@0: do_check_true(download.stopped); michael@0: do_check_true(download.succeeded); michael@0: do_check_false(download.canceled); michael@0: do_check_true(download.error === null); michael@0: michael@0: yield promiseVerifyContents(download.target.path, michael@0: TEST_DATA_SHORT + TEST_DATA_SHORT); michael@0: }); michael@0: michael@0: /** michael@0: * Cancels a download midway, then restarts it immediately. michael@0: */ michael@0: add_task(function test_cancel_midway_restart_immediately() michael@0: { michael@0: mustInterruptResponses(); michael@0: michael@0: let download = yield promiseStartDownload(httpUrl("interruptible.txt")); michael@0: let promiseAttempt = download.start(); michael@0: michael@0: // The first time, cancel the download midway. michael@0: yield promiseDownloadMidway(download); michael@0: download.cancel(); michael@0: do_check_true(download.canceled); michael@0: michael@0: let promiseRestarted = download.start(); michael@0: do_check_false(download.stopped); michael@0: do_check_false(download.canceled); michael@0: do_check_true(download.error === null); michael@0: michael@0: // For the following test, we rely on the network layer reporting its progress michael@0: // asynchronously. Otherwise, there is nothing stopping the restarted michael@0: // download from reaching the same progress as the first request already. michael@0: do_check_eq(download.hasProgress, false); michael@0: do_check_eq(download.progress, 0); michael@0: do_check_eq(download.totalBytes, 0); michael@0: do_check_eq(download.currentBytes, 0); michael@0: michael@0: // The second request is allowed to complete. michael@0: continueResponses(); michael@0: try { michael@0: yield promiseAttempt; michael@0: do_throw("The download should have been canceled."); michael@0: } catch (ex if ex instanceof Downloads.Error) { michael@0: do_check_false(ex.becauseSourceFailed); michael@0: do_check_false(ex.becauseTargetFailed); michael@0: } michael@0: michael@0: yield promiseRestarted; michael@0: michael@0: do_check_true(download.stopped); michael@0: do_check_true(download.succeeded); michael@0: do_check_false(download.canceled); michael@0: do_check_true(download.error === null); michael@0: michael@0: yield promiseVerifyContents(download.target.path, michael@0: TEST_DATA_SHORT + TEST_DATA_SHORT); michael@0: }); michael@0: michael@0: /** michael@0: * Calls the "cancel" method on a successful download. michael@0: */ michael@0: add_task(function test_cancel_successful() michael@0: { michael@0: let download = yield promiseStartDownload(); michael@0: yield promiseDownloadStopped(download); michael@0: michael@0: // The cancel method should succeed with no effect. michael@0: yield download.cancel(); michael@0: michael@0: do_check_true(download.stopped); michael@0: do_check_true(download.succeeded); michael@0: do_check_false(download.canceled); michael@0: do_check_true(download.error === null); michael@0: michael@0: yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT); michael@0: }); michael@0: michael@0: /** michael@0: * Calls the "cancel" method two times in a row. michael@0: */ michael@0: add_task(function test_cancel_twice() michael@0: { michael@0: mustInterruptResponses(); michael@0: michael@0: let download = yield promiseStartDownload(httpUrl("interruptible.txt")); michael@0: michael@0: let promiseAttempt = download.start(); michael@0: do_check_false(download.stopped); michael@0: michael@0: let promiseCancel1 = download.cancel(); michael@0: do_check_true(download.canceled); michael@0: let promiseCancel2 = download.cancel(); michael@0: michael@0: try { michael@0: yield promiseAttempt; michael@0: do_throw("The download should have been canceled."); michael@0: } catch (ex if ex instanceof Downloads.Error) { michael@0: do_check_false(ex.becauseSourceFailed); michael@0: do_check_false(ex.becauseTargetFailed); michael@0: } michael@0: michael@0: // Both promises should now be resolved. michael@0: yield promiseCancel1; michael@0: yield promiseCancel2; michael@0: michael@0: do_check_true(download.stopped); michael@0: do_check_false(download.succeeded); michael@0: do_check_true(download.canceled); michael@0: do_check_true(download.error === null); michael@0: michael@0: do_check_false(yield OS.File.exists(download.target.path)); michael@0: }); michael@0: michael@0: /** michael@0: * Checks that a download cannot be restarted after the "finalize" method. michael@0: */ michael@0: add_task(function test_finalize() michael@0: { michael@0: mustInterruptResponses(); michael@0: michael@0: let download = yield promiseStartDownload(httpUrl("interruptible.txt")); michael@0: michael@0: let promiseFinalized = download.finalize(); michael@0: michael@0: try { michael@0: yield download.start(); michael@0: do_throw("It should not be possible to restart after finalization."); michael@0: } catch (ex) { } michael@0: michael@0: yield promiseFinalized; michael@0: michael@0: do_check_true(download.stopped); michael@0: do_check_false(download.succeeded); michael@0: do_check_true(download.canceled); michael@0: do_check_true(download.error === null); michael@0: michael@0: do_check_false(yield OS.File.exists(download.target.path)); michael@0: }); michael@0: michael@0: /** michael@0: * Checks that the "finalize" method can remove partially downloaded data. michael@0: */ michael@0: add_task(function test_finalize_tryToKeepPartialData() michael@0: { michael@0: // Check finalization without removing partial data. michael@0: let download = yield promiseStartDownload_tryToKeepPartialData(); michael@0: yield download.finalize(); michael@0: michael@0: do_check_true(download.hasPartialData); michael@0: do_check_true(yield OS.File.exists(download.target.partFilePath)); michael@0: michael@0: // Clean up. michael@0: yield download.removePartialData(); michael@0: michael@0: // Check finalization while removing partial data. michael@0: download = yield promiseStartDownload_tryToKeepPartialData(); michael@0: yield download.finalize(true); michael@0: michael@0: do_check_false(download.hasPartialData); michael@0: do_check_false(yield OS.File.exists(download.target.partFilePath)); michael@0: }); michael@0: michael@0: /** michael@0: * Checks that whenSucceeded returns a promise that is resolved after a restart. michael@0: */ michael@0: add_task(function test_whenSucceeded_after_restart() michael@0: { michael@0: mustInterruptResponses(); michael@0: michael@0: let promiseSucceeded; michael@0: michael@0: let download; michael@0: if (!gUseLegacySaver) { michael@0: // When testing DownloadCopySaver, we have control over the download, thus michael@0: // we can verify getting a reference before the first download attempt. michael@0: download = yield promiseNewDownload(httpUrl("interruptible.txt")); michael@0: promiseSucceeded = download.whenSucceeded(); michael@0: download.start(); michael@0: } else { michael@0: // When testing DownloadLegacySaver, the download is already started when it michael@0: // is created, thus we cannot get the reference before the first attempt. michael@0: download = yield promiseStartLegacyDownload(httpUrl("interruptible.txt")); michael@0: promiseSucceeded = download.whenSucceeded(); michael@0: } michael@0: michael@0: // Cancel the first download attempt. michael@0: yield download.cancel(); michael@0: michael@0: // The second request is allowed to complete. michael@0: continueResponses(); michael@0: download.start(); michael@0: michael@0: // Wait for the download to finish by waiting on the whenSucceeded promise. michael@0: yield promiseSucceeded; michael@0: michael@0: do_check_true(download.stopped); michael@0: do_check_true(download.succeeded); michael@0: do_check_false(download.canceled); michael@0: do_check_true(download.error === null); michael@0: michael@0: yield promiseVerifyContents(download.target.path, michael@0: TEST_DATA_SHORT + TEST_DATA_SHORT); michael@0: }); michael@0: michael@0: /** michael@0: * Ensures download error details are reported on network failures. michael@0: */ michael@0: add_task(function test_error_source() michael@0: { michael@0: let serverSocket = startFakeServer(); michael@0: try { michael@0: let sourceUrl = "http://localhost:" + serverSocket.port + "/source.txt"; michael@0: michael@0: let download; michael@0: try { michael@0: if (!gUseLegacySaver) { michael@0: // When testing DownloadCopySaver, we want to check that the promise michael@0: // returned by the "start" method is rejected. michael@0: download = yield promiseNewDownload(sourceUrl); michael@0: michael@0: do_check_true(download.error === null); michael@0: michael@0: yield download.start(); michael@0: } else { michael@0: // When testing DownloadLegacySaver, we cannot be sure whether we are michael@0: // testing the promise returned by the "start" method or we are testing michael@0: // the "error" property checked by promiseDownloadStopped. This happens michael@0: // because we don't have control over when the download is started. michael@0: download = yield promiseStartLegacyDownload(sourceUrl); michael@0: yield promiseDownloadStopped(download); michael@0: } michael@0: do_throw("The download should have failed."); michael@0: } catch (ex if ex instanceof Downloads.Error && ex.becauseSourceFailed) { michael@0: // A specific error object is thrown when reading from the source fails. michael@0: } michael@0: michael@0: // Check the properties now that the download stopped. michael@0: do_check_true(download.stopped); michael@0: do_check_false(download.canceled); michael@0: do_check_true(download.error !== null); michael@0: do_check_true(download.error.becauseSourceFailed); michael@0: do_check_false(download.error.becauseTargetFailed); michael@0: michael@0: do_check_false(yield OS.File.exists(download.target.path)); michael@0: } finally { michael@0: serverSocket.close(); michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * Ensures download error details are reported on local writing failures. michael@0: */ michael@0: add_task(function test_error_target() michael@0: { michael@0: // Create a file without write access permissions before downloading. michael@0: let targetFile = getTempFile(TEST_TARGET_FILE_NAME); michael@0: targetFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0); michael@0: try { michael@0: let download; michael@0: try { michael@0: if (!gUseLegacySaver) { michael@0: // When testing DownloadCopySaver, we want to check that the promise michael@0: // returned by the "start" method is rejected. michael@0: download = yield Downloads.createDownload({ michael@0: source: httpUrl("source.txt"), michael@0: target: targetFile, michael@0: }); michael@0: yield download.start(); michael@0: } else { michael@0: // When testing DownloadLegacySaver, we cannot be sure whether we are michael@0: // testing the promise returned by the "start" method or we are testing michael@0: // the "error" property checked by promiseDownloadStopped. This happens michael@0: // because we don't have control over when the download is started. michael@0: download = yield promiseStartLegacyDownload(null, michael@0: { targetFile: targetFile }); michael@0: yield promiseDownloadStopped(download); michael@0: } michael@0: do_throw("The download should have failed."); michael@0: } catch (ex if ex instanceof Downloads.Error && ex.becauseTargetFailed) { michael@0: // A specific error object is thrown when writing to the target fails. michael@0: } michael@0: michael@0: // Check the properties now that the download stopped. michael@0: do_check_true(download.stopped); michael@0: do_check_false(download.canceled); michael@0: do_check_true(download.error !== null); michael@0: do_check_true(download.error.becauseTargetFailed); michael@0: do_check_false(download.error.becauseSourceFailed); michael@0: } finally { michael@0: // Restore the default permissions to allow deleting the file on Windows. michael@0: if (targetFile.exists()) { michael@0: targetFile.permissions = FileUtils.PERMS_FILE; michael@0: targetFile.remove(false); michael@0: } michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * Restarts a failed download. michael@0: */ michael@0: add_task(function test_error_restart() michael@0: { michael@0: let download; michael@0: michael@0: // Create a file without write access permissions before downloading. michael@0: let targetFile = getTempFile(TEST_TARGET_FILE_NAME); michael@0: targetFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0); michael@0: try { michael@0: // Use DownloadCopySaver or DownloadLegacySaver based on the test run, michael@0: // specifying the target file we created. michael@0: if (!gUseLegacySaver) { michael@0: download = yield Downloads.createDownload({ michael@0: source: httpUrl("source.txt"), michael@0: target: targetFile, michael@0: }); michael@0: download.start(); michael@0: } else { michael@0: download = yield promiseStartLegacyDownload(null, michael@0: { targetFile: targetFile }); michael@0: } michael@0: yield promiseDownloadStopped(download); michael@0: do_throw("The download should have failed."); michael@0: } catch (ex if ex instanceof Downloads.Error && ex.becauseTargetFailed) { michael@0: // A specific error object is thrown when writing to the target fails. michael@0: } finally { michael@0: // Restore the default permissions to allow deleting the file on Windows. michael@0: if (targetFile.exists()) { michael@0: targetFile.permissions = FileUtils.PERMS_FILE; michael@0: michael@0: // Also for Windows, rename the file before deleting. This makes the michael@0: // current file name available immediately for a new file, while deleting michael@0: // in place prevents creation of a file with the same name for some time. michael@0: targetFile.moveTo(null, targetFile.leafName + ".delete.tmp"); michael@0: targetFile.remove(false); michael@0: } michael@0: } michael@0: michael@0: // Restart the download and wait for completion. michael@0: yield download.start(); michael@0: michael@0: do_check_true(download.stopped); michael@0: do_check_true(download.succeeded); michael@0: do_check_false(download.canceled); michael@0: do_check_true(download.error === null); michael@0: do_check_eq(download.progress, 100); michael@0: michael@0: yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT); michael@0: }); michael@0: michael@0: /** michael@0: * Executes download in both public and private modes. michael@0: */ michael@0: add_task(function test_public_and_private() michael@0: { michael@0: let sourcePath = "/test_public_and_private.txt"; michael@0: let sourceUrl = httpUrl("test_public_and_private.txt"); michael@0: let testCount = 0; michael@0: michael@0: // Apply pref to allow all cookies. michael@0: Services.prefs.setIntPref("network.cookie.cookieBehavior", 0); michael@0: michael@0: function cleanup() { michael@0: Services.prefs.clearUserPref("network.cookie.cookieBehavior"); michael@0: Services.cookies.removeAll(); michael@0: gHttpServer.registerPathHandler(sourcePath, null); michael@0: } michael@0: do_register_cleanup(cleanup); michael@0: michael@0: gHttpServer.registerPathHandler(sourcePath, function (aRequest, aResponse) { michael@0: aResponse.setHeader("Content-Type", "text/plain", false); michael@0: michael@0: if (testCount == 0) { michael@0: // No cookies should exist for first public download. michael@0: do_check_false(aRequest.hasHeader("Cookie")); michael@0: aResponse.setHeader("Set-Cookie", "foobar=1", false); michael@0: testCount++; michael@0: } else if (testCount == 1) { michael@0: // The cookie should exists for second public download. michael@0: do_check_true(aRequest.hasHeader("Cookie")); michael@0: do_check_eq(aRequest.getHeader("Cookie"), "foobar=1"); michael@0: testCount++; michael@0: } else if (testCount == 2) { michael@0: // No cookies should exist for first private download. michael@0: do_check_false(aRequest.hasHeader("Cookie")); michael@0: } michael@0: }); michael@0: michael@0: let targetFile = getTempFile(TEST_TARGET_FILE_NAME); michael@0: yield Downloads.fetch(sourceUrl, targetFile); michael@0: yield Downloads.fetch(sourceUrl, targetFile); michael@0: michael@0: if (!gUseLegacySaver) { michael@0: let download = yield Downloads.createDownload({ michael@0: source: { url: sourceUrl, isPrivate: true }, michael@0: target: targetFile, michael@0: }); michael@0: yield download.start(); michael@0: } else { michael@0: let download = yield promiseStartLegacyDownload(sourceUrl, michael@0: { isPrivate: true }); michael@0: yield promiseDownloadStopped(download); michael@0: } michael@0: michael@0: cleanup(); michael@0: }); michael@0: michael@0: /** michael@0: * Checks the startTime gets updated even after a restart. michael@0: */ michael@0: add_task(function test_cancel_immediately_restart_and_check_startTime() michael@0: { michael@0: let download = yield promiseStartDownload(); michael@0: michael@0: let startTime = download.startTime; michael@0: do_check_true(isValidDate(download.startTime)); michael@0: michael@0: yield download.cancel(); michael@0: do_check_eq(download.startTime.getTime(), startTime.getTime()); michael@0: michael@0: // Wait for a timeout. michael@0: yield promiseTimeout(10); michael@0: michael@0: yield download.start(); michael@0: do_check_true(download.startTime.getTime() > startTime.getTime()); michael@0: }); michael@0: michael@0: /** michael@0: * Executes download with content-encoding. michael@0: */ michael@0: add_task(function test_with_content_encoding() michael@0: { michael@0: let sourcePath = "/test_with_content_encoding.txt"; michael@0: let sourceUrl = httpUrl("test_with_content_encoding.txt"); michael@0: michael@0: function cleanup() { michael@0: gHttpServer.registerPathHandler(sourcePath, null); michael@0: } michael@0: do_register_cleanup(cleanup); michael@0: michael@0: gHttpServer.registerPathHandler(sourcePath, function (aRequest, aResponse) { michael@0: aResponse.setHeader("Content-Type", "text/plain", false); michael@0: aResponse.setHeader("Content-Encoding", "gzip", false); michael@0: aResponse.setHeader("Content-Length", michael@0: "" + TEST_DATA_SHORT_GZIP_ENCODED.length, false); michael@0: michael@0: let bos = new BinaryOutputStream(aResponse.bodyOutputStream); michael@0: bos.writeByteArray(TEST_DATA_SHORT_GZIP_ENCODED, michael@0: TEST_DATA_SHORT_GZIP_ENCODED.length); michael@0: }); michael@0: michael@0: let download = yield promiseStartDownload(sourceUrl); michael@0: yield promiseDownloadStopped(download); michael@0: michael@0: do_check_eq(download.progress, 100); michael@0: do_check_eq(download.totalBytes, TEST_DATA_SHORT_GZIP_ENCODED.length); michael@0: michael@0: // Ensure the content matches the decoded test data. michael@0: yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT); michael@0: michael@0: cleanup(); michael@0: }); michael@0: michael@0: /** michael@0: * Checks that the file is not decoded if the extension matches the encoding. michael@0: */ michael@0: add_task(function test_with_content_encoding_ignore_extension() michael@0: { michael@0: let sourcePath = "/test_with_content_encoding_ignore_extension.gz"; michael@0: let sourceUrl = httpUrl("test_with_content_encoding_ignore_extension.gz"); michael@0: michael@0: function cleanup() { michael@0: gHttpServer.registerPathHandler(sourcePath, null); michael@0: } michael@0: do_register_cleanup(cleanup); michael@0: michael@0: gHttpServer.registerPathHandler(sourcePath, function (aRequest, aResponse) { michael@0: aResponse.setHeader("Content-Type", "text/plain", false); michael@0: aResponse.setHeader("Content-Encoding", "gzip", false); michael@0: aResponse.setHeader("Content-Length", michael@0: "" + TEST_DATA_SHORT_GZIP_ENCODED.length, false); michael@0: michael@0: let bos = new BinaryOutputStream(aResponse.bodyOutputStream); michael@0: bos.writeByteArray(TEST_DATA_SHORT_GZIP_ENCODED, michael@0: TEST_DATA_SHORT_GZIP_ENCODED.length); michael@0: }); michael@0: michael@0: let download = yield promiseStartDownload(sourceUrl); michael@0: yield promiseDownloadStopped(download); michael@0: michael@0: do_check_eq(download.progress, 100); michael@0: do_check_eq(download.totalBytes, TEST_DATA_SHORT_GZIP_ENCODED.length); michael@0: michael@0: // Ensure the content matches the encoded test data. We convert the data to a michael@0: // string before executing the content check. michael@0: yield promiseVerifyContents(download.target.path, michael@0: String.fromCharCode.apply(String, TEST_DATA_SHORT_GZIP_ENCODED)); michael@0: michael@0: cleanup(); michael@0: }); michael@0: michael@0: /** michael@0: * Cancels and restarts a download sequentially with content-encoding. michael@0: */ michael@0: add_task(function test_cancel_midway_restart_with_content_encoding() michael@0: { michael@0: mustInterruptResponses(); michael@0: michael@0: let download = yield promiseStartDownload(httpUrl("interruptible_gzip.txt")); michael@0: michael@0: // The first time, cancel the download midway. michael@0: let deferCancel = Promise.defer(); michael@0: let onchange = function () { michael@0: if (!download.stopped && !download.canceled && michael@0: download.currentBytes == TEST_DATA_SHORT_GZIP_ENCODED_FIRST.length) { michael@0: deferCancel.resolve(download.cancel()); michael@0: } michael@0: }; michael@0: michael@0: // Register for the notification, but also call the function directly in michael@0: // case the download already reached the expected progress. michael@0: download.onchange = onchange; michael@0: onchange(); michael@0: michael@0: yield deferCancel.promise; michael@0: michael@0: do_check_true(download.stopped); michael@0: michael@0: // The second time, we'll provide the entire interruptible response. michael@0: continueResponses(); michael@0: download.onchange = null; michael@0: yield download.start(); michael@0: michael@0: do_check_eq(download.progress, 100); michael@0: do_check_eq(download.totalBytes, TEST_DATA_SHORT_GZIP_ENCODED.length); michael@0: michael@0: yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT); michael@0: }); michael@0: michael@0: /** michael@0: * Download with parental controls enabled. michael@0: */ michael@0: add_task(function test_blocked_parental_controls() michael@0: { michael@0: function cleanup() { michael@0: DownloadIntegration.shouldBlockInTest = false; michael@0: } michael@0: do_register_cleanup(cleanup); michael@0: DownloadIntegration.shouldBlockInTest = true; michael@0: michael@0: let download; michael@0: try { michael@0: if (!gUseLegacySaver) { michael@0: // When testing DownloadCopySaver, we want to check that the promise michael@0: // returned by the "start" method is rejected. michael@0: download = yield promiseNewDownload(); michael@0: yield download.start(); michael@0: } else { michael@0: // When testing DownloadLegacySaver, we cannot be sure whether we are michael@0: // testing the promise returned by the "start" method or we are testing michael@0: // the "error" property checked by promiseDownloadStopped. This happens michael@0: // because we don't have control over when the download is started. michael@0: download = yield promiseStartLegacyDownload(); michael@0: yield promiseDownloadStopped(download); michael@0: } michael@0: do_throw("The download should have blocked."); michael@0: } catch (ex if ex instanceof Downloads.Error && ex.becauseBlocked) { michael@0: do_check_true(ex.becauseBlockedByParentalControls); michael@0: do_check_true(download.error.becauseBlockedByParentalControls); michael@0: } michael@0: michael@0: // Now that the download stopped, the target file should not exist. michael@0: do_check_false(yield OS.File.exists(download.target.path)); michael@0: michael@0: cleanup(); michael@0: }); michael@0: michael@0: /** michael@0: * Test a download that will be blocked by Windows parental controls by michael@0: * resulting in an HTTP status code of 450. michael@0: */ michael@0: add_task(function test_blocked_parental_controls_httpstatus450() michael@0: { michael@0: let download; michael@0: try { michael@0: if (!gUseLegacySaver) { michael@0: download = yield promiseNewDownload(httpUrl("parentalblocked.zip")); michael@0: yield download.start(); michael@0: } michael@0: else { michael@0: download = yield promiseStartLegacyDownload(httpUrl("parentalblocked.zip")); michael@0: yield promiseDownloadStopped(download); michael@0: } michael@0: do_throw("The download should have blocked."); michael@0: } catch (ex if ex instanceof Downloads.Error && ex.becauseBlocked) { michael@0: do_check_true(ex.becauseBlockedByParentalControls); michael@0: do_check_true(download.error.becauseBlockedByParentalControls); michael@0: do_check_true(download.stopped); michael@0: } michael@0: michael@0: do_check_false(yield OS.File.exists(download.target.path)); michael@0: }); michael@0: michael@0: /** michael@0: * Check that DownloadCopySaver can always retrieve the hash. michael@0: * DownloadLegacySaver can only retrieve the hash when michael@0: * nsIExternalHelperAppService is invoked. michael@0: */ michael@0: add_task(function test_getSha256Hash() michael@0: { michael@0: if (!gUseLegacySaver) { michael@0: let download = yield promiseStartDownload(httpUrl("source.txt")); michael@0: yield promiseDownloadStopped(download); michael@0: do_check_true(download.stopped); michael@0: do_check_eq(32, download.saver.getSha256Hash().length); michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * Checks that application reputation blocks the download and the target file michael@0: * does not exist. michael@0: */ michael@0: add_task(function test_blocked_applicationReputation() michael@0: { michael@0: function cleanup() { michael@0: DownloadIntegration.shouldBlockInTestForApplicationReputation = false; michael@0: } michael@0: do_register_cleanup(cleanup); michael@0: DownloadIntegration.shouldBlockInTestForApplicationReputation = true; michael@0: michael@0: let download; michael@0: try { michael@0: if (!gUseLegacySaver) { michael@0: // When testing DownloadCopySaver, we want to check that the promise michael@0: // returned by the "start" method is rejected. michael@0: download = yield promiseNewDownload(); michael@0: yield download.start(); michael@0: } else { michael@0: // When testing DownloadLegacySaver, we cannot be sure whether we are michael@0: // testing the promise returned by the "start" method or we are testing michael@0: // the "error" property checked by promiseDownloadStopped. This happens michael@0: // because we don't have control over when the download is started. michael@0: download = yield promiseStartLegacyDownload(); michael@0: yield promiseDownloadStopped(download); michael@0: } michael@0: do_throw("The download should have blocked."); michael@0: } catch (ex if ex instanceof Downloads.Error && ex.becauseBlocked) { michael@0: do_check_true(ex.becauseBlockedByReputationCheck); michael@0: do_check_true(download.error.becauseBlockedByReputationCheck); michael@0: } michael@0: michael@0: // Now that the download is blocked, the target file should not exist. michael@0: do_check_false(yield OS.File.exists(download.target.path)); michael@0: cleanup(); michael@0: }); michael@0: michael@0: /** michael@0: * download.showContainingDirectory() action michael@0: */ michael@0: add_task(function test_showContainingDirectory() { michael@0: DownloadIntegration._deferTestShowDir = Promise.defer(); michael@0: michael@0: let targetPath = getTempFile(TEST_TARGET_FILE_NAME).path; michael@0: michael@0: let download = yield Downloads.createDownload({ michael@0: source: { url: httpUrl("source.txt") }, michael@0: target: "" michael@0: }); michael@0: michael@0: try { michael@0: yield download.showContainingDirectory(); michael@0: do_throw("Should have failed because of an invalid path."); michael@0: } catch (ex if ex instanceof Components.Exception) { michael@0: // Invalid paths on Windows are reported with NS_ERROR_FAILURE, michael@0: // but with NS_ERROR_FILE_UNRECOGNIZED_PATH on Mac/Linux michael@0: let validResult = ex.result == Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH || michael@0: ex.result == Cr.NS_ERROR_FAILURE; michael@0: do_check_true(validResult); michael@0: } michael@0: michael@0: download = yield Downloads.createDownload({ michael@0: source: { url: httpUrl("source.txt") }, michael@0: target: targetPath michael@0: }); michael@0: michael@0: michael@0: DownloadIntegration._deferTestShowDir = Promise.defer(); michael@0: download.showContainingDirectory(); michael@0: let result = yield DownloadIntegration._deferTestShowDir.promise; michael@0: do_check_eq(result, "success"); michael@0: }); michael@0: michael@0: /** michael@0: * download.launch() action michael@0: */ michael@0: add_task(function test_launch() { michael@0: let customLauncher = getTempFile("app-launcher"); michael@0: michael@0: // Test both with and without setting a custom application. michael@0: for (let launcherPath of [null, customLauncher.path]) { michael@0: let download; michael@0: if (!gUseLegacySaver) { michael@0: // When testing DownloadCopySaver, we have control over the download, thus michael@0: // we can test that file is not launched if download.succeeded is not set. michael@0: download = yield Downloads.createDownload({ michael@0: source: httpUrl("source.txt"), michael@0: target: getTempFile(TEST_TARGET_FILE_NAME).path, michael@0: launcherPath: launcherPath, michael@0: launchWhenSucceeded: true michael@0: }); michael@0: michael@0: try { michael@0: yield download.launch(); michael@0: do_throw("Can't launch download file as it has not completed yet"); michael@0: } catch (ex) { michael@0: do_check_eq(ex.message, michael@0: "launch can only be called if the download succeeded"); michael@0: } michael@0: michael@0: yield download.start(); michael@0: } else { michael@0: // When testing DownloadLegacySaver, the download is already started when michael@0: // it is created, thus we don't test calling "launch" before starting. michael@0: download = yield promiseStartLegacyDownload( michael@0: httpUrl("source.txt"), michael@0: { launcherPath: launcherPath, michael@0: launchWhenSucceeded: true }); michael@0: yield promiseDownloadStopped(download); michael@0: } michael@0: michael@0: do_check_true(download.launchWhenSucceeded); michael@0: michael@0: DownloadIntegration._deferTestOpenFile = Promise.defer(); michael@0: download.launch(); michael@0: let result = yield DownloadIntegration._deferTestOpenFile.promise; michael@0: michael@0: // Verify that the results match the test case. michael@0: if (!launcherPath) { michael@0: // This indicates that the default handler has been chosen. michael@0: do_check_true(result === null); michael@0: } else { michael@0: // Check the nsIMIMEInfo instance that would have been used for launching. michael@0: do_check_eq(result.preferredAction, Ci.nsIMIMEInfo.useHelperApp); michael@0: do_check_true(result.preferredApplicationHandler michael@0: .QueryInterface(Ci.nsILocalHandlerApp) michael@0: .executable.equals(customLauncher)); michael@0: } michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * Test passing an invalid path as the launcherPath property. michael@0: */ michael@0: add_task(function test_launcherPath_invalid() { michael@0: let download = yield Downloads.createDownload({ michael@0: source: { url: httpUrl("source.txt") }, michael@0: target: { path: getTempFile(TEST_TARGET_FILE_NAME).path }, michael@0: launcherPath: " " michael@0: }); michael@0: michael@0: DownloadIntegration._deferTestOpenFile = Promise.defer(); michael@0: yield download.start(); michael@0: try { michael@0: download.launch(); michael@0: result = yield DownloadIntegration._deferTestOpenFile.promise; michael@0: do_throw("Can't launch file with invalid custom launcher") michael@0: } catch (ex if ex instanceof Components.Exception) { michael@0: // Invalid paths on Windows are reported with NS_ERROR_FAILURE, michael@0: // but with NS_ERROR_FILE_UNRECOGNIZED_PATH on Mac/Linux michael@0: let validResult = ex.result == Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH || michael@0: ex.result == Cr.NS_ERROR_FAILURE; michael@0: do_check_true(validResult); michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * Tests that download.launch() is automatically called after michael@0: * the download finishes if download.launchWhenSucceeded = true michael@0: */ michael@0: add_task(function test_launchWhenSucceeded() { michael@0: let customLauncher = getTempFile("app-launcher"); michael@0: michael@0: // Test both with and without setting a custom application. michael@0: for (let launcherPath of [null, customLauncher.path]) { michael@0: DownloadIntegration._deferTestOpenFile = Promise.defer(); michael@0: michael@0: if (!gUseLegacySaver) { michael@0: let download = yield Downloads.createDownload({ michael@0: source: httpUrl("source.txt"), michael@0: target: getTempFile(TEST_TARGET_FILE_NAME).path, michael@0: launchWhenSucceeded: true, michael@0: launcherPath: launcherPath, michael@0: }); michael@0: yield download.start(); michael@0: } else { michael@0: let download = yield promiseStartLegacyDownload( michael@0: httpUrl("source.txt"), michael@0: { launcherPath: launcherPath, michael@0: launchWhenSucceeded: true }); michael@0: yield promiseDownloadStopped(download); michael@0: } michael@0: michael@0: let result = yield DownloadIntegration._deferTestOpenFile.promise; michael@0: michael@0: // Verify that the results match the test case. michael@0: if (!launcherPath) { michael@0: // This indicates that the default handler has been chosen. michael@0: do_check_true(result === null); michael@0: } else { michael@0: // Check the nsIMIMEInfo instance that would have been used for launching. michael@0: do_check_eq(result.preferredAction, Ci.nsIMIMEInfo.useHelperApp); michael@0: do_check_true(result.preferredApplicationHandler michael@0: .QueryInterface(Ci.nsILocalHandlerApp) michael@0: .executable.equals(customLauncher)); michael@0: } michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * Tests that the proper content type is set for a normal download. michael@0: */ michael@0: add_task(function test_contentType() { michael@0: let download = yield promiseStartDownload(httpUrl("source.txt")); michael@0: yield promiseDownloadStopped(download); michael@0: michael@0: do_check_eq("text/plain", download.contentType); michael@0: }); michael@0: michael@0: /** michael@0: * Tests that the serialization/deserialization of the startTime Date michael@0: * object works correctly. michael@0: */ michael@0: add_task(function test_toSerializable_startTime() michael@0: { michael@0: let download1 = yield promiseStartDownload(httpUrl("source.txt")); michael@0: yield promiseDownloadStopped(download1); michael@0: michael@0: let serializable = download1.toSerializable(); michael@0: let reserialized = JSON.parse(JSON.stringify(serializable)); michael@0: michael@0: let download2 = yield Downloads.createDownload(reserialized); michael@0: michael@0: do_check_eq(download1.startTime.constructor.name, "Date"); michael@0: do_check_eq(download2.startTime.constructor.name, "Date"); michael@0: do_check_eq(download1.startTime.toJSON(), download2.startTime.toJSON()); michael@0: }); michael@0: michael@0: /** michael@0: * This test will call the platform specific operations within michael@0: * DownloadPlatform::DownloadDone. While there is no test to verify the michael@0: * specific behaviours, this at least ensures that there is no error or crash. michael@0: */ michael@0: add_task(function test_platform_integration() michael@0: { michael@0: let downloadFiles = []; michael@0: function cleanup() { michael@0: for (let file of downloadFiles) { michael@0: file.remove(true); michael@0: } michael@0: } michael@0: do_register_cleanup(cleanup); michael@0: michael@0: for (let isPrivate of [false, true]) { michael@0: DownloadIntegration.downloadDoneCalled = false; michael@0: michael@0: // Some platform specific operations only operate on files outside the michael@0: // temporary directory or in the Downloads directory (such as setting michael@0: // the Windows searchable attribute, and the Mac Downloads icon bouncing), michael@0: // so use the system Downloads directory for the target file. michael@0: let targetFilePath = yield DownloadIntegration.getSystemDownloadsDirectory(); michael@0: targetFilePath = OS.Path.join(targetFilePath, michael@0: "test" + (Math.floor(Math.random() * 1000000))); michael@0: let targetFile = new FileUtils.File(targetFilePath); michael@0: downloadFiles.push(targetFile); michael@0: michael@0: let download; michael@0: if (gUseLegacySaver) { michael@0: download = yield promiseStartLegacyDownload(httpUrl("source.txt"), michael@0: { targetFile: targetFile }); michael@0: } michael@0: else { michael@0: download = yield Downloads.createDownload({ michael@0: source: httpUrl("source.txt"), michael@0: target: targetFile, michael@0: }); michael@0: download.start(); michael@0: } michael@0: michael@0: // Wait for the whenSucceeded promise to be resolved first. michael@0: // downloadDone should be called before the whenSucceeded promise is resolved. michael@0: yield download.whenSucceeded().then(function () { michael@0: do_check_true(DownloadIntegration.downloadDoneCalled); michael@0: }); michael@0: michael@0: // Then, wait for the promise returned by "start" to be resolved. michael@0: yield promiseDownloadStopped(download); michael@0: michael@0: yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT); michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * Checks that downloads are added to browsing history when they start. michael@0: */ michael@0: add_task(function test_history() michael@0: { michael@0: mustInterruptResponses(); michael@0: michael@0: // We will wait for the visit to be notified during the download. michael@0: yield promiseClearHistory(); michael@0: let promiseVisit = promiseWaitForVisit(httpUrl("interruptible.txt")); michael@0: michael@0: // Start a download that is not allowed to finish yet. michael@0: let download = yield promiseStartDownload(httpUrl("interruptible.txt")); michael@0: michael@0: // The history notifications should be received before the download completes. michael@0: let [time, transitionType] = yield promiseVisit; michael@0: do_check_eq(time, download.startTime.getTime() * 1000); michael@0: do_check_eq(transitionType, Ci.nsINavHistoryService.TRANSITION_DOWNLOAD); michael@0: michael@0: // Restart and complete the download after clearing history. michael@0: yield promiseClearHistory(); michael@0: download.cancel(); michael@0: continueResponses(); michael@0: yield download.start(); michael@0: michael@0: // The restart should not have added a new history visit. michael@0: do_check_false(yield promiseIsURIVisited(httpUrl("interruptible.txt"))); michael@0: }); michael@0: michael@0: /** michael@0: * Checks that downloads started by nsIHelperAppService are added to the michael@0: * browsing history when they start. michael@0: */ michael@0: add_task(function test_history_tryToKeepPartialData() michael@0: { michael@0: // We will wait for the visit to be notified during the download. michael@0: yield promiseClearHistory(); michael@0: let promiseVisit = michael@0: promiseWaitForVisit(httpUrl("interruptible_resumable.txt")); michael@0: michael@0: // Start a download that is not allowed to finish yet. michael@0: let beforeStartTimeMs = Date.now(); michael@0: let download = yield promiseStartDownload_tryToKeepPartialData(); michael@0: michael@0: // The history notifications should be received before the download completes. michael@0: let [time, transitionType] = yield promiseVisit; michael@0: do_check_eq(transitionType, Ci.nsINavHistoryService.TRANSITION_DOWNLOAD); michael@0: michael@0: // The time set by nsIHelperAppService may be different than the start time in michael@0: // the download object, thus we only check that it is a meaningful time. Note michael@0: // that we subtract one second from the earliest time to account for rounding. michael@0: do_check_true(time >= beforeStartTimeMs * 1000 - 1000000); michael@0: michael@0: // Complete the download before finishing the test. michael@0: continueResponses(); michael@0: yield promiseDownloadStopped(download); michael@0: }); michael@0: michael@0: /** michael@0: * Tests that the temp download files are removed on exit and exiting private michael@0: * mode after they have been launched. michael@0: */ michael@0: add_task(function test_launchWhenSucceeded_deleteTempFileOnExit() { michael@0: const kDeleteTempFileOnExit = "browser.helperApps.deleteTempFileOnExit"; michael@0: michael@0: let customLauncherPath = getTempFile("app-launcher").path; michael@0: let autoDeleteTargetPathOne = getTempFile(TEST_TARGET_FILE_NAME).path; michael@0: let autoDeleteTargetPathTwo = getTempFile(TEST_TARGET_FILE_NAME).path; michael@0: let noAutoDeleteTargetPath = getTempFile(TEST_TARGET_FILE_NAME).path; michael@0: michael@0: let autoDeleteDownloadOne = yield Downloads.createDownload({ michael@0: source: { url: httpUrl("source.txt"), isPrivate: true }, michael@0: target: autoDeleteTargetPathOne, michael@0: launchWhenSucceeded: true, michael@0: launcherPath: customLauncherPath, michael@0: }); michael@0: yield autoDeleteDownloadOne.start(); michael@0: michael@0: Services.prefs.setBoolPref(kDeleteTempFileOnExit, true); michael@0: let autoDeleteDownloadTwo = yield Downloads.createDownload({ michael@0: source: httpUrl("source.txt"), michael@0: target: autoDeleteTargetPathTwo, michael@0: launchWhenSucceeded: true, michael@0: launcherPath: customLauncherPath, michael@0: }); michael@0: yield autoDeleteDownloadTwo.start(); michael@0: michael@0: Services.prefs.setBoolPref(kDeleteTempFileOnExit, false); michael@0: let noAutoDeleteDownload = yield Downloads.createDownload({ michael@0: source: httpUrl("source.txt"), michael@0: target: noAutoDeleteTargetPath, michael@0: launchWhenSucceeded: true, michael@0: launcherPath: customLauncherPath, michael@0: }); michael@0: yield noAutoDeleteDownload.start(); michael@0: michael@0: Services.prefs.clearUserPref(kDeleteTempFileOnExit); michael@0: michael@0: do_check_true(yield OS.File.exists(autoDeleteTargetPathOne)); michael@0: do_check_true(yield OS.File.exists(autoDeleteTargetPathTwo)); michael@0: do_check_true(yield OS.File.exists(noAutoDeleteTargetPath)); michael@0: michael@0: // Simulate leaving private browsing mode michael@0: Services.obs.notifyObservers(null, "last-pb-context-exited", null); michael@0: do_check_false(yield OS.File.exists(autoDeleteTargetPathOne)); michael@0: michael@0: // Simulate browser shutdown michael@0: let expire = Cc["@mozilla.org/uriloader/external-helper-app-service;1"] michael@0: .getService(Ci.nsIObserver); michael@0: expire.observe(null, "profile-before-change", null); michael@0: do_check_false(yield OS.File.exists(autoDeleteTargetPathTwo)); michael@0: do_check_true(yield OS.File.exists(noAutoDeleteTargetPath)); michael@0: });