Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  head.js   Sprache: JAVA

 
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */


/**
 * Provides infrastructure for automated download components tests.
 */


"use strict";

var { Integration } = ChromeUtils.importESModule(
  "resource://gre/modules/Integration.sys.mjs"
);
var { XPCOMUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/XPCOMUtils.sys.mjs"
);
const { AppConstants } = ChromeUtils.importESModule(
  "resource://gre/modules/AppConstants.sys.mjs"
);

ChromeUtils.defineESModuleGetters(this, {
  DownloadPaths: "resource://gre/modules/DownloadPaths.sys.mjs",
  Downloads: "resource://gre/modules/Downloads.sys.mjs",
  E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
  FileTestUtils: "resource://testing-common/FileTestUtils.sys.mjs",
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
  HttpServer: "resource://testing-common/httpd.sys.mjs",
  MockRegistrar: "resource://testing-common/MockRegistrar.sys.mjs",
  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
  TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs",
  TestUtils: "resource://testing-common/TestUtils.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  this,
  "gExternalHelperAppService",
  "@mozilla.org/uriloader/external-helper-app-service;1",
  Ci.nsIExternalHelperAppService
);

/* global DownloadIntegration */
Integration.downloads.defineESModuleGetter(
  this,
  "DownloadIntegration",
  "resource://gre/modules/DownloadIntegration.sys.mjs"
);

const ServerSocket = Components.Constructor(
  "@mozilla.org/network/server-socket;1",
  "nsIServerSocket",
  "init"
);
const BinaryOutputStream = Components.Constructor(
  "@mozilla.org/binaryoutputstream;1",
  "nsIBinaryOutputStream",
  "setOutputStream"
);

XPCOMUtils.defineLazyServiceGetter(
  this,
  "gMIMEService",
  "@mozilla.org/mime;1",
  "nsIMIMEService"
);

const ReferrerInfo = Components.Constructor(
  "@mozilla.org/referrer-info;1",
  "nsIReferrerInfo",
  "init"
);

const TEST_TARGET_FILE_NAME = "test-download.txt";
const TEST_STORE_FILE_NAME = "test-downloads.json";

// We are testing an HTTPS referrer with HTTP downloads in order to verify that
// the default policy will not prevent the referrer from being passed around.
const TEST_REFERRER_URL = "https://www.example.com/referrer.html";

const TEST_DATA_SHORT = "This test string is downloaded.";
// Generate using gzipCompressString in TelemetryController.sys.mjs.
const TEST_DATA_SHORT_GZIP_ENCODED_FIRST = [
  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,
];
const TEST_DATA_SHORT_GZIP_ENCODED_SECOND = [
  75, 87, 0, 114, 83, 242, 203, 243, 114, 242, 19, 83, 82, 83, 244, 0, 151, 222,
  109, 43, 31, 0, 0, 0,
];
const TEST_DATA_SHORT_GZIP_ENCODED = TEST_DATA_SHORT_GZIP_ENCODED_FIRST.concat(
  TEST_DATA_SHORT_GZIP_ENCODED_SECOND
);

/**
 * All the tests are implemented with add_task, this starts them automatically.
 */

function run_test() {
  do_get_profile();
  run_next_test();
}

// Support functions

/**
 * HttpServer object initialized before tests start.
 */

var gHttpServer;

/**
 * Given a file name, returns a string containing an URI that points to the file
 * on the currently running instance of the test HTTP server.
 */

function httpUrl(aFileName) {
  return (
    "http://www.example.com:" +
    gHttpServer.identity.primaryPort +
    "/" +
    aFileName
  );
}

/**
 * Returns a reference to a temporary file that is guaranteed not to exist and
 * is cleaned up later. See FileTestUtils.getTempFile for details.
 */

function getTempFile(leafName) {
  return FileTestUtils.getTempFile(leafName);
}

/**
 * Check for file existence.
 * @param {string} path The file path.
 */

async function fileExists(path) {
  try {
    return (await IOUtils.stat(path)).type == "regular";
  } catch (ex) {
    return false;
  }
}

/**
 * Waits for pending events to be processed.
 *
 * @return {Promise}
 * @resolves When pending events have been processed.
 * @rejects Never.
 */

function promiseExecuteSoon() {
  return new Promise(resolve => {
    executeSoon(resolve);
  });
}

/**
 * Waits for a pending events to be processed after a timeout.
 *
 * @return {Promise}
 * @resolves When pending events have been processed.
 * @rejects Never.
 */

function promiseTimeout(aTime) {
  return new Promise(resolve => {
    do_timeout(aTime, resolve);
  });
}

/**
 * Waits for a new history visit to be notified for the specified URI.
 *
 * @param aUrl
 *        String containing the URI that will be visited.
 *
 * @return {Promise}
 * @resolves Array [aTime, aTransitionType] from page-visited places event.
 * @rejects Never.
 */

function promiseWaitForVisit(aUrl) {
  return new Promise(resolve => {
    function listener(aEvents) {
      Assert.equal(aEvents.length, 1);
      let event = aEvents[0];
      Assert.equal(event.type, "page-visited");
      if (event.url == aUrl) {
        PlacesObservers.removeListener(["page-visited"], listener);
        resolve([event.visitTime, event.transitionType, event.lastKnownTitle]);
      }
    }
    PlacesObservers.addListener(["page-visited"], listener);
  });
}

/**
 * Creates a new Download object, setting a temporary file as the target.
 *
 * @param aSourceUrl
 *        String containing the URI for the download source, or null to use
 *        httpUrl("source.txt").
 *
 * @return {Promise}
 * @resolves The newly created Download object.
 * @rejects JavaScript exception.
 */

function promiseNewDownload(aSourceUrl) {
  return Downloads.createDownload({
    source: aSourceUrl || httpUrl("source.txt"),
    target: getTempFile(TEST_TARGET_FILE_NAME),
  });
}

/**
 * Starts a new download using the nsIWebBrowserPersist interface, and controls
 * it using the legacy nsITransfer interface.
 *
 * @param aSourceUrl
 *        String containing the URI for the download source, or null to use
 *        httpUrl("source.txt").
 * @param aOptions
 *        An optional object used to control the behavior of this function.
 *        You may pass an object with a subset of the following fields:
 *        {
 *          isPrivate: Boolean indicating whether the download originated from a
 *                     private window.
 *          referrerInfo: referrerInfo for the download source.
 *          cookieJarSettings: cookieJarSettings for the download source.
 *          targetFile: nsIFile for the target, or null to use a temporary file.
 *          outPersist: Receives a reference to the created nsIWebBrowserPersist
 *                      instance.
 *          launchWhenSucceeded: Boolean indicating whether the target should
 *                               be launched when it has completed successfully.
 *          launcherPath: String containing the path of the custom executable to
 *                        use to launch the target of the download.
 *        }
 *
 * @return {Promise}
 * @resolves The Download object created as a consequence of controlling the
 *           download through the legacy nsITransfer interface.
 * @rejects Never.  The current test fails in case of exceptions.
 */

function promiseStartLegacyDownload(aSourceUrl, aOptions) {
  let sourceURI = NetUtil.newURI(aSourceUrl || httpUrl("source.txt"));
  let targetFile =
    (aOptions && aOptions.targetFile) || getTempFile(TEST_TARGET_FILE_NAME);

  let persist = Cc[
    "@mozilla.org/embedding/browser/nsWebBrowserPersist;1"
  ].createInstance(Ci.nsIWebBrowserPersist);
  if (aOptions) {
    aOptions.outPersist = persist;
  }

  let fileExtension = null,
    mimeInfo = null;
  let match = sourceURI.pathQueryRef.match(/\.([^.\/]+)$/);
  if (match) {
    fileExtension = match[1];
  }

  if (fileExtension) {
    try {
      mimeInfo = gMIMEService.getFromTypeAndExtension(null, fileExtension);
      mimeInfo.preferredAction = Ci.nsIMIMEInfo.saveToDisk;
    } catch (ex) {}
  }

  if (aOptions && aOptions.launcherPath) {
    Assert.ok(mimeInfo != null);

    let localHandlerApp = Cc[
      "@mozilla.org/uriloader/local-handler-app;1"
    ].createInstance(Ci.nsILocalHandlerApp);
    localHandlerApp.executable = new FileUtils.File(aOptions.launcherPath);

    mimeInfo.preferredApplicationHandler = localHandlerApp;
    mimeInfo.preferredAction = Ci.nsIMIMEInfo.useHelperApp;
  }
  if (aOptions && aOptions.launcherId) {
    Assert.ok(mimeInfo != null);

    let gioHandlerApp = Cc["@mozilla.org/gio-service;1"]
      .getService(Ci.nsIGIOService)
      .createHandlerAppFromAppId(aOptions.launcherId);

    mimeInfo.preferredApplicationHandler = gioHandlerApp;
    mimeInfo.preferredAction = Ci.nsIMIMEInfo.useHelperApp;
  }

  if (aOptions && aOptions.launchWhenSucceeded) {
    Assert.ok(mimeInfo != null);

    mimeInfo.preferredAction = Ci.nsIMIMEInfo.useHelperApp;
  }

  // Apply decoding if required by the "Content-Encoding" header.
  persist.persistFlags &= ~Ci.nsIWebBrowserPersist.PERSIST_FLAGS_NO_CONVERSION;
  persist.persistFlags |=
    Ci.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;

  let transfer = Cc["@mozilla.org/transfer;1"].createInstance(Ci.nsITransfer);

  return new Promise(resolve => {
    Downloads.getList(Downloads.ALL)
      .then(function (aList) {
        // Temporarily register a view that will get notified when the download we
        // are controlling becomes visible in the list of downloads.
        aList
          .addView({
            onDownloadAdded(aDownload) {
              aList.removeView(this).catch(do_report_unexpected_exception);

              // Remove the download to keep the list empty for the next test.  This
              // also allows the caller to register the "onchange" event directly.
              let promise = aList.remove(aDownload);

              // When the download object is ready, make it available to the caller.
              promise.then(
                () => resolve(aDownload),
                do_report_unexpected_exception
              );
            },
          })
          .catch(do_report_unexpected_exception);

        let isPrivate = aOptions && aOptions.isPrivate;
        let referrerInfo = aOptions ? aOptions.referrerInfo : null;
        let cookieJarSettings = aOptions ? aOptions.cookieJarSettings : null;
        let classification =
          aOptions?.downloadClassification ??
          Ci.nsITransfer.DOWNLOAD_ACCEPTABLE;
        // Initialize the components so they reference each other.  This will cause
        // the Download object to be created and added to the public downloads.
        transfer.init(
          sourceURI,
          null,
          NetUtil.newURI(targetFile),
          null,
          mimeInfo,
          null,
          null,
          persist,
          isPrivate,
          classification,
          null
        );
        persist.progressListener = transfer;

        // Start the actual download process.
        persist.saveURI(
          sourceURI,
          Services.scriptSecurityManager.getSystemPrincipal(),
          0,
          referrerInfo,
          cookieJarSettings,
          null,
          null,
          targetFile,
          Ci.nsIContentPolicy.TYPE_SAVEAS_DOWNLOAD,
          isPrivate
        );
      })
      .catch(do_report_unexpected_exception);
  });
}

/**
 * Starts a new download using the nsIHelperAppService interface, and controls
 * it using the legacy nsITransfer interface.  The source of the download will
 * be "interruptible_resumable.txt" and partially downloaded data will be kept.
 *
 * @param aSourceUrl
 *        String containing the URI for the download source, or null to use
 *        httpUrl("interruptible_resumable.txt").
 *
 * @return {Promise}
 * @resolves The Download object created as a consequence of controlling the
 *           download through the legacy nsITransfer interface.
 * @rejects Never.  The current test fails in case of exceptions.
 */

function promiseStartExternalHelperAppServiceDownload(aSourceUrl) {
  let sourceURI = NetUtil.newURI(
    aSourceUrl || httpUrl("interruptible_resumable.txt")
  );

  return new Promise(resolve => {
    Downloads.getList(Downloads.PUBLIC)
      .then(function (aList) {
        // Temporarily register a view that will get notified when the download we
        // are controlling becomes visible in the list of downloads.
        aList
          .addView({
            onDownloadAdded(aDownload) {
              aList.removeView(this).catch(do_report_unexpected_exception);

              // Remove the download to keep the list empty for the next test.  This
              // also allows the caller to register the "onchange" event directly.
              let promise = aList.remove(aDownload);

              // When the download object is ready, make it available to the caller.
              promise.then(
                () => resolve(aDownload),
                do_report_unexpected_exception
              );
            },
          })
          .catch(do_report_unexpected_exception);

        let channel = NetUtil.newChannel({
          uri: sourceURI,
          loadUsingSystemPrincipal: true,
        });

        // Start the actual download process.
        channel.asyncOpen({
          contentListener: null,

          onStartRequest(aRequest) {
            let requestChannel = aRequest.QueryInterface(Ci.nsIChannel);
            this.contentListener = gExternalHelperAppService.doContent(
              requestChannel.contentType,
              aRequest,
              null,
              true
            );
            this.contentListener.onStartRequest(aRequest);
          },

          onStopRequest(aRequest, aStatusCode) {
            this.contentListener.onStopRequest(aRequest, aStatusCode);
          },

          onDataAvailable(aRequest, aInputStream, aOffset, aCount) {
            this.contentListener.onDataAvailable(
              aRequest,
              aInputStream,
              aOffset,
              aCount
            );
          },
        });
      })
      .catch(do_report_unexpected_exception);
  });
}

/**
 * Waits for a download to reach half of its progress, in case it has not
 * reached the expected progress already.
 *
 * @param aDownload
 *        The Download object to wait upon.
 *
 * @return {Promise}
 * @resolves When the download has reached half of its progress.
 * @rejects Never.
 */

function promiseDownloadMidway(aDownload) {
  return new Promise(resolve => {
    // Wait for the download to reach half of its progress.
    let onchange = function () {
      if (
        !aDownload.stopped &&
        !aDownload.canceled &&
        aDownload.progress == 50
      ) {
        aDownload.onchange = null;
        resolve();
      }
    };

    // Register for the notification, but also call the function directly in
    // case the download already reached the expected progress.
    aDownload.onchange = onchange;
    onchange();
  });
}

/**
 * Waits for a download to make any amount of progress.
 *
 * @param aDownload
 *        The Download object to wait upon.
 *
 * @return {Promise}
 * @resolves When the download has transfered any number of bytes.
 * @rejects Never.
 */

function promiseDownloadStarted(aDownload) {
  return new Promise(resolve => {
    // Wait for the download to transfer some amount of bytes.
    let onchange = function () {
      if (
        !aDownload.stopped &&
        !aDownload.canceled &&
        aDownload.currentBytes > 0
      ) {
        aDownload.onchange = null;
        resolve();
      }
    };

    // Register for the notification, but also call the function directly in
    // case the download already reached the expected progress.
    aDownload.onchange = onchange;
    onchange();
  });
}

/**
 * Waits for a download to finish.
 *
 * @param aDownload
 *        The Download object to wait upon.
 *
 * @return {Promise}
 * @resolves When the download succeeded or errored.
 * @rejects Never.
 */

function promiseDownloadFinished(aDownload) {
  return new Promise(resolve => {
    // Wait for the download to finish.
    let onchange = function () {
      if (aDownload.succeeded || aDownload.error) {
        aDownload.onchange = null;
        resolve();
      }
    };

    // Register for the notification, but also call the function directly in
    // case the download already reached the expected progress.
    aDownload.onchange = onchange;
    onchange();
  });
}

/**
 * Waits for a download to finish, in case it has not finished already.
 *
 * @param aDownload
 *        The Download object to wait upon.
 *
 * @return {Promise}
 * @resolves When the download has finished successfully.
 * @rejects JavaScript exception if the download failed.
 */

function promiseDownloadStopped(aDownload) {
  if (!aDownload.stopped) {
    // The download is in progress, wait for the current attempt to finish and
    // report any errors that may occur.
    return aDownload.start();
  }

  if (aDownload.succeeded) {
    return Promise.resolve();
  }

  // The download failed or was canceled.
  return Promise.reject(aDownload.error || new Error("Download canceled."));
}

/**
 * Returns a new public or private DownloadList object.
 *
 * @param aIsPrivate
 *        True for the private list, false or undefined for the public list.
 *
 * @return {Promise}
 * @resolves The newly created DownloadList object.
 * @rejects JavaScript exception.
 */

function promiseNewList(aIsPrivate) {
  // We need to clear all the internal state for the list and summary objects,
  // since all the objects are interdependent internally.
  Downloads._promiseListsInitialized = null;
  Downloads._lists = {};
  Downloads._summaries = {};

  return Downloads.getList(aIsPrivate ? Downloads.PRIVATE : Downloads.PUBLIC);
}

/**
 * Ensures that the given file contents are equal to the given string.
 *
 * @param aPath
 *        String containing the path of the file whose contents should be
 *        verified.
 * @param aExpectedContents
 *        String containing the octets that are expected in the file.
 *
 * @return {Promise}
 * @resolves When the operation completes.
 * @rejects Never.
 */

async function promiseVerifyContents(aPath, aExpectedContents) {
  let file = new FileUtils.File(aPath);

  if (!(await IOUtils.exists(aPath))) {
    do_throw("File does not exist: " + aPath);
  }

  if ((await IOUtils.stat(aPath)).size == 0) {
    do_throw("File is empty: " + aPath);
  }

  await new Promise(resolve => {
    NetUtil.asyncFetch(
      { uri: NetUtil.newURI(file), loadUsingSystemPrincipal: true },
      function (aInputStream, aStatus) {
        Assert.ok(Components.isSuccessCode(aStatus));
        let contents = NetUtil.readInputStreamToString(
          aInputStream,
          aInputStream.available()
        );
        if (
          contents.length > TEST_DATA_SHORT.length * 2 ||
          /[^\x20-\x7E]/.test(contents)
        ) {
          // Do not print the entire content string to the test log.
          Assert.equal(contents.length, aExpectedContents.length);
          Assert.ok(contents == aExpectedContents);
        } else {
          // Print the string if it is short and made of printable characters.
          Assert.equal(contents, aExpectedContents);
        }
        resolve();
      }
    );
  });
}

/**
 * Creates and starts a new download, configured to keep partial data, and
 * returns only when the first part of "interruptible_resumable.txt" has been
 * saved to disk.  You must call "continueResponses" to allow the interruptible
 * request to continue.
 *
 * This function uses either DownloadCopySaver or DownloadLegacySaver based on
 * the current test run.
 *
 * @param aOptions
 *        An optional object used to control the behavior of this function.
 *        You may pass an object with a subset of the following fields:
 *        {
 *          useLegacySaver: Boolean indicating whether to launch a legacy download.
 *        }
 *
 * @return {Promise}
 * @resolves The newly created Download object, still in progress.
 * @rejects JavaScript exception.
 */

async function promiseStartDownload_tryToKeepPartialData({
  useLegacySaver = false,
} = {}) {
  mustInterruptResponses();

  // Start a new download and configure it to keep partially downloaded data.
  let download;
  if (!useLegacySaver) {
    let targetFilePath = getTempFile(TEST_TARGET_FILE_NAME).path;
    download = await Downloads.createDownload({
      source: httpUrl("interruptible_resumable.txt"),
      target: {
        path: targetFilePath,
        partFilePath: targetFilePath + ".part",
      },
    });
    download.tryToKeepPartialData = true;
    download.start().catch(() => {});
  } else {
    // Start a download using nsIExternalHelperAppService, that is configured
    // to keep partially downloaded data by default.
    download = await promiseStartExternalHelperAppServiceDownload();
  }

  await promiseDownloadMidway(download);
  await promisePartFileReady(download);

  return download;
}

/**
 * This function should be called after the progress notification for a download
 * is received, and waits for the worker thread of BackgroundFileSaver to
 * receive the data to be written to the ".part" file on disk.
 *
 * @return {Promise}
 * @resolves When the ".part" file has been written to disk.
 * @rejects JavaScript exception.
 */

async function promisePartFileReady(aDownload) {
  // We don't have control over the file output code in BackgroundFileSaver.
  // After we receive the download progress notification, we may only check
  // that the ".part" file has been created, while its size cannot be
  // determined because the file is currently open.
  try {
    do {
      await promiseTimeout(50);
    } while (!(await IOUtils.exists(aDownload.target.partFilePath)));
  } catch (ex) {
    if (!DOMException.isInstance(ex)) {
      throw ex;
    }
    // This indicates that the file has been created and cannot be accessed.
    // The specific error might vary with the platform.
    info("IOUtils exception while checking existence: " + ex.name);
    // Wait some more time to allow the write to complete.
    await promiseTimeout(100);
  }
}

/**
 * Create a download which will be reputation blocked.
 *
 * @param options
 *        {
 *           keepPartialData: bool,
 *           keepBlockedData: bool,
 *           useLegacySaver: bool,
 *           verdict: string indicating the detailed reason for the block,
 *        }
 * @return {Promise}
 * @resolves The reputation blocked download.
 * @rejects JavaScript exception.
 */

async function promiseBlockedDownload({
  keepPartialData,
  keepBlockedData,
  useLegacySaver,
  verdict = Downloads.Error.BLOCK_VERDICT_UNCOMMON,
} = {}) {
  let blockFn = () => ({
    shouldBlockForReputationCheck: () =>
      Promise.resolve({
        shouldBlock: true,
        verdict,
      }),
    shouldKeepBlockedData: () => Promise.resolve(keepBlockedData),
  });

  Integration.downloads.register(blockFn);
  function cleanup() {
    Integration.downloads.unregister(blockFn);
  }
  registerCleanupFunction(cleanup);

  let download;

  try {
    if (keepPartialData) {
      download = await promiseStartDownload_tryToKeepPartialData({
        useLegacySaver,
      });
      continueResponses();
    } else if (useLegacySaver) {
      download = await promiseStartLegacyDownload();
    } else {
      download = await promiseNewDownload();
      await download.start();
      do_throw("The download should have blocked.");
    }

    await promiseDownloadStopped(download);
    do_throw("The download should have blocked.");
  } catch (ex) {
    if (!(ex instanceof Downloads.Error) || !ex.becauseBlocked) {
      throw ex;
    }
    Assert.ok(ex.becauseBlockedByReputationCheck);
    Assert.equal(ex.reputationCheckVerdict, verdict);
    Assert.ok(download.error.becauseBlockedByReputationCheck);
    Assert.equal(download.error.reputationCheckVerdict, verdict);
  }

  Assert.ok(download.stopped);
  Assert.ok(!download.succeeded);

  cleanup();
  return download;
}

/**
 * Starts a socket listener that closes each incoming connection.
 *
 * @returns nsIServerSocket that listens for connections.  Call its "close"
 *          method to stop listening and free the server port.
 */

function startFakeServer() {
  let serverSocket = new ServerSocket(-1, true, -1);
  serverSocket.asyncListen({
    onSocketAccepted(aServ, aTransport) {
      aTransport.close(Cr.NS_BINDING_ABORTED);
    },
    onStopListening() {},
  });
  return serverSocket;
}

/**
 * This is an internal reference that should not be used directly by tests.
 */

var _gDeferResponses = Promise.withResolvers();

/**
 * Ensures that all the interruptible requests started after this function is
 * called won't complete until the continueResponses function is called.
 *
 * Normally, the internal HTTP server returns all the available data as soon as
 * a request is received.  In order for some requests to be served one part at a
 * time, special interruptible handlers are registered on the HTTP server.  This
 * allows testing events or actions that need to happen in the middle of a
 * download.
 *
 * For example, the handler accessible at the httpUri("interruptible.txt")
 * address returns the TEST_DATA_SHORT text, then it may block until the
 * continueResponses method is called.  At this point, the handler sends the
 * TEST_DATA_SHORT text again to complete the response.
 *
 * If an interruptible request is started before the function is called, it may
 * or may not be blocked depending on the actual sequence of events.
 */

function mustInterruptResponses() {
  // If there are pending blocked requests, allow them to complete.  This is
  // done to prevent requests from being blocked forever, but should not affect
  // the test logic, since previously started requests should not be monitored
  // on the client side anymore.
  _gDeferResponses.resolve();

  info("Interruptible responses will be blocked midway.");
  _gDeferResponses = Promise.withResolvers();
}

/**
 * Allows all the current and future interruptible requests to complete.
 */

function continueResponses() {
  info("Interruptible responses are now allowed to continue.");
  _gDeferResponses.resolve();
}

/**
 * Registers an interruptible response handler.
 *
 * @param aPath
 *        Path passed to nsIHttpServer.registerPathHandler.
 * @param aFirstPartFn
 *        This function is called when the response is received, with the
 *        aRequest and aResponse arguments of the server.
 * @param aSecondPartFn
 *        This function is called with the aRequest and aResponse arguments of
 *        the server, when the continueResponses function is called.
 */

function registerInterruptibleHandler(aPath, aFirstPartFn, aSecondPartFn) {
  gHttpServer.registerPathHandler(aPath, function (aRequest, aResponse) {
    info("Interruptible request started.");

    // Process the first part of the response.
    aResponse.processAsync();
    aFirstPartFn(aRequest, aResponse);

    // Wait on the current deferred object, then finish the request.
    _gDeferResponses.promise
      .then(function RIH_onSuccess() {
        aSecondPartFn(aRequest, aResponse);
        aResponse.finish();
        info("Interruptible request finished.");
      })
      .catch(console.error);
  });
}

/**
 * Ensure the given date object is valid.
 *
 * @param aDate
 *        The date object to be checked. This value can be null.
 */

function isValidDate(aDate) {
  return aDate && aDate.getTime && !isNaN(aDate.getTime());
}

/**
 * Check actual ReferrerInfo is the same as expected.
 * Because the actual download's referrer info's computedReferrer is computed
 * from referrerPolicy and originalReferrer and is non-null, and the expected
 * referrer info was constructed in isolation and therefore the computedReferrer
 * is null, it isn't possible to use equals here. */

function checkEqualReferrerInfos(aActualInfo, aExpectedInfo) {
  Assert.equal(
    !!aExpectedInfo.originalReferrer,
    !!aActualInfo.originalReferrer
  );
  if (aExpectedInfo.originalReferrer && aActualInfo.originalReferrer) {
    Assert.equal(
      aExpectedInfo.originalReferrer.spec,
      aActualInfo.originalReferrer.spec
    );
  }

  Assert.equal(aExpectedInfo.sendReferrer, aActualInfo.sendReferrer);
  Assert.equal(aExpectedInfo.referrerPolicy, aActualInfo.referrerPolicy);
}

/**
 * Waits for the download annotations to be set for the given page, required
 * because the addDownload method will add these to the database asynchronously.
 */

function waitForAnnotation(sourceUriSpec, annotationName, optionalValue) {
  return TestUtils.waitForCondition(async () => {
    let pageInfo = await PlacesUtils.history.fetch(sourceUriSpec, {
      includeAnnotations: true,
    });
    if (optionalValue) {
      return pageInfo?.annotations.get(annotationName) == optionalValue;
    }
    return pageInfo?.annotations.has(annotationName);
  }, `Should have found annotation ${annotationName} for ${sourceUriSpec}`);
}

/**
 * Position of the first byte served by the "interruptible_resumable.txt"
 * handler during the most recent response.
 */

var gMostRecentFirstBytePos;

// Initialization functions common to all tests

add_setup(function test_common_initialize() {
  // Start the HTTP server.
  gHttpServer = new HttpServer();
  gHttpServer.registerDirectory("/", do_get_file("../data"));
  gHttpServer.start(-1);
  registerCleanupFunction(() => {
    return new Promise(resolve => {
      // Ensure all the pending HTTP requests have a chance to finish.
      continueResponses();
      // Stop the HTTP server, calling resolve when it's done.
      gHttpServer.stop(resolve);
    });
  });

  // Serve the downloads from a domain located in the Internet zone on Windows.
  gHttpServer.identity.setPrimary(
    "http",
    "www.example.com",
    gHttpServer.identity.primaryPort
  );
  Services.prefs.setCharPref("network.dns.localDomains""www.example.com");
  registerCleanupFunction(function () {
    Services.prefs.clearUserPref("network.dns.localDomains");
  });

  // Cache locks might prevent concurrent requests to the same resource, and
  // this may block tests that use the interruptible handlers.
  Services.prefs.setBoolPref("browser.cache.disk.enable"false);
  Services.prefs.setBoolPref("browser.cache.memory.enable"false);
  registerCleanupFunction(function () {
    Services.prefs.clearUserPref("browser.cache.disk.enable");
    Services.prefs.clearUserPref("browser.cache.memory.enable");
  });

  // Allow relaxing default referrer.
  Services.prefs.setBoolPref(
    "network.http.referer.disallowCrossSiteRelaxingDefault",
    false
  );
  registerCleanupFunction(function () {
    Services.prefs.clearUserPref(
      "network.http.referer.disallowCrossSiteRelaxingDefault"
    );
  });

  registerInterruptibleHandler(
    "/interruptible.txt",
    function firstPart(aRequest, aResponse) {
      aResponse.setHeader("Content-Type""text/plain"false);
      aResponse.setHeader(
        "Content-Length",
        "" + TEST_DATA_SHORT.length * 2,
        false
      );
      aResponse.write(TEST_DATA_SHORT);
    },
    function secondPart(aRequest, aResponse) {
      aResponse.write(TEST_DATA_SHORT);
    }
  );

  registerInterruptibleHandler(
    "/interruptible_nosize.txt",
    function firstPart(aRequest, aResponse) {
      aResponse.setHeader("Content-Type""text/plain"false);
      aResponse.write(TEST_DATA_SHORT);
    },
    function secondPart(aRequest, aResponse) {
      aResponse.write(TEST_DATA_SHORT);
    }
  );

  registerInterruptibleHandler(
    "/interruptible_resumable.txt",
    function firstPart(aRequest, aResponse) {
      aResponse.setHeader("Content-Type""text/plain"false);

      // Determine if only part of the data should be sent.
      let data = TEST_DATA_SHORT + TEST_DATA_SHORT;
      if (aRequest.hasHeader("Range")) {
        var matches = aRequest
          .getHeader("Range")
          .match(/^\s*bytes=(\d+)?-(\d+)?\s*$/);
        var firstBytePos = matches[1] === undefined ? 0 : matches[1];
        var lastBytePos =
          matches[2] === undefined ? data.length - 1 : matches[2];
        if (firstBytePos >= data.length) {
          aResponse.setStatusLine(
            aRequest.httpVersion,
            416,
            "Requested Range Not Satisfiable"
          );
          aResponse.setHeader("Content-Range""*/" + data.length, false);
          aResponse.finish();
          return;
        }

        aResponse.setStatusLine(aRequest.httpVersion, 206, "Partial Content");
        aResponse.setHeader(
          "Content-Range",
          firstBytePos + "-" + lastBytePos + "/" + data.length,
          false
        );

        data = data.substring(firstBytePos, lastBytePos + 1);

        gMostRecentFirstBytePos = firstBytePos;
      } else {
        gMostRecentFirstBytePos = 0;
      }

      aResponse.setHeader("Content-Length""" + data.length, false);

      aResponse.write(data.substring(0, data.length / 2));

      // Store the second part of the data on the response object, so that it
      // can be used by the secondPart function.
      aResponse.secondPartData = data.substring(data.length / 2);
    },
    function secondPart(aRequest, aResponse) {
      aResponse.write(aResponse.secondPartData);
    }
  );

  registerInterruptibleHandler(
    "/interruptible_gzip.txt",
    function firstPart(aRequest, aResponse) {
      aResponse.setHeader("Content-Type""text/plain"false);
      aResponse.setHeader("Content-Encoding""gzip"false);
      aResponse.setHeader(
        "Content-Length",
        "" + TEST_DATA_SHORT_GZIP_ENCODED.length
      );

      let bos = new BinaryOutputStream(aResponse.bodyOutputStream);
      bos.writeByteArray(TEST_DATA_SHORT_GZIP_ENCODED_FIRST);
    },
    function secondPart(aRequest, aResponse) {
      let bos = new BinaryOutputStream(aResponse.bodyOutputStream);
      bos.writeByteArray(TEST_DATA_SHORT_GZIP_ENCODED_SECOND);
    }
  );

  gHttpServer.registerPathHandler(
    "/shorter-than-content-length-http-1-1.txt",
    function (aRequest, aResponse) {
      aResponse.processAsync();
      aResponse.setStatusLine("1.1", 200, "OK");
      aResponse.setHeader("Content-Type""text/plain"false);
      aResponse.setHeader(
        "Content-Length",
        "" + TEST_DATA_SHORT.length * 2,
        false
      );
      aResponse.write(TEST_DATA_SHORT);
      aResponse.finish();
    }
  );

  gHttpServer.registerPathHandler("/busy.txt"function (aRequest, aResponse) {
    aResponse.setStatusLine("1.1", 504, "Gateway Timeout");
    aResponse.setHeader("Content-Type""text/plain"false);
    aResponse.setHeader("Content-Length""" + TEST_DATA_SHORT.length, false);
    aResponse.write(TEST_DATA_SHORT);
  });

  gHttpServer.registerPathHandler("/redirect"function (aRequest, aResponse) {
    aResponse.setStatusLine("1.1", 301, "Moved Permanently");
    aResponse.setHeader("Location", httpUrl("busy.txt"), false);
    aResponse.setHeader("Content-Type""text/javascript"false);
    aResponse.setHeader("Content-Length""0"false);
  });

  // This URL will emulate being blocked by Windows Parental controls
  gHttpServer.registerPathHandler(
    "/parentalblocked.zip",
    function (aRequest, aResponse) {
      aResponse.setStatusLine(
        aRequest.httpVersion,
        450,
        "Blocked by Windows Parental Controls"
      );
    }
  );

  // This URL sends some data followed by an RST packet
  gHttpServer.registerPathHandler(
    "/netreset.txt",
    function (aRequest, aResponse) {
      info("Starting response that will be aborted.");
      aResponse.processAsync();
      aResponse.setHeader("Content-Type""text/plain"false);
      aResponse.write(TEST_DATA_SHORT);
      promiseExecuteSoon()
        .then(() => {
          aResponse.abort(nulltrue);
          aResponse.finish();
          info("Aborting response with network reset.");
        })
        .then(null, console.error);
    }
  );

  // During unit tests, most of the functions that require profile access or
  // operating system features will be disabled. Individual tests may override
  // them again to check for specific behaviors.
  Integration.downloads.register(base => {
    let override = {
      loadPublicDownloadListFromStore: () => Promise.resolve(),
      shouldKeepBlockedData: () => Promise.resolve(false),
      shouldBlockForParentalControls: () => Promise.resolve(false),
      shouldBlockForReputationCheck: () =>
        Promise.resolve({
          shouldBlock: false,
          verdict: "",
        }),
      confirmLaunchExecutable: () => Promise.resolve(),
      launchFile: () => Promise.resolve(),
      showContainingDirectory: () => Promise.resolve(),
      // This flag allows re-enabling the default observers during their tests.
      allowObservers: false,
      addListObservers() {
        return this.allowObservers
          ? super.addListObservers(...arguments)
          : Promise.resolve();
      },
      // This flag allows re-enabling the download directory logic for its tests.
      _allowDirectories: false,
      set allowDirectories(value) {
        this._allowDirectories = value;
        // We have to invalidate the previously computed directory path.
        this._downloadsDirectory = null;
      },
      _getDirectory(name) {
        return super._getDirectory(this._allowDirectories ? name : "TmpD");
      },
    };
    Object.setPrototypeOf(override, base);
    return override;
  });

  // Make sure that downloads started using nsIExternalHelperAppService are
  // saved to disk without asking for a destination interactively.
  let mock = {
    QueryInterface: ChromeUtils.generateQI(["nsIHelperAppLauncherDialog"]),
    promptForSaveToFileAsync(aLauncher) {
      // The dialog should create the empty placeholder file.
      let file = getTempFile(TEST_TARGET_FILE_NAME);
      file.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
      aLauncher.saveDestinationAvailable(file);
    },
  };

  let cid = MockRegistrar.register(
    "@mozilla.org/helperapplauncherdialog;1",
    mock
  );
  registerCleanupFunction(() => {
    MockRegistrar.unregister(cid);
  });
});

Messung V0.5
C=90 H=98 G=94

¤ Dauer der Verarbeitung: 0.3 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge