/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Module that listens for requests to start a `DevToolsServer` for an entire content
 * process.  Loaded into content processes by the main process during
 * content-process-connector.js' `connectToContentProcess` via the process
 * script `content-process.js`.
 *
 * The actual server startup itself is in this JSM so that code can be cached.
 */

export function initContentProcessTarget(msg) {
  const mm = msg.target;
  const prefix = msg.data.prefix;
  const watcherActorID = msg.data.watcherActorID;

  // Lazy load Loader.sys.mjs to prevent loading any devtools dependency too early.
  const {
    useDistinctSystemPrincipalLoader,
    releaseDistinctSystemPrincipalLoader,
  } = ChromeUtils.importESModule(
    "resource://devtools/shared/loader/DistinctSystemPrincipalLoader.sys.mjs"
  );

  // Use a unique object to identify this one usage of the loader
  const loaderRequester = {};

  // Init a custom, invisible DevToolsServer, in order to not pollute the
  // debugger with all devtools modules, nor break the debugger itself with
  // using it in the same process.
  const loader = useDistinctSystemPrincipalLoader(loaderRequester);

  const { DevToolsServer } = loader.require(
    "resource://devtools/server/devtools-server.js"
  );

  DevToolsServer.init();
  // For browser content toolbox, we do need a regular root actor and all tab
  // actors, but don't need all the "browser actors" that are only useful when
  // debugging the parent process via the browser toolbox.
  DevToolsServer.registerActors({ root: true, target: true });

  // Connect both parent/child processes devtools servers RDP via message
  // managers
  const conn = DevToolsServer.connectToParent(prefix, mm);

  const { ContentProcessTargetActor } = loader.require(
    "resource://devtools/server/actors/targets/content-process.js"
  );

  const actor = new ContentProcessTargetActor(conn, {
    sessionContext: msg.data.sessionContext,
  });
  actor.manage(actor);

  const response = { watcherActorID, prefix, actor: actor.form() };
  mm.sendAsyncMessage("debug:content-process-actor", response);

  function onDestroy(options) {
    mm.removeMessageListener(
      "debug:content-process-disconnect",
      onContentProcessDisconnect
    );
    actor.off("destroyed", onDestroy);

    // Notify the parent process that the actor is being destroyed
    mm.sendAsyncMessage("debug:content-process-actor-destroyed", {
      watcherActorID,
    });

    // Call DevToolsServerConnection.close to destroy all child actors. It should end up
    // calling DevToolsServerConnection.onTransportClosed that would actually cleanup all actor
    // pools.
    conn.close(options);

    // Destroy the related loader when the target is destroyed
    // and we were the last user of the special loader
    releaseDistinctSystemPrincipalLoader(loaderRequester);
  }
  function onContentProcessDisconnect(message) {
    if (message.data.prefix != prefix) {
      // Several copies of this process script can be running for a single process if
      // we are debugging the same process from multiple clients.
      // If this disconnect request doesn't match a connection known here, ignore it.
      return;
    }
    onDestroy();
  }

  // Clean up things when the client disconnects
  mm.addMessageListener(
    "debug:content-process-disconnect",
    onContentProcessDisconnect
  );
  // And also when the target actor is destroyed
  actor.on("destroyed", onDestroy);

  return {
    actor,
    connection: conn,
  };
}
