/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* 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/. */

export class DOMFullscreenChild extends JSWindowActorChild {
  receiveMessage(aMessage) {
    let window = this.contentWindow;
    let windowUtils = window?.windowUtils;

    switch (aMessage.name) {
      case "DOMFullscreen:Entered": {
        if (!windowUtils) {
          // If we are not able to enter fullscreen, tell the parent to just
          // exit.
          this.sendAsyncMessage("DOMFullscreen:Exit", {});
          break;
        }

        let remoteFrameBC = aMessage.data.remoteFrameBC;
        if (remoteFrameBC) {
          let remoteFrame = remoteFrameBC.embedderElement;
          if (!remoteFrame) {
            // This could happen when the page navigate away and trigger a
            // process switching during fullscreen transition, tell the parent
            // to just exit.
            this.sendAsyncMessage("DOMFullscreen:Exit", {});
            break;
          }
          this._isNotTheRequestSource = true;
          windowUtils.remoteFrameFullscreenChanged(remoteFrame);
        } else {
          this._waitForMozAfterPaint = true;
          this._lastTransactionId = windowUtils.lastTransactionId;
          if (
            !windowUtils.handleFullscreenRequests() &&
            !this.document.fullscreenElement
          ) {
            // If we don't actually have any pending fullscreen request
            // to handle, neither we have been in fullscreen, tell the
            // parent to just exit.
            this.sendAsyncMessage("DOMFullscreen:Exit", {});
          }
        }
        break;
      }
      case "DOMFullscreen:CleanUp": {
        let isNotTheRequestSource = !!aMessage.data.remoteFrameBC;
        // If we've exited fullscreen at this point, no need to record
        // transaction id or call exit fullscreen. This is especially
        // important for pre-e10s, since in that case, it is possible
        // that no more paint would be triggered after this point.
        if (this.document.fullscreenElement) {
          this._isNotTheRequestSource = isNotTheRequestSource;
          // Need to wait for the MozAfterPaint after exiting fullscreen if
          // this is the request source.
          this._waitForMozAfterPaint = !this._isNotTheRequestSource;
          // windowUtils could be null if the associated window is not current
          // active window. In this case, document must be in the process of
          // exiting fullscreen, it is okay to not ask it to exit fullscreen.
          if (windowUtils) {
            this._lastTransactionId = windowUtils.lastTransactionId;
            windowUtils.exitFullscreen();
          }
        } else if (isNotTheRequestSource) {
          // If we are not the request source and have exited fullscreen, reply
          // Exited to parent as parent is waiting for our reply.
          this.sendAsyncMessage("DOMFullscreen:Exited", {});
        } else {
          // If we've already exited fullscreen, it is possible that no more
          // paint would be triggered, so don't wait for MozAfterPaint.
          // TODO: There might be some way to move this code around a bit to
          //       make it easier to follow. Somehow handle the "local" case in
          //       one place and the isNotTheRequestSource case after that.
          this.sendAsyncMessage("DOMFullscreen:Painted", {});
        }
        break;
      }
      case "DOMFullscreen:Painted": {
        Services.obs.notifyObservers(window, "fullscreen-painted");
        break;
      }
    }
  }

  handleEvent(aEvent) {
    if (this.hasBeenDestroyed()) {
      // Make sure that this actor is alive before going further because
      // if it's not the case, any attempt to send a message or access
      // objects such as 'contentWindow' will fail. (See bug 1590138)
      return;
    }

    switch (aEvent.type) {
      case "MozDOMFullscreen:Request": {
        this.sendAsyncMessage("DOMFullscreen:Request", {});
        break;
      }
      case "MozDOMFullscreen:NewOrigin": {
        this.sendAsyncMessage("DOMFullscreen:NewOrigin", {
          originNoSuffix: aEvent.target.nodePrincipal.originNoSuffix,
        });
        break;
      }
      case "MozDOMFullscreen:Exit": {
        this.sendAsyncMessage("DOMFullscreen:Exit", {});
        break;
      }
      case "MozDOMFullscreen:Entered":
      case "MozDOMFullscreen:Exited": {
        if (this._isNotTheRequestSource) {
          // Fullscreen change event for a frame in the
          // middle (content frame embedding the oop frame where the
          // request comes from)

          delete this._isNotTheRequestSource;
          this.sendAsyncMessage(aEvent.type.replace("Moz", ""), {});
          break;
        }

        if (this._waitForMozAfterPaint) {
          delete this._waitForMozAfterPaint;
          this._listeningWindow = this.contentWindow.windowRoot;
          this._listeningWindow.addEventListener("MozAfterPaint", this);
        }

        if (!this.document || !this.document.fullscreenElement) {
          // If we receive any fullscreen change event, and find we are
          // actually not in fullscreen, also ask the parent to exit to
          // ensure that the parent always exits fullscreen when we do.
          this.sendAsyncMessage("DOMFullscreen:Exit", {});
        }
        break;
      }
      case "MozAfterPaint": {
        // Only send Painted signal after we actually finish painting
        // the transition for the fullscreen change.
        // Note that this._lastTransactionId is not set when in pre-e10s
        // mode, so we need to check that explicitly.
        if (
          !this._lastTransactionId ||
          aEvent.transactionId > this._lastTransactionId
        ) {
          this._listeningWindow.removeEventListener("MozAfterPaint", this);
          delete this._listeningWindow;
          this.sendAsyncMessage("DOMFullscreen:Painted", {});
        }
        break;
      }
    }
  }

  hasBeenDestroyed() {
    // The 'didDestroy' callback is not always getting called.
    // So we can't rely on it here. Instead, we will try to access
    // the browsing context to judge wether the actor has
    // been destroyed or not.
    try {
      return !this.browsingContext;
    } catch {
      return true;
    }
  }
}
