/* 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";

/*
 * Target actor for all Web Extension Content Scripts running against matching
 * web pages defined in the extension manifest.
 * They are running the same thread as the page (i.e. the main thread).
 *
 * See devtools/docs/contributor/backend/actor-hierarchy.md for more details about all the targets.
 */

const { ThreadActor } = require("resource://devtools/server/actors/thread.js");
const {
  WebConsoleActor,
} = require("resource://devtools/server/actors/webconsole.js");
const makeDebugger = require("resource://devtools/server/actors/utils/make-debugger.js");
const { assert } = require("resource://devtools/shared/DevToolsUtils.js");
const {
  SourcesManager,
} = require("resource://devtools/server/actors/utils/sources-manager.js");
const {
  contentScriptTargetSpec,
} = require("resource://devtools/shared/specs/targets/content-script.js");
const Targets = require("resource://devtools/server/actors/targets/index.js");
const Resources = require("resource://devtools/server/actors/resources/index.js");
const {
  BaseTargetActor,
} = require("resource://devtools/server/actors/targets/base-target-actor.js");

loader.lazyRequireGetter(
  this,
  "TracerActor",
  "resource://devtools/server/actors/tracer.js",
  true
);

class WebExtensionContentScriptTargetActor extends BaseTargetActor {
  constructor(conn, { sessionContext, contentScriptSandbox } = {}) {
    super(conn, Targets.TYPES.CONTENT_SCRIPT, contentScriptTargetSpec);

    this.threadActor = null;
    this.sessionContext = sessionContext;
    this.contentScriptSandbox = contentScriptSandbox;
    const metadata = Cu.getSandboxMetadata(contentScriptSandbox);
    this.addonId = metadata.addonId;
    this.innerWindowId = metadata["inner-window-id"];

    // Use a debugger against a unique global
    this.makeDebugger = makeDebugger.bind(null, {
      findDebuggees: _dbg => [this.contentScriptSandbox],

      // Only accept the content script sandbox and nothing else.
      shouldAddNewGlobalAsDebuggee: () => false,
    });
  }

  get isRootActor() {
    return false;
  }

  // This will be used by the Console actor for evaluations
  get targetGlobal() {
    return this.contentScriptSandbox;
  }

  get sourcesManager() {
    if (!this._sourcesManager) {
      assert(
        this.threadActor,
        "threadActor should exist when creating SourcesManager."
      );
      this._sourcesManager = new SourcesManager(this.threadActor);
    }
    return this._sourcesManager;
  }

  /*
   * Return a Debugger instance or create one if there is none yet
   */
  get dbg() {
    if (!this._dbg) {
      this._dbg = this.makeDebugger();
    }
    return this._dbg;
  }

  form() {
    if (!this._consoleActor) {
      this._consoleActor = new WebConsoleActor(this.conn, this);
      this.manage(this._consoleActor);
    }

    if (!this.threadActor) {
      this.threadActor = new ThreadActor(this);
      this.manage(this.threadActor);
    }
    if (!this.tracerActor) {
      this.tracerActor = new TracerActor(this.conn, this);
      this.manage(this.tracerActor);
    }

    const policy = WebExtensionPolicy.getByID(this.addonId);

    return {
      actor: this.actorID,

      // Use the related extension as content script title
      // as content scripts have no name, they are just a group of JS files
      // running against a web page.
      title: policy.name,

      // The ID of the document against which this content script executes
      innerWindowId: this.innerWindowId,

      consoleActor: this._consoleActor.actorID,
      threadActor: this.threadActor.actorID,
      tracerActor: this.tracerActor.actorID,

      traits: {
        networkMonitor: false,
        // See trait description in browsing-context.js
        supportsTopLevelTargetFlag: false,
      },
    };
  }

  destroy({ isModeSwitching } = {}) {
    // Avoid reentrancy. We will destroy the Transport when emitting "destroyed",
    // which will force destroying all actors.
    if (this.destroying) {
      return;
    }
    this.destroying = true;

    // Unregistering watchers first is important
    // otherwise you might have leaks reported when running browser_browser_toolbox_netmonitor.js in debug builds
    Resources.unwatchAllResources(this);

    this.emit("destroyed", { isModeSwitching });

    super.destroy();

    if (this.threadActor) {
      this.threadActor = null;
    }

    if (this._sourcesManager) {
      this._sourcesManager.destroy();
      this._sourcesManager = null;
    }

    if (this._dbg) {
      this._dbg.disable();
      this._dbg = null;
    }
  }
}

exports.WebExtensionContentScriptTargetActor =
  WebExtensionContentScriptTargetActor;
