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

/*
 * Tests the nsIHandlerService interface.
 */

// Set up an nsIWebHandlerApp instance that can be used in multiple tests.
let webHandlerApp = Cc[
  "@mozilla.org/uriloader/web-handler-app;1"
].createInstance(Ci.nsIWebHandlerApp);
webHandlerApp.name = "Web Handler";
webHandlerApp.uriTemplate = "https://www.example.com/?url=%s";
let expectedWebHandlerApp = {
  name: webHandlerApp.name,
  uriTemplate: webHandlerApp.uriTemplate,
};

// Set up an nsILocalHandlerApp instance that can be used in multiple tests. The
// executable should exist, but it doesn't need to point to an actual file, so
// we simply initialize it to the path of an existing directory.
let localHandlerApp = Cc[
  "@mozilla.org/uriloader/local-handler-app;1"
].createInstance(Ci.nsILocalHandlerApp);
localHandlerApp.name = "Local Handler";
localHandlerApp.executable = new FileUtils.File(PathUtils.tempDir);
let expectedLocalHandlerApp = {
  name: localHandlerApp.name,
  executable: localHandlerApp.executable,
};

/**
 * Returns a new nsIHandlerInfo instance initialized to known values that don't
 * depend on the platform and are easier to verify later.
 *
 * @param type
 *        Because the "preferredAction" is initialized to saveToDisk, this
 *        should represent a MIME type rather than a protocol.
 */
function getKnownHandlerInfo(type) {
  let handlerInfo = HandlerServiceTestUtils.getBlankHandlerInfo(type);
  handlerInfo.preferredAction = Ci.nsIHandlerInfo.saveToDisk;
  handlerInfo.alwaysAskBeforeHandling = false;
  return handlerInfo;
}

/**
 * Checks that the information stored in the handler service instance under
 * testing matches the test data files.
 */
function assertAllHandlerInfosMatchTestData() {
  let handlerInfos = HandlerServiceTestUtils.getAllHandlerInfos();

  // It's important that the MIME types we check here do not exist at the
  // operating system level, otherwise the list of handlers and file extensions
  // will be merged. The current implementation avoids duplicate entries.

  HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfos.shift(), {
    type: "example/type.handleinternally",
    preferredAction: Ci.nsIHandlerInfo.handleInternally,
    alwaysAskBeforeHandling: false,
    fileExtensions: ["example_one"],
  });

  HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfos.shift(), {
    type: "example/type.savetodisk",
    preferredAction: Ci.nsIHandlerInfo.saveToDisk,
    alwaysAskBeforeHandling: true,
    preferredApplicationHandler: {
      name: "Example Default Handler",
      uriTemplate: "https://www.example.com/?url=%s",
    },
    possibleApplicationHandlers: [
      {
        name: "Example Default Handler",
        uriTemplate: "https://www.example.com/?url=%s",
      },
    ],
    fileExtensions: ["example_two", "example_three"],
  });

  HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfos.shift(), {
    type: "example/type.usehelperapp",
    preferredAction: Ci.nsIHandlerInfo.useHelperApp,
    alwaysAskBeforeHandling: true,
    preferredApplicationHandler: {
      name: "Example Default Handler",
      uriTemplate: "https://www.example.com/?url=%s",
    },
    possibleApplicationHandlers: [
      {
        name: "Example Default Handler",
        uriTemplate: "https://www.example.com/?url=%s",
      },
      {
        name: "Example Possible Handler One",
        uriTemplate: "http://www.example.com/?id=1&url=%s",
      },
      {
        name: "Example Possible Handler Two",
        uriTemplate: "http://www.example.com/?id=2&url=%s",
      },
    ],
    fileExtensions: ["example_two", "example_three"],
  });

  HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfos.shift(), {
    type: "example/type.usesystemdefault",
    preferredAction: Ci.nsIHandlerInfo.useSystemDefault,
    alwaysAskBeforeHandling: false,
    possibleApplicationHandlers: [
      {
        name: "Example Possible Handler",
        uriTemplate: "http://www.example.com/?url=%s",
      },
    ],
  });

  HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfos.shift(), {
    type: "examplescheme.usehelperapp",
    preferredAction: Ci.nsIHandlerInfo.useHelperApp,
    alwaysAskBeforeHandling: true,
    preferredApplicationHandler: {
      name: "Example Default Handler",
      uriTemplate: "https://www.example.com/?url=%s",
    },
    possibleApplicationHandlers: [
      {
        name: "Example Default Handler",
        uriTemplate: "https://www.example.com/?url=%s",
      },
      {
        name: "Example Possible Handler One",
        uriTemplate: "http://www.example.com/?id=1&url=%s",
      },
      {
        name: "Example Possible Handler Two",
        uriTemplate: "http://www.example.com/?id=2&url=%s",
      },
    ],
  });

  HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfos.shift(), {
    type: "examplescheme.usesystemdefault",
    preferredAction: Ci.nsIHandlerInfo.useSystemDefault,
    alwaysAskBeforeHandling: false,
    possibleApplicationHandlers: [
      {
        name: "Example Possible Handler",
        uriTemplate: "http://www.example.com/?url=%s",
      },
    ],
  });

  Assert.equal(handlerInfos.length, 0);
}

