1/* vim: set ts=2 sw=2 sts=2 et tw=80: */
2/* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5"use strict";
6
7var EXPORTED_SYMBOLS = ["EncryptedMediaChild"];
8
9const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
10
11/**
12 * GlobalCaptureListener is a class that listens for changes to the global
13 * capture state of windows and screens. It uses this information to notify
14 * observers if it's possible that media is being shared by these captures.
15 * You probably only want one instance of this class per content process.
16 */
17class GlobalCaptureListener {
18  constructor() {
19    Services.cpmm.sharedData.addEventListener("change", this);
20    // Tracks if screen capture is taking place based on shared data. Default
21    // to true for safety.
22    this._isScreenCaptured = true;
23    // Tracks if any windows are being captured. Default to true for safety.
24    this._isAnyWindowCaptured = true;
25  }
26
27  /**
28   * Updates the capture state and forces that the state is notified to
29   * observers even if it hasn't changed since the last update.
30   */
31  requestUpdateAndNotify() {
32    this._updateCaptureState({ forceNotify: true });
33  }
34
35  /**
36   * Handle changes in shared data that may alter the capture state.
37   * @param event a notification that sharedData has changed. If this includes
38   * changes to screen or window sharing state then we'll update the capture
39   * state.
40   */
41  handleEvent(event) {
42    if (
43      event.changedKeys.includes("webrtcUI:isSharingScreen") ||
44      event.changedKeys.includes("webrtcUI:sharedTopInnerWindowIds")
45    ) {
46      this._updateCaptureState();
47    }
48  }
49
50  /**
51   * Updates the capture state and notifies the state to observers if the
52   * state has changed since last update, or if forced.
53   * @param forceNotify if true then the capture state will be sent to
54   * observers even if it didn't change since the last update.
55   */
56  _updateCaptureState({ forceNotify = false } = {}) {
57    const previousCaptureState =
58      this._isScreenCaptured || this._isAnyWindowCaptured;
59
60    this._isScreenCaptured = Boolean(
61      Services.cpmm.sharedData.get("webrtcUI:isSharingScreen")
62    );
63
64    const capturedTopInnerWindowIds = Services.cpmm.sharedData.get(
65      "webrtcUI:sharedTopInnerWindowIds"
66    );
67    if (capturedTopInnerWindowIds && capturedTopInnerWindowIds.size > 0) {
68      this._isAnyWindowCaptured = true;
69    } else {
70      this._isAnyWindowCaptured = false;
71    }
72    const newCaptureState = this._isScreenCaptured || this._isAnyWindowCaptured;
73
74    const captureStateChanged = previousCaptureState != newCaptureState;
75
76    if (forceNotify || captureStateChanged) {
77      // Notify the state if the caller forces it, or if the state changed.
78      this._notifyCaptureState();
79    }
80  }
81
82  /**
83   * Notifies observers of the current capture state. Notifies observers
84   * with a null subject, "mediakeys-response" topic, and data that is either
85   * "capture-possible" or "capture-not-possible", depending on if capture is
86   * possible or not.
87   */
88  _notifyCaptureState() {
89    const isCapturePossible =
90      this._isScreenCaptured || this._isAnyWindowCaptured;
91    const isCapturePossibleString = isCapturePossible
92      ? "capture-possible"
93      : "capture-not-possible";
94    Services.obs.notifyObservers(
95      null,
96      "mediakeys-response",
97      isCapturePossibleString
98    );
99  }
100}
101
102const gGlobalCaptureListener = new GlobalCaptureListener();
103
104class EncryptedMediaChild extends JSWindowActorChild {
105  // Expected to observe 'mediakeys-request' as notified from MediaKeySystemAccess.
106  // @param aSubject the nsPIDOMWindowInner associated with the notifying MediaKeySystemAccess.
107  // @param aTopic should be "mediakeys-request".
108  // @param aData json containing a `status` and a `keysystem`.
109  observe(aSubject, aTopic, aData) {
110    let parsedData;
111    try {
112      parsedData = JSON.parse(aData);
113    } catch (ex) {
114      Cu.reportError("Malformed EME video message with data: " + aData);
115      return;
116    }
117    const { status } = parsedData;
118    if (status == "is-capture-possible") {
119      // We handle this status in process -- don't send a message to the parent.
120      gGlobalCaptureListener.requestUpdateAndNotify();
121      return;
122    }
123
124    this.sendAsyncMessage("EMEVideo:ContentMediaKeysRequest", aData);
125  }
126}
127