summaryrefslogtreecommitdiffstats
path: root/browser/actors/EncryptedMediaChild.sys.mjs
blob: 7db643df67f1736f707d88ef76bba77f8aacfc1e (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
/* 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/. */

/**
 * GlobalCaptureListener is a class that listens for changes to the global
 * capture state of windows and screens. It uses this information to notify
 * observers if it's possible that media is being shared by these captures.
 * You probably only want one instance of this class per content process.
 */
class GlobalCaptureListener {
  constructor() {
    Services.cpmm.sharedData.addEventListener("change", this);
    // Tracks if screen capture is taking place based on shared data. Default
    // to true for safety.
    this._isScreenCaptured = true;
    // Tracks if any windows are being captured. Default to true for safety.
    this._isAnyWindowCaptured = true;
  }

  /**
   * Updates the capture state and forces that the state is notified to
   * observers even if it hasn't changed since the last update.
   */
  requestUpdateAndNotify() {
    this._updateCaptureState({ forceNotify: true });
  }

  /**
   * Handle changes in shared data that may alter the capture state.
   * @param event a notification that sharedData has changed. If this includes
   * changes to screen or window sharing state then we'll update the capture
   * state.
   */
  handleEvent(event) {
    if (
      event.changedKeys.includes("webrtcUI:isSharingScreen") ||
      event.changedKeys.includes("webrtcUI:sharedTopInnerWindowIds")
    ) {
      this._updateCaptureState();
    }
  }

  /**
   * Updates the capture state and notifies the state to observers if the
   * state has changed since last update, or if forced.
   * @param forceNotify if true then the capture state will be sent to
   * observers even if it didn't change since the last update.
   */
  _updateCaptureState({ forceNotify = false } = {}) {
    const previousCaptureState =
      this._isScreenCaptured || this._isAnyWindowCaptured;

    this._isScreenCaptured = Boolean(
      Services.cpmm.sharedData.get("webrtcUI:isSharingScreen")
    );

    const capturedTopInnerWindowIds = Services.cpmm.sharedData.get(
      "webrtcUI:sharedTopInnerWindowIds"
    );
    if (capturedTopInnerWindowIds && capturedTopInnerWindowIds.size > 0) {
      this._isAnyWindowCaptured = true;
    } else {
      this._isAnyWindowCaptured = false;
    }
    const newCaptureState = this._isScreenCaptured || this._isAnyWindowCaptured;

    const captureStateChanged = previousCaptureState != newCaptureState;

    if (forceNotify || captureStateChanged) {
      // Notify the state if the caller forces it, or if the state changed.
      this._notifyCaptureState();
    }
  }

  /**
   * Notifies observers of the current capture state. Notifies observers
   * with a null subject, "mediakeys-response" topic, and data that is either
   * "capture-possible" or "capture-not-possible", depending on if capture is
   * possible or not.
   */
  _notifyCaptureState() {
    const isCapturePossible =
      this._isScreenCaptured || this._isAnyWindowCaptured;
    const isCapturePossibleString = isCapturePossible
      ? "capture-possible"
      : "capture-not-possible";
    Services.obs.notifyObservers(
      null,
      "mediakeys-response",
      isCapturePossibleString
    );
  }
}

const gGlobalCaptureListener = new GlobalCaptureListener();

export class EncryptedMediaChild extends JSWindowActorChild {
  // Expected to observe 'mediakeys-request' as notified from MediaKeySystemAccess.
  // @param aSubject the nsPIDOMWindowInner associated with the notifying MediaKeySystemAccess.
  // @param aTopic should be "mediakeys-request".
  // @param aData json containing a `status` and a `keysystem`.
  observe(aSubject, aTopic, aData) {
    let parsedData;
    try {
      parsedData = JSON.parse(aData);
    } catch (ex) {
      console.error("Malformed EME video message with data: ", aData);
      return;
    }
    const { status } = parsedData;
    if (status == "is-capture-possible") {
      // We handle this status in process -- don't send a message to the parent.
      gGlobalCaptureListener.requestUpdateAndNotify();
      return;
    }

    this.sendAsyncMessage("EMEVideo:ContentMediaKeysRequest", aData);
  }
}