uriloader/exthandler/tests/HandlerServiceTestUtils.jsm
author Marco Bonardo <mbonardo@mozilla.com>
Tue, 13 Mar 2018 17:50:17 +0100
changeset 1455643 ace79d388801661c7668b7e6449c21aafd0c7c16
parent 1436499 b599a95ce057712c6b7804f7f108c9c4acff7e6d
child 1672539 8dab0fd5c3e5da2ab96b83ff962820b553bd97de
child 1672581 d3a3e23d78fb8c37660baa15e87b261c7d1bf3ff
permissions -rw-r--r--
Bug 474043 - Part 4 - Cleanup some JSON suffixes. r=Paolo MozReview-Commit-ID: cja118sBOO

/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

/*
 * Shared functions for tests related to invoking external handler applications.
 */

"use strict";

var EXPORTED_SYMBOLS = [
  "HandlerServiceTestUtils",
];

ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://testing-common/Assert.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "gExternalProtocolService",
                                   "@mozilla.org/uriloader/external-protocol-service;1",
                                   "nsIExternalProtocolService");
XPCOMUtils.defineLazyServiceGetter(this, "gMIMEService",
                                   "@mozilla.org/mime;1",
                                   "nsIMIMEService");
XPCOMUtils.defineLazyServiceGetter(this, "gHandlerService",
                                  "@mozilla.org/uriloader/handler-service;1",
                                  "nsIHandlerService");

