Bug 823512: Part 2: Update PeerConnection to spec. states + DataChannel. r=bz,rjesup,ekr
authorJan-Ivar Bruaroey <jib@mozilla.com>
Thu, 16 May 2013 19:40:23 -0400
changeset 143720 0221d61cef72d22159bc023be50000feea0b2a03
parent 143719 7e02515f7a1d337826610416b4db84bf665f913c
child 143721 b115d0dbc1d50464629101242e2f1742e1b89548
push id2697
push userbbajaj@mozilla.com
push dateMon, 05 Aug 2013 18:49:53 +0000
treeherdermozilla-beta@dfec938c7b63 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, rjesup, ekr
bugs823512
milestone24.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 823512: Part 2: Update PeerConnection to spec. states + DataChannel. r=bz,rjesup,ekr
dom/bindings/Bindings.conf
dom/media/PeerConnection.js
dom/media/PeerConnection.manifest
dom/media/tests/crashtests/837421.html
dom/media/tests/crashtests/crashtests.list
dom/webidl/RTCDataChannelEvent.webidl
dom/webidl/RTCPeerConnection.webidl
dom/webidl/RTCPeerConnectionIceEvent.webidl
dom/webidl/WebIDL.mk
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1548,16 +1548,17 @@ addExternalIface('ActivityOptions', nati
                  headerFile='nsIDOMActivityOptions.h')
 addExternalIface('Counter')
 addExternalIface('CSSRule')
 addExternalIface('DeviceAcceleration', headerFile='nsIDOMDeviceMotionEvent.h', notflattened=True)
 addExternalIface('DeviceRotationRate', headerFile='nsIDOMDeviceMotionEvent.h', notflattened=True)
 addExternalIface('DOMError')
 addExternalIface('CSSRuleList')
 addExternalIface('DOMStringList')
+addExternalIface('RTCDataChannel', nativeType='nsIDOMDataChannel')
 addExternalIface('File')
 addExternalIface('FileCallback', nativeType='nsIFileCallback',
                  headerFile='nsIDOMHTMLCanvasElement.h')
 addExternalIface('HitRegionOptions', nativeType='nsISupports')
 addExternalIface('IDBOpenDBRequest', nativeType='nsIIDBOpenDBRequest')
 addExternalIface('imgINotificationObserver', nativeType='imgINotificationObserver')
 addExternalIface('imgIRequest', nativeType='imgIRequest', notflattened=True)
 addExternalIface('LockedFile')
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -10,23 +10,25 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const PC_CONTRACT = "@mozilla.org/dom/peerconnection;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_ICEEVENT_CONTRACT = "@mozilla.org/dom/rtcpeerconnectioniceevent;1";
 const MSEVENT_CONTRACT = "@mozilla.org/dom/mediastreamevent;1";
+const DCEVENT_CONTRACT = "@mozilla.org/dom/datachannelevent;1";
 
 const PC_CID = Components.ID("{9878b414-afaa-4176-a887-1e02b3b047c2}");
 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_ICEEVENT_CID = Components.ID("{b9cd25a7-9859-4f9e-8f84-ef5181ff36c0}");
 const MSEVENT_CID = Components.ID("{a722a8a9-2290-4e99-a5ed-07b504292d08}");
+const DCEVENT_CID = Components.ID("{d5ed7fbf-01a8-4b18-af6c-861cf2aac920}");
 
 // Global list of PeerConnection objects, so they can be cleaned up when
 // a page is torn down. (Maps inner window ID to an array of PC objects).
 function GlobalPCList() {
   this._list = [];
   this._networkdown = false; // XXX Need to query current state somehow
   Services.obs.addObserver(this, "inner-window-destroyed", true);
   Services.obs.addObserver(this, "profile-change-net-teardown", true);
@@ -171,33 +173,55 @@ MediaStreamEvent.prototype = {
     this.__DOM_IMPL__.initEvent(type, dict.bubbles || false,
                                 dict.cancelable || false);
     this._stream = dict.stream;
   },
 
   get stream() { return this._stream; }
 };
 
