/* 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/. */

"use strict";

var DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js");
var { dumpn } = DevToolsUtils;
var {
  createContentProcessSessionContext,
} = require("resource://devtools/server/actors/watcher/session-context.js");

loader.lazyRequireGetter(
  this,
  "ChildDebuggerTransport",
  "resource://devtools/shared/transport/child-transport.js",
  true
);

const CONTENT_PROCESS_SERVER_STARTUP_SCRIPT =
  "resource://devtools/server/startup/content-process.js";

loader.lazyRequireGetter(
  this,
  "EventEmitter",
  "resource://devtools/shared/event-emitter.js"
);

/**
 * Start a DevTools server in a content process (representing the entire process, not
 * just a single frame) and add it as a child server for an active connection.
 */
function connectToContentProcess(connection, mm, onDestroy) {
  return new Promise(resolve => {
    const prefix = connection.allocID("content-process");
    let actor, childTransport;

    mm.addMessageListener(
      "debug:content-process-actor",
      function listener(msg) {
        // Ignore actors being created by a Watcher actor,
        // they will be handled by devtools/server/watcher/target-helpers/process.js
        if (msg.watcherActorID) {
          return;
        }
        mm.removeMessageListener("debug:content-process-actor", listener);

        // Pipe Debugger message from/to parent/child via the message manager
        childTransport = new ChildDebuggerTransport(mm, prefix);
        childTransport.hooks = {
          onPacket: connection.send.bind(connection),
        };
        childTransport.ready();

        connection.setForwarding(prefix, childTransport);

        dumpn(`Start forwarding for process with prefix ${prefix}`);

        actor = msg.json.actor;

        resolve(actor);
      }
    );

    // Load the content process server startup script only once.
    const isContentProcessServerStartupScripLoaded = Services.ppmm
      .getDelayedProcessScripts()
      .some(([uri]) => uri === CONTENT_PROCESS_SERVER_STARTUP_SCRIPT);
    if (!isContentProcessServerStartupScripLoaded) {
      // Load the process script that will receive the debug:init-content-server message
      Services.ppmm.loadProcessScript(
        CONTENT_PROCESS_SERVER_STARTUP_SCRIPT,
        true
      );
    }

    // Send a message to the content process server startup script to forward it the
    // prefix.
    mm.sendAsyncMessage("debug:init-content-server", {
      prefix,
      // This connector is only used for the Browser Content Toolbox,
      // when creating the content process target from the Process Descriptor.
      sessionContext: createContentProcessSessionContext(),
    });

    function onClose() {
      Services.obs.removeObserver(
        onMessageManagerClose,
        "message-manager-close"
      );
      EventEmitter.off(connection, "closed", onClose);
      if (childTransport) {
        // If we have a child transport, the actor has already
        // been created. We need to stop using this message manager.
        childTransport.close();
        childTransport = null;
        connection.cancelForwarding(prefix);

        // ... and notify the child process to clean the target-scoped actors.
        try {
          mm.sendAsyncMessage("debug:content-process-disconnect", { prefix });
        } catch (e) {
          // Nothing to do
        }
      }

      if (onDestroy) {
        onDestroy(mm);
      }
    }

    const onMessageManagerClose = DevToolsUtils.makeInfallible(subject => {
      if (subject == mm) {
        onClose();
      }
    });
    Services.obs.addObserver(onMessageManagerClose, "message-manager-close");

    EventEmitter.on(connection, "closed", onClose);
  });
}

exports.connectToContentProcess = connectToContentProcess;