/**
 * Loads data from a file in a predefined format, verifying that the format is
 * recognized and all the known properties are loaded and saved.
 */
add_task(async function test_store_fillHandlerInfo_predefined() {
  // Test that the file format used in previous versions can be loaded.
  await copyTestDataToHandlerStore();
  await assertAllHandlerInfosMatchTestData();

  // Keep a copy of the nsIHandlerInfo instances, then delete the handler store
  // and populate it with the known data. Since the handler store is empty, the
  // default handlers for the current locale are also injected, so we have to
  // delete them manually before adding the other nsIHandlerInfo instances.
  let testHandlerInfos = HandlerServiceTestUtils.getAllHandlerInfos();
  await deleteHandlerStore();
  for (let handlerInfo of HandlerServiceTestUtils.getAllHandlerInfos()) {
    gHandlerService.remove(handlerInfo);
  }
  for (let handlerInfo of testHandlerInfos) {
    gHandlerService.store(handlerInfo);
  }

  // Test that the known data still matches after saving it and reloading.
  await unloadHandlerStore();
  await assertAllHandlerInfosMatchTestData();
});

/**
 * Check that "store" is able to add new instances, that "remove" and "exists"
 * work, and that "fillHandlerInfo" throws when the instance does not exist.
 */
add_task(async function test_store_remove_exists() {
  // Test both MIME types and protocols.
  for (let type of [
    "example/type.usehelperapp",
    "examplescheme.usehelperapp",
  ]) {
    // Create new nsIHandlerInfo instances before loading the test data.
    await deleteHandlerStore();
    let handlerInfoPresent = HandlerServiceTestUtils.getHandlerInfo(type);
    let handlerInfoAbsent = HandlerServiceTestUtils.getHandlerInfo(type + "2");

    // Set up known properties that we can verify later.
    handlerInfoAbsent.preferredAction = Ci.nsIHandlerInfo.saveToDisk;
    handlerInfoAbsent.alwaysAskBeforeHandling = false;

    await copyTestDataToHandlerStore();

    Assert.ok(gHandlerService.exists(handlerInfoPresent));
    Assert.ok(!gHandlerService.exists(handlerInfoAbsent));

    gHandlerService.store(handlerInfoAbsent);
    gHandlerService.remove(handlerInfoPresent);

    await unloadHandlerStore();

    Assert.ok(!gHandlerService.exists(handlerInfoPresent));
    Assert.ok(gHandlerService.exists(handlerInfoAbsent));

    Assert.throws(
      () => gHandlerService.fillHandlerInfo(handlerInfoPresent, ""),
      ex => ex.result == Cr.NS_ERROR_NOT_AVAILABLE
    );

    let actualHandlerInfo = HandlerServiceTestUtils.getHandlerInfo(type + "2");
    HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
      type: type + "2",
      preferredAction: Ci.nsIHandlerInfo.saveToDisk,
      alwaysAskBeforeHandling: false,
    });
  }
});