+function RTCDataChannelEvent() {
+  this.type = this._channel = null;
+}
+RTCDataChannelEvent.prototype = {
+  classDescription: "RTCDataChannelEvent",
+  classID: DCEVENT_CID,
+  contractID: DCEVENT_CONTRACT,
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
+                                         Ci.nsIDOMGlobalPropertyInitializer]),
+
+  init: function(win) { this._win = win; },
+
+  __init: function(type, dict) {
+    this.type = type;
+    this.__DOM_IMPL__.initEvent(type, dict.bubbles || false,
+                                dict.cancelable || false);
+    this._channel = dict.channel;
+  },
+
+  get channel() { return this._channel; }
+};
+
 function RTCPeerConnectionIceEvent() {
   this.type = this._candidate = null;
 }
 RTCPeerConnectionIceEvent.prototype = {
   classDescription: "RTCPeerConnectionIceEvent",
   classID: PC_ICEEVENT_CID,
   contractID: PC_ICEEVENT_CONTRACT,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
                                          Ci.nsIDOMGlobalPropertyInitializer]),
 
   init: function(win) { this._win = win; },
 
   __init: function(type, dict) {
     this.type = type;
     this.__DOM_IMPL__.initEvent(type, dict.bubbles || false,
                                 dict.cancelable || false);
-    this._candidate = dict.candidate || null;
+    this._candidate = dict.candidate;
   },
 
   get candidate() { return this._candidate; }
 };
 
 function RTCPeerConnection() {
   this._queue = [];
 
@@ -219,27 +243,22 @@ function RTCPeerConnection() {
    * there are no pending operations though, we will execute it immediately.
    * In PeerConnectionObserver, whenever we are notified that an operation
    * has finished, we will check the queue for the next operation and execute
    * if neccesary. The _pending flag indicates whether an operation is currently
    * in progress.
    */
   this._pending = false;
 
-  // Event handlers
-  this.onopen = null;
-  this.onremovestream = null;
-  this.onstatechange = null;
-  this.ongatheringchange = null;
-  this.onicechange = null;
+  // States
+  this._iceGatheringState = this._iceConnectionState = "new";
 
-  // Data channel.
-  this.ondatachannel = null;
-  this.onconnection = null;
-  this.onclosedconnection = null;
+  // Deprecated callbacks
+  this._ongatheringchange = null;
+  this._onicechange = null;
 }
 RTCPeerConnection.prototype = {
   classDescription: "mozRTCPeerConnection",
   classID: PC_CID,
   contractID: PC_CONTRACT,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
                                          Ci.nsIDOMGlobalPropertyInitializer,
                                          Ci.nsISupportsWeakReference]),
@@ -252,16 +271,26 @@ RTCPeerConnection.prototype = {
         JSON.parse(Services.prefs.getCharPref("media.peerconnection.default_iceservers"))};
     }
     this._mustValidateRTCConfiguration(rtcConfig,
         "RTCPeerConnection constructor passed invalid RTCConfiguration");
     if (_globalPCList._networkdown) {
       throw new Components.Exception("Can't create RTCPeerConnections when the network is down");
     }
 
+    this.makeGetterSetterEH("onaddstream");
+    this.makeGetterSetterEH("onicecandidate");
+    this.makeGetterSetterEH("onnegotiationneeded");
+    this.makeGetterSetterEH("onsignalingstatechange");
+    this.makeGetterSetterEH("onremovestream");
+    this.makeGetterSetterEH("ondatachannel");
+    this.makeGetterSetterEH("onconnection");
+    this.makeGetterSetterEH("onclosedconnection");
+    this.makeGetterSetterEH("oniceconnectionstatechange");
+
     this._pc = Cc["@mozilla.org/peerconnection;1"].
              createInstance(Ci.IPeerConnection);
     this._observer = new PeerConnectionObserver(this);
 
     // Nothing starts until ICE gathering completes.
     this._queueOrRun({
       func: this._getPC().initialize,
       args: [this._observer, this._win, rtcConfig, Services.tm.currentThread],
@@ -440,47 +469,70 @@ RTCPeerConnection.prototype = {
       throw new Components.Exception("Peer connection is closed");
     }
   },
 
   dispatchEvent: function(event) {
     this.__DOM_IMPL__.dispatchEvent(event);
   },
 
-  getEventHandler: function(type) {
+  getEH: function(type) {
     return this.__DOM_IMPL__.getEventHandler(type);
   },
 
-  setEventHandler: function(type, handler) {
+  setEH: function(type, handler) {
     this.__DOM_IMPL__.setEventHandler(type, handler);
   },
 
-  get onaddstream()    { return this.getEventHandler("onaddstream"); },
-  get onicecandidate() { return this.getEventHandler("onicecandidate"); },
+  makeGetterSetterEH: function(name) {
+    Object.defineProperty(this, name,
+                          {
+                            get:function()  { return this.getEH(name); },
+                            set:function(h) { return this.setEH(name, h); }
+                          });
+  },
+
+  get onicechange()       { return this._onicechange; },
+  get ongatheringchange() { return this._ongatheringchange; },
 
-  set onaddstream(handler) { this.setEventHandler("onaddstream", handler); },
-  set onicecandidate(h)    { this.setEventHandler("onicecandidate", h); },
+  set onicechange(cb) {
+    this.deprecated("onicechange");
+    this._onicechange = cb;
+  },
+  set ongatheringchange(cb) {
+    this.deprecated("ongatheringchange");
+    this._ongatheringchange = cb;
+  },
+
+  deprecated: function(name) {
+    dump(name + " is deprecated!\n");
+  },
 
   createOffer: function(onSuccess, onError, constraints) {
     if (!constraints) {
       constraints = {};
     }
-
+    if (!onError) {
+      this.deprecated("calling createOffer without failureCallback");
+    }
     this._mustValidateConstraints(constraints, "createOffer passed invalid constraints");
     this._onCreateOfferSuccess = onSuccess;
     this._onCreateOfferFailure = onError;
 
     this._queueOrRun({
       func: this._getPC().createOffer,
       args: [constraints],
       wait: true
     });
   },
 
   _createAnswer: function(onSuccess, onError, constraints, provisional) {
+    if (!onError) {
+      this.deprecated("calling createAnswer without failureCallback");
+    }
     this._onCreateAnswerSuccess = onSuccess;
     this._onCreateAnswerFailure = onError;
 
     if (!this.remoteDescription) {
 
       this._observer.onCreateAnswerError(Ci.IPeerConnection.kInvalidState,
                                          "setRemoteDescription not called");
       return;
@@ -526,20 +578,22 @@ RTCPeerConnection.prototype = {
     let type;
     switch (desc.type) {
       case "offer":
         type = Ci.IPeerConnection.kActionOffer;
         break;
       case "answer":
         type = Ci.IPeerConnection.kActionAnswer;
         break;
+      case "pranswer":
+        throw new Components.Exception("pranswer not yet implemented",
+                                       Cr.NS_ERROR_NOT_IMPLEMENTED);
       default:
         throw new Components.Exception("Invalid type " + desc.type +
                                        " provided to setLocalDescription");
-        break;
     }
 
     this._queueOrRun({
       func: this._getPC().setLocalDescription,
       args: [type, desc.sdp],
       wait: true,
       type: desc.type
     });
@@ -555,33 +609,35 @@ RTCPeerConnection.prototype = {
     let type;
     switch (desc.type) {
       case "offer":
         type = Ci.IPeerConnection.kActionOffer;
         break;
       case "answer":
         type = Ci.IPeerConnection.kActionAnswer;
         break;
+      case "pranswer":
+        throw new Components.Exception("pranswer not yet implemented",
+                                       Cr.NS_ERROR_NOT_IMPLEMENTED);
       default:
         throw new Components.Exception("Invalid type " + desc.type +
                                        " provided to setRemoteDescription");
-        break;
     }
 
     this._queueOrRun({
       func: this._getPC().setRemoteDescription,
       args: [type, desc.sdp],
       wait: true,
       type: desc.type
     });
   },
 
   updateIce: function(config, constraints) {
-    throw new Components.Exception ("updateIce not yet implemented",
-                                    Cr.NS_ERROR_NOT_IMPLEMENTED);
+    throw new Components.Exception("updateIce not yet implemented",
+                                   Cr.NS_ERROR_NOT_IMPLEMENTED);
   },
 
   addIceCandidate: function(cand, onSuccess, onError) {
     if (!cand.candidate && !cand.sdpMLineIndex) {
       throw new Components.Exception("Invalid candidate passed to addIceCandidate!");
     }
     this._onAddIceCandidateSuccess = onSuccess || null;
     this._onAddIceCandidateError = onError || null;
@@ -601,45 +657,55 @@ RTCPeerConnection.prototype = {
     this._queueOrRun({
       func: this._getPC().addStream,
       args: [stream],
       wait: false
     });
   },
 
   removeStream: function(stream) {
-     //Bug844295: Not implemeting this functionality.
-     throw new Components.Exception ("removeStream not yet implemented",
-                                     Cr.NS_ERROR_NOT_IMPLEMENTED);
+     //Bug 844295: Not implementing this functionality.
+     throw new Components.Exception("removeStream not yet implemented",
+                                    Cr.NS_ERROR_NOT_IMPLEMENTED);
+  },
+
+  getStreamById: function(id) {
+    throw new Components.Exception("getStreamById not yet implemented",
+                                   Cr.NS_ERROR_NOT_IMPLEMENTED);
   },
 
   close: function() {
     this._queueOrRun({
       func: this._getPC().close,
       args: [false],
       wait: false
     });
     this._closed = true;
+    this.changeIceConnectionState("closed");
   },
 
   getLocalStreams: function() {
     this._checkClosed();
     return this._getPC().localStreams;
   },
 
   getRemoteStreams: function() {
     this._checkClosed();
     return this._getPC().remoteStreams;
   },
 
-  // Four backwards-compatible attributes (to pass mochitests)
-  get localStreams() { return this.getLocalStreams(); },
-  get remoteStreams() { return this.getRemoteStreams(); },
-  set localDescription(desc) { this.setLocalDescription(desc); },
-  set remoteDescription(desc) { this.setLocalDescription(desc); },
+  // Backwards-compatible attributes
+  get localStreams() {
+    this.deprecated("localStreams");
+    return this.getLocalStreams();
+  },
+  get remoteStreams() {
+    this.deprecated("remoteStreams");
+    return this.getRemoteStreams();
+  },
 
   get localDescription() {
     this._checkClosed();
     let sdp = this._getPC().localDescription;
     if (sdp.length == 0) {
       return null;
     }
     return new this._win.mozRTCSessionDescription({ type: this._localType,
@@ -651,17 +717,31 @@ RTCPeerConnection.prototype = {
     let sdp = this._getPC().remoteDescription;
     if (sdp.length == 0) {
       return null;
     }
     return new this._win.mozRTCSessionDescription({ type: this._remoteType,
                                                     sdp: sdp });
   },
 
+  get signalingState()     { return "stable"; }, // not yet implemented
+  get iceGatheringState()  { return this._iceGatheringState; },
+  get iceConnectionState() { return this._iceConnectionState; },
+
+  changeIceGatheringState: function(state) {
+    this._iceGatheringState = state;
+  },
+
+  changeIceConnectionState: function(state) {
+    this._iceConnectionState = state;
+    this.dispatchEvent(new this._win.Event("iceconnectionstatechange"));
+  },
+
   get readyState() {
+    this.deprecated("readyState");
     // checking for our local pc closed indication
     // before invoking the pc methods.
     if(this._closed) {
       return "closed";
     }
 
     var state="undefined";
     switch (this._getPC().readyState) {
@@ -880,33 +960,39 @@ PeerConnectionObserver.prototype = {
   },
 
   onStateChange: function(state) {
     if (state != Ci.IPeerConnectionObserver.kIceState) {
       return;
     }
 
     switch (this._dompc._pc.iceState) {
-      case Ci.IPeerConnection.kIceGathering:
-        this.callCB(this._dompc.ongatheringchange, "gathering");
-        break;
       case Ci.IPeerConnection.kIceWaiting:
-        this.callCB(this._dompc.onicechange, "starting");
+        this._dompc.changeIceConnectionState("completed");
+        this.callCB(this._dompc.ongatheringchange, "complete");
+        this.callCB(this._onicechange, "starting");
+        // Now that the PC is ready to go, execute any pending operations.
         this._dompc._executeNext();
         break;
       case Ci.IPeerConnection.kIceChecking:
-        this.callCB(this._dompc.onicechange, "checking");
+        this._dompc.changeIceConnectionState("checking");
+        this.callCB(this._onicechange, "checking");
+        break;
+      case Ci.IPeerConnection.kIceGathering:
+        this._dompc.changeIceGatheringState("gathering");
+        this.callCB(this._ongatheringchange, "gathering");
         break;
       case Ci.IPeerConnection.kIceConnected:
         // ICE gathering complete.
-        this.callCB(this._dompc.onicechange, "connected");
-        this.callCB(this._dompc.ongatheringchange, "complete");
+        this._dompc.changeIceConnectionState("connected");
+        this.callCB(this._onicechange, "connected");
         break;
       case Ci.IPeerConnection.kIceFailed:
-        this.callCB(this._dompc.onicechange, "failed");
+        this._dompc.changeIceConnectionState("failed");
+        this.callCB(this._onicechange, "failed");
         break;
       default:
         // Unknown state!
         break;
     }
   },
 
   onAddStream: function(stream) {
@@ -920,25 +1006,25 @@ PeerConnectionObserver.prototype = {
   },
 
   foundIceCandidate: function(c) {
     this.dispatchEvent(new this._dompc._win.RTCPeerConnectionIceEvent("icecandidate",
                                                                       { candidate: c }));
   },
 
   notifyDataChannel: function(channel) {
-    this.callCB(this._dompc.ondatachannel,
-                { channel: channel, __exposedProps__: { channel: "r" } });
+    this.dispatchEvent(new this._dompc._win.RTCDataChannelEvent("datachannel",
+                                                                { channel: channel }));
   },
 
   notifyConnection: function() {
-    this.callCB (this._dompc.onconnection);
+    this.dispatchEvent(new this._dompc._win.Event("connection"));
   },
 
   notifyClosedConnection: function() {
-    this.callCB (this._dompc.onclosedconnection);
+    this.dispatchEvent(new this._dompc._win.Event("closedconnection"));
   }
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(
   [GlobalPCList, RTCIceCandidate, RTCSessionDescription, RTCPeerConnection,
-   RTCPeerConnectionIceEvent, MediaStreamEvent]
+   RTCPeerConnectionIceEvent, MediaStreamEvent, RTCDataChannelEvent]
 );
--- a/dom/media/PeerConnection.manifest
+++ b/dom/media/PeerConnection.manifest
@@ -1,13 +1,15 @@
 component {9878b414-afaa-4176-a887-1e02b3b047c2} PeerConnection.js
 component {02b9970c-433d-4cc2-923d-f7028ac66073} PeerConnection.js
 component {1775081b-b62d-4954-8ffe-a067bbf508a7} PeerConnection.js
 component {7293e901-2be3-4c02-b4bd-cbef6fc24f78} PeerConnection.js
 component {b9cd25a7-9859-4f9e-8f84-ef5181ff36c0} PeerConnection.js
 component {a722a8a9-2290-4e99-a5ed-07b504292d08} PeerConnection.js
+component {d5ed7fbf-01a8-4b18-af6c-861cf2aac920} PeerConnection.js
 
 contract @mozilla.org/dom/peerconnection;1 {9878b414-afaa-4176-a887-1e02b3b047c2}
 contract @mozilla.org/dom/rtcicecandidate;1 {02b9970c-433d-4cc2-923d-f7028ac66073}
 contract @mozilla.org/dom/rtcsessiondescription;1 {1775081b-b62d-4954-8ffe-a067bbf508a7}
 contract @mozilla.org/dom/peerconnectionmanager;1 {7293e901-2be3-4c02-b4bd-cbef6fc24f78}
 contract @mozilla.org/dom/rtcpeerconnectioniceevent;1 {b9cd25a7-9859-4f9e-8f84-ef5181ff36c0}
 contract @mozilla.org/dom/mediastreamevent;1 {a722a8a9-2290-4e99-a5ed-07b504292d08}
+contract @mozilla.org/dom/datachannelevent;1 {d5ed7fbf-01a8-4b18-af6c-861cf2aac920}
deleted file mode 100644
--- a/dom/media/tests/crashtests/837421.html
+++ /dev/null
@@ -1,34 +0,0 @@
-<!DOCTYPE html>
-<html class="reftest-wait">
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=837421
--->
-<head>
-  <meta charset="utf-8">
-  <title>Bug 837421</title>
-  <script type="application/javascript">
-    function finish() {
-      document.documentElement.removeAttribute("class");
-    }
-
-    function start() {
-        var o0 = mozRTCPeerConnection();
-        var o1 = new mozRTCIceCandidate({"candidate":"127 15 UDP 1 stun.sipgate.net 134117531 type 2001:db8:85a3:0:0:8a2e:370:3478 rport","sdpMid":"application 3566220586 RTP/AVP 5000","sdpMLineIndex":-32767});
-        o0.connectDataConnection(3478,-1);
-        o0.connectDataConnection(-1,3478,2);
-        var o2 = mozRTCPeerConnection();
-        try {o2.addIceCandidate(o1);} catch(e) {} // bug 842075 - remove try when fixed
-        o2.connectDataConnection(-1,3478,0.5);
-        o2.connectDataConnection(3478,-1,0.5);
-        var o3 = new mozRTCIceCandidate({"candidate":"31 2097151 IP 33554431 ::ffff:192.0.2.128 3999799469 type numb.viagenie.ca host","sdpMid":"application 1261077875 RTP/AVP 5000","sdpMLineIndex":16777215});
-        o2.connectDataConnection(1,3478);
-        o2.connectDataConnection(3478,1);
-        try {o2.updateIce()} catch(e) {}
-        try {o2.addIceCandidate(o3);} catch(e) {} // bug 842075 - remove try when fixed
-        finish();
-    }
-  </script>
-</head>
-
-<body onload="setTimeout(start, 100)">
-</html>
--- a/dom/media/tests/crashtests/crashtests.list
+++ b/dom/media/tests/crashtests/crashtests.list
@@ -5,13 +5,12 @@ load 791270.html
 load 791278.html
 load 791330.html
 load 799419.html
 load 802982.html
 load 812785.html
 load 834100.html
 load 836349.html
 load 837324.html
-load 837421.html
 load 855796.html
 load 860143.html
 load 861958.html
 load 863929.html
new file mode 100644
--- /dev/null
+++ b/dom/webidl/RTCDataChannelEvent.webidl
@@ -0,0 +1,21 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://dev.w3.org/2011/webrtc/editor/webrtc.html#idl-def-RTCDataChannelEvent
+ */
+
+interface RTCDataChannel;
+
+dictionary RTCDataChannelEventInit : EventInit {
+    RTCDataChannel? channel = null;
+};
+
+[Pref="media.peerconnection.enabled",
+ JSImplementation="@mozilla.org/dom/datachannelevent;1",
+ Constructor(DOMString type, optional RTCDataChannelEventInit eventInitDict)]
+interface RTCDataChannelEvent : Event {
+  readonly attribute RTCDataChannel? channel;
+};
--- a/dom/webidl/RTCPeerConnection.webidl
+++ b/dom/webidl/RTCPeerConnection.webidl
@@ -31,16 +31,27 @@ enum RTCIceConnectionState {
     "checking",
     "connected",
     "completed",
     "failed",
     "disconnected",
     "closed"
 };
 
+dictionary RTCDataChannelInit {
+  boolean outOfOrderAllowed;
+  unsigned short maxRetransmitTime;
+  unsigned short maxRetransmitNum;
+  DOMString protocol;
+  boolean preset;
+  unsigned short stream;
+};
+
+interface RTCDataChannel;
+
 [Pref="media.peerconnection.enabled",
  JSImplementation="@mozilla.org/dom/peerconnection;1",
  Constructor (optional RTCConfiguration configuration,
               optional object? constraints)]
 // moz-prefixed until sufficiently standardized.
 interface mozRTCPeerConnection : EventTarget  {
   void createOffer (RTCSessionDescriptionCallback successCallback,
                     RTCPeerConnectionErrorCallback? failureCallback, // for apprtc
@@ -49,18 +60,18 @@ interface mozRTCPeerConnection : EventTa
                      RTCPeerConnectionErrorCallback? failureCallback, // for apprtc
                      optional object? constraints);
   void setLocalDescription (mozRTCSessionDescription description,
                             optional VoidFunction successCallback,
                             optional RTCPeerConnectionErrorCallback failureCallback);
   void setRemoteDescription (mozRTCSessionDescription description,
                              optional VoidFunction successCallback,
                              optional RTCPeerConnectionErrorCallback failureCallback);
-  readonly attribute mozRTCSessionDescription localDescription;
-  readonly attribute mozRTCSessionDescription remoteDescription;
+  readonly attribute mozRTCSessionDescription? localDescription;
+  readonly attribute mozRTCSessionDescription? remoteDescription;
   readonly attribute RTCSignalingState signalingState;
   void updateIce (optional RTCConfiguration configuration,
                   optional object? constraints);
   void addIceCandidate (mozRTCIceCandidate candidate,
                         optional VoidFunction successCallback,
                         optional RTCPeerConnectionErrorCallback failureCallback);
   readonly attribute RTCIceGatheringState iceGatheringState;
   readonly attribute RTCIceConnectionState iceConnectionState;
@@ -75,12 +86,24 @@ interface mozRTCPeerConnection : EventTa
   attribute EventHandler onsignalingstatechange;
   attribute EventHandler onaddstream;
   attribute EventHandler onremovestream;
   attribute EventHandler oniceconnectionstatechange;
 };
 
 // Mozilla extensions.
 partial interface mozRTCPeerConnection {
-  // Backwards-compatible attributes
+  // Deprecated callbacks (use causes warning)
+  attribute RTCPeerConnectionErrorCallback onicechange;
+  attribute RTCPeerConnectionErrorCallback ongatheringchange;
+
+  // Deprecated attributes (use causes warning)
   readonly attribute object localStreams;
   readonly attribute object remoteStreams;
+  readonly attribute DOMString readyState;
+
+  // Data channel.
+  RTCDataChannel createDataChannel (DOMString label,
+                                    optional RTCDataChannelInit dataChannelDict);
+  attribute EventHandler ondatachannel;
+  attribute EventHandler onconnection;
+  attribute EventHandler onclosedconnection;
 };
--- a/dom/webidl/RTCPeerConnectionIceEvent.webidl
+++ b/dom/webidl/RTCPeerConnectionIceEvent.webidl
@@ -3,17 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  *
  * The origin of this IDL file is
  * http://dev.w3.org/2011/webrtc/editor/webrtc.html#idl-def-RTCPeerConnectionIceEvent
  */
 
 dictionary RTCPeerConnectionIceEventInit : EventInit {
-  mozRTCIceCandidate? candidate;
+  mozRTCIceCandidate? candidate = null;
 };
 
 [Pref="media.peerconnection.enabled",
  JSImplementation="@mozilla.org/dom/rtcpeerconnectioniceevent;1",
  Constructor(DOMString type,
              optional RTCPeerConnectionIceEventInit eventInitDict)]
 interface RTCPeerConnectionIceEvent : Event {
   readonly attribute mozRTCIceCandidate? candidate;
--- a/dom/webidl/WebIDL.mk
+++ b/dom/webidl/WebIDL.mk
@@ -189,16 +189,17 @@ webidl_files = \
   PerformanceTiming.webidl \
   Position.webidl \
   PositionError.webidl \
   ProcessingInstruction.webidl \
   Range.webidl \
   Rect.webidl \
   RGBColor.webidl \
   RTCConfiguration.webidl \
+  RTCDataChannelEvent.webidl \
   RTCIceCandidate.webidl \
   RTCPeerConnection.webidl \
   RTCPeerConnectionIceEvent.webidl \
   RTCSessionDescription.webidl \
   Screen.webidl \
   ScriptProcessorNode.webidl \
   ScrollAreaEvent.webidl \
   SimpleGestureEvent.webidl \