Bug 1393306 - Add deprecation warning in 57 for removal of stat.isRemote in 58. draft
authorJan-Ivar Bruaroey <jib@mozilla.com>
Wed, 23 Aug 2017 17:13:27 -0400
changeset 657840 c265c97c975c05d6fb799d8b216a876224099afb
parent 657648 d1292636950a4ac1bb818cf05b113e3fcd6babe1
child 657841 dcd2a932b0000a8488e6e0b947bc7729a85ced44
push id77637
push userjbruaroey@mozilla.com
push dateSat, 02 Sep 2017 02:30:27 +0000
bugs1393306
milestone57.0a1
Bug 1393306 - Add deprecation warning in 57 for removal of stat.isRemote in 58. MozReview-Commit-ID: 7wKBg3KQxjo
dom/media/PeerConnection.js
dom/webidl/RTCStatsReport.webidl
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -17,28 +17,30 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/AppConstants.jsm");
 
 const PC_CONTRACT = "@mozilla.org/dom/peerconnection;1";
 const PC_OBS_CONTRACT = "@mozilla.org/dom/peerconnectionobserver;1";
 const PC_ICE_CONTRACT = "@mozilla.org/dom/rtcicecandidate;1";
 const PC_SESSION_CONTRACT = "@mozilla.org/dom/rtcsessiondescription;1";
 const PC_MANAGER_CONTRACT = "@mozilla.org/dom/peerconnectionmanager;1";
 const PC_STATS_CONTRACT = "@mozilla.org/dom/rtcstatsreport;1";
+const PC_STATS_WARN_CONTRACT = "@mozilla.org/dom/rtcstatsdeprecationwarningentry;1";
 const PC_STATIC_CONTRACT = "@mozilla.org/dom/peerconnectionstatic;1";
 const PC_SENDER_CONTRACT = "@mozilla.org/dom/rtpsender;1";
 const PC_RECEIVER_CONTRACT = "@mozilla.org/dom/rtpreceiver;1";
 const PC_COREQUEST_CONTRACT = "@mozilla.org/dom/createofferrequest;1";
 const PC_DTMF_SENDER_CONTRACT = "@mozilla.org/dom/rtcdtmfsender;1";
 
 const PC_CID = Components.ID("{bdc2e533-b308-4708-ac8e-a8bfade6d851}");
 const PC_OBS_CID = Components.ID("{d1748d4c-7f6a-4dc5-add6-d55b7678537e}");
 const PC_ICE_CID = Components.ID("{02b9970c-433d-4cc2-923d-f7028ac66073}");
 const PC_SESSION_CID = Components.ID("{1775081b-b62d-4954-8ffe-a067bbf508a7}");
 const PC_MANAGER_CID = Components.ID("{7293e901-2be3-4c02-b4bd-cbef6fc24f78}");
 const PC_STATS_CID = Components.ID("{7fe6e18b-0da3-4056-bf3b-440ef3809e06}");
