summaryrefslogtreecommitdiffstats
path: root/browser/actors/DOMFullscreenChild.sys.mjs
blob: 4088def44b869da7bcadaa0d3b34ca826becf393 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/* 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;
    }
  }
}