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

"use strict";

// Test basic features of DevToolsServer

add_task(async function () {
  // When running some other tests before, they may not destroy the main server.
  // Do it manually before running our tests.
  if (DevToolsServer.initialized) {
    DevToolsServer.destroy();
  }

  await testDevToolsServerInitialized();
  await testDevToolsServerKeepAlive();
});

async function testDevToolsServerInitialized() {
  const tab = await addTab("data:text/html;charset=utf-8,foo");

  ok(
    !DevToolsServer.initialized,
    "By default, the DevToolsServer isn't initialized in parent process"
  );
  await assertServerInitialized(
    tab,
    false,
    "By default, the DevToolsServer isn't initialized not in content process"
  );
  await assertDevToolsOpened(
    tab,
    false,
    "By default, the DevTools are reported as closed"
  );

  const commands = await CommandsFactory.forTab(tab);

  ok(
    DevToolsServer.initialized,
    "Creating the commands will initialize the DevToolsServer in parent process"
  );
  await assertServerInitialized(
    tab,
    false,
    "Creating the commands isn't enough to initialize the DevToolsServer in content process"
  );
  await assertDevToolsOpened(
    tab,
    false,
    "DevTools are still reported as closed after having created the commands"
  );

  await commands.targetCommand.startListening();

  await assertServerInitialized(
    tab,
    true,
    "Initializing the TargetCommand will initialize the DevToolsServer in content process"
  );
  await assertDevToolsOpened(
    tab,
    true,
    "Initializing the TargetCommand will start reporting the DevTools as opened"
  );

  await commands.destroy();

  // Disconnecting the client will remove all connections from both server, in parent and content process.
  ok(
    !DevToolsServer.initialized,
    "Destroying the commands destroys the DevToolsServer in the parent process"
  );
  await assertServerInitialized(
    tab,
    false,
    "But destroying the commands ends up destroying the DevToolsServer in the content process"
  );
  await assertDevToolsOpened(
    tab,
    false,
    "Destroying the commands will report DevTools as being closed"
  );

  gBrowser.removeCurrentTab();
  DevToolsServer.destroy();
}

async function testDevToolsServerKeepAlive() {
  const tab = await addTab("data:text/html;charset=utf-8,foo");

  await assertServerInitialized(
    tab,
    false,
    "Server not started in content process"
  );
  await assertDevToolsOpened(tab, false, "DevTools are reported as closed");

  const commands = await CommandsFactory.forTab(tab);
  await commands.targetCommand.startListening();

  await assertServerInitialized(tab, true, "Server started in content process");
  await assertDevToolsOpened(tab, true, "DevTools are reported as opened");

  info("Set DevToolsServer.keepAlive to true in the content process");
  DevToolsServer.keepAlive = true;
  await setContentServerKeepAlive(tab, true);

  info("Destroy the commands, the content server should be kept alive");
  await commands.destroy();

  await assertServerInitialized(
    tab,
    true,
    "Server still running in content process"
  );
  await assertDevToolsOpened(
    tab,
    false,
    "DevTools are reported as close, even if the server is still running because there is no more client connected"
  );

  ok(
    DevToolsServer.initialized,
    "Destroying the commands never destroys the DevToolsServer in the parent process when keepAlive is true"
  );

  info("Set DevToolsServer.keepAlive back to false");
  DevToolsServer.keepAlive = false;
  await setContentServerKeepAlive(tab, false);

  info("Create and destroy a commands again");
  const newCommands = await CommandsFactory.forTab(tab);
  await newCommands.targetCommand.startListening();

  await newCommands.destroy();

  await assertServerInitialized(
    tab,
    false,
    "Server stopped in content process"
  );
  await assertDevToolsOpened(
    tab,
    false,
    "DevTools are reported as closed after destroying the second commands"
  );

  ok(
    !DevToolsServer.initialized,
    "When turning keepAlive to false, the server in the parent process is destroyed"
  );

  gBrowser.removeCurrentTab();
  DevToolsServer.destroy();
}

async function assertServerInitialized(tab, expected, message) {
  await SpecialPowers.spawn(
    tab.linkedBrowser,
    [expected, message],
    function (_expected, _message) {
      const { require } = ChromeUtils.importESModule(
        "resource://devtools/shared/loader/Loader.sys.mjs"
      );
      const {
        DevToolsServer,
      } = require("resource://devtools/server/devtools-server.js");
      is(DevToolsServer.initialized, _expected, _message);
    }
  );
}

async function assertDevToolsOpened(tab, expected, message) {
  await SpecialPowers.spawn(
    tab.linkedBrowser,
    [expected, message],
    function (_expected, _message) {
      is(ChromeUtils.isDevToolsOpened(), _expected, _message);
    }
  );
}

async function setContentServerKeepAlive(tab, keepAlive, message) {
  await SpecialPowers.spawn(
    tab.linkedBrowser,
    [keepAlive],
    function (_keepAlive) {
      const { require } = ChromeUtils.importESModule(
        "resource://devtools/shared/loader/Loader.sys.mjs"
      );
      const {
        DevToolsServer,
      } = require("resource://devtools/server/devtools-server.js");
      DevToolsServer.keepAlive = _keepAlive;
    }
  );
}