+const PC_STATS_WARN_CID = Components.ID("{7b78e3f4-4720-4c14-b02d-5421cbeee134}");
 const PC_STATIC_CID = Components.ID("{0fb47c47-a205-4583-a9fc-cbadf8c95880}");
 const PC_SENDER_CID = Components.ID("{4fff5d46-d827-4cd4-a970-8fd53977440e}");
 const PC_RECEIVER_CID = Components.ID("{d974b814-8fde-411c-8c45-b86791b81030}");
 const PC_COREQUEST_CID = Components.ID("{74b2122d-65a8-4824-aa9e-3d664cb75dc2}");
 const PC_DTMF_SENDER_CID = Components.ID("{3610C242-654E-11E6-8EC0-6D1BE389A607}");
 
 function logMsg(msg, file, line, flag, winID) {
   let scriptErrorClass = Cc["@mozilla.org/scripterror;1"];
@@ -280,49 +282,96 @@ class RTCSessionDescription {
 setupPrototype(RTCSessionDescription, {
   classID: PC_SESSION_CID,
   contractID: PC_SESSION_CONTRACT,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
                                          Ci.nsIDOMGlobalPropertyInitializer])
 });
 
 class RTCStatsReport {
-  constructor(win, dict) {
-    this._win = win;
+  constructor(pc, dict) {
+    this._pc = pc;
+    this._win = pc._win;
     this._pcid = dict.pcid;
     this._report = convertToRTCStatsReport(dict);
   }
 
   setInternal(aKey, aObj) {
     return this.__DOM_IMPL__.__set(aKey, aObj);
   }
 
+  warnGet(id) {
+    let stat = this.__DOM_IMPL__.nativeGet(id);
+    if (stat && stat._isRemote &&
+        this._pc._warnDeprecatedStatsRemoteAccessNullable.warn) {
+      // Proper stats.get(localStat.remoteId) access detected. Null warning.
+      this._pc._warnDeprecatedStatsRemoteAccessNullable.warn = null;
+    }
+    return stat;
+  }
+
   // TODO: Remove legacy API eventually
   // see Bug 1328194
   //
   // Since maplike is recent, we still also make the stats available as legacy
   // enumerable read-only properties directly on our content-facing object.
+  //
+  // In addition, we warn on iteration over isRemote:true entries, which is set
+  // to break in Firefox 58.
+  //
   // Must be called after our webidl sandwich is made.
 
-  makeStatsPublic(warnNullable, isLegacy) {
+  makeStatsPublic(warnCallbackNullable, warnRemoteNullable, isLegacy) {
     let legacyProps = {};
     for (let key in this._report) {
       let internal = Cu.cloneInto(this._report[key], this._win);
       if (isLegacy) {
         internal.type = this._specToLegacyFieldMapping[internal.type] || internal.type;
+      } else if (warnRemoteNullable.warn) {
+        let entry = new RTCStatsDeprecationWarningEntry();
+        entry = this._win.RTCStatsDeprecationWarningEntry._create(this._win, entry);
+        let warnProps = {}, stat = internal;
+        for (let key in stat) {
+          warnProps[key] = {
+            enumerable: true, configurable: false,
+            get: Cu.exportFunction(function() {
+              // Warn on remote stat access other than the recommended approach of
+              //
+              // for (let stat of stats.values()) {
+              //   switch (stat.type) {
+              //     case "outbound-rtp": {
+              //       if (stat.isRemote) continue;
+              //       let rtcp = stats.get(stat.remoteId);
+              //
+              if (warnRemoteNullable.warn && stat.isRemote &&
+                  key != "type" &&
+                  key != "isRemote") {
+                // id is first prop, a sign of JSON.stringify(), cancel warnings.
+                if (key != "id") {
+                  warnRemoteNullable.warn();
+                }
+                warnRemoteNullable.warn = null;
+              }
+              return stat[key];
+            }, entry)
+          };
+        }
+        Object.defineProperties(entry.wrappedJSObject, warnProps);
+        entry._isRemote = stat.isRemote;
+        internal = entry;
       }
       this.setInternal(key, internal);
       let value = Cu.cloneInto(this._report[key], this._win);
       value.type = this._specToLegacyFieldMapping[value.type] || value.type;
       legacyProps[key] = {
         enumerable: true, configurable: false,
         get: Cu.exportFunction(function() {
-          if (warnNullable.warn) {
-            warnNullable.warn();
-            warnNullable.warn = null;
+          if (warnCallbackNullable.warn) {
+            warnCallbackNullable.warn();
+            warnCallbackNullable.warn = null;
           }
           return value;
         }, this.__DOM_IMPL__.wrappedJSObject)
       };
     }
     Object.defineProperties(this.__DOM_IMPL__.wrappedJSObject, legacyProps);
   }
 
@@ -340,16 +389,26 @@ setupPrototype(RTCStatsReport, {
         "inbound-rtp": "inboundrtp",
         "outbound-rtp": "outboundrtp",
         "candidate-pair": "candidatepair",
         "local-candidate": "localcandidate",
         "remote-candidate": "remotecandidate"
   }
 });
 
+
+class RTCStatsDeprecationWarningEntry {
+}
+setupPrototype(RTCStatsDeprecationWarningEntry, {
+  classID: PC_STATS_WARN_CID,
+  contractID: PC_STATS_WARN_CONTRACT,
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
+});
+
+
 class RTCPeerConnection {
   constructor() {
     this._senders = [];
     this._receivers = [];
 
     this._pc = null;
     this._closed = false;
 
@@ -443,16 +502,20 @@ class RTCPeerConnection {
     this._warnDeprecatedStatsAccessNullable = { warn: () =>
       this.logWarning("non-maplike pc.getStats access is deprecated, and will be removed in the near future! " +
                       "See http://w3c.github.io/webrtc-pc/#getstats-example for usage.") };
 
     this._warnDeprecatedStatsCallbacksNullable = { warn: () =>
       this.logWarning("Callback-based pc.getStats is deprecated, and will be removed in the near future! Use promise-version! " +
                       "See http://w3c.github.io/webrtc-pc/#getstats-example for usage.") };
 
+    this._warnDeprecatedStatsRemoteAccessNullable = { warn: () =>
+      this.logWarning("Detected soon-to-break getStats() use! stat.isRemote goes away in Firefox 58, but won't warn there!\
+ - See https://blog.mozilla.org/webrtc/getstats-firefox-58/") };
+
     // Add a reference to the PeerConnection to global list (before init).
     _globalPCList.addPC(this);
 
     this._impl.initialize(this._observer, this._win, rtcConfig,
                           Services.tm.currentThread);
 
     this._certificateReady = this._initCertificate(rtcConfig.certificates);
     this._initIdp();
@@ -1541,21 +1604,25 @@ class PeerConnectionObserver {
       default:
         this._dompc.logWarning("Unhandled state type: " + state);
         break;
     }
   }
 
   onGetStatsSuccess(dict) {
     let pc = this._dompc;
-    let chromeobj = new RTCStatsReport(pc._win, dict);
+    let chromeobj = new RTCStatsReport(pc, dict);
     let webidlobj = pc._win.RTCStatsReport._create(pc._win, chromeobj);
-    chromeobj.makeStatsPublic(pc._warnDeprecatedStatsCallbacksNullable &&
-                              pc._warnDeprecatedStatsAccessNullable,
+    chromeobj.makeStatsPublic(pc._warnDeprecatedStatsCallbacksNullable,
+                              pc._warnDeprecatedStatsRemoteAccessNullable,
                               pc._onGetStatsIsLegacy);
+    webidlobj.nativeGet = webidlobj.get;
+    if (pc._warnDeprecatedStatsRemoteAccessNullable.warn) {
+      webidlobj.wrappedJSObject.get = webidlobj.wrappedJSObject.warnGet;
+    }
     pc._onGetStatsSuccess(webidlobj);
   }
 
   onGetStatsError(code, message) {
     this._dompc._onGetStatsFailure(this.newError(message, code));
   }
 
   onAddStream(stream) {
--- a/dom/webidl/RTCStatsReport.webidl
+++ b/dom/webidl/RTCStatsReport.webidl
@@ -184,16 +184,24 @@ dictionary RTCStatsReportInternal {
   DOMString                               remoteSdp;
   DOMHighResTimeStamp                     timestamp;
   unsigned long                           iceRestarts;
   unsigned long                           iceRollbacks;
   boolean                                 closed; // Is the PC now closed
 };
 
 [Pref="media.peerconnection.enabled",
-// TODO: Use MapClass here once it's available (Bug 928114)
-// MapClass(DOMString, object)
  JSImplementation="@mozilla.org/dom/rtcstatsreport;1"]
 interface RTCStatsReport {
   readonly maplike<DOMString, object>;
   [ChromeOnly]
   readonly attribute DOMString mozPcid;
+
+  // Special internal method to help intercept Map.get() to detect proper
+  // stats.get(otherStat.remoteId) use to turn off deprecation warnings.
+  any warnGet(DOMString id);
 };
+
+[Pref="media.peerconnection.enabled",
+ ChromeOnly,
+ JSImplementation="@mozilla.org/dom/rtcstatsdeprecationwarningentry;1"]
+interface RTCStatsDeprecationWarningEntry {
+};