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

"use strict";

// Test the TargetCommand API around workers

const FISSION_TEST_URL = URL_ROOT_SSL + "fission_document.html";

add_task(async function () {
  // 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);

  await pushPref("devtools.debugger.show-content-scripts", true);

  const extension = ExtensionTestUtils.loadExtension({
    manifest: {
      name: "Addon with content script",
      content_scripts: [
        {
          matches: [`https://example.com/*`],
          js: ["content-script.js"],
        },
      ],
    },
    files: {
      "content-script.js": function () {
        // eslint-disable-next-line no-undef
        browser.test.notifyPass("contentScriptRan");
      },
    },
  });

  await extension.startup();

  info("Test TargetCommand against content scripts via a tab target");
  const tab = await addTab(FISSION_TEST_URL);

  await extension.awaitFinish("contentScriptRan");

  // Create a TargetCommand for the tab
  const commands = await CommandsFactory.forTab(tab);
  const targetCommand = commands.targetCommand;

  await commands.targetCommand.startListening();

  const { TYPES } = targetCommand;

  info("Check that getAllTargets only returns content scripts");
  const contentScripts = await targetCommand.getAllTargets([
    TYPES.CONTENT_SCRIPT,
  ]);

  is(contentScripts.length, 1, "Retrieved the content script");
  const [contentScript] = contentScripts;
  Assert.stringContains(contentScript.title, "Addon with content script");

  info("Assert that watchTargets works for the existing content script");
  const targets = [];
  const destroyedTargets = [];
  const onAvailable = async ({ targetFront }) => {
    info(`onAvailable called for ${targetFront.title}`);
    is(
      targetFront.targetType,
      TYPES.CONTENT_SCRIPT,
      "We are only notified about content script targets"
    );
    ok(!targetFront.isTopLevel, "The content scripts are never top level");
    targets.push(targetFront);
    info(`Handled ${targets.length} targets\n`);
  };
  const onDestroyed = async ({ targetFront }) => {
    is(
      targetFront.targetType,
      TYPES.CONTENT_SCRIPT,
      "We are only notified about content script targets"
    );
    destroyedTargets.push(targetFront);
  };

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

  is(targets.length, 1, "watchTargets notifies about a unique target");
  is(
    targets[0],
    contentScript,
    "watchTargets reports the same target instance"
  );

  await reloadBrowser();

  await waitFor(
    () => destroyedTargets.length == 1,
    "Wait for content script target to be destroyed on navigation"
  );
  await waitFor(
    () => targets.length == 2,
    "Wait for a new content script target to be created on navigation"
  );

  is(destroyedTargets[0], contentScript, "the previous target is destroyed");
  is(targets.length, 2, "a new target is notified");
  Assert.stringContains(
    targets[1].title,
    "Addon with content script",
    "the new target is still about the same content script"
  );

  await extension.unload();

  await waitFor(
    () => destroyedTargets.length == 2,
    "Content scripts are destroyed on extension destruction"
  );

  targetCommand.unwatchTargets({
    types: [TYPES.CONTENT_SCRIPT],
    onAvailable,
    onDestroyed,
  });
  targetCommand.destroy();

  BrowserTestUtils.removeTab(tab);
  await commands.destroy();
});

// Cover the special codepath used by VS.Code which listens to CONTENT SCRIPT targets
// on a parent process watcher.
add_task(async function () {
  info(
    "Test TargetCommand against content scripts via multiprocess descriptor"
  );

  await pushPref("devtools.browsertoolbox.scope", "everything");

  const extension = ExtensionTestUtils.loadExtension({
    manifest: {
      name: "Addon with content script",
      content_scripts: [
        {
          matches: [`https://example.com/*`],
          js: ["content-script.js"],
        },
      ],
    },
    files: {
      "content-script.js": function () {
        // eslint-disable-next-line no-undef
        browser.test.notifyPass("contentScriptRan");
      },
    },
  });

  await extension.startup();

  const tab = await addTab(FISSION_TEST_URL);

  await extension.awaitFinish("contentScriptRan");

  info(" ### Listen for content scripts");
  {
    // Create a TargetCommand for the tab
    const commands = await CommandsFactory.forMainProcess();
    const targetCommand = commands.targetCommand;

    targetCommand.listenForContentScripts = true;
    await targetCommand.startListening();

    const { TYPES } = targetCommand;

    info("Check that getAllTargets only returns content scripts");
    const contentScripts = await targetCommand.getAllTargets([
      TYPES.CONTENT_SCRIPT,
    ]);

    is(contentScripts.length, 1, "Retrieved the content script");
    const [contentScript] = contentScripts;
    Assert.stringContains(contentScript.title, "Addon with content script");

    await commands.destroy();
  }

  info(" ### Do not listen for content script");
  {
    // Create a TargetCommand for the tab
    const commands = await CommandsFactory.forMainProcess();
    const targetCommand = commands.targetCommand;

    targetCommand.listenForContentScripts = false;
    await targetCommand.startListening();

    const { TYPES } = targetCommand;

    info("Check that getAllTargets only returns content scripts");
    const contentScripts = await targetCommand.getAllTargets([
      TYPES.CONTENT_SCRIPT,
    ]);

    is(contentScripts.length, 0, "No content script is retrieved");

    await commands.destroy();
  }

  await extension.unload();

  BrowserTestUtils.removeTab(tab);
});
