tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

GeckoViewPermissionProcessChild.sys.mjs (5615B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 import { GeckoViewUtils } from "resource://gre/modules/GeckoViewUtils.sys.mjs";
      6 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
      7 
      8 const lazy = {};
      9 
     10 XPCOMUtils.defineLazyServiceGetter(
     11  lazy,
     12  "MediaManagerService",
     13  "@mozilla.org/mediaManagerService;1",
     14  Ci.nsIMediaManagerService
     15 );
     16 
     17 const STATUS_RECORDING = "recording";
     18 const STATUS_INACTIVE = "inactive";
     19 const TYPE_CAMERA = "camera";
     20 const TYPE_MICROPHONE = "microphone";
     21 
     22 export class GeckoViewPermissionProcessChild extends JSProcessActorChild {
     23  getActor(window) {
     24    return window.windowGlobalChild.getActor("GeckoViewPermission");
     25  }
     26 
     27  /* ----------  nsIObserver  ---------- */
     28  async observe(aSubject, aTopic, aData) {
     29    switch (aTopic) {
     30      case "getUserMedia:ask-device-permission": {
     31        await this.sendQuery("AskDevicePermission", aData);
     32        Services.obs.notifyObservers(
     33          aSubject,
     34          "getUserMedia:got-device-permission"
     35        );
     36        break;
     37      }
     38      case "getUserMedia:request": {
     39        const { callID } = aSubject;
     40        const allowedDevices = await this.handleMediaRequest(aSubject);
     41        Services.obs.notifyObservers(
     42          allowedDevices,
     43          allowedDevices
     44            ? "getUserMedia:response:allow"
     45            : "getUserMedia:response:deny",
     46          callID
     47        );
     48        break;
     49      }
     50      case "PeerConnection:request": {
     51        Services.obs.notifyObservers(
     52          null,
     53          "PeerConnection:response:allow",
     54          aSubject.callID
     55        );
     56        break;
     57      }
     58      case "recording-device-events": {
     59        this.handleRecordingDeviceEvents(aSubject);
     60        break;
     61      }
     62    }
     63  }
     64 
     65  handleRecordingDeviceEvents(aRequest) {
     66    aRequest.QueryInterface(Ci.nsIPropertyBag2);
     67    const contentWindow = aRequest.getProperty("window");
     68    const devices = [];
     69 
     70    const getStatusString = function (activityStatus) {
     71      switch (activityStatus) {
     72        case lazy.MediaManagerService.STATE_CAPTURE_ENABLED:
     73        case lazy.MediaManagerService.STATE_CAPTURE_DISABLED:
     74          return STATUS_RECORDING;
     75        case lazy.MediaManagerService.STATE_NOCAPTURE:
     76          return STATUS_INACTIVE;
     77        default:
     78          throw new Error("Unexpected activityStatus value");
     79      }
     80    };
     81 
     82    const hasCamera = {};
     83    const hasMicrophone = {};
     84    const screen = {};
     85    const window = {};
     86    const browser = {};
     87    const mediaDevices = {};
     88    lazy.MediaManagerService.mediaCaptureWindowState(
     89      contentWindow,
     90      hasCamera,
     91      hasMicrophone,
     92      screen,
     93      window,
     94      browser,
     95      mediaDevices
     96    );
     97    var cameraStatus = getStatusString(hasCamera.value);
     98    var microphoneStatus = getStatusString(hasMicrophone.value);
     99    if (hasCamera.value != lazy.MediaManagerService.STATE_NOCAPTURE) {
    100      devices.push({
    101        type: TYPE_CAMERA,
    102        status: cameraStatus,
    103      });
    104    }
    105    if (hasMicrophone.value != lazy.MediaManagerService.STATE_NOCAPTURE) {
    106      devices.push({
    107        type: TYPE_MICROPHONE,
    108        status: microphoneStatus,
    109      });
    110    }
    111    this.getActor(contentWindow).mediaRecordingStatusChanged(devices);
    112  }
    113 
    114  async handleMediaRequest(aRequest) {
    115    const constraints = aRequest.getConstraints();
    116    const { devices, windowID } = aRequest;
    117    const window = Services.wm.getOuterWindowWithId(windowID);
    118    if (window.closed) {
    119      return null;
    120    }
    121 
    122    // Release the request first
    123    aRequest = undefined;
    124 
    125    const sources = devices.map(device => {
    126      device = device.QueryInterface(Ci.nsIMediaDevice);
    127      return {
    128        type: device.type,
    129        id: device.rawId,
    130        rawId: device.rawId,
    131        name: device.rawName, // unfiltered device name to show to the user
    132        mediaSource: device.mediaSource,
    133      };
    134    });
    135 
    136    if (
    137      constraints.video &&
    138      !sources.some(source => source.type === "videoinput")
    139    ) {
    140      console.error("Media device error: no video source");
    141      return null;
    142    } else if (
    143      constraints.audio &&
    144      !sources.some(source => source.type === "audioinput")
    145    ) {
    146      console.error("Media device error: no audio source");
    147      return null;
    148    }
    149 
    150    const response = await this.getActor(window).getMediaPermission({
    151      uri: window.document.documentURI,
    152      video: constraints.video
    153        ? sources.filter(source => source.type === "videoinput")
    154        : null,
    155      audio: constraints.audio
    156        ? sources.filter(source => source.type === "audioinput")
    157        : null,
    158    });
    159 
    160    if (!response) {
    161      // Rejected.
    162      return null;
    163    }
    164 
    165    const allowedDevices = Cc["@mozilla.org/array;1"].createInstance(
    166      Ci.nsIMutableArray
    167    );
    168    if (constraints.video) {
    169      const video = devices.find(device => response.video === device.rawId);
    170      if (!video) {
    171        console.error("Media device error: invalid video id");
    172        return null;
    173      }
    174      await this.getActor(window).addCameraPermission();
    175      allowedDevices.appendElement(video);
    176    }
    177    if (constraints.audio) {
    178      const audio = devices.find(device => response.audio === device.rawId);
    179      if (!audio) {
    180        console.error("Media device error: invalid audio id");
    181        return null;
    182      }
    183      allowedDevices.appendElement(audio);
    184    }
    185    return allowedDevices;
    186  }
    187 }
    188 
    189 const { debug, warn } = GeckoViewUtils.initLogging(
    190  "GeckoViewPermissionProcessChild"
    191 );