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