/**
 * Tests that it is possible to save an nsIHandlerInfo instance with a
 * "preferredAction" that is either a valid or an unknown value, and the
 * action always takes on an appropriate value when reloading.
 */
add_task(async function test_store_preferredAction() {
  await deleteHandlerStore();

  let handlerInfo = getKnownHandlerInfo("example/new");
  // Valid action values should all remain unchanged across a refresh, except
  // for alwaysAsk which may be overridden with useHelperApp depending on prefs.
  // Invalid action values should always convert to useHelperApp.
  const actions = [
    {
      preferred: Ci.nsIHandlerInfo.alwaysAsk,
      expected: Ci.nsIHandlerInfo.alwaysAsk,
    },
    {
      preferred: Ci.nsIHandlerInfo.handleInternally,
      expected: Ci.nsIHandlerInfo.handleInternally,
    },
    { preferred: 999, expected: Ci.nsIHandlerInfo.useHelperApp },
  ];

  for (let action of actions) {
    handlerInfo.preferredAction = action.preferred;
    gHandlerService.store(handlerInfo);
    gHandlerService.fillHandlerInfo(handlerInfo, "");
    Assert.equal(handlerInfo.preferredAction, action.expected);
  }
});

/**
 * Tests that it is possible to save an nsIHandlerInfo instance containing an
 * nsILocalHandlerApp instance pointing to an executable that doesn't exist, but
 * this entry is ignored when reloading.
 */
add_task(async function test_store_localHandlerApp_missing() {
  if (!("@mozilla.org/uriloader/dbus-handler-app;1" in Cc)) {
    info("Skipping test because it does not apply to this platform.");
    return;
  }

  let missingHandlerApp = Cc[
    "@mozilla.org/uriloader/local-handler-app;1"
  ].createInstance(Ci.nsILocalHandlerApp);
  missingHandlerApp.name = "Non-existing Handler";
  missingHandlerApp.executable = new FileUtils.File(
    PathUtils.join(PathUtils.tempDir, "nonexisting")
  );

  await deleteHandlerStore();

  let handlerInfo = getKnownHandlerInfo("example/new");
  handlerInfo.preferredApplicationHandler = missingHandlerApp;
  handlerInfo.possibleApplicationHandlers.appendElement(missingHandlerApp);
  handlerInfo.possibleApplicationHandlers.appendElement(webHandlerApp);
  gHandlerService.store(handlerInfo);

  await unloadHandlerStore();

  let actualHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("example/new");
  HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
    type: "example/new",
    preferredAction: Ci.nsIHandlerInfo.saveToDisk,
    alwaysAskBeforeHandling: false,
    possibleApplicationHandlers: [expectedWebHandlerApp],
  });
});

/**
 * Test saving and reloading an instance of nsIDBusHandlerApp.
 */
add_task(async function test_store_dBusHandlerApp() {
  if (!("@mozilla.org/uriloader/dbus-handler-app;1" in Cc)) {
    info("Skipping test because it does not apply to this platform.");
    return;
  }

  // Set up an nsIDBusHandlerApp instance for testing.
  let dBusHandlerApp = Cc[
    "@mozilla.org/uriloader/dbus-handler-app;1"
  ].createInstance(Ci.nsIDBusHandlerApp);
  dBusHandlerApp.name = "DBus Handler";
  dBusHandlerApp.service = "test.method.server";
  dBusHandlerApp.method = "Method";
  dBusHandlerApp.dBusInterface = "test.method.Type";
  dBusHandlerApp.objectPath = "/test/method/Object";
  let expectedDBusHandlerApp = {
    name: dBusHandlerApp.name,
    service: dBusHandlerApp.service,
    method: dBusHandlerApp.method,
    dBusInterface: dBusHandlerApp.dBusInterface,
    objectPath: dBusHandlerApp.objectPath,
  };

  await deleteHandlerStore();

  let handlerInfo = getKnownHandlerInfo("example/new");
  handlerInfo.preferredApplicationHandler = dBusHandlerApp;
  handlerInfo.possibleApplicationHandlers.appendElement(dBusHandlerApp);
  gHandlerService.store(handlerInfo);

  await unloadHandlerStore();

  let actualHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("example/new");
  HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
    type: "example/new",
    preferredAction: Ci.nsIHandlerInfo.saveToDisk,
    alwaysAskBeforeHandling: false,
    preferredApplicationHandler: expectedDBusHandlerApp,
    possibleApplicationHandlers: [expectedDBusHandlerApp],
  });
});

/**
 * Tests that it is possible to save an nsIHandlerInfo instance with a
 * "preferredApplicationHandler" and no "possibleApplicationHandlers", but the
 * former is always included in the latter list when reloading.
 */
add_task(
  async function test_store_possibleApplicationHandlers_includes_preferred() {
    await deleteHandlerStore();

    let handlerInfo = getKnownHandlerInfo("example/new");
    handlerInfo.preferredApplicationHandler = localHandlerApp;
    gHandlerService.store(handlerInfo);

    await unloadHandlerStore();

    let actualHandlerInfo =
      HandlerServiceTestUtils.getHandlerInfo("example/new");
    HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
      type: "example/new",
      preferredAction: Ci.nsIHandlerInfo.saveToDisk,
      alwaysAskBeforeHandling: false,
      preferredApplicationHandler: expectedLocalHandlerApp,
      possibleApplicationHandlers: [expectedLocalHandlerApp],
    });
  }
);

/**
 * Tests that it is possible to save an nsIHandlerInfo instance with a
 * "preferredApplicationHandler" that is not the first element in
 * "possibleApplicationHandlers", but the former is always included as the first
 * element of the latter list when reloading.
 */
add_task(
  async function test_store_possibleApplicationHandlers_preferred_first() {
    await deleteHandlerStore();

    let handlerInfo = getKnownHandlerInfo("example/new");
    handlerInfo.preferredApplicationHandler = webHandlerApp;
    // The preferred handler is appended after the other one.
    handlerInfo.possibleApplicationHandlers.appendElement(localHandlerApp);
    handlerInfo.possibleApplicationHandlers.appendElement(webHandlerApp);
    gHandlerService.store(handlerInfo);

    await unloadHandlerStore();

    let actualHandlerInfo =
      HandlerServiceTestUtils.getHandlerInfo("example/new");
    HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
      type: "example/new",
      preferredAction: Ci.nsIHandlerInfo.saveToDisk,
      alwaysAskBeforeHandling: false,
      preferredApplicationHandler: expectedWebHandlerApp,
      possibleApplicationHandlers: [
        expectedWebHandlerApp,
        expectedLocalHandlerApp,
      ],
    });
  }
);

/**
 * Tests that it is possible to save an nsIHandlerInfo instance with an
 * uppercase file extension, but it is converted to lowercase when reloading.
 */
add_task(async function test_store_fileExtensions_lowercase() {
  await deleteHandlerStore();

  let handlerInfo = getKnownHandlerInfo("example/new");
  handlerInfo.appendExtension("extension_test1");
  handlerInfo.appendExtension("EXTENSION_test2");
  gHandlerService.store(handlerInfo);

  await unloadHandlerStore();

  let actualHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("example/new");
  HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
    type: "example/new",
    preferredAction: Ci.nsIHandlerInfo.saveToDisk,
    alwaysAskBeforeHandling: false,
    fileExtensions: ["extension_test1", "extension_test2"],
  });
});

/**
 * Tests that appendExtension doesn't add duplicates, and that anyway duplicates
 * from possibleApplicationHandlers are removed when saving and reloading.
 */
add_task(async function test_store_no_duplicates() {
  await deleteHandlerStore();

  let handlerInfo = getKnownHandlerInfo("example/new");
  handlerInfo.preferredApplicationHandler = webHandlerApp;
  handlerInfo.possibleApplicationHandlers.appendElement(webHandlerApp);
  handlerInfo.possibleApplicationHandlers.appendElement(localHandlerApp);
  handlerInfo.possibleApplicationHandlers.appendElement(localHandlerApp);
  handlerInfo.possibleApplicationHandlers.appendElement(webHandlerApp);
  handlerInfo.appendExtension("extension_test1");
  handlerInfo.appendExtension("extension_test2");
  handlerInfo.appendExtension("extension_test1");
  handlerInfo.appendExtension("EXTENSION_test1");
  Assert.deepEqual(Array.from(handlerInfo.getFileExtensions()), [
    "extension_test1",
    "extension_test2",
  ]);
  gHandlerService.store(handlerInfo);

  await unloadHandlerStore();

  let actualHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("example/new");
  HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
    type: "example/new",
    preferredAction: Ci.nsIHandlerInfo.saveToDisk,
    alwaysAskBeforeHandling: false,
    preferredApplicationHandler: expectedWebHandlerApp,
    possibleApplicationHandlers: [
      expectedWebHandlerApp,
      expectedLocalHandlerApp,
    ],
    fileExtensions: ["extension_test1", "extension_test2"],
  });
});

/**
 * Tests that setFileExtensions doesn't add duplicates.
 */
add_task(async function test_setFileExtensions_no_duplicates() {
  await deleteHandlerStore();

  let handlerInfo = getKnownHandlerInfo("example/new");
  handlerInfo.setFileExtensions("a,b,A,b,c,a");
  let expected = ["a", "b", "c"];
  Assert.deepEqual(Array.from(handlerInfo.getFileExtensions()), expected);
  // Test empty extensions, also at begin and end.
  handlerInfo.setFileExtensions(",a,,b,A,c,");
  Assert.deepEqual(Array.from(handlerInfo.getFileExtensions()), expected);
});

/**
 * Tests that "store" deletes properties that have their default values from
 * the data store.
 *
 * File extensions are never deleted once they have been associated.
 */
add_task(async function test_store_deletes_properties_except_extensions() {
  await deleteHandlerStore();

  // Prepare an nsIHandlerInfo instance with all the properties set to values
  // that will result in deletions. The preferredAction is also set to a defined
  // value so we can more easily verify it later.
  let handlerInfo = HandlerServiceTestUtils.getBlankHandlerInfo(
    "example/type.savetodisk"
  );
  handlerInfo.preferredAction = Ci.nsIHandlerInfo.saveToDisk;
  handlerInfo.alwaysAskBeforeHandling = false;

  // All the properties for "example/type.savetodisk" are present in the test
  // data, so we load the data before overwriting their values.
  await copyTestDataToHandlerStore();
  gHandlerService.store(handlerInfo);

  // Now we can reload the data and verify that no extra values have been kept.
  await unloadHandlerStore();
  let actualHandlerInfo = HandlerServiceTestUtils.getHandlerInfo(
    "example/type.savetodisk"
  );
  HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
    type: "example/type.savetodisk",
    preferredAction: Ci.nsIHandlerInfo.saveToDisk,
    alwaysAskBeforeHandling: false,
    fileExtensions: ["example_two", "example_three"],
  });
});

/**
 * Tests the "overrideType" argument of "fillHandlerInfo".
 */
add_task(async function test_fillHandlerInfo_overrideType() {
  // Test both MIME types and protocols.
  for (let type of [
    "example/type.usesystemdefault",
    "examplescheme.usesystemdefault",
  ]) {
    await deleteHandlerStore();

    // Create new nsIHandlerInfo instances before loading the test data.
    let handlerInfoAbsent = HandlerServiceTestUtils.getHandlerInfo(type + "2");

    // Fill the nsIHandlerInfo instance using the type that actually exists.
    await copyTestDataToHandlerStore();
    gHandlerService.fillHandlerInfo(handlerInfoAbsent, type);
    HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfoAbsent, {
      // While the data is populated from another type, the type is unchanged.
      type: type + "2",
      preferredAction: Ci.nsIHandlerInfo.useSystemDefault,
      alwaysAskBeforeHandling: false,
      possibleApplicationHandlers: [
        {
          name: "Example Possible Handler",
          uriTemplate: "http://www.example.com/?url=%s",
        },
      ],
    });
  }
});

/**
 * Tests "getTypeFromExtension" including unknown extensions.
 */
add_task(async function test_getTypeFromExtension() {
  await copyTestDataToHandlerStore();

  Assert.equal(gHandlerService.getTypeFromExtension(""), "");
  Assert.equal(gHandlerService.getTypeFromExtension("example_unknown"), "");
  Assert.equal(
    gHandlerService.getTypeFromExtension("example_one"),
    "example/type.handleinternally"
  );
  Assert.equal(
    gHandlerService.getTypeFromExtension("EXAMPLE_one"),
    "example/type.handleinternally"
  );
});

/**
 * Checks that the information stored in the handler service instance under
 * testing matches the default handlers for the English locale.
 */
function assertAllHandlerInfosMatchDefaultHandlers() {
  let handlerInfos = HandlerServiceTestUtils.getAllHandlerInfos();

  HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfos.shift(), {
    type: "mailto",
    preferredActionOSDependent: true,
    possibleApplicationHandlers: [
      {
        name: "Gmail",
        uriTemplate: "https://mail.google.com/mail/?extsrc=mailto&url=%s",
      },
    ],
  });

  Assert.equal(handlerInfos.length, 0);
}

/**
 * Tests the default protocol handlers imported from the locale-specific data.
 */
add_task(
  { skip_if: () => AppConstants.MOZ_APP_NAME == "thunderbird" },
  async function test_default_protocol_handlers() {
    if (
      !Services.prefs.getPrefType("gecko.handlerService.defaultHandlersVersion")
    ) {
      info("This platform or locale does not have default handlers.");
      return;
    }

    // This will inject the default protocol handlers for the current locale.
    await deleteHandlerStore();

    await assertAllHandlerInfosMatchDefaultHandlers();
  }
);

/**
 * Tests that the default protocol handlers are not imported again from the
 * locale-specific data if they already exist.
 */
add_task(
  { skip_if: () => AppConstants.MOZ_APP_NAME == "thunderbird" },
  async function test_default_protocol_handlers_no_duplicates() {
    if (
      !Services.prefs.getPrefType("gecko.handlerService.defaultHandlersVersion")
    ) {
      info("This platform or locale does not have default handlers.");
      return;
    }

    // This will inject the default protocol handlers for the current locale.
    await deleteHandlerStore();

    // Clear the preference to force injecting again.
    Services.prefs.clearUserPref("gecko.handlerService.defaultHandlersVersion");

    await unloadHandlerStore();

    // There should be no duplicate handlers in the protocols.
    assertAllHandlerInfosMatchDefaultHandlers();
  }
);

/**
 * Ensures forward compatibility by checking that the "store" method preserves
 * unknown properties in the test data.
 */
add_task(async function test_store_keeps_unknown_properties() {
  // Create a new nsIHandlerInfo instance before loading the test data.
  await deleteHandlerStore();
  let handlerInfo = HandlerServiceTestUtils.getHandlerInfo(
    "example/type.handleinternally"
  );

  await copyTestDataToHandlerStore();
  gHandlerService.store(handlerInfo);

  await unloadHandlerStore();
  let data = await IOUtils.readJSON(jsonPath);
  Assert.equal(
    data.mimeTypes["example/type.handleinternally"].unknownProperty,
    "preserved"
  );
});

/**
 * Runs the asyncInit method, ensuring that it successfully inits the store
 * and calls the handlersvc-store-initialized topic.
 */
add_task(async function test_async_init() {
  await deleteHandlerStore();
  await copyTestDataToHandlerStore();
  gHandlerService.asyncInit();
  await TestUtils.topicObserved("handlersvc-store-initialized");
  await assertAllHandlerInfosMatchTestData();

  await unloadHandlerStore();
});

/**
 * Races the asyncInit method against the sync init (implicit in enumerate),
 * to ensure that the store will be synchronously initialized without any
 * ill effects.
 */
add_task(async function test_race_async_init() {
  await deleteHandlerStore();
  await copyTestDataToHandlerStore();
  let storeInitialized = false;
  // Pass a callback to synchronously observe the topic, as a promise would
  // resolve asynchronously
  TestUtils.topicObserved("handlersvc-store-initialized", () => {
    storeInitialized = true;
    return true;
  });
  gHandlerService.asyncInit();
  Assert.ok(!storeInitialized);
  gHandlerService.enumerate();
  Assert.ok(storeInitialized);
  await assertAllHandlerInfosMatchTestData();

  await unloadHandlerStore();
});

/**
 * Test saving and reloading an instance of nsIGIOMimeApp.
 */
add_task(async function test_store_gioHandlerApp() {
  if (!("@mozilla.org/gio-service;1" in Cc)) {
    info("Skipping test because it does not apply to this platform.");
    return;
  }

  // Create dummy exec file that following won't fail because file not found error
  let dummyHandlerFile = await IOUtils.getFile(
    PathUtils.tempDir,
    "dummyHandler"
  );
  dummyHandlerFile.createUnique(
    Ci.nsIFile.NORMAL_FILE_TYPE,
    parseInt("777", 8)
  );

  // Set up an nsIGIOMimeApp instance for testing.
  let handlerApp = Cc["@mozilla.org/gio-service;1"]
    .getService(Ci.nsIGIOService)
    .createAppFromCommand(dummyHandlerFile.path, "Dummy GIO handler");
  let expectedGIOMimeHandlerApp = {
    name: handlerApp.name,
    command: handlerApp.command,
  };

  await deleteHandlerStore();

  let handlerInfo = getKnownHandlerInfo("example/new");
  handlerInfo.preferredApplicationHandler = handlerApp;
  handlerInfo.possibleApplicationHandlers.appendElement(handlerApp);
  handlerInfo.possibleApplicationHandlers.appendElement(webHandlerApp);
  gHandlerService.store(handlerInfo);

  await unloadHandlerStore();

  let actualHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("example/new");
  HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
    type: "example/new",
    preferredAction: Ci.nsIHandlerInfo.saveToDisk,
    alwaysAskBeforeHandling: false,
    preferredApplicationHandler: expectedGIOMimeHandlerApp,
    possibleApplicationHandlers: [expectedGIOMimeHandlerApp, webHandlerApp],
  });

  await IOUtils.remove(dummyHandlerFile.path);

  // After removing dummyHandlerFile, the handler should disappear from the
  // list of possibleApplicationHandlers and preferredAppHandler should be null.
  actualHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("example/new");
  HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
    type: "example/new",
    preferredAction: Ci.nsIHandlerInfo.saveToDisk,
    alwaysAskBeforeHandling: false,
    preferredApplicationHandler: null,
    possibleApplicationHandlers: [webHandlerApp],
  });
});
