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

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ts=2 et sw=2 tw=80: */
     3 /* Any copyright is dedicated to the Public Domain.
     4  * http://creativecommons.org/publicdomain/zero/1.0/ */
     6 /**
     7  * Provides infrastructure for automated download components tests.
     8  */
    10 "use strict";
    12 ////////////////////////////////////////////////////////////////////////////////
    13 //// Globals
    15 const Cc = Components.classes;
    16 const Ci = Components.interfaces;
    17 const Cu = Components.utils;
    18 const Cr = Components.results;
    20 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    22 XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
    23                                   "resource://gre/modules/DownloadPaths.jsm");
    24 XPCOMUtils.defineLazyModuleGetter(this, "DownloadIntegration",
    25                                   "resource://gre/modules/DownloadIntegration.jsm");
    26 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
    27                                   "resource://gre/modules/Downloads.jsm");
    28 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
    29                                   "resource://gre/modules/FileUtils.jsm");
    30 XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
    31                                   "resource://testing-common/httpd.js");
    32 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
    33                                   "resource://gre/modules/NetUtil.jsm");
    34 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
    35                                   "resource://gre/modules/PlacesUtils.jsm");
    36 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
    37                                   "resource://gre/modules/Promise.jsm");
    38 XPCOMUtils.defineLazyModuleGetter(this, "Services",
    39                                   "resource://gre/modules/Services.jsm");
    40 XPCOMUtils.defineLazyModuleGetter(this, "Task",
    41                                   "resource://gre/modules/Task.jsm");
    42 XPCOMUtils.defineLazyModuleGetter(this, "OS",
    43                                   "resource://gre/modules/osfile.jsm");
    45 XPCOMUtils.defineLazyServiceGetter(this, "gExternalHelperAppService",
    46            "@mozilla.org/uriloader/external-helper-app-service;1",
    47            Ci.nsIExternalHelperAppService);
    49 const ServerSocket = Components.Constructor(
    50                                 "@mozilla.org/network/server-socket;1",
    51                                 "nsIServerSocket",
    52                                 "init");
    53 const BinaryOutputStream = Components.Constructor(
    54                                       "@mozilla.org/binaryoutputstream;1",
    55                                       "nsIBinaryOutputStream",
    56                                       "setOutputStream")
    58 XPCOMUtils.defineLazyServiceGetter(this, "gMIMEService",
    59                                    "@mozilla.org/mime;1",
    60                                    "nsIMIMEService");
    62 const TEST_TARGET_FILE_NAME = "test-download.txt";
    63 const TEST_STORE_FILE_NAME = "test-downloads.json";
    65 const TEST_REFERRER_URL = "http://www.example.com/referrer.html";
    67 const TEST_DATA_SHORT = "This test string is downloaded.";
    68 // Generate using gzipCompressString in TelemetryPing.jsm.
    69 const TEST_DATA_SHORT_GZIP_ENCODED_FIRST = [
    70  31,139,8,0,0,0,0,0,0,3,11,201,200,44,86,40,73,45,46,81,40,46,41,202,204
    71 ];
    72 const TEST_DATA_SHORT_GZIP_ENCODED_SECOND = [
    73   75,87,0,114,83,242,203,243,114,242,19,83,82,83,244,0,151,222,109,43,31,0,0,0
    74 ];
    75 const TEST_DATA_SHORT_GZIP_ENCODED =
    76   TEST_DATA_SHORT_GZIP_ENCODED_FIRST.concat(TEST_DATA_SHORT_GZIP_ENCODED_SECOND);
    78 /**
    79  * All the tests are implemented with add_task, this starts them automatically.
    80  */
    81 function run_test()
    82 {
    83   do_get_profile();
    84   run_next_test();
    85 }
    87 ////////////////////////////////////////////////////////////////////////////////
    88 //// Support functions
    90 /**
    91  * HttpServer object initialized before tests start.
    92  */
    93 let gHttpServer;
    95 /**
    96  * Given a file name, returns a string containing an URI that points to the file
    97  * on the currently running instance of the test HTTP server.
    98  */
    99 function httpUrl(aFileName) {
   100   return "http://localhost:" + gHttpServer.identity.primaryPort + "/" +
   101          aFileName;
   102 }
   104 // While the previous test file should have deleted all the temporary files it
   105 // used, on Windows these might still be pending deletion on the physical file
   106 // system.  Thus, start from a new base number every time, to make a collision
   107 // with a file that is still pending deletion highly unlikely.
   108 let gFileCounter = Math.floor(Math.random() * 1000000);
   110 /**
   111  * Returns a reference to a temporary file, that is guaranteed not to exist, and
   112  * to have never been created before.
   113  *
   114  * @param aLeafName
   115  *        Suggested leaf name for the file to be created.
   116  *
   117  * @return nsIFile pointing to a non-existent file in a temporary directory.
   118  *
   119  * @note It is not enough to delete the file if it exists, or to delete the file
   120  *       after calling nsIFile.createUnique, because on Windows the delete
   121  *       operation in the file system may still be pending, preventing a new
   122  *       file with the same name to be created.
   123  */
   124 function getTempFile(aLeafName)
   125 {
   126   // Prepend a serial number to the extension in the suggested leaf name.
   127   let [base, ext] = DownloadPaths.splitBaseNameAndExtension(aLeafName);
   128   let leafName = base + "-" + gFileCounter + ext;
   129   gFileCounter++;
   131   // Get a file reference under the temporary directory for this test file.
   132   let file = FileUtils.getFile("TmpD", [leafName]);
   133   do_check_false(file.exists());
   135   do_register_cleanup(function () {
   136     if (file.exists()) {
   137       file.remove(false);
   138     }
   139   });
   141   return file;
   142 }
   144 /**
   145  * Waits for pending events to be processed.
   146  *
   147  * @return {Promise}
   148  * @resolves When pending events have been processed.
   149  * @rejects Never.
   150  */
   151 function promiseExecuteSoon()
   152 {
   153   let deferred = Promise.defer();
   154   do_execute_soon(deferred.resolve);
   155   return deferred.promise;
   156 }
   158 /**
   159  * Waits for a pending events to be processed after a timeout.
   160  *
   161  * @return {Promise}
   162  * @resolves When pending events have been processed.
   163  * @rejects Never.
   164  */
   165 function promiseTimeout(aTime)
   166 {
   167   let deferred = Promise.defer();
   168   do_timeout(aTime, deferred.resolve);
   169   return deferred.promise;
   170 }
   172 /**
   173  * Allows waiting for an observer notification once.
   174  *
   175  * @param aTopic
   176  *        Notification topic to observe.
   177  *
   178  * @return {Promise}
   179  * @resolves The array [aSubject, aData] from the observed notification.
   180  * @rejects Never.
   181  */
   182 function promiseTopicObserved(aTopic)
   183 {
   184   let deferred = Promise.defer();
   186   Services.obs.addObserver(
   187     function PTO_observe(aSubject, aTopic, aData) {
   188       Services.obs.removeObserver(PTO_observe, aTopic);
   189       deferred.resolve([aSubject, aData]);
   190     }, aTopic, false);
   192   return deferred.promise;
   193 }
   195 /**
   196  * Clears history asynchronously.
   197  *
   198  * @return {Promise}
   199  * @resolves When history has been cleared.
   200  * @rejects Never.
   201  */
   202 function promiseClearHistory()
   203 {
   204   let promise = promiseTopicObserved(PlacesUtils.TOPIC_EXPIRATION_FINISHED);
   205   do_execute_soon(function() PlacesUtils.bhistory.removeAllPages());
   206   return promise;
   207 }
   209 /**
   210  * Waits for a new history visit to be notified for the specified URI.
   211  *
   212  * @param aUrl
   213  *        String containing the URI that will be visited.
   214  *
   215  * @return {Promise}
   216  * @resolves Array [aTime, aTransitionType] from nsINavHistoryObserver.onVisit.
   217  * @rejects Never.
   218  */
   219 function promiseWaitForVisit(aUrl)
   220 {
   221   let deferred = Promise.defer();
   223   let uri = NetUtil.newURI(aUrl);
   225   PlacesUtils.history.addObserver({
   226     QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver]),
   227     onBeginUpdateBatch: function () {},
   228     onEndUpdateBatch: function () {},
   229     onVisit: function (aURI, aVisitID, aTime, aSessionID, aReferringID,
   230                        aTransitionType, aGUID, aHidden) {
   231       if (aURI.equals(uri)) {
   232         PlacesUtils.history.removeObserver(this);
   233         deferred.resolve([aTime, aTransitionType]);
   234       }
   235     },
   236     onTitleChanged: function () {},
   237     onDeleteURI: function () {},
   238     onClearHistory: function () {},
   239     onPageChanged: function () {},
   240     onDeleteVisits: function () {},
   241   }, false);
   243   return deferred.promise;
   244 }
   246 /**
   247  * Check browsing history to see whether the given URI has been visited.
   248  *
   249  * @param aUrl
   250  *        String containing the URI that will be visited.
   251  *
   252  * @return {Promise}
   253  * @resolves Boolean indicating whether the URI has been visited.
   254  * @rejects JavaScript exception.
   255  */
   256 function promiseIsURIVisited(aUrl) {
   257   let deferred = Promise.defer();
   259   PlacesUtils.asyncHistory.isURIVisited(NetUtil.newURI(aUrl),
   260     function (aURI, aIsVisited) {
   261       deferred.resolve(aIsVisited);
   262     });
   264   return deferred.promise;
   265 }
   267 /**
   268  * Creates a new Download object, setting a temporary file as the target.
   269  *
   270  * @param aSourceUrl
   271  *        String containing the URI for the download source, or null to use
   272  *        httpUrl("source.txt").
   273  *
   274  * @return {Promise}
   275  * @resolves The newly created Download object.
   276  * @rejects JavaScript exception.
   277  */
   278 function promiseNewDownload(aSourceUrl) {
   279   return Downloads.createDownload({
   280     source: aSourceUrl || httpUrl("source.txt"),
   281     target: getTempFile(TEST_TARGET_FILE_NAME),
   282   });
   283 }
   285 /**
   286  * Starts a new download using the nsIWebBrowserPersist interface, and controls
   287  * it using the legacy nsITransfer interface.
   288  *
   289  * @param aSourceUrl
   290  *        String containing the URI for the download source, or null to use
   291  *        httpUrl("source.txt").
   292  * @param aOptions
   293  *        An optional object used to control the behavior of this function.
   294  *        You may pass an object with a subset of the following fields:
   295  *        {
   296  *          isPrivate: Boolean indicating whether the download originated from a
   297  *                     private window.
   298  *          targetFile: nsIFile for the target, or null to use a temporary file.
   299  *          outPersist: Receives a reference to the created nsIWebBrowserPersist
   300  *                      instance.
   301  *          launchWhenSucceeded: Boolean indicating whether the target should
   302  *                               be launched when it has completed successfully.
   303  *          launcherPath: String containing the path of the custom executable to
   304  *                        use to launch the target of the download.
   305  *        }
   306  *
   307  * @return {Promise}
   308  * @resolves The Download object created as a consequence of controlling the
   309  *           download through the legacy nsITransfer interface.
   310  * @rejects Never.  The current test fails in case of exceptions.
   311  */
   312 function promiseStartLegacyDownload(aSourceUrl, aOptions) {
   313   let sourceURI = NetUtil.newURI(aSourceUrl || httpUrl("source.txt"));
   314   let targetFile = (aOptions && aOptions.targetFile)
   315                    || getTempFile(TEST_TARGET_FILE_NAME);
   317   let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
   318                   .createInstance(Ci.nsIWebBrowserPersist);
   319   if (aOptions) {
   320     aOptions.outPersist = persist;
   321   }
   323   let fileExtension = null, mimeInfo = null;
   324   let match = sourceURI.path.match(/\.([^.\/]+)$/);
   325   if (match) {
   326     fileExtension = match[1];
   327   }
   329   if (fileExtension) {
   330     try {
   331       mimeInfo = gMIMEService.getFromTypeAndExtension(null, fileExtension);
   332       mimeInfo.preferredAction = Ci.nsIMIMEInfo.saveToDisk;
   333     } catch (ex) { }
   334   }
   336   if (aOptions && aOptions.launcherPath) {
   337     do_check_true(mimeInfo != null);
   339     let localHandlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"]
   340                             .createInstance(Ci.nsILocalHandlerApp);
   341     localHandlerApp.executable = new FileUtils.File(aOptions.launcherPath);
   343     mimeInfo.preferredApplicationHandler = localHandlerApp;
   344     mimeInfo.preferredAction = Ci.nsIMIMEInfo.useHelperApp;
   345   }
   347   if (aOptions && aOptions.launchWhenSucceeded) {
   348     do_check_true(mimeInfo != null);
   350     mimeInfo.preferredAction = Ci.nsIMIMEInfo.useHelperApp;
   351   }
   353   // Apply decoding if required by the "Content-Encoding" header.
   354   persist.persistFlags &= ~Ci.nsIWebBrowserPersist.PERSIST_FLAGS_NO_CONVERSION;
   355   persist.persistFlags |=
   356     Ci.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
   358   // We must create the nsITransfer implementation using its class ID because
   359   // the "@mozilla.org/transfer;1" contract is currently implemented in
   360   // "toolkit/components/downloads".  When the other folder is not included in
   361   // builds anymore (bug 851471), we'll be able to use the contract ID.
   362   let transfer =
   363       Components.classesByID["{1b4c85df-cbdd-4bb6-b04e-613caece083c}"]
   364                 .createInstance(Ci.nsITransfer);
   366   let deferred = Promise.defer();
   368   Downloads.getList(Downloads.ALL).then(function (aList) {
   369     // Temporarily register a view that will get notified when the download we
   370     // are controlling becomes visible in the list of downloads.
   371     aList.addView({
   372       onDownloadAdded: function (aDownload) {
   373         aList.removeView(this).then(null, do_report_unexpected_exception);
   375         // Remove the download to keep the list empty for the next test.  This
   376         // also allows the caller to register the "onchange" event directly.
   377         let promise = aList.remove(aDownload);
   379         // When the download object is ready, make it available to the caller.
   380         promise.then(() => deferred.resolve(aDownload),
   381                      do_report_unexpected_exception);
   382       },
   383     }).then(null, do_report_unexpected_exception);
   385     let isPrivate = aOptions && aOptions.isPrivate;
   387     // Initialize the components so they reference each other.  This will cause
   388     // the Download object to be created and added to the public downloads.
   389     transfer.init(sourceURI, NetUtil.newURI(targetFile), null, mimeInfo, null,
   390                   null, persist, isPrivate);
   391     persist.progressListener = transfer;
   393     // Start the actual download process.
   394     persist.savePrivacyAwareURI(sourceURI, null, null, null, null, targetFile,
   395                                 isPrivate);
   396   }.bind(this)).then(null, do_report_unexpected_exception);
   398   return deferred.promise;
   399 }
   401 /**
   402  * Starts a new download using the nsIHelperAppService interface, and controls
   403  * it using the legacy nsITransfer interface.  The source of the download will
   404  * be "interruptible_resumable.txt" and partially downloaded data will be kept.
   405  *
   406  * @param aSourceUrl
   407  *        String containing the URI for the download source, or null to use
   408  *        httpUrl("interruptible_resumable.txt").
   409  *
   410  * @return {Promise}
   411  * @resolves The Download object created as a consequence of controlling the
   412  *           download through the legacy nsITransfer interface.
   413  * @rejects Never.  The current test fails in case of exceptions.
   414  */
   415 function promiseStartExternalHelperAppServiceDownload(aSourceUrl) {
   416   let sourceURI = NetUtil.newURI(aSourceUrl ||
   417                                  httpUrl("interruptible_resumable.txt"));
   419   let deferred = Promise.defer();
   421   Downloads.getList(Downloads.PUBLIC).then(function (aList) {
   422     // Temporarily register a view that will get notified when the download we
   423     // are controlling becomes visible in the list of downloads.
   424     aList.addView({
   425       onDownloadAdded: function (aDownload) {
   426         aList.removeView(this).then(null, do_report_unexpected_exception);
   428         // Remove the download to keep the list empty for the next test.  This
   429         // also allows the caller to register the "onchange" event directly.
   430         let promise = aList.remove(aDownload);
   432         // When the download object is ready, make it available to the caller.
   433         promise.then(() => deferred.resolve(aDownload),
   434                      do_report_unexpected_exception);
   435       },
   436     }).then(null, do_report_unexpected_exception);
   438     let channel = NetUtil.newChannel(sourceURI);
   440     // Start the actual download process.
   441     channel.asyncOpen({
   442       contentListener: null,
   444       onStartRequest: function (aRequest, aContext)
   445       {
   446         let channel = aRequest.QueryInterface(Ci.nsIChannel);
   447         this.contentListener = gExternalHelperAppService.doContent(
   448                                      channel.contentType, aRequest, null, true);
   449         this.contentListener.onStartRequest(aRequest, aContext);
   450       },
   452       onStopRequest: function (aRequest, aContext, aStatusCode)
   453       {
   454         this.contentListener.onStopRequest(aRequest, aContext, aStatusCode);
   455       },
   457       onDataAvailable: function (aRequest, aContext, aInputStream, aOffset,
   458                                  aCount)
   459       {
   460         this.contentListener.onDataAvailable(aRequest, aContext, aInputStream,
   461                                              aOffset, aCount);
   462       },
   463     }, null);
   464   }.bind(this)).then(null, do_report_unexpected_exception);
   466   return deferred.promise;
   467 }
   469 /**
   470  * Waits for a download to finish, in case it has not finished already.
   471  *
   472  * @param aDownload
   473  *        The Download object to wait upon.
   474  *
   475  * @return {Promise}
   476  * @resolves When the download has finished successfully.
   477  * @rejects JavaScript exception if the download failed.
   478  */
   479 function promiseDownloadStopped(aDownload) {
   480   if (!aDownload.stopped) {
   481     // The download is in progress, wait for the current attempt to finish and
   482     // report any errors that may occur.
   483     return aDownload.start();
   484   }
   486   if (aDownload.succeeded) {
   487     return Promise.resolve();
   488   }
   490   // The download failed or was canceled.
   491   return Promise.reject(aDownload.error || new Error("Download canceled."));
   492 }
   494 /**
   495  * Waits for a download to reach half of its progress, in case it has not
   496  * reached the expected progress already.
   497  *
   498  * @param aDownload
   499  *        The Download object to wait upon.
   500  *
   501  * @return {Promise}
   502  * @resolves When the download has reached half of its progress.
   503  * @rejects Never.
   504  */
   505 function promiseDownloadMidway(aDownload) {
   506   let deferred = Promise.defer();
   508   // Wait for the download to reach half of its progress.
   509   let onchange = function () {
   510     if (!aDownload.stopped && !aDownload.canceled && aDownload.progress == 50) {
   511       aDownload.onchange = null;
   512       deferred.resolve();
   513     }
   514   };
   516   // Register for the notification, but also call the function directly in
   517   // case the download already reached the expected progress.
   518   aDownload.onchange = onchange;
   519   onchange();
   521   return deferred.promise;
   522 }
   524 /**
   525  * Waits for a download to finish, in case it has not finished already.
   526  *
   527  * @param aDownload
   528  *        The Download object to wait upon.
   529  *
   530  * @return {Promise}
   531  * @resolves When the download has finished successfully.
   532  * @rejects JavaScript exception if the download failed.
   533  */
   534 function promiseDownloadStopped(aDownload) {
   535   if (!aDownload.stopped) {
   536     // The download is in progress, wait for the current attempt to finish and
   537     // report any errors that may occur.
   538     return aDownload.start();
   539   }
   541   if (aDownload.succeeded) {
   542     return Promise.resolve();
   543   }
   545   // The download failed or was canceled.
   546   return Promise.reject(aDownload.error || new Error("Download canceled."));
   547 }
   549 /**
   550  * Returns a new public or private DownloadList object.
   551  *
   552  * @param aIsPrivate
   553  *        True for the private list, false or undefined for the public list.
   554  *
   555  * @return {Promise}
   556  * @resolves The newly created DownloadList object.
   557  * @rejects JavaScript exception.
   558  */
   559 function promiseNewList(aIsPrivate)
   560 {
   561   // We need to clear all the internal state for the list and summary objects,
   562   // since all the objects are interdependent internally.
   563   Downloads._promiseListsInitialized = null;
   564   Downloads._lists = {};
   565   Downloads._summaries = {};
   567   return Downloads.getList(aIsPrivate ? Downloads.PRIVATE : Downloads.PUBLIC);
   568 }
   570 /**
   571  * Ensures that the given file contents are equal to the given string.
   572  *
   573  * @param aPath
   574  *        String containing the path of the file whose contents should be
   575  *        verified.
   576  * @param aExpectedContents
   577  *        String containing the octets that are expected in the file.
   578  *
   579  * @return {Promise}
   580  * @resolves When the operation completes.
   581  * @rejects Never.
   582  */
   583 function promiseVerifyContents(aPath, aExpectedContents)
   584 {
   585   return Task.spawn(function() {
   586     let file = new FileUtils.File(aPath);
   588     if (!(yield OS.File.exists(aPath))) {
   589       do_throw("File does not exist: " + aPath);
   590     }
   592     if ((yield OS.File.stat(aPath)).size == 0) {
   593       do_throw("File is empty: " + aPath);
   594     }
   596     let deferred = Promise.defer();
   597     NetUtil.asyncFetch(file, function(aInputStream, aStatus) {
   598       do_check_true(Components.isSuccessCode(aStatus));
   599       let contents = NetUtil.readInputStreamToString(aInputStream,
   600                                                      aInputStream.available());
   601       if (contents.length > TEST_DATA_SHORT.length * 2 ||
   602           /[^\x20-\x7E]/.test(contents)) {
   603         // Do not print the entire content string to the test log.
   604         do_check_eq(contents.length, aExpectedContents.length);
   605         do_check_true(contents == aExpectedContents);
   606       } else {
   607         // Print the string if it is short and made of printable characters.
   608         do_check_eq(contents, aExpectedContents);
   609       }
   610       deferred.resolve();
   611     });
   612     yield deferred.promise;
   613   });
   614 }
   616 /**
   617  * Starts a socket listener that closes each incoming connection.
   618  *
   619  * @returns nsIServerSocket that listens for connections.  Call its "close"
   620  *          method to stop listening and free the server port.
   621  */
   622 function startFakeServer()
   623 {
   624   let serverSocket = new ServerSocket(-1, true, -1);
   625   serverSocket.asyncListen({
   626     onSocketAccepted: function (aServ, aTransport) {
   627       aTransport.close(Cr.NS_BINDING_ABORTED);
   628     },
   629     onStopListening: function () { },
   630   });
   631   return serverSocket;
   632 }
   634 /**
   635  * This is an internal reference that should not be used directly by tests.
   636  */
   637 let _gDeferResponses = Promise.defer();
   639 /**
   640  * Ensures that all the interruptible requests started after this function is
   641  * called won't complete until the continueResponses function is called.
   642  *
   643  * Normally, the internal HTTP server returns all the available data as soon as
   644  * a request is received.  In order for some requests to be served one part at a
   645  * time, special interruptible handlers are registered on the HTTP server.  This
   646  * allows testing events or actions that need to happen in the middle of a
   647  * download.
   648  *
   649  * For example, the handler accessible at the httpUri("interruptible.txt")
   650  * address returns the TEST_DATA_SHORT text, then it may block until the
   651  * continueResponses method is called.  At this point, the handler sends the
   652  * TEST_DATA_SHORT text again to complete the response.
   653  *
   654  * If an interruptible request is started before the function is called, it may
   655  * or may not be blocked depending on the actual sequence of events.
   656  */
   657 function mustInterruptResponses()
   658 {
   659   // If there are pending blocked requests, allow them to complete.  This is
   660   // done to prevent requests from being blocked forever, but should not affect
   661   // the test logic, since previously started requests should not be monitored
   662   // on the client side anymore.
   663   _gDeferResponses.resolve();
   665   do_print("Interruptible responses will be blocked midway.");
   666   _gDeferResponses = Promise.defer();
   667 }
   669 /**
   670  * Allows all the current and future interruptible requests to complete.
   671  */
   672 function continueResponses()
   673 {
   674   do_print("Interruptible responses are now allowed to continue.");
   675   _gDeferResponses.resolve();
   676 }
   678 /**
   679  * Registers an interruptible response handler.
   680  *
   681  * @param aPath
   682  *        Path passed to nsIHttpServer.registerPathHandler.
   683  * @param aFirstPartFn
   684  *        This function is called when the response is received, with the
   685  *        aRequest and aResponse arguments of the server.
   686  * @param aSecondPartFn
   687  *        This function is called with the aRequest and aResponse arguments of
   688  *        the server, when the continueResponses function is called.
   689  */
   690 function registerInterruptibleHandler(aPath, aFirstPartFn, aSecondPartFn)
   691 {
   692   gHttpServer.registerPathHandler(aPath, function (aRequest, aResponse) {
   693     do_print("Interruptible request started.");
   695     // Process the first part of the response.
   696     aResponse.processAsync();
   697     aFirstPartFn(aRequest, aResponse);
   699     // Wait on the current deferred object, then finish the request.
   700     _gDeferResponses.promise.then(function RIH_onSuccess() {
   701       aSecondPartFn(aRequest, aResponse);
   702       aResponse.finish();
   703       do_print("Interruptible request finished.");
   704     }).then(null, Cu.reportError);
   705   });
   706 }
   708 /**
   709  * Ensure the given date object is valid.
   710  *
   711  * @param aDate
   712  *        The date object to be checked. This value can be null.
   713  */
   714 function isValidDate(aDate) {
   715   return aDate && aDate.getTime && !isNaN(aDate.getTime());
   716 }
   718 /**
   719  * Position of the first byte served by the "interruptible_resumable.txt"
   720  * handler during the most recent response.
   721  */
   722 let gMostRecentFirstBytePos;
   724 ////////////////////////////////////////////////////////////////////////////////
   725 //// Initialization functions common to all tests
   727 add_task(function test_common_initialize()
   728 {
   729   // Start the HTTP server.
   730   gHttpServer = new HttpServer();
   731   gHttpServer.registerDirectory("/", do_get_file("../data"));
   732   gHttpServer.start(-1);
   734   // Cache locks might prevent concurrent requests to the same resource, and
   735   // this may block tests that use the interruptible handlers.
   736   Services.prefs.setBoolPref("browser.cache.disk.enable", false);
   737   Services.prefs.setBoolPref("browser.cache.memory.enable", false);
   738   do_register_cleanup(function () {
   739     Services.prefs.clearUserPref("browser.cache.disk.enable");
   740     Services.prefs.clearUserPref("browser.cache.memory.enable");
   741   });
   743   registerInterruptibleHandler("/interruptible.txt",
   744     function firstPart(aRequest, aResponse) {
   745       aResponse.setHeader("Content-Type", "text/plain", false);
   746       aResponse.setHeader("Content-Length", "" + (TEST_DATA_SHORT.length * 2),
   747                           false);
   748       aResponse.write(TEST_DATA_SHORT);
   749     }, function secondPart(aRequest, aResponse) {
   750       aResponse.write(TEST_DATA_SHORT);
   751     });
   753   registerInterruptibleHandler("/interruptible_resumable.txt",
   754     function firstPart(aRequest, aResponse) {
   755       aResponse.setHeader("Content-Type", "text/plain", false);
   757       // Determine if only part of the data should be sent.
   758       let data = TEST_DATA_SHORT + TEST_DATA_SHORT;
   759       if (aRequest.hasHeader("Range")) {
   760         var matches = aRequest.getHeader("Range")
   761                               .match(/^\s*bytes=(\d+)?-(\d+)?\s*$/);
   762         var firstBytePos = (matches[1] === undefined) ? 0 : matches[1];
   763         var lastBytePos = (matches[2] === undefined) ? data.length - 1
   764                                             : matches[2];
   765         if (firstBytePos >= data.length) {
   766           aResponse.setStatusLine(aRequest.httpVersion, 416,
   767                              "Requested Range Not Satisfiable");
   768           aResponse.setHeader("Content-Range", "*/" + data.length, false);
   769           aResponse.finish();
   770           return;
   771         }
   773         aResponse.setStatusLine(aRequest.httpVersion, 206, "Partial Content");
   774         aResponse.setHeader("Content-Range", firstBytePos + "-" +
   775                                              lastBytePos + "/" +
   776                                              data.length, false);
   778         data = data.substring(firstBytePos, lastBytePos + 1);
   780         gMostRecentFirstBytePos = firstBytePos;
   781       } else {
   782         gMostRecentFirstBytePos = 0;
   783       }
   785       aResponse.setHeader("Content-Length", "" + data.length, false);
   787       aResponse.write(data.substring(0, data.length / 2));
   789       // Store the second part of the data on the response object, so that it
   790       // can be used by the secondPart function.
   791       aResponse.secondPartData = data.substring(data.length / 2);
   792     }, function secondPart(aRequest, aResponse) {
   793       aResponse.write(aResponse.secondPartData);
   794     });
   796   registerInterruptibleHandler("/interruptible_gzip.txt",
   797     function firstPart(aRequest, aResponse) {
   798       aResponse.setHeader("Content-Type", "text/plain", false);
   799       aResponse.setHeader("Content-Encoding", "gzip", false);
   800       aResponse.setHeader("Content-Length", "" + TEST_DATA_SHORT_GZIP_ENCODED.length);
   802       let bos =  new BinaryOutputStream(aResponse.bodyOutputStream);
   803       bos.writeByteArray(TEST_DATA_SHORT_GZIP_ENCODED_FIRST,
   804                          TEST_DATA_SHORT_GZIP_ENCODED_FIRST.length);
   805     }, function secondPart(aRequest, aResponse) {
   806       let bos =  new BinaryOutputStream(aResponse.bodyOutputStream);
   807       bos.writeByteArray(TEST_DATA_SHORT_GZIP_ENCODED_SECOND,
   808                          TEST_DATA_SHORT_GZIP_ENCODED_SECOND.length);
   809     });
   811   // This URL will emulate being blocked by Windows Parental controls
   812   gHttpServer.registerPathHandler("/parentalblocked.zip",
   813     function (aRequest, aResponse) {
   814       aResponse.setStatusLine(aRequest.httpVersion, 450,
   815                               "Blocked by Windows Parental Controls");
   816     });
   818   // Disable integration with the host application requiring profile access.
   819   DownloadIntegration.dontLoadList = true;
   820   DownloadIntegration.dontLoadObservers = true;
   821   // Disable the parental controls checking.
   822   DownloadIntegration.dontCheckParentalControls = true;
   823   // Disable application reputation checks.
   824   DownloadIntegration.dontCheckApplicationReputation = true;
   825   // Disable the calls to the OS to launch files and open containing folders
   826   DownloadIntegration.dontOpenFileAndFolder = true;
   827   DownloadIntegration._deferTestOpenFile = Promise.defer();
   828   DownloadIntegration._deferTestShowDir = Promise.defer();
   830   // Get a reference to nsIComponentRegistrar, and ensure that is is freed
   831   // before the XPCOM shutdown.
   832   let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
   833   do_register_cleanup(() => registrar = null);
   835   // Make sure that downloads started using nsIExternalHelperAppService are
   836   // saved to disk without asking for a destination interactively.
   837   let mockFactory = {
   838     createInstance: function (aOuter, aIid) {
   839       return {
   840         QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog]),
   841         promptForSaveToFile: function (aLauncher, aWindowContext,
   842                                        aDefaultFileName,
   843                                        aSuggestedFileExtension,
   844                                        aForcePrompt)
   845         {
   846           throw new Components.Exception(
   847                              "Synchronous promptForSaveToFile not implemented.",
   848                              Cr.NS_ERROR_NOT_AVAILABLE);
   849         },
   850         promptForSaveToFileAsync: function (aLauncher, aWindowContext,
   851                                             aDefaultFileName,
   852                                             aSuggestedFileExtension,
   853                                             aForcePrompt)
   854         {
   855           // The dialog should create the empty placeholder file.
   856           let file = getTempFile(TEST_TARGET_FILE_NAME);
   857           file.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
   858           aLauncher.saveDestinationAvailable(file);
   859         },
   860       }.QueryInterface(aIid);
   861     }
   862   };
   864   let contractID = "@mozilla.org/helperapplauncherdialog;1";
   865   let cid = registrar.contractIDToCID(contractID);
   866   let oldFactory = Components.manager.getClassObject(Cc[contractID],
   867                                                      Ci.nsIFactory);
   869   registrar.unregisterFactory(cid, oldFactory);
   870   registrar.registerFactory(cid, "", contractID, mockFactory);
   871   do_register_cleanup(function () {
   872     registrar.unregisterFactory(cid, mockFactory);
   873     registrar.registerFactory(cid, "", contractID, oldFactory);
   874   });
   876   // We must also make sure that nsIExternalHelperAppService uses the
   877   // JavaScript implementation of nsITransfer, because the
   878   // "@mozilla.org/transfer;1" contract is currently implemented in
   879   // "toolkit/components/downloads".  When the other folder is not included in
   880   // builds anymore (bug 851471), we'll not need to do this anymore.
   881   let transferContractID = "@mozilla.org/transfer;1";
   882   let transferNewCid = Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}");
   883   let transferCid = registrar.contractIDToCID(transferContractID);
   885   registrar.registerFactory(transferNewCid, "", transferContractID, null);
   886   do_register_cleanup(function () {
   887     registrar.registerFactory(transferCid, "", transferContractID, null);
   888   });
   889 });

mercurial