tor-browser

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

PeerConnection.sys.mjs (59635B)


      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 const lazy = {};
      6 ChromeUtils.defineESModuleGetters(lazy, {
      7  PeerConnectionIdp: "resource://gre/modules/media/PeerConnectionIdp.sys.mjs",
      8 });
      9 
     10 const PC_CONTRACT = "@mozilla.org/dom/peerconnection;1";
     11 const PC_OBS_CONTRACT = "@mozilla.org/dom/peerconnectionobserver;1";
     12 const PC_ICE_CONTRACT = "@mozilla.org/dom/rtcicecandidate;1";
     13 const PC_SESSION_CONTRACT = "@mozilla.org/dom/rtcsessiondescription;1";
     14 const PC_STATIC_CONTRACT = "@mozilla.org/dom/peerconnectionstatic;1";
     15 const PC_COREQUEST_CONTRACT = "@mozilla.org/dom/createofferrequest;1";
     16 
     17 const PC_CID = Components.ID("{bdc2e533-b308-4708-ac8e-a8bfade6d851}");
     18 const PC_OBS_CID = Components.ID("{d1748d4c-7f6a-4dc5-add6-d55b7678537e}");
     19 const PC_ICE_CID = Components.ID("{02b9970c-433d-4cc2-923d-f7028ac66073}");
     20 const PC_SESSION_CID = Components.ID("{1775081b-b62d-4954-8ffe-a067bbf508a7}");
     21 const PC_MANAGER_CID = Components.ID("{7293e901-2be3-4c02-b4bd-cbef6fc24f78}");
     22 const PC_STATIC_CID = Components.ID("{0fb47c47-a205-4583-a9fc-cbadf8c95880}");
     23 const PC_COREQUEST_CID = Components.ID(
     24  "{74b2122d-65a8-4824-aa9e-3d664cb75dc2}"
     25 );
     26 
     27 function logWebRTCMsg(msg, file, line, flag, win) {
     28  let scriptErrorClass = Cc["@mozilla.org/scripterror;1"];
     29  let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError);
     30  scriptError.initWithWindowID(
     31    `WebRTC: ${msg}`,
     32    file,
     33    line,
     34    0,
     35    flag,
     36    "content javascript",
     37    win.windowGlobalChild.innerWindowId
     38  );
     39  Services.console.logMessage(scriptError);
     40  if (
     41    Services.prefs.getBoolPref("media.peerconnection.treat_warnings_as_errors")
     42  ) {
     43    throw new win.TypeError(msg);
     44  }
     45 }
     46 
     47 let setupPrototype = (_class, dict) => {
     48  _class.prototype.classDescription = _class.name;
     49  Object.assign(_class.prototype, dict);
     50 };
     51 
     52 // Global list of PeerConnection objects, so they can be cleaned up when
     53 // a page is torn down. (Maps inner window ID to an array of PC objects).
     54 export class GlobalPCList {
     55  constructor() {
     56    this._list = {};
     57    this._networkdown = false; // XXX Need to query current state somehow
     58    this._lifecycleobservers = {};
     59    this._nextId = 1;
     60    Services.obs.addObserver(this, "inner-window-destroyed");
     61    Services.obs.addObserver(this, "profile-change-net-teardown");
     62    Services.obs.addObserver(this, "network:offline-about-to-go-offline");
     63    Services.obs.addObserver(this, "network:offline-status-changed");
     64    Services.obs.addObserver(this, "gmp-plugin-crash");
     65    Services.obs.addObserver(this, "PeerConnection:response:allow");
     66    Services.obs.addObserver(this, "PeerConnection:response:deny");
     67    if (Services.cpmm) {
     68      Services.cpmm.addMessageListener("gmp-plugin-crash", this);
     69    }
     70  }
     71 
     72  notifyLifecycleObservers(pc, type) {
     73    for (var key of Object.keys(this._lifecycleobservers)) {
     74      this._lifecycleobservers[key](pc, pc._winID, type);
     75    }
     76  }
     77 
     78  addPC(pc) {
     79    let winID = pc._winID;
     80    if (this._list[winID]) {
     81      this._list[winID].push(Cu.getWeakReference(pc));
     82    } else {
     83      this._list[winID] = [Cu.getWeakReference(pc)];
     84    }
     85    pc._globalPCListId = this._nextId++;
     86    this.removeNullRefs(winID);
     87  }
     88 
     89  findPC(globalPCListId) {
     90    for (let winId in this._list) {
     91      if (this._list.hasOwnProperty(winId)) {
     92        for (let pcref of this._list[winId]) {
     93          let pc = pcref.get();
     94          if (pc && pc._globalPCListId == globalPCListId) {
     95            return pc;
     96          }
     97        }
     98      }
     99    }
    100    return null;
    101  }
    102 
    103  removeNullRefs(winID) {
    104    if (this._list[winID] === undefined) {
    105      return;
    106    }
    107    this._list[winID] = this._list[winID].filter(function (e) {
    108      return e.get() !== null;
    109    });
    110 
    111    if (this._list[winID].length === 0) {
    112      delete this._list[winID];
    113    }
    114  }
    115 
    116  handleGMPCrash(data) {
    117    let broadcastPluginCrash = function (list, winID, pluginID, pluginName) {
    118      if (list.hasOwnProperty(winID)) {
    119        list[winID].forEach(function (pcref) {
    120          let pc = pcref.get();
    121          if (pc) {
    122            pc._pc.pluginCrash(pluginID, pluginName);
    123          }
    124        });
    125      }
    126    };
    127 
    128    // a plugin crashed; if it's associated with any of our PCs, fire an
    129    // event to the DOM window
    130    for (let winId in this._list) {
    131      broadcastPluginCrash(this._list, winId, data.pluginID, data.pluginName);
    132    }
    133  }
    134 
    135  receiveMessage({ name, data }) {
    136    if (name == "gmp-plugin-crash") {
    137      this.handleGMPCrash(data);
    138    }
    139  }
    140 
    141  observe(subject, topic, data) {
    142    let cleanupPcRef = function (pcref) {
    143      let pc = pcref.get();
    144      if (pc) {
    145        pc._suppressEvents = true;
    146        pc.close();
    147      }
    148    };
    149 
    150    let cleanupWinId = function (list, winID) {
    151      if (list.hasOwnProperty(winID)) {
    152        list[winID].forEach(cleanupPcRef);
    153        delete list[winID];
    154      }
    155    };
    156 
    157    if (topic == "inner-window-destroyed") {
    158      let winID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
    159      cleanupWinId(this._list, winID);
    160 
    161      if (this._lifecycleobservers.hasOwnProperty(winID)) {
    162        delete this._lifecycleobservers[winID];
    163      }
    164    } else if (
    165      topic == "profile-change-net-teardown" ||
    166      topic == "network:offline-about-to-go-offline"
    167    ) {
    168      // As Necko doesn't prevent us from accessing the network we still need to
    169      // monitor the network offline/online state here. See bug 1326483
    170      this._networkdown = true;
    171    } else if (topic == "network:offline-status-changed") {
    172      if (data == "offline") {
    173        this._networkdown = true;
    174      } else if (data == "online") {
    175        this._networkdown = false;
    176      }
    177    } else if (topic == "gmp-plugin-crash") {
    178      if (subject instanceof Ci.nsIWritablePropertyBag2) {
    179        let pluginID = subject.getPropertyAsUint32("pluginID");
    180        let pluginName = subject.getPropertyAsAString("pluginName");
    181        let data = { pluginID, pluginName };
    182        this.handleGMPCrash(data);
    183      }
    184    } else if (
    185      topic == "PeerConnection:response:allow" ||
    186      topic == "PeerConnection:response:deny"
    187    ) {
    188      var pc = this.findPC(data);
    189      if (pc) {
    190        if (topic == "PeerConnection:response:allow") {
    191          pc._settlePermission.allow();
    192        } else {
    193          let err = new pc._win.DOMException(
    194            "The request is not allowed by " +
    195              "the user agent or the platform in the current context.",
    196            "NotAllowedError"
    197          );
    198          pc._settlePermission.deny(err);
    199        }
    200      }
    201    }
    202  }
    203 
    204  _registerPeerConnectionLifecycleCallback(winID, cb) {
    205    this._lifecycleobservers[winID] = cb;
    206  }
    207 }
    208 
    209 setupPrototype(GlobalPCList, {
    210  QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
    211  classID: PC_MANAGER_CID,
    212 });
    213 
    214 var _globalPCList = new GlobalPCList();
    215 
    216 // Parses grammar in RFC5245 section 15 and ICE TCP from RFC6544 section 4.5.
    217 function parseCandidate(line) {
    218  const match = line.match(
    219    /^(a=)?candidate:([A-Za-z0-9+\/]{1,32}) (\d+) (UDP|TCP) (\d+) ([A-Za-z0-9.:-]+) (\d+) typ (host|srflx|prflx|relay)(?: raddr ([A-Za-z0-9.:-]+) rport (\d+))?(.*)$/i
    220  );
    221  if (!match) {
    222    return null;
    223  }
    224  const candidate = {
    225    foundation: match[2],
    226    componentId: parseInt(match[3], 10),
    227    transport: match[4],
    228    priority: parseInt(match[5], 10),
    229    address: match[6],
    230    port: parseInt(match[7], 10),
    231    type: match[8],
    232    relatedAddress: match[9],
    233    relatedPort: match[10],
    234  };
    235  if (candidate.componentId < 1 || candidate.componentId > 256) {
    236    return null;
    237  }
    238  if (candidate.priority < 0 || candidate.priority > 4294967295) {
    239    return null;
    240  }
    241  if (candidate.port < 0 || candidate.port > 65535) {
    242    return null;
    243  }
    244  candidate.component = { 1: "rtp", 2: "rtcp" }[candidate.componentId] || null;
    245  candidate.protocol =
    246    { udp: "udp", tcp: "tcp" }[candidate.transport.toLowerCase()] || null;
    247 
    248  const tcpTypeMatch = match[11].match(/tcptype (\S+)/i);
    249  if (tcpTypeMatch) {
    250    candidate.tcpType = tcpTypeMatch[1];
    251    if (
    252      candidate.protocol != "tcp" ||
    253      !["active", "passive", "so"].includes(candidate.tcpType)
    254    ) {
    255      return null;
    256    }
    257  }
    258  return candidate;
    259 }
    260 
    261 export class RTCIceCandidate {
    262  init(win) {
    263    this._win = win;
    264  }
    265 
    266  __init(dict) {
    267    if (dict.sdpMid == null && dict.sdpMLineIndex == null) {
    268      throw new this._win.TypeError(
    269        "Either sdpMid or sdpMLineIndex must be specified"
    270      );
    271    }
    272    Object.assign(this, dict);
    273    const candidate = parseCandidate(this.candidate);
    274    if (!candidate) {
    275      return;
    276    }
    277    Object.assign(this, candidate);
    278  }
    279 
    280  toJSON() {
    281    return {
    282      candidate: this.candidate,
    283      sdpMid: this.sdpMid,
    284      sdpMLineIndex: this.sdpMLineIndex,
    285      usernameFragment: this.usernameFragment,
    286    };
    287  }
    288 }
    289 
    290 setupPrototype(RTCIceCandidate, {
    291  classID: PC_ICE_CID,
    292  contractID: PC_ICE_CONTRACT,
    293  QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
    294 });
    295 
    296 export class RTCSessionDescription {
    297  init(win) {
    298    this._win = win;
    299    this._winID = this._win.windowGlobalChild.innerWindowId;
    300    this._legacyPref = Services.prefs.getBoolPref(
    301      "media.peerconnection.description.legacy.enabled"
    302    );
    303  }
    304 
    305  __init({ type, sdp }) {
    306    Object.assign(this, { _type: type, _sdp: sdp });
    307  }
    308 
    309  get type() {
    310    return this._type;
    311  }
    312  set type(type) {
    313    if (!this._legacyPref) {
    314      // TODO: this throws even in sloppy mode. Remove in bug 1883992
    315      throw new this._win.TypeError("setting getter-only property type");
    316    }
    317    this.warn();
    318    this._type = type;
    319  }
    320 
    321  get sdp() {
    322    return this._sdp;
    323  }
    324  set sdp(sdp) {
    325    if (!this._legacyPref) {
    326      // TODO: this throws even in sloppy mode. Remove in bug 1883992
    327      throw new this._win.TypeError("setting getter-only property sdp");
    328    }
    329    this.warn();
    330    this._sdp = sdp;
    331  }
    332 
    333  warn() {
    334    if (!this._warned) {
    335      // Warn once per RTCSessionDescription about deprecated writable usage.
    336      if (this._legacyPref) {
    337        this.logMsg(
    338          "RTCSessionDescription's members are readonly! " +
    339            "Writing to them is deprecated and will break soon!",
    340          Ci.nsIScriptError.warningFlag
    341        );
    342      } else {
    343        this.logMsg(
    344          "RTCSessionDescription's members are readonly! " +
    345            "Writing to them no longer works!",
    346          Ci.nsIScriptError.errorFlag
    347        );
    348      }
    349      this._warned = true;
    350    }
    351  }
    352 
    353  logMsg(msg, flag) {
    354    let err = this._win.Error();
    355    logWebRTCMsg(msg, err.fileName, err.lineNumber, flag, this._win);
    356  }
    357 }
    358 
    359 setupPrototype(RTCSessionDescription, {
    360  classID: PC_SESSION_CID,
    361  contractID: PC_SESSION_CONTRACT,
    362  QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
    363 });
    364 
    365 export class RTCPeerConnection {
    366  constructor() {
    367    this._pc = null;
    368    this._closed = false;
    369    this._pendingLocalDescription = null;
    370    this._pendingRemoteDescription = null;
    371    this._currentLocalDescription = null;
    372    this._currentRemoteDescription = null;
    373    this._legacyPref = Services.prefs.getBoolPref(
    374      "media.peerconnection.description.legacy.enabled"
    375    );
    376 
    377    // http://rtcweb-wg.github.io/jsep/#rfc.section.4.1.9
    378    // canTrickle == null means unknown; when a remote description is received it
    379    // is set to true or false based on the presence of the "trickle" ice-option
    380    this._canTrickle = null;
    381 
    382    // So we can record telemetry on state transitions
    383    this._iceConnectionState = "new";
    384 
    385    this._hasStunServer = this._hasTurnServer = false;
    386    this._iceGatheredRelayCandidates = false;
    387  }
    388 
    389  init(win) {
    390    this._win = win;
    391  }
    392 
    393  // Pref-based overrides; will _not_ be reflected in getConfiguration
    394  _applyPrefsToConfig(rtcConfig) {
    395    if (
    396      rtcConfig.iceTransportPolicy == "all" &&
    397      Services.prefs.getBoolPref("media.peerconnection.ice.relay_only")
    398    ) {
    399      rtcConfig.iceTransportPolicy = "relay";
    400    }
    401 
    402    if (
    403      !rtcConfig.iceServers ||
    404      !Services.prefs.getBoolPref(
    405        "media.peerconnection.use_document_iceservers"
    406      )
    407    ) {
    408      try {
    409        rtcConfig.iceServers = JSON.parse(
    410          Services.prefs.getCharPref(
    411            "media.peerconnection.default_iceservers"
    412          ) || "[]"
    413        );
    414      } catch (e) {
    415        this.logWarning(
    416          "Ignoring invalid media.peerconnection.default_iceservers in about:config"
    417        );
    418        rtcConfig.iceServers = [];
    419      }
    420      try {
    421        this._validateIceServers(
    422          rtcConfig.iceServers,
    423          "Ignoring invalid media.peerconnection.default_iceservers in about:config"
    424        );
    425      } catch (e) {
    426        this.logWarning(e.message);
    427        rtcConfig.iceServers = [];
    428      }
    429    }
    430  }
    431 
    432  _validateConfig(rtcConfig) {
    433    if ("sdpSemantics" in rtcConfig) {
    434      if (rtcConfig.sdpSemantics == "plan-b") {
    435        this.logWarning(
    436          `Outdated and non-standard {sdpSemantics: "plan-b"} is not ` +
    437            `supported! WebRTC may be unreliable. Please update code to ` +
    438            `follow standard "unified-plan".`
    439        );
    440      }
    441      // Don't let it show up in getConfiguration.
    442      delete rtcConfig.sdpSemantics;
    443    }
    444 
    445    if (this._config) {
    446      // certificates must match
    447      if (rtcConfig.certificates.length != this._config.certificates.length) {
    448        throw new this._win.DOMException(
    449          "Cannot change certificates with setConfiguration (length differs)",
    450          "InvalidModificationError"
    451        );
    452      }
    453      for (let i = 0; i < rtcConfig.certificates.length; i++) {
    454        if (rtcConfig.certificates[i] != this._config.certificates[i]) {
    455          throw new this._win.DOMException(
    456            `Cannot change certificates with setConfiguration ` +
    457              `(cert at index ${i} differs)`,
    458            "InvalidModificationError"
    459          );
    460        }
    461      }
    462 
    463      // bundlePolicy must match
    464      if (rtcConfig.bundlePolicy != this._config.bundlePolicy) {
    465        throw new this._win.DOMException(
    466          "Cannot change bundlePolicy with setConfiguration",
    467          "InvalidModificationError"
    468        );
    469      }
    470 
    471      // peerIdentity must match
    472      if (
    473        rtcConfig.peerIdentity &&
    474        rtcConfig.peerIdentity != this._config.peerIdentity
    475      ) {
    476        throw new this._win.DOMException(
    477          "Cannot change peerIdentity with setConfiguration",
    478          "InvalidModificationError"
    479        );
    480      }
    481 
    482      // TODO (bug 1339203): rtcpMuxPolicy must match
    483      // TODO (bug 1529398): iceCandidatePoolSize must match if sLD has ever
    484      // been called.
    485    }
    486 
    487    // This gets executed in the typical case when iceServers
    488    // are passed in through the web page.
    489    this._validateIceServers(
    490      rtcConfig.iceServers,
    491      "RTCPeerConnection constructor passed invalid RTCConfiguration"
    492    );
    493  }
    494 
    495  _checkIfIceRestartRequired(rtcConfig) {
    496    if (this._config) {
    497      if (rtcConfig.iceTransportPolicy != this._config.iceTransportPolicy) {
    498        this._pc.restartIceNoRenegotiationNeeded();
    499        return;
    500      }
    501      if (
    502        JSON.stringify(this._config.iceServers) !=
    503        JSON.stringify(rtcConfig.iceServers)
    504      ) {
    505        this._pc.restartIceNoRenegotiationNeeded();
    506      }
    507    }
    508  }
    509 
    510  __init(rtcConfig) {
    511    this._winID = this._win.windowGlobalChild.innerWindowId;
    512    let certificates = rtcConfig.certificates || [];
    513 
    514    if (certificates.some(c => c.expires <= Date.now())) {
    515      throw new this._win.DOMException(
    516        "Unable to create RTCPeerConnection with an expired certificate",
    517        "InvalidAccessError"
    518      );
    519    }
    520 
    521    // TODO(bug 1531875): Check origin of certs
    522 
    523    // TODO(bug 1176518): Remove this code once we support multiple certs
    524    let certificate;
    525    if (certificates.length == 1) {
    526      certificate = certificates[0];
    527    } else if (certificates.length) {
    528      throw new this._win.DOMException(
    529        "RTCPeerConnection does not currently support multiple certificates",
    530        "NotSupportedError"
    531      );
    532    }
    533 
    534    this._documentPrincipal = Cu.getWebIDLCallerPrincipal();
    535 
    536    if (_globalPCList._networkdown) {
    537      throw new this._win.DOMException(
    538        "Can't create RTCPeerConnections when the network is down",
    539        "InvalidStateError"
    540      );
    541    }
    542 
    543    this.makeGetterSetterEH("ontrack");
    544    this.makeLegacyGetterSetterEH(
    545      "onaddstream",
    546      "Use peerConnection.ontrack instead."
    547    );
    548    this.makeLegacyGetterSetterEH(
    549      "onaddtrack",
    550      "Use peerConnection.ontrack instead."
    551    );
    552    this.makeGetterSetterEH("onicecandidate");
    553    this.makeGetterSetterEH("onnegotiationneeded");
    554    this.makeGetterSetterEH("onsignalingstatechange");
    555    this.makeGetterSetterEH("ondatachannel");
    556    this.makeGetterSetterEH("oniceconnectionstatechange");
    557    this.makeGetterSetterEH("onicegatheringstatechange");
    558    this.makeGetterSetterEH("onconnectionstatechange");
    559    this.makeGetterSetterEH("onidentityresult");
    560    this.makeGetterSetterEH("onpeeridentity");
    561    this.makeGetterSetterEH("onidpassertionerror");
    562    this.makeGetterSetterEH("onidpvalidationerror");
    563 
    564    this._pc = new this._win.PeerConnectionImpl();
    565 
    566    this.__DOM_IMPL__._innerObject = this;
    567    const observer = new this._win.PeerConnectionObserver(this.__DOM_IMPL__);
    568 
    569    // Add a reference to the PeerConnection to global list (before init).
    570    _globalPCList.addPC(this);
    571 
    572    this._pc.initialize(observer, this._win);
    573 
    574    this.setConfiguration(rtcConfig);
    575 
    576    this._certificateReady = this._initCertificate(certificate);
    577    this._initIdp();
    578    _globalPCList.notifyLifecycleObservers(this, "initialized");
    579  }
    580 
    581  getConfiguration() {
    582    const config = Object.assign({}, this._config);
    583    delete config.sdpSemantics;
    584    return config;
    585  }
    586 
    587  setConfiguration(rtcConfig) {
    588    this._checkClosed();
    589    this._validateConfig(rtcConfig);
    590    this._checkIfIceRestartRequired(rtcConfig);
    591 
    592    // Allow prefs to tweak these settings before passing to c++, but hide all
    593    // of that from JS.
    594    const configWithPrefTweaks = Object.assign({}, rtcConfig);
    595    this._applyPrefsToConfig(configWithPrefTweaks);
    596    this._pc.setConfiguration(configWithPrefTweaks);
    597 
    598    this._config = Object.assign({}, rtcConfig);
    599  }
    600 
    601  async _initCertificate(certificate) {
    602    if (!certificate) {
    603      certificate = await this._win.RTCPeerConnection.generateCertificate({
    604        name: "ECDSA",
    605        namedCurve: "P-256",
    606      });
    607    }
    608    this._pc.certificate = certificate;
    609  }
    610 
    611  _resetPeerIdentityPromise() {
    612    this._peerIdentity = new this._win.Promise((resolve, reject) => {
    613      this._resolvePeerIdentity = resolve;
    614      this._rejectPeerIdentity = reject;
    615    });
    616  }
    617 
    618  _initIdp() {
    619    this._resetPeerIdentityPromise();
    620    this._lastIdentityValidation = this._win.Promise.resolve();
    621 
    622    let prefName = "media.peerconnection.identity.timeout";
    623    let idpTimeout = Services.prefs.getIntPref(prefName);
    624    this._localIdp = new lazy.PeerConnectionIdp(this._win, idpTimeout);
    625    this._remoteIdp = new lazy.PeerConnectionIdp(this._win, idpTimeout);
    626  }
    627 
    628  // Add a function to the internal operations chain.
    629 
    630  _chain(operation) {
    631    return this._pc.chain(operation);
    632  }
    633 
    634  // It's basically impossible to use async directly in JSImplemented code,
    635  // because the implicit promise must be wrapped to the right type for content.
    636  //
    637  // The _async wrapper takes care of this. The _legacy wrapper implements
    638  // legacy callbacks in a manner that produces correct line-numbers in errors,
    639  // provided that methods validate their inputs before putting themselves on
    640  // the pc's operations chain.
    641  //
    642  // These wrappers also serve as guards against settling promises past close().
    643 
    644  _async(func) {
    645    return this._win.Promise.resolve(this._closeWrapper(func));
    646  }
    647 
    648  _legacy(...args) {
    649    return this._win.Promise.resolve(this._legacyCloseWrapper(...args));
    650  }
    651 
    652  _auto(onSucc, onErr, func) {
    653    return typeof onSucc == "function"
    654      ? this._legacy(onSucc, onErr, func)
    655      : this._async(func);
    656  }
    657 
    658  async _closeWrapper(func) {
    659    let closed = this._closed;
    660    try {
    661      let result = await func();
    662      if (!closed && this._closed) {
    663        await new Promise(() => {});
    664      }
    665      return result;
    666    } catch (e) {
    667      if (!closed && this._closed) {
    668        await new Promise(() => {});
    669      }
    670      throw e;
    671    }
    672  }
    673 
    674  async _legacyCloseWrapper(onSucc, onErr, func) {
    675    let wrapCallback = cb => result => {
    676      try {
    677        cb && cb(result);
    678      } catch (e) {
    679        this.logErrorAndCallOnError(e);
    680      }
    681    };
    682 
    683    try {
    684      wrapCallback(onSucc)(await func());
    685    } catch (e) {
    686      wrapCallback(onErr)(e);
    687    }
    688  }
    689 
    690  // This implements the fairly common "Queue a task" logic
    691  async _queueTaskWithClosedCheck(func) {
    692    const pc = this;
    693    return new this._win.Promise((resolve, reject) => {
    694      Services.tm.dispatchToMainThread({
    695        run() {
    696          try {
    697            if (!pc._closed) {
    698              func();
    699              resolve();
    700            }
    701          } catch (e) {
    702            reject(e);
    703          }
    704        },
    705      });
    706    });
    707  }
    708 
    709  /**
    710   * An RTCConfiguration may look like this:
    711   *
    712   * { "iceServers": [ { urls: "stun:stun.example.org", },
    713   *                   { url: "stun:stun.example.org", }, // deprecated version
    714   *                   { urls: ["turn:turn1.x.org", "turn:turn2.x.org"],
    715   *                     username:"jib", credential:"mypass"} ] }
    716   *
    717   * This function normalizes the structure of the input for rtcConfig.iceServers for us,
    718   * so we test well-formed stun/turn urls before passing along to C++.
    719   *   msg - Error message to detail which array-entry failed, if any.
    720   */
    721  _validateIceServers(iceServers, msg) {
    722    // Normalize iceServers input
    723    iceServers.forEach(server => {
    724      if (typeof server.urls === "string") {
    725        server.urls = [server.urls];
    726      } else if (!server.urls && server.url) {
    727        // TODO: Remove support for legacy iceServer.url eventually (Bug 1116766)
    728        server.urls = [server.url];
    729        this.logWarning("RTCIceServer.url is deprecated! Use urls instead.");
    730      }
    731    });
    732 
    733    let nicerNewURI = uriStr => {
    734      try {
    735        return Services.io.newURI(uriStr);
    736      } catch (e) {
    737        if (e.result == Cr.NS_ERROR_MALFORMED_URI) {
    738          throw new this._win.DOMException(
    739            `${msg} - malformed URI: ${uriStr}`,
    740            "SyntaxError"
    741          );
    742        }
    743        throw e;
    744      }
    745    };
    746 
    747    let stunServers = 0;
    748 
    749    iceServers.forEach(({ urls, username, credential, credentialType }) => {
    750      if (!urls) {
    751        // TODO: Remove once url is deprecated (Bug 1369563)
    752        throw new this._win.TypeError(
    753          "Missing required 'urls' member of RTCIceServer"
    754        );
    755      }
    756      if (!urls.length) {
    757        throw new this._win.DOMException(
    758          `${msg} - urls is empty`,
    759          "SyntaxError"
    760        );
    761      }
    762      urls
    763        .map(url => nicerNewURI(url))
    764        .forEach(({ scheme, spec, query }) => {
    765          if (scheme in { turn: 1, turns: 1 }) {
    766            if (username == undefined) {
    767              throw new this._win.DOMException(
    768                `${msg} - missing username: ${spec}`,
    769                "InvalidAccessError"
    770              );
    771            }
    772            if (username.length > 512) {
    773              throw new this._win.DOMException(
    774                `${msg} - username longer then 512 bytes: ${username}`,
    775                "InvalidAccessError"
    776              );
    777            }
    778            if (credential == undefined) {
    779              throw new this._win.DOMException(
    780                `${msg} - missing credential: ${spec}`,
    781                "InvalidAccessError"
    782              );
    783            }
    784            if (credentialType != "password") {
    785              this.logWarning(
    786                `RTCConfiguration TURN credentialType \"${credentialType}\"` +
    787                  " is not yet implemented. Treating as password." +
    788                  " https://bugzil.la/1247616"
    789              );
    790            }
    791            this._hasTurnServer = true;
    792            // If this is not a TURN TCP/TLS server, it is also a STUN server
    793            const parameters = query.split("&");
    794            if (!parameters.includes("transport=tcp")) {
    795              this._hasStunServer = true;
    796            }
    797            stunServers += 1;
    798          } else if (scheme in { stun: 1, stuns: 1 }) {
    799            this._hasStunServer = true;
    800            stunServers += 1;
    801          } else {
    802            throw new this._win.DOMException(
    803              `${msg} - improper scheme: ${scheme}`,
    804              "SyntaxError"
    805            );
    806          }
    807          if (scheme in { stuns: 1 }) {
    808            this.logWarning(scheme.toUpperCase() + " is not yet supported.");
    809          }
    810          if (stunServers >= 5) {
    811            this.logError(
    812              "Using five or more STUN/TURN servers slows down discovery"
    813            );
    814          }
    815        });
    816    });
    817  }
    818 
    819  // Ideally, this should be of the form _checkState(state),
    820  // where the state is taken from an enumeration containing
    821  // the valid peer connection states defined in the WebRTC
    822  // spec. See Bug 831756.
    823  _checkClosed() {
    824    if (this._closed) {
    825      throw new this._win.DOMException(
    826        "Peer connection is closed",
    827        "InvalidStateError"
    828      );
    829    }
    830  }
    831 
    832  dispatchEvent(event) {
    833    // PC can close while events are firing if there is an async dispatch
    834    // in c++ land. But let through "closed" signaling and ice connection events.
    835    if (!this._suppressEvents) {
    836      this.__DOM_IMPL__.dispatchEvent(event);
    837    }
    838  }
    839 
    840  // Log error message to web console and window.onerror, if present.
    841  logErrorAndCallOnError(e) {
    842    this.logMsg(
    843      e.message,
    844      e.fileName,
    845      e.lineNumber,
    846      Ci.nsIScriptError.errorFlag
    847    );
    848 
    849    // Safely call onerror directly if present (necessary for testing)
    850    try {
    851      if (typeof this._win.onerror === "function") {
    852        this._win.onerror(e.message, e.fileName, e.lineNumber);
    853      }
    854    } catch (e) {
    855      // If onerror itself throws, service it.
    856      try {
    857        this.logMsg(
    858          e.message,
    859          e.fileName,
    860          e.lineNumber,
    861          Ci.nsIScriptError.errorFlag
    862        );
    863      } catch (e) {}
    864    }
    865  }
    866 
    867  logError(msg) {
    868    this.logStackMsg(msg, Ci.nsIScriptError.errorFlag);
    869  }
    870 
    871  logWarning(msg) {
    872    this.logStackMsg(msg, Ci.nsIScriptError.warningFlag);
    873  }
    874 
    875  logStackMsg(msg, flag) {
    876    let err = this._win.Error();
    877    this.logMsg(msg, err.fileName, err.lineNumber, flag);
    878  }
    879 
    880  logMsg(msg, file, line, flag) {
    881    return logWebRTCMsg(msg, file, line, flag, this._win);
    882  }
    883 
    884  getEH(type) {
    885    return this.__DOM_IMPL__.getEventHandler(type);
    886  }
    887 
    888  setEH(type, handler) {
    889    this.__DOM_IMPL__.setEventHandler(type, handler);
    890  }
    891 
    892  makeGetterSetterEH(name) {
    893    Object.defineProperty(this, name, {
    894      get() {
    895        return this.getEH(name);
    896      },
    897      set(h) {
    898        this.setEH(name, h);
    899      },
    900    });
    901  }
    902 
    903  makeLegacyGetterSetterEH(name, msg) {
    904    Object.defineProperty(this, name, {
    905      get() {
    906        return this.getEH(name);
    907      },
    908      set(h) {
    909        this.logWarning(name + " is deprecated! " + msg);
    910        this.setEH(name, h);
    911      },
    912    });
    913  }
    914 
    915  createOffer(optionsOrOnSucc, onErr, options) {
    916    let onSuccess = null;
    917    if (typeof optionsOrOnSucc == "function") {
    918      onSuccess = optionsOrOnSucc;
    919    } else {
    920      options = optionsOrOnSucc;
    921    }
    922    // This entry-point handles both new and legacy call sig. Decipher which one
    923    if (onSuccess) {
    924      return this._legacy(onSuccess, onErr, () => this._createOffer(options));
    925    }
    926    return this._async(() => this._createOffer(options));
    927  }
    928 
    929  // Ensures that we have at least one transceiver of |kind| that is
    930  // configured to receive. It will create one if necessary.
    931  _ensureOfferToReceive(kind) {
    932    let hasRecv = this.getTransceivers().some(
    933      transceiver =>
    934        transceiver.getKind() == kind &&
    935        (transceiver.direction == "sendrecv" ||
    936          transceiver.direction == "recvonly") &&
    937        !transceiver.stopped
    938    );
    939 
    940    if (!hasRecv) {
    941      this._addTransceiverNoEvents(kind, { direction: "recvonly" });
    942    }
    943  }
    944 
    945  // Handles offerToReceiveAudio/Video
    946  _ensureTransceiversForOfferToReceive(options) {
    947    if (options.offerToReceiveAudio) {
    948      this._ensureOfferToReceive("audio");
    949    }
    950 
    951    if (options.offerToReceiveVideo) {
    952      this._ensureOfferToReceive("video");
    953    }
    954 
    955    this.getTransceivers()
    956      .filter(transceiver => {
    957        return (
    958          (options.offerToReceiveVideo === false &&
    959            transceiver.receiver.track.kind == "video") ||
    960          (options.offerToReceiveAudio === false &&
    961            transceiver.receiver.track.kind == "audio")
    962        );
    963      })
    964      .forEach(transceiver => {
    965        if (transceiver.direction == "sendrecv") {
    966          transceiver.setDirectionInternal("sendonly");
    967        } else if (transceiver.direction == "recvonly") {
    968          transceiver.setDirectionInternal("inactive");
    969        }
    970      });
    971  }
    972 
    973  _maybeDuplicateFingerprints(sdp) {
    974    if (this._pc.duplicateFingerprintQuirk) {
    975      // hack to put a=fingerprint under all m= lines
    976      const lines = sdp.split("\n");
    977      const fingerprint = lines.find(line => line.startsWith("a=fingerprint"));
    978      if (fingerprint) {
    979        for (let i = 0; i < lines.length; i++) {
    980          if (lines[i].startsWith("m=")) {
    981            let j = i + 1;
    982            while (j < lines.length && lines[j].startsWith("c=")) {
    983              j++;
    984            }
    985            lines.splice(j, 0, fingerprint);
    986          }
    987        }
    988        sdp = lines.join("\n");
    989      }
    990    }
    991    return sdp;
    992  }
    993 
    994  _createOffer(options) {
    995    this._checkClosed();
    996    this._ensureTransceiversForOfferToReceive(options);
    997    return this._chain(async () =>
    998      Cu.cloneInto(await this._createAnOffer(options), this._win)
    999    );
   1000  }
   1001 
   1002  async _createAnOffer(options = {}) {
   1003    switch (this.signalingState) {
   1004      case "stable":
   1005      case "have-local-offer":
   1006        break;
   1007      default:
   1008        throw new this._win.DOMException(
   1009          `Cannot create offer in ${this.signalingState}`,
   1010          "InvalidStateError"
   1011        );
   1012    }
   1013    let haveAssertion;
   1014    if (this._localIdp.enabled) {
   1015      haveAssertion = this._getIdentityAssertion();
   1016    }
   1017    await this._getPermission();
   1018    await this._certificateReady;
   1019    let sdp = await new Promise((resolve, reject) => {
   1020      this._onCreateOfferSuccess = resolve;
   1021      this._onCreateOfferFailure = reject;
   1022      this._pc.createOffer(options);
   1023    });
   1024    sdp = this._maybeDuplicateFingerprints(sdp);
   1025    if (haveAssertion) {
   1026      await haveAssertion;
   1027      sdp = this._localIdp.addIdentityAttribute(sdp);
   1028    }
   1029    return { type: "offer", sdp };
   1030  }
   1031 
   1032  createAnswer(optionsOrOnSucc, onErr) {
   1033    // This entry-point handles both new and legacy call sig. Decipher which one
   1034    if (typeof optionsOrOnSucc == "function") {
   1035      return this._legacy(optionsOrOnSucc, onErr, () => this._createAnswer({}));
   1036    }
   1037    return this._async(() => this._createAnswer(optionsOrOnSucc));
   1038  }
   1039 
   1040  _createAnswer() {
   1041    this._checkClosed();
   1042    return this._chain(async () =>
   1043      Cu.cloneInto(await this._createAnAnswer(), this._win)
   1044    );
   1045  }
   1046 
   1047  async _createAnAnswer() {
   1048    if (this.signalingState != "have-remote-offer") {
   1049      throw new this._win.DOMException(
   1050        `Cannot create answer in ${this.signalingState}`,
   1051        "InvalidStateError"
   1052      );
   1053    }
   1054    let haveAssertion;
   1055    if (this._localIdp.enabled) {
   1056      haveAssertion = this._getIdentityAssertion();
   1057    }
   1058    await this._getPermission();
   1059    await this._certificateReady;
   1060    let sdp = await new Promise((resolve, reject) => {
   1061      this._onCreateAnswerSuccess = resolve;
   1062      this._onCreateAnswerFailure = reject;
   1063      this._pc.createAnswer();
   1064    });
   1065    sdp = this._maybeDuplicateFingerprints(sdp);
   1066    if (haveAssertion) {
   1067      await haveAssertion;
   1068      sdp = this._localIdp.addIdentityAttribute(sdp);
   1069    }
   1070    return { type: "answer", sdp };
   1071  }
   1072 
   1073  async _getPermission() {
   1074    if (!this._havePermission) {
   1075      const privileged =
   1076        this._documentPrincipal.isSystemPrincipal ||
   1077        Services.prefs.getBoolPref("media.navigator.permission.disabled");
   1078 
   1079      if (privileged) {
   1080        this._havePermission = Promise.resolve();
   1081      } else {
   1082        this._havePermission = new Promise((resolve, reject) => {
   1083          this._settlePermission = { allow: resolve, deny: reject };
   1084          let outerId = this._win.docShell.outerWindowID;
   1085 
   1086          let chrome = new CreateOfferRequest(
   1087            outerId,
   1088            this._winID,
   1089            this._globalPCListId,
   1090            false
   1091          );
   1092          let request = this._win.CreateOfferRequest._create(this._win, chrome);
   1093          Services.obs.notifyObservers(request, "PeerConnection:request");
   1094        });
   1095      }
   1096    }
   1097    return this._havePermission;
   1098  }
   1099 
   1100  _sanityCheckSdp(sdp) {
   1101    // The fippo butter finger filter AKA non-ASCII chars
   1102    // Note: SDP allows non-ASCII character in the subject (who cares?)
   1103    // eslint-disable-next-line no-control-regex
   1104    let pos = sdp.search(/[^\u0000-\u007f]/);
   1105    if (pos != -1) {
   1106      throw new this._win.DOMException(
   1107        "SDP contains non ASCII characters at position " + pos,
   1108        "InvalidParameterError"
   1109      );
   1110    }
   1111  }
   1112 
   1113  setLocalDescription(desc, onSucc, onErr) {
   1114    return this._auto(onSucc, onErr, () => this._setLocalDescription(desc));
   1115  }
   1116 
   1117  _setLocalDescription({ type, sdp }) {
   1118    if (type == "pranswer") {
   1119      throw new this._win.DOMException(
   1120        "pranswer not yet implemented",
   1121        "NotSupportedError"
   1122      );
   1123    }
   1124    this._checkClosed();
   1125    return this._chain(async () => {
   1126      // Avoid Promise.all ahead of synchronous part of spec algorithm, since it
   1127      // defers. NOTE: The spec says to return an already-rejected promise in
   1128      // some cases, which is difficult to achieve in practice from JS (would
   1129      // require avoiding await and then() entirely), but we want to come as
   1130      // close as we reasonably can.
   1131      const p = this._getPermission();
   1132      if (!type) {
   1133        switch (this.signalingState) {
   1134          case "stable":
   1135          case "have-local-offer":
   1136          case "have-remote-pranswer":
   1137            type = "offer";
   1138            break;
   1139          default:
   1140            type = "answer";
   1141            break;
   1142        }
   1143      }
   1144      if (!sdp) {
   1145        if (type == "offer") {
   1146          sdp = (await this._createAnOffer()).sdp;
   1147        } else if (type == "answer") {
   1148          sdp = (await this._createAnAnswer()).sdp;
   1149        }
   1150      } else {
   1151        this._sanityCheckSdp(sdp);
   1152      }
   1153 
   1154      try {
   1155        await new Promise((resolve, reject) => {
   1156          this._onSetDescriptionSuccess = resolve;
   1157          this._onSetDescriptionFailure = reject;
   1158          this._pc.setLocalDescription(this._actions[type], sdp);
   1159        });
   1160        await p;
   1161      } catch (e) {
   1162        this._pc.onSetDescriptionError();
   1163        throw e;
   1164      }
   1165      await this._pc.onSetDescriptionSuccess(type, false);
   1166    });
   1167  }
   1168 
   1169  async _validateIdentity(sdp, origin) {
   1170    // Only run a single identity verification at a time.  We have to do this to
   1171    // avoid problems with the fact that identity validation doesn't block the
   1172    // resolution of setRemoteDescription().
   1173    const validate = async () => {
   1174      // Access this._pc synchronously in case pc is closed later
   1175      const identity = this._pc.peerIdentity;
   1176      await this._lastIdentityValidation;
   1177      const msg = await this._remoteIdp.verifyIdentityFromSDP(sdp, origin);
   1178      // If this pc has an identity already, then the identity in sdp must match
   1179      if (identity && (!msg || msg.identity !== identity)) {
   1180        throw new this._win.DOMException(
   1181          "Peer Identity mismatch, expected: " + identity,
   1182          "OperationError"
   1183        );
   1184      }
   1185      if (this._closed) {
   1186        return;
   1187      }
   1188      if (msg) {
   1189        // Set new identity and generate an event.
   1190        this._pc.peerIdentity = msg.identity;
   1191        this._resolvePeerIdentity(
   1192          Cu.cloneInto(
   1193            {
   1194              idp: this._remoteIdp.provider,
   1195              name: msg.identity,
   1196            },
   1197            this._win
   1198          )
   1199        );
   1200      }
   1201    };
   1202 
   1203    const haveValidation = validate();
   1204 
   1205    // Always eat errors on this chain
   1206    this._lastIdentityValidation = haveValidation.catch(() => {});
   1207 
   1208    // If validation fails, we have some work to do. Fork it so it cannot
   1209    // interfere with the validation chain itself, even if the catch function
   1210    // throws.
   1211    haveValidation.catch(e => {
   1212      if (this._closed) {
   1213        return;
   1214      }
   1215      this._rejectPeerIdentity(e);
   1216 
   1217      // If we don't expect a specific peer identity, failure to get a valid
   1218      // peer identity is not a terminal state, so replace the promise to
   1219      // allow another attempt.
   1220      if (!this._pc.peerIdentity) {
   1221        this._resetPeerIdentityPromise();
   1222      }
   1223    });
   1224 
   1225    if (this._closed) {
   1226      return;
   1227    }
   1228    // Only wait for IdP validation if we need identity matching
   1229    if (this._pc.peerIdentity) {
   1230      await haveValidation;
   1231    }
   1232  }
   1233 
   1234  setRemoteDescription(desc, onSucc, onErr) {
   1235    return this._auto(onSucc, onErr, () => this._setRemoteDescription(desc));
   1236  }
   1237 
   1238  _setRemoteDescription({ type, sdp }) {
   1239    if (type == "pranswer") {
   1240      throw new this._win.DOMException(
   1241        "pranswer not yet implemented",
   1242        "NotSupportedError"
   1243      );
   1244    }
   1245    this._checkClosed();
   1246    return this._chain(async () => {
   1247      try {
   1248        if (type == "offer" && this.signalingState == "have-local-offer") {
   1249          await new Promise((resolve, reject) => {
   1250            this._onSetDescriptionSuccess = resolve;
   1251            this._onSetDescriptionFailure = reject;
   1252            this._pc.setLocalDescription(
   1253              Ci.IPeerConnection.kActionRollback,
   1254              ""
   1255            );
   1256          });
   1257          await this._pc.onSetDescriptionSuccess("rollback", false);
   1258          this._updateCanTrickle();
   1259        }
   1260 
   1261        if (this._closed) {
   1262          return;
   1263        }
   1264 
   1265        this._sanityCheckSdp(sdp);
   1266 
   1267        const p = this._getPermission();
   1268 
   1269        const haveSetRemote = new Promise((resolve, reject) => {
   1270          this._onSetDescriptionSuccess = resolve;
   1271          this._onSetDescriptionFailure = reject;
   1272          this._pc.setRemoteDescription(this._actions[type], sdp);
   1273        });
   1274 
   1275        if (type != "rollback") {
   1276          // Do setRemoteDescription and identity validation in parallel
   1277          await this._validateIdentity(sdp);
   1278        }
   1279        await p;
   1280        await haveSetRemote;
   1281      } catch (e) {
   1282        this._pc.onSetDescriptionError();
   1283        throw e;
   1284      }
   1285 
   1286      await this._pc.onSetDescriptionSuccess(type, true);
   1287      this._updateCanTrickle();
   1288    });
   1289  }
   1290 
   1291  setIdentityProvider(provider, { protocol, usernameHint, peerIdentity } = {}) {
   1292    this._checkClosed();
   1293    peerIdentity = peerIdentity || this._pc.peerIdentity;
   1294    this._localIdp.setIdentityProvider(
   1295      provider,
   1296      protocol,
   1297      usernameHint,
   1298      peerIdentity
   1299    );
   1300  }
   1301 
   1302  async _getIdentityAssertion() {
   1303    await this._certificateReady;
   1304    return this._localIdp.getIdentityAssertion(
   1305      this._pc.fingerprint,
   1306      this._documentPrincipal.origin
   1307    );
   1308  }
   1309 
   1310  getIdentityAssertion() {
   1311    this._checkClosed();
   1312    return this._win.Promise.resolve(
   1313      this._chain(() => this._getIdentityAssertion())
   1314    );
   1315  }
   1316 
   1317  get canTrickleIceCandidates() {
   1318    return this._canTrickle;
   1319  }
   1320 
   1321  _updateCanTrickle() {
   1322    let containsTrickle = section => {
   1323      let lines = section.toLowerCase().split(/(?:\r\n?|\n)/);
   1324      return lines.some(line => {
   1325        let prefix = "a=ice-options:";
   1326        if (line.substring(0, prefix.length) !== prefix) {
   1327          return false;
   1328        }
   1329        let tokens = line.substring(prefix.length).split(" ");
   1330        return tokens.some(x => x === "trickle");
   1331      });
   1332    };
   1333 
   1334    let desc = null;
   1335    try {
   1336      // The getter for remoteDescription can throw if the pc is closed.
   1337      desc = this.remoteDescription;
   1338    } catch (e) {}
   1339    if (!desc) {
   1340      this._canTrickle = null;
   1341      return;
   1342    }
   1343 
   1344    let sections = desc.sdp.split(/(?:\r\n?|\n)m=/);
   1345    let topSection = sections.shift();
   1346    this._canTrickle =
   1347      containsTrickle(topSection) || sections.every(containsTrickle);
   1348  }
   1349 
   1350  addIceCandidate(cand, onSucc, onErr) {
   1351    if (
   1352      cand.candidate != "" &&
   1353      cand.sdpMid == null &&
   1354      cand.sdpMLineIndex == null
   1355    ) {
   1356      throw new this._win.TypeError(
   1357        "Cannot add a candidate without specifying either sdpMid or sdpMLineIndex"
   1358      );
   1359    }
   1360    return this._auto(onSucc, onErr, () => this._addIceCandidate(cand));
   1361  }
   1362 
   1363  async _addIceCandidate({
   1364    candidate,
   1365    sdpMid,
   1366    sdpMLineIndex,
   1367    usernameFragment,
   1368  }) {
   1369    this._checkClosed();
   1370    return this._chain(async () => {
   1371      if (
   1372        !this._pc.pendingRemoteDescription.length &&
   1373        !this._pc.currentRemoteDescription.length
   1374      ) {
   1375        throw new this._win.DOMException(
   1376          "No remoteDescription.",
   1377          "InvalidStateError"
   1378        );
   1379      }
   1380      return new Promise((resolve, reject) => {
   1381        this._onAddIceCandidateSuccess = resolve;
   1382        this._onAddIceCandidateError = reject;
   1383        this._pc.addIceCandidate(
   1384          candidate,
   1385          sdpMid || "",
   1386          usernameFragment || "",
   1387          sdpMLineIndex
   1388        );
   1389      });
   1390    });
   1391  }
   1392 
   1393  restartIce() {
   1394    this._pc.restartIce();
   1395  }
   1396 
   1397  addStream(stream) {
   1398    stream.getTracks().forEach(track => this.addTrack(track, stream));
   1399  }
   1400 
   1401  addTrack(track, ...streams) {
   1402    this._checkClosed();
   1403 
   1404    if (
   1405      this.getTransceivers().some(
   1406        transceiver => transceiver.sender.track == track
   1407      )
   1408    ) {
   1409      throw new this._win.DOMException(
   1410        "This track is already set on a sender.",
   1411        "InvalidAccessError"
   1412      );
   1413    }
   1414 
   1415    let transceiver = this.getTransceivers().find(transceiver => {
   1416      return (
   1417        transceiver.sender.track == null &&
   1418        transceiver.getKind() == track.kind &&
   1419        !transceiver.stopped &&
   1420        !transceiver.hasBeenUsedToSend()
   1421      );
   1422    });
   1423 
   1424    if (transceiver) {
   1425      transceiver.sender.setTrack(track);
   1426      transceiver.sender.setStreamsImpl(...streams);
   1427      if (transceiver.direction == "recvonly") {
   1428        transceiver.setDirectionInternal("sendrecv");
   1429      } else if (transceiver.direction == "inactive") {
   1430        transceiver.setDirectionInternal("sendonly");
   1431      }
   1432    } else {
   1433      transceiver = this._addTransceiverNoEvents(
   1434        track,
   1435        {
   1436          streams,
   1437          direction: "sendrecv",
   1438        },
   1439        true
   1440      );
   1441    }
   1442 
   1443    this.updateNegotiationNeeded();
   1444    return transceiver.sender;
   1445  }
   1446 
   1447  removeTrack(sender) {
   1448    this._checkClosed();
   1449 
   1450    if (!this._pc.createdSender(sender)) {
   1451      throw new this._win.DOMException(
   1452        "This sender was not created by this PeerConnection",
   1453        "InvalidAccessError"
   1454      );
   1455    }
   1456 
   1457    let transceiver = this.getTransceivers().find(
   1458      transceiver => !transceiver.stopped && transceiver.sender == sender
   1459    );
   1460 
   1461    // If the transceiver was removed due to rollback, let it slide.
   1462    if (!transceiver || !sender.track) {
   1463      return;
   1464    }
   1465 
   1466    sender.setTrack(null);
   1467    if (transceiver.direction == "sendrecv") {
   1468      transceiver.setDirectionInternal("recvonly");
   1469    } else if (transceiver.direction == "sendonly") {
   1470      transceiver.setDirectionInternal("inactive");
   1471    }
   1472 
   1473    this.updateNegotiationNeeded();
   1474  }
   1475 
   1476  _addTransceiverNoEvents(sendTrackOrKind, init, addTrackMagic) {
   1477    let sendTrack = null;
   1478    let kind;
   1479    if (typeof sendTrackOrKind == "string") {
   1480      kind = sendTrackOrKind;
   1481      switch (kind) {
   1482        case "audio":
   1483        case "video":
   1484          break;
   1485        default:
   1486          throw new this._win.TypeError("Invalid media kind");
   1487      }
   1488    } else {
   1489      sendTrack = sendTrackOrKind;
   1490      kind = sendTrack.kind;
   1491    }
   1492 
   1493    try {
   1494      return this._pc.addTransceiver(init, kind, sendTrack, addTrackMagic);
   1495    } catch (e) {
   1496      // Exceptions thrown by c++ code do not propagate. In most cases, that's
   1497      // fine because we're using Promises, which can be copied. But this is
   1498      // not promise-based, so we have to do this sketchy stuff.
   1499      const holder = new StructuredCloneHolder(
   1500        "",
   1501        "",
   1502        new ClonedErrorHolder(e)
   1503      );
   1504      throw holder.deserialize(this._win);
   1505    }
   1506  }
   1507 
   1508  addTransceiver(sendTrackOrKind, init) {
   1509    this._checkClosed();
   1510    let transceiver = this._addTransceiverNoEvents(sendTrackOrKind, init);
   1511    this.updateNegotiationNeeded();
   1512    return transceiver;
   1513  }
   1514 
   1515  updateNegotiationNeeded() {
   1516    this._pc.updateNegotiationNeeded();
   1517  }
   1518 
   1519  close() {
   1520    if (this._closed) {
   1521      return;
   1522    }
   1523    this._closed = true;
   1524    this.changeIceConnectionState("closed");
   1525    if (this._localIdp) {
   1526      this._localIdp.close();
   1527    }
   1528    if (this._remoteIdp) {
   1529      this._remoteIdp.close();
   1530    }
   1531    this._pc.close();
   1532    this._suppressEvents = true;
   1533  }
   1534 
   1535  getLocalStreams() {
   1536    this._checkClosed();
   1537    let localStreams = new Set();
   1538    this.getTransceivers().forEach(transceiver => {
   1539      transceiver.sender.getStreams().forEach(stream => {
   1540        localStreams.add(stream);
   1541      });
   1542    });
   1543    return [...localStreams.values()];
   1544  }
   1545 
   1546  getRemoteStreams() {
   1547    this._checkClosed();
   1548    return this._pc.getRemoteStreams();
   1549  }
   1550 
   1551  getSenders() {
   1552    return this.getTransceivers()
   1553      .filter(transceiver => !transceiver.stopped)
   1554      .map(transceiver => transceiver.sender);
   1555  }
   1556 
   1557  getReceivers() {
   1558    return this.getTransceivers()
   1559      .filter(transceiver => !transceiver.stopped)
   1560      .map(transceiver => transceiver.receiver);
   1561  }
   1562 
   1563  mozSetPacketCallback(callback) {
   1564    this._onPacket = callback;
   1565  }
   1566 
   1567  mozEnablePacketDump(level, type, sending) {
   1568    this._pc.enablePacketDump(level, type, sending);
   1569  }
   1570 
   1571  mozDisablePacketDump(level, type, sending) {
   1572    this._pc.disablePacketDump(level, type, sending);
   1573  }
   1574 
   1575  getTransceivers() {
   1576    return this._pc.getTransceivers();
   1577  }
   1578 
   1579  get localDescription() {
   1580    return this.pendingLocalDescription || this.currentLocalDescription;
   1581  }
   1582 
   1583  cacheDescription(name, type, sdp) {
   1584    if (
   1585      !this[name] ||
   1586      this[name].type != type ||
   1587      this[name].sdp != sdp ||
   1588      this._legacyPref
   1589    ) {
   1590      this[name] = sdp.length
   1591        ? new this._win.RTCSessionDescription({ type, sdp })
   1592        : null;
   1593    }
   1594    return this[name];
   1595  }
   1596 
   1597  get currentLocalDescription() {
   1598    this._checkClosed();
   1599    return this.cacheDescription(
   1600      "_currentLocalDescription",
   1601      this._pc.currentOfferer ? "offer" : "answer",
   1602      this._pc.currentLocalDescription
   1603    );
   1604  }
   1605 
   1606  get pendingLocalDescription() {
   1607    this._checkClosed();
   1608    return this.cacheDescription(
   1609      "_pendingLocalDescription",
   1610      this._pc.pendingOfferer ? "offer" : "answer",
   1611      this._pc.pendingLocalDescription
   1612    );
   1613  }
   1614 
   1615  get remoteDescription() {
   1616    return this.pendingRemoteDescription || this.currentRemoteDescription;
   1617  }
   1618 
   1619  get currentRemoteDescription() {
   1620    this._checkClosed();
   1621    return this.cacheDescription(
   1622      "_currentRemoteDescription",
   1623      this._pc.currentOfferer ? "answer" : "offer",
   1624      this._pc.currentRemoteDescription
   1625    );
   1626  }
   1627 
   1628  get pendingRemoteDescription() {
   1629    this._checkClosed();
   1630    return this.cacheDescription(
   1631      "_pendingRemoteDescription",
   1632      this._pc.pendingOfferer ? "answer" : "offer",
   1633      this._pc.pendingRemoteDescription
   1634    );
   1635  }
   1636 
   1637  get peerIdentity() {
   1638    return this._peerIdentity;
   1639  }
   1640  get idpLoginUrl() {
   1641    return this._localIdp.idpLoginUrl;
   1642  }
   1643  get id() {
   1644    return this._pc.id;
   1645  }
   1646  set id(s) {
   1647    this._pc.id = s;
   1648  }
   1649  get iceGatheringState() {
   1650    return this._pc.iceGatheringState;
   1651  }
   1652  get iceConnectionState() {
   1653    return this._iceConnectionState;
   1654  }
   1655  get connectionState() {
   1656    return this._pc.connectionState;
   1657  }
   1658 
   1659  get signalingState() {
   1660    // checking for our local pc closed indication
   1661    // before invoking the pc methods.
   1662    if (this._closed) {
   1663      return "closed";
   1664    }
   1665    return this._pc.signalingState;
   1666  }
   1667 
   1668  handleIceGatheringStateChange() {
   1669    _globalPCList.notifyLifecycleObservers(this, "icegatheringstatechange");
   1670    this.dispatchEvent(new this._win.Event("icegatheringstatechange"));
   1671    if (this.iceGatheringState === "complete") {
   1672      this.dispatchEvent(
   1673        new this._win.RTCPeerConnectionIceEvent("icecandidate", {
   1674          candidate: null,
   1675        })
   1676      );
   1677    }
   1678  }
   1679 
   1680  changeIceConnectionState(state) {
   1681    if (state != this._iceConnectionState) {
   1682      this._iceConnectionState = state;
   1683      _globalPCList.notifyLifecycleObservers(this, "iceconnectionstatechange");
   1684      if (!this._closed) {
   1685        this.dispatchEvent(new this._win.Event("iceconnectionstatechange"));
   1686      }
   1687    }
   1688  }
   1689 
   1690  getStats(selector, onSucc, onErr) {
   1691    if (selector !== null) {
   1692      let matchingSenders = this.getSenders().filter(s => s.track === selector);
   1693      let matchingReceivers = this.getReceivers().filter(
   1694        r => r.track === selector
   1695      );
   1696 
   1697      if (matchingSenders.length + matchingReceivers.length != 1) {
   1698        throw new this._win.DOMException(
   1699          "track must be associated with a unique sender or receiver, but " +
   1700            " is associated with " +
   1701            matchingSenders.length +
   1702            " senders and " +
   1703            matchingReceivers.length +
   1704            " receivers.",
   1705          "InvalidAccessError"
   1706        );
   1707      }
   1708    }
   1709 
   1710    return this._auto(onSucc, onErr, () => this._pc.getStats(selector));
   1711  }
   1712 
   1713  get sctp() {
   1714    return this._pc.sctp;
   1715  }
   1716 
   1717  createDataChannel(
   1718    label,
   1719    {
   1720      maxRetransmits,
   1721      ordered,
   1722      negotiated,
   1723      id = null,
   1724      maxRetransmitTime,
   1725      maxPacketLifeTime,
   1726      protocol,
   1727    } = {}
   1728  ) {
   1729    this._checkClosed();
   1730 
   1731    if (maxPacketLifeTime === undefined) {
   1732      maxPacketLifeTime = maxRetransmitTime;
   1733    }
   1734 
   1735    if (maxRetransmitTime !== undefined) {
   1736      this.logWarning(
   1737        "Use maxPacketLifeTime instead of deprecated maxRetransmitTime which will stop working soon in createDataChannel!"
   1738      );
   1739    }
   1740 
   1741    if (protocol.length > 32767) {
   1742      // At least 65536/2 UTF-16 characters. UTF-8 might be too long.
   1743      // Spec says to check how long |protocol| and |label| are in _bytes_. This
   1744      // is a little ambiguous. For now, examine the length of the utf-8 encoding.
   1745      const byteCounter = new TextEncoder();
   1746 
   1747      if (byteCounter.encode(protocol).length > 65535) {
   1748        throw new this._win.TypeError(
   1749          "protocol cannot be longer than 65535 bytes"
   1750        );
   1751      }
   1752    }
   1753 
   1754    if (label.length > 32767) {
   1755      const byteCounter = new TextEncoder();
   1756      if (byteCounter.encode(label).length > 65535) {
   1757        throw new this._win.TypeError(
   1758          "label cannot be longer than 65535 bytes"
   1759        );
   1760      }
   1761    }
   1762 
   1763    if (!negotiated) {
   1764      id = null;
   1765    } else if (id === null) {
   1766      throw new this._win.TypeError("id is required when negotiated is true");
   1767    }
   1768    if (maxPacketLifeTime !== undefined && maxRetransmits !== undefined) {
   1769      throw new this._win.TypeError(
   1770        "Both maxPacketLifeTime and maxRetransmits cannot be provided"
   1771      );
   1772    }
   1773    if (id == 65535) {
   1774      throw new this._win.TypeError("id cannot be 65535");
   1775    }
   1776    // Must determine the type where we still know if entries are undefined.
   1777    let type;
   1778    if (maxPacketLifeTime !== undefined) {
   1779      type = Ci.IPeerConnection.kDataChannelPartialReliableTimed;
   1780    } else if (maxRetransmits !== undefined) {
   1781      type = Ci.IPeerConnection.kDataChannelPartialReliableRexmit;
   1782    } else {
   1783      type = Ci.IPeerConnection.kDataChannelReliable;
   1784    }
   1785    // Synchronous since it doesn't block.
   1786    let dataChannel;
   1787    try {
   1788      dataChannel = this._pc.createDataChannel(
   1789        label,
   1790        protocol,
   1791        type,
   1792        ordered,
   1793        maxPacketLifeTime,
   1794        maxRetransmits,
   1795        negotiated,
   1796        id
   1797      );
   1798    } catch (e) {
   1799      if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
   1800        throw e;
   1801      }
   1802 
   1803      const msg =
   1804        id === null ? "No available id could be generated" : "Id is in use";
   1805      throw new this._win.DOMException(msg, "OperationError");
   1806    }
   1807 
   1808    // Spec says to only do this if this is the first DataChannel created,
   1809    // but the c++ code that does the "is negotiation needed" checking will
   1810    // only ever return true on the first one.
   1811    this.updateNegotiationNeeded();
   1812 
   1813    return dataChannel;
   1814  }
   1815 }
   1816 
   1817 setupPrototype(RTCPeerConnection, {
   1818  classID: PC_CID,
   1819  contractID: PC_CONTRACT,
   1820  QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
   1821  _actions: {
   1822    offer: Ci.IPeerConnection.kActionOffer,
   1823    answer: Ci.IPeerConnection.kActionAnswer,
   1824    pranswer: Ci.IPeerConnection.kActionPRAnswer,
   1825    rollback: Ci.IPeerConnection.kActionRollback,
   1826  },
   1827 });
   1828 
   1829 // This is a separate class because we don't want to expose it to DOM.
   1830 
   1831 export class PeerConnectionObserver {
   1832  init(win) {
   1833    this._win = win;
   1834  }
   1835 
   1836  __init(dompc) {
   1837    this._dompc = dompc._innerObject;
   1838  }
   1839 
   1840  newError({ message, name }) {
   1841    return new this._dompc._win.DOMException(message, name);
   1842  }
   1843 
   1844  dispatchEvent(event) {
   1845    this._dompc.dispatchEvent(event);
   1846  }
   1847 
   1848  onCreateOfferSuccess(sdp) {
   1849    this._dompc._onCreateOfferSuccess(sdp);
   1850  }
   1851 
   1852  onCreateOfferError(error) {
   1853    this._dompc._onCreateOfferFailure(this.newError(error));
   1854  }
   1855 
   1856  onCreateAnswerSuccess(sdp) {
   1857    this._dompc._onCreateAnswerSuccess(sdp);
   1858  }
   1859 
   1860  onCreateAnswerError(error) {
   1861    this._dompc._onCreateAnswerFailure(this.newError(error));
   1862  }
   1863 
   1864  onSetDescriptionSuccess() {
   1865    this._dompc._onSetDescriptionSuccess();
   1866  }
   1867 
   1868  onSetDescriptionError(error) {
   1869    this._dompc._onSetDescriptionFailure(this.newError(error));
   1870  }
   1871 
   1872  onAddIceCandidateSuccess() {
   1873    this._dompc._onAddIceCandidateSuccess();
   1874  }
   1875 
   1876  onAddIceCandidateError(error) {
   1877    this._dompc._onAddIceCandidateError(this.newError(error));
   1878  }
   1879 
   1880  onIceCandidate(sdpMLineIndex, sdpMid, candidate, usernameFragment) {
   1881    let win = this._dompc._win;
   1882    if (candidate || sdpMid || usernameFragment) {
   1883      if (candidate.includes(" typ relay ")) {
   1884        this._dompc._iceGatheredRelayCandidates = true;
   1885      }
   1886      candidate = new win.RTCIceCandidate({
   1887        candidate,
   1888        sdpMid,
   1889        sdpMLineIndex,
   1890        usernameFragment,
   1891      });
   1892    }
   1893    this.dispatchEvent(
   1894      new win.RTCPeerConnectionIceEvent("icecandidate", { candidate })
   1895    );
   1896  }
   1897 
   1898  // This method is primarily responsible for updating iceConnectionState.
   1899  // This state is defined in the WebRTC specification as follows:
   1900  //
   1901  // iceConnectionState:
   1902  // -------------------
   1903  //   new           Any of the RTCIceTransports are in the new state and none
   1904  //                 of them are in the checking, failed or disconnected state.
   1905  //
   1906  //   checking      Any of the RTCIceTransports are in the checking state and
   1907  //                 none of them are in the failed or disconnected state.
   1908  //
   1909  //   connected     All RTCIceTransports are in the connected, completed or
   1910  //                 closed state and at least one of them is in the connected
   1911  //                 state.
   1912  //
   1913  //   completed     All RTCIceTransports are in the completed or closed state
   1914  //                 and at least one of them is in the completed state.
   1915  //
   1916  //   failed        Any of the RTCIceTransports are in the failed state.
   1917  //
   1918  //   disconnected  Any of the RTCIceTransports are in the disconnected state
   1919  //                 and none of them are in the failed state.
   1920  //
   1921  //   closed        All of the RTCIceTransports are in the closed state.
   1922 
   1923  handleIceConnectionStateChange(iceConnectionState) {
   1924    let pc = this._dompc;
   1925    if (pc.iceConnectionState === iceConnectionState) {
   1926      return;
   1927    }
   1928 
   1929    if (iceConnectionState === "failed") {
   1930      if (!pc._hasStunServer) {
   1931        pc.logError(
   1932          "ICE failed, add a STUN server and see about:webrtc for more details"
   1933        );
   1934      } else if (!pc._hasTurnServer) {
   1935        pc.logError(
   1936          "ICE failed, add a TURN server and see about:webrtc for more details"
   1937        );
   1938      } else if (pc._hasTurnServer && !pc._iceGatheredRelayCandidates) {
   1939        pc.logError(
   1940          "ICE failed, your TURN server appears to be broken, see about:webrtc for more details"
   1941        );
   1942      } else {
   1943        pc.logError("ICE failed, see about:webrtc for more details");
   1944      }
   1945    }
   1946 
   1947    pc.changeIceConnectionState(iceConnectionState);
   1948  }
   1949 
   1950  onStateChange(state) {
   1951    if (!this._dompc) {
   1952      return;
   1953    }
   1954 
   1955    if (state == "SignalingState") {
   1956      this.dispatchEvent(new this._win.Event("signalingstatechange"));
   1957      return;
   1958    }
   1959 
   1960    if (!this._dompc._pc) {
   1961      return;
   1962    }
   1963 
   1964    switch (state) {
   1965      case "IceConnectionState":
   1966        this.handleIceConnectionStateChange(this._dompc._pc.iceConnectionState);
   1967        break;
   1968 
   1969      case "IceGatheringState":
   1970        this._dompc.handleIceGatheringStateChange();
   1971        break;
   1972 
   1973      case "ConnectionState":
   1974        _globalPCList.notifyLifecycleObservers(this, "connectionstatechange");
   1975        this.dispatchEvent(new this._win.Event("connectionstatechange"));
   1976        break;
   1977 
   1978      default:
   1979        this._dompc.logWarning("Unhandled state type: " + state);
   1980        break;
   1981    }
   1982  }
   1983 
   1984  onTransceiverNeeded(kind, transceiverImpl) {
   1985    this._dompc._onTransceiverNeeded(kind, transceiverImpl);
   1986  }
   1987 
   1988  notifyDataChannel(channel) {
   1989    this.dispatchEvent(
   1990      new this._dompc._win.RTCDataChannelEvent("datachannel", { channel })
   1991    );
   1992  }
   1993 
   1994  fireTrackEvent(receiver, streams) {
   1995    const pc = this._dompc;
   1996    const transceiver = pc.getTransceivers().find(t => t.receiver == receiver);
   1997    if (!transceiver) {
   1998      return;
   1999    }
   2000    const track = receiver.track;
   2001    this.dispatchEvent(
   2002      new this._win.RTCTrackEvent("track", {
   2003        transceiver,
   2004        receiver,
   2005        track,
   2006        streams,
   2007      })
   2008    );
   2009    // Fire legacy event as well for a little bit.
   2010    this.dispatchEvent(
   2011      new this._win.MediaStreamTrackEvent("addtrack", { track })
   2012    );
   2013  }
   2014 
   2015  fireStreamEvent(stream) {
   2016    const ev = new this._win.MediaStreamEvent("addstream", { stream });
   2017    this.dispatchEvent(ev);
   2018  }
   2019 
   2020  fireNegotiationNeededEvent() {
   2021    this.dispatchEvent(new this._win.Event("negotiationneeded"));
   2022  }
   2023 
   2024  onPacket(level, type, sending, packet) {
   2025    var pc = this._dompc;
   2026    if (pc._onPacket) {
   2027      pc._onPacket(level, type, sending, packet);
   2028    }
   2029  }
   2030 }
   2031 
   2032 setupPrototype(PeerConnectionObserver, {
   2033  classID: PC_OBS_CID,
   2034  contractID: PC_OBS_CONTRACT,
   2035  QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
   2036 });
   2037 
   2038 export class RTCPeerConnectionStatic {
   2039  init(win) {
   2040    this._winID = win.windowGlobalChild.innerWindowId;
   2041  }
   2042 
   2043  registerPeerConnectionLifecycleCallback(cb) {
   2044    _globalPCList._registerPeerConnectionLifecycleCallback(this._winID, cb);
   2045  }
   2046 }
   2047 
   2048 setupPrototype(RTCPeerConnectionStatic, {
   2049  classID: PC_STATIC_CID,
   2050  contractID: PC_STATIC_CONTRACT,
   2051  QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
   2052 });
   2053 
   2054 export class CreateOfferRequest {
   2055  constructor(windowID, innerWindowID, callID, isSecure) {
   2056    Object.assign(this, { windowID, innerWindowID, callID, isSecure });
   2057  }
   2058 }
   2059 
   2060 setupPrototype(CreateOfferRequest, {
   2061  classID: PC_COREQUEST_CID,
   2062  contractID: PC_COREQUEST_CONTRACT,
   2063  QueryInterface: ChromeUtils.generateQI([]),
   2064 });