// This code runs in the content process that holds the window to which
// this actor is attached. There is one instance of this class for each
// "inner window" (i.e. one per content document, including iframes/nested
// iframes).
// There is a 1:1 relationship between instances of this class and
// FissionTestHelperParent instances, and the pair are entangled such
// that they can communicate with each other regardless of which process
// they live in.

export class FissionTestHelperChild extends JSWindowActorChild {
  constructor() {
    super();
    this._msgCounter = 0;
    this._oopifResponsePromiseResolvers = [];
  }

  cw() {
    return this.contentWindow.wrappedJSObject;
  }

  initialize() {
    // This exports a bunch of things into the content window so that
    // the test can access them. Most things are scoped inside the
    // FissionTestHelper object on the window to avoid polluting the global
    // namespace.

    let cw = this.cw();
    Cu.exportFunction(
      (cond, msg) => this.sendAsyncMessage("ok", { cond, msg }),
      cw,
      { defineAs: "ok" }
    );
    Cu.exportFunction(
      (a, b, msg) => this.sendAsyncMessage("is", { a, b, msg }),
      cw,
      { defineAs: "is" }
    );

    let FissionTestHelper = Cu.createObjectIn(cw, {
      defineAs: "FissionTestHelper",
    });
    FissionTestHelper.startTestPromise = new cw.Promise(
      Cu.exportFunction(resolve => {
        this._startTestPromiseResolver = resolve;
      }, cw)
    );

    Cu.exportFunction(this.subtestDone.bind(this), FissionTestHelper, {
      defineAs: "subtestDone",
    });

    Cu.exportFunction(this.subtestFailed.bind(this), FissionTestHelper, {
      defineAs: "subtestFailed",
    });

    Cu.exportFunction(this.sendToOopif.bind(this), FissionTestHelper, {
      defineAs: "sendToOopif",
    });
    Cu.exportFunction(this.fireEventInEmbedder.bind(this), FissionTestHelper, {
      defineAs: "fireEventInEmbedder",
    });
  }

  // Called by the subtest to indicate completion to the top-level browser-chrome
  // mochitest.
  subtestDone() {
    let cw = this.cw();
    if (cw.ApzCleanup) {
      cw.ApzCleanup.execute();
    }
    this.sendAsyncMessage("Test:Complete", {});
  }

  // Called by the subtest to indicate subtest failure. Only one of subtestDone
  // or subtestFailed should be called.
  subtestFailed(msg) {
    this.sendAsyncMessage("ok", { cond: false, msg });
    this.subtestDone();
  }

  // Called by the subtest to eval some code in the OOP iframe. This returns
  // a promise that resolves to the return value from the eval.
  sendToOopif(iframeElement, stringToEval) {
    let browsingContextId = iframeElement.browsingContext.id;
    let msgId = ++this._msgCounter;
    let cw = this.cw();
    let responsePromise = new cw.Promise(
      Cu.exportFunction(resolve => {
        this._oopifResponsePromiseResolvers[msgId] = resolve;
      }, cw)
    );
    this.sendAsyncMessage("EmbedderToOopif", {
      browsingContextId,
      msgId,
      stringToEval,
    });
    return responsePromise;
  }

  // Called by OOP iframes to dispatch an event in the embedder window. This
  // can be used by the OOP iframe to asynchronously notify the embedder of
  // things that happen. The embedder can use promiseOneEvent from
  // helper_fission_utils.js to listen for these events.
  fireEventInEmbedder(eventType, data) {
    this.sendAsyncMessage("OopifToEmbedder", { eventType, data });
  }

  handleEvent(evt) {
    switch (evt.type) {
      case "FissionTestHelper:Init":
        this.initialize();
        break;
    }
  }

  receiveMessage(msg) {
    switch (msg.name) {
      case "Test:Start":
        this._startTestPromiseResolver();
        delete this._startTestPromiseResolver;
        break;
      case "FromEmbedder":
        let evalResult = this.contentWindow.eval(msg.data.stringToEval);
        this.sendAsyncMessage("OopifToEmbedder", {
          msgId: msg.data.msgId,
          evalResult,
        });
        break;
      case "FromOopif":
        if (typeof msg.data.msgId == "number") {
          if (!(msg.data.msgId in this._oopifResponsePromiseResolvers)) {
            dump(
              "Error: FromOopif got a message with unknown numeric msgId in " +
                this.contentWindow.location.href +
                "\n"
            );
          }
          this._oopifResponsePromiseResolvers[msg.data.msgId](
            msg.data.evalResult
          );
          delete this._oopifResponsePromiseResolvers[msg.data.msgId];
        } else if (typeof msg.data.eventType == "string") {
          let cw = this.cw();
          let event = new cw.Event(msg.data.eventType);
          event.data = Cu.cloneInto(msg.data.data, cw);
          this.contentWindow.dispatchEvent(event);
        } else {
          dump(
            "Warning: Unrecognized FromOopif message received in " +
              this.contentWindow.location.href +
              "\n"
          );
        }
        break;
    }
  }
}