var HandlerServiceTestUtils = {
  /**
   * Retrieves the names of all the MIME types and protocols configured in the
   * handler service instance currently under testing.
   *
   * @return Array of strings like "example/type" or "example-scheme", sorted
   *         alphabetically regardless of category.
   */
  getAllHandlerInfoTypes() {
    let handlerInfoTypes = [];
    let handlerInfoEnumerator = gHandlerService.enumerate();
    while (handlerInfoEnumerator.hasMoreElements()) {
      let handlerInfo = handlerInfoEnumerator.getNext()
                                             .QueryInterface(Ci.nsIHandlerInfo);
      handlerInfoTypes.push(handlerInfo.type);
    }
    return handlerInfoTypes.sort();
  },

  /**
   * Retrieves all the configured handlers for MIME types and protocols.
   *
   * @note The nsIHandlerInfo instances returned by the "enumerate" method
   *       cannot be used for testing because they incorporate information from
   *       the operating system and also from the default nsIHandlerService
   *       instance, independently from what instance is under testing.
   *
   * @return Array of nsIHandlerInfo instances sorted by their "type" property.
   */
  getAllHandlerInfos() {
    return this.getAllHandlerInfoTypes().map(type => this.getHandlerInfo(type));
  },

  /**
   * Retrieves an nsIHandlerInfo for the given MIME type or protocol, which
   * incorporates information from the operating system and also from the
   * handler service instance currently under testing.
   *
   * @note If the handler service instance currently under testing is not the
   *       default one and the requested type is a MIME type, the returned
   *       nsIHandlerInfo will include information from the default
   *       nsIHandlerService instance. This cannot be avoided easily because the
   *       getMIMEInfoFromOS method is not exposed to JavaScript.
   *
   * @param type
   *        MIME type or scheme name of the nsIHandlerInfo to retrieve.
   *
   * @return The populated nsIHandlerInfo instance.
   */
  getHandlerInfo(type) {
    if (type.includes("/")) {
      // We have to use the getFromTypeAndExtension method because we don't have
      // access to getMIMEInfoFromOS. This means that we have to reset the data
      // that may have been imported from the default nsIHandlerService instance
      // and is not overwritten by fillHandlerInfo later.
      let handlerInfo = gMIMEService.getFromTypeAndExtension(type, null);
      if (AppConstants.platform == "android") {
        // On Android, the first handler application is always the internal one.
        while (handlerInfo.possibleApplicationHandlers.length > 1) {
          handlerInfo.possibleApplicationHandlers.removeElementAt(1);
        }
      } else {
        handlerInfo.possibleApplicationHandlers.clear();
      }
      handlerInfo.setFileExtensions("");
      // Populate the object from the handler service instance under testing.
      if (gHandlerService.exists(handlerInfo)) {
        gHandlerService.fillHandlerInfo(handlerInfo, "");
      }
      return handlerInfo;
    }

    // Populate the protocol information from the handler service instance under
    // testing, like the nsIExternalProtocolService::GetProtocolHandlerInfo
    // method does on the default nsIHandlerService instance.
    let osDefaultHandlerFound = {};
    let handlerInfo = gExternalProtocolService.getProtocolHandlerInfoFromOS(
                                                  type, osDefaultHandlerFound);
    if (gHandlerService.exists(handlerInfo)) {
      gHandlerService.fillHandlerInfo(handlerInfo, "");
    } else {
      gExternalProtocolService.setProtocolHandlerDefaults(
                                  handlerInfo, osDefaultHandlerFound.value);
    }
    return handlerInfo;
  },

  /**
   * Creates an nsIHandlerInfo for the given MIME type or protocol, initialized
   * to the default values for the current platform.
   *
   * @note For this method to work, the specified MIME type or protocol must not
   *       be configured in the default handler service instance or the one
   *       under testing, and must not be registered in the operating system.
   *
   * @param type
   *        MIME type or scheme name of the nsIHandlerInfo to create.
   *
   * @return The blank nsIHandlerInfo instance.
   */
  getBlankHandlerInfo(type) {
    let handlerInfo = this.getHandlerInfo(type);

    let preferredAction, preferredApplicationHandler;
    if (AppConstants.platform == "android") {
      // On Android, the default preferredAction for MIME types is useHelperApp.
      // For protocols, we always behave as if an operating system provided
      // handler existed, and as such we initialize them to useSystemDefault.
      // This is because the AndroidBridge is not available in xpcshell tests.
      preferredAction = type.includes("/")
                        ? Ci.nsIHandlerInfo.useHelperApp
                        : Ci.nsIHandlerInfo.useSystemDefault;
      // On Android, the default handler application is always the internal one.
      preferredApplicationHandler = {
        name: "Android chooser",
      };
    } else {
      // On Desktop, the default preferredAction for MIME types is saveToDisk,
      // while for protocols it is alwaysAsk.
      preferredAction = type.includes("/")
                        ? Ci.nsIHandlerInfo.saveToDisk
                        : Ci.nsIHandlerInfo.alwaysAsk;
      preferredApplicationHandler = null;
    }

    this.assertHandlerInfoMatches(handlerInfo, {
      type,
      preferredAction,
      alwaysAskBeforeHandling: true,
      preferredApplicationHandler,
    });
    return handlerInfo;
  },

  /**
   * Checks whether an nsIHandlerInfo instance matches the provided object.
   */
  assertHandlerInfoMatches(handlerInfo, expected) {
    let expectedInterface = expected.type.includes("/") ? Ci.nsIMIMEInfo
                                                        : Ci.nsIHandlerInfo;
    Assert.ok(handlerInfo instanceof expectedInterface);
    Assert.equal(handlerInfo.type, expected.type);

    if (!expected.preferredActionOSDependent) {
      Assert.equal(handlerInfo.preferredAction, expected.preferredAction);
      Assert.equal(handlerInfo.alwaysAskBeforeHandling,
                   expected.alwaysAskBeforeHandling);
    }

    if (expectedInterface == Ci.nsIMIMEInfo) {
      let fileExtensionsEnumerator = handlerInfo.getFileExtensions();
      for (let expectedFileExtension of expected.fileExtensions || []) {
        Assert.equal(fileExtensionsEnumerator.getNext(), expectedFileExtension);
      }
      Assert.ok(!fileExtensionsEnumerator.hasMore());
    }

    if (expected.preferredApplicationHandler) {
      this.assertHandlerAppMatches(handlerInfo.preferredApplicationHandler,
                                   expected.preferredApplicationHandler);
    } else {
      Assert.equal(handlerInfo.preferredApplicationHandler, null);
    }

    let handlerAppsArrayEnumerator =
        handlerInfo.possibleApplicationHandlers.enumerate();
    if (AppConstants.platform == "android") {
      // On Android, the first handler application is always the internal one.
      this.assertHandlerAppMatches(handlerAppsArrayEnumerator.getNext(), {
        name: "Android chooser",
      });
    }
    for (let expectedHandlerApp of expected.possibleApplicationHandlers || []) {
      this.assertHandlerAppMatches(handlerAppsArrayEnumerator.getNext(),
                                   expectedHandlerApp);
    }
    Assert.ok(!handlerAppsArrayEnumerator.hasMoreElements());
  },

  /**
   * Checks whether an nsIHandlerApp instance matches the provided object.
   */
  assertHandlerAppMatches(handlerApp, expected) {
    Assert.ok(handlerApp instanceof Ci.nsIHandlerApp);
    Assert.equal(handlerApp.name, expected.name);
    if (expected.executable) {
      Assert.ok(handlerApp instanceof Ci.nsILocalHandlerApp);
      Assert.ok(expected.executable.equals(handlerApp.executable));
    } else if (expected.uriTemplate) {
      Assert.ok(handlerApp instanceof Ci.nsIWebHandlerApp);
      Assert.equal(handlerApp.uriTemplate, expected.uriTemplate);
    } else if (expected.service) {
      Assert.ok(handlerApp instanceof Ci.nsIDBusHandlerApp);
      Assert.equal(handlerApp.service, expected.service);
      Assert.equal(handlerApp.method, expected.method);
      Assert.equal(handlerApp.dBusInterface, expected.dBusInterface);
      Assert.equal(handlerApp.objectPath, expected.objectPath);
    }
  },
};