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

"use strict";

/* import-globals-from head.js */

/* exported setupServiceWorkerNavigationTest */
async function setupServiceWorkerNavigationTest() {
  // Disable the preloaded process as it creates processes intermittently
  // which forces the emission of RDP requests we aren't correctly waiting for.
  await pushPref("dom.ipc.processPrelaunch.enabled", false);
}

/* exported watchServiceWorkerTargets */
async function watchServiceWorkerTargets(tab) {
  info("Create a target list for a tab target");
  const commands = await CommandsFactory.forTab(tab);
  const targetCommand = commands.targetCommand;

  // Enable Service Worker listening.
  targetCommand.listenForServiceWorkers = true;
  await targetCommand.startListening();

  // Setup onAvailable & onDestroyed callbacks so that we can check how many
  // times they are called and with which targetFront.
  const hooks = {
    availableCount: 0,
    destroyedCount: 0,
    targets: [],
  };

  const onAvailable = async ({ targetFront }) => {
    info(` + Service worker target available for ${targetFront.url}\n`);
    hooks.availableCount++;
    hooks.targets.push(targetFront);
  };

  const onDestroyed = ({ targetFront }) => {
    info(` - Service worker target destroy for ${targetFront.url}\n`);
    hooks.destroyedCount++;
    hooks.targets.splice(hooks.targets.indexOf(targetFront), 1);
  };

  await targetCommand.watchTargets({
    types: [targetCommand.TYPES.SERVICE_WORKER],
    onAvailable,
    onDestroyed,
  });

  return { hooks, commands, targetCommand };
}

/**
 * Wait until the expected URL is loaded and win.registration has resolved.
 */
/* exported waitForRegistrationReady */
async function waitForRegistrationReady(tab, expectedPageUrl, workerUrl) {
  await asyncWaitUntil(() =>
    SpecialPowers.spawn(
      tab.linkedBrowser,
      [expectedPageUrl],
      async function (_url) {
        try {
          const win = content.wrappedJSObject;
          const isExpectedUrl = win.location.href === _url;
          const hasRegistration = !!(await win.registrationPromise);
          return isExpectedUrl && hasRegistration;
        } catch (e) {
          return false;
        }
      }
    )
  );
  // On debug builds, the registration may not be yet ready in the parent process
  // so we also need to ensure it is ready.
  const swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
    Ci.nsIServiceWorkerManager
  );
  await waitFor(() => {
    // Unfortunately we can't use swm.getRegistrationByPrincipal, as it requires a "scope", which doesn't seem to be the worker URL.
    const registrations = swm.getAllRegistrations();
    for (let i = 0; i < registrations.length; i++) {
      const info = registrations.queryElementAt(
        i,
        Ci.nsIServiceWorkerRegistrationInfo
      );
      // Lookup for an exact URL match.
      if (info.scriptSpec === workerUrl) {
        return true;
      }
    }
    return false;
  });
}

/**
 * Assert helper for the `hooks` object, updated by the onAvailable and
 * onDestroyed callbacks. Assert that the callbacks have been called the
 * expected number of times, with the expected targets.
 */
/* exported checkHooks */
async function checkHooks(hooks, { available, destroyed, targets }) {
  await waitUntil(
    () => hooks.availableCount == available && hooks.destroyedCount == destroyed
  );
  is(hooks.availableCount, available, "onAvailable was called as expected");
  is(hooks.destroyedCount, destroyed, "onDestroyed was called as expected");

  is(hooks.targets.length, targets.length, "Expected number of targets");
  targets.forEach((url, i) => {
    is(hooks.targets[i].url, url, `SW target ${i} has the expected url`);
  });
}
