Bug 694807: Implement PeerConnection DOM interface; r=jst
authorAnant Narayanan <anant@kix.in>
Sun, 07 Oct 2012 01:34:30 -0400
changeset 109564 86aef70706f94982d178146c7e3851064af9846f
parent 109563 750fa0811f41f2e1cf40eeab385e65dd3663ac37
child 109565 884757053d1d799ad60a2b2b322ac5f2b1fd3e27
push id23632
push userphilringnalda@gmail.com
push dateSun, 07 Oct 2012 19:14:37 +0000
treeherdermozilla-central@83d38854c21e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjst
bugs694807
milestone18.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 694807: Implement PeerConnection DOM interface; r=jst
browser/installer/package-manifest.in
dom/media/Makefile.in
dom/media/PeerConnection.js
dom/media/PeerConnection.manifest
dom/media/nsIDOMRTCPeerConnection.idl
dom/tests/mochitest/general/test_interfaces.html
modules/libpref/src/init/all.js
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -261,16 +261,19 @@
 @BINPATH@/components/necko_viewsource.xpt
 #ifdef NECKO_WIFI
 @BINPATH@/components/necko_wifi.xpt
 #endif
 @BINPATH@/components/necko_wyciwyg.xpt
 @BINPATH@/components/necko.xpt
 @BINPATH@/components/loginmgr.xpt
 @BINPATH@/components/parentalcontrols.xpt
+#ifdef MOZ_WEBRTC
+@BINPATH@/components/peerconnection.xpt
+#endif
 @BINPATH@/components/places.xpt
 @BINPATH@/components/plugin.xpt
 @BINPATH@/components/pref.xpt
 @BINPATH@/components/prefetch.xpt
 @BINPATH@/components/profile.xpt
 #ifdef MOZ_ENABLE_PROFILER_SPS
 @BINPATH@/components/profiler.xpt
 #endif
@@ -485,16 +488,21 @@
 @BINPATH@/components/ContactManager.js
 @BINPATH@/components/ContactManager.manifest
 @BINPATH@/components/AlarmsManager.js
 @BINPATH@/components/AlarmsManager.manifest
 @BINPATH@/components/TCPSocket.js
 @BINPATH@/components/TCPSocketParentIntermediary.js
 @BINPATH@/components/TCPSocket.manifest
 
+#ifdef MOZ_WEBRTC
+@BINPATH@/components/PeerConnection.js
+@BINPATH@/components/PeerConnection.manifest
+#endif
+
 #ifdef ENABLE_MARIONETTE
 @BINPATH@/chrome/marionette@JAREXT@
 @BINPATH@/chrome/marionette.manifest
 @BINPATH@/components/MarionetteComponents.manifest
 @BINPATH@/components/marionettecomponent.js
 #endif
 
 ; Modules
--- a/dom/media/Makefile.in
+++ b/dom/media/Makefile.in
@@ -14,18 +14,24 @@ MODULE           = dom
 XPIDL_MODULE     = dom_media
 LIBRARY_NAME     = dom_media_s
 LIBXUL_LIBRARY   = 1
 FORCE_STATIC_LIB = 1
 FAIL_ON_WARNINGS := 1
 
 include $(topsrcdir)/dom/dom-config.mk
 
+EXTRA_COMPONENTS = \
+  PeerConnection.js \
+  PeerConnection.manifest \
+  $(NULL)
+
 XPIDLSRCS = \
   nsIDOMMediaStream.idl \
+  nsIDOMRTCPeerConnection.idl \
   nsIDOMNavigatorUserMedia.idl \
   $(NULL)
 
 EXPORTS_NAMESPACE = mozilla
 
 EXPORTS_mozilla = \
   MediaManager.h \
   $(NULL)
new file mode 100644
--- /dev/null
+++ b/dom/media/PeerConnection.js
@@ -0,0 +1,575 @@
+/* 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/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+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_CID = Components.ID("{7cb2b368-b1ce-4560-acac-8e0dbda7d3d0}");
+const PC_ICE_CID = Components.ID("{8c5dbd70-2c8e-4ecb-a5ad-2fc919099f01}");
+const PC_SESSION_CID = Components.ID("{5f21ffd9-b73f-4ba0-a685-56b4667aaf1c}");
+
+// 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 = {};
+  Services.obs.addObserver(this, "inner-window-destroyed", true);
+}
+GlobalPCList.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+                                         Ci.nsISupportsWeakReference]),
+
+  addPC: function(pc) {
+    let winID = pc._winID;
+    if (this._list[winID]) {
+      this._list[winID].push(pc);
+    } else {
+      this._list[winID] = [pc];
+    }
+  },
+
+  observe: function(subject, topic, data) {
+    if (topic != "inner-window-destroyed") {
+      return;
+    }
+    let winID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
+    if (this._list[winID]) {
+      this._list[winID].forEach(function(pc) {
+        pc._pc.close();
+        delete pc._observer;
+      });
+      delete this._list[winID];
+    }
+  }
+};
+let _globalPCList = new GlobalPCList();
+
+function IceCandidate(candidate) {
+  this.candidate = candidate;
+  this.sdpMid = null;
+  this.sdpMLineIndex = null;
+}
+IceCandidate.prototype = {
+  classID: PC_ICE_CID,
+
+  classInfo: XPCOMUtils.generateCI({classID: PC_ICE_CID,
+                                    contractID: PC_ICE_CONTRACT,
+                                    classDescription: "IceCandidate",
+                                    interfaces: [
+                                      Ci.nsIDOMRTCIceCandidate
+                                    ],
+                                    flags: Ci.nsIClassInfo.DOM_OBJECT}),
+
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIDOMRTCIceCandidate, Ci.nsIDOMGlobalObjectConstructor
+  ]),
+
+  constructor: function(win, cand, mid, mline) {
+    if (this._win) {
+      throw new Error("Constructor already called");
+    }
+    this._win = win;
+    this.candidate = cand;
+    this.sdpMid = mid;
+    this.sdpMLineIndex = mline;
+  }
+};
+
+function SessionDescription(type, sdp) {
+  this.type = type;
+  this.sdp = sdp;
+}
+SessionDescription.prototype = {
+  classID: PC_SESSION_CID,
+
+  classInfo: XPCOMUtils.generateCI({classID: PC_SESSION_CID,
+                                    contractID: PC_SESSION_CONTRACT,
+                                    classDescription: "SessionDescription",
+                                    interfaces: [
+                                      Ci.nsIDOMRTCSessionDescription
+                                    ],
+                                    flags: Ci.nsIClassInfo.DOM_OBJECT}),
+
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIDOMRTCSessionDescription, Ci.nsIDOMGlobalObjectConstructor
+  ]),
+
+  constructor: function(win, type, sdp) {
+    if (this._win) {
+      throw new Error("Constructor already called");
+    }
+    this._win = win;
+    this.type = type;
+    this.sdp = sdp;
+  },
+
+  toString: function() {
+    return JSON.stringify({
+      type: this.type, sdp: this.sdp
+    });
+  }
+};
+
+function PeerConnection() {
+  this._queue = [];
+
+  this._pc = null;
+  this._observer = null;
+
+  this._onCreateOfferSuccess = null;
+  this._onCreateOfferFailure = null;
+  this._onCreateAnswerSuccess = null;
+  this._onCreateAnswerFailure = null;
+
+  /**
+   * Everytime we get a request from content, we put it in the queue. If
+   * 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;
+
+  // Public attributes.
+  this.onaddstream = null;
+  this.onremovestream = null;
+  this.onicecandidate = null;
+  this.onstatechange = null;
+  this.ongatheringchange = null;
+  this.onicechange = null;
+}
+PeerConnection.prototype = {
+  classID: PC_CID,
+
+  classInfo: XPCOMUtils.generateCI({classID: PC_CID,
+                                    contractID: PC_CONTRACT,
+                                    classDescription: "PeerConnection",
+                                    interfaces: [
+                                      Ci.nsIDOMRTCPeerConnection
+                                    ],
+                                    flags: Ci.nsIClassInfo.DOM_OBJECT}),
+
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIDOMRTCPeerConnection, Ci.nsIDOMGlobalObjectConstructor
+  ]),
+
+  // Constructor is an explicit function, because of nsIDOMGlobalObjectConstructor.
+  constructor: function(win) {
+    if (!Services.prefs.getBoolPref("media.peerconnection.enabled")) {
+      throw new Error("PeerConnection not enabled (did you set the pref?)");
+    }
+    if (this._win) {
+      throw new Error("Constructor already called");
+    }
+
+    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._pc.initialize,
+      args: [this._observer, win, Services.tm.currentThread],
+      wait: true
+    });
+
+    this._win = win;
+    this._winID = this._win.QueryInterface(Ci.nsIInterfaceRequestor)
+                           .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
+
+    // Add a reference to the PeerConnection to global list.
+    _globalPCList.addPC(this);
+  },
+
+  /**
+   * Add a function to the queue or run it immediately if the queue is empty.
+   * Argument is an object with the func, args and wait properties; wait should
+   * be set to true if the function has a success/error callback that will
+   * call _executeNext, false if it doesn't have a callback.
+   */
+  _queueOrRun: function(obj) {
+    if (!this._pending) {
+      obj.func.apply(this, obj.args);
+      if (obj.wait) {
+        this._pending = true;
+      }
+    } else {
+      this._queue.push(obj);
+    }
+  },
+
+  // Pick the next item from the queue and run it.
+  _executeNext: function() {
+    if (this._queue.length) {
+      let obj = this._queue.shift();
+      obj.func.apply(this, obj.args);
+      if (!obj.wait) {
+        this._executeNext();
+      }
+    } else {
+      this._pending = false;
+    }
+  },
+
+  createOffer: function(onSuccess, onError, constraints) {
+    if (this._onCreateOfferSuccess) {
+      if (onError) {
+        onError.onCallback("createOffer already called");
+      }
+      return;
+    }
+
+    this._onCreateOfferSuccess = onSuccess;
+    this._onCreateOfferFailure = onError;
+
+    // TODO: Implement constraints/hints.
+    if (!constraints) {
+      constraints = "";
+    }
+
+    this._queueOrRun({
+      func: this._pc.createOffer,
+      args: [constraints],
+      wait: true
+    });
+  },
+
+  createAnswer: function(offer, onSuccess, onError, constraints, provisional) {
+    if (this._onCreateAnswerSuccess) {
+      if (onError) {
+        onError.onCallback("createAnswer already called");
+      }
+      return;
+    }
+
+    this._onCreateAnswerSuccess = onSuccess;
+    this._onCreateAnswerFailure = onError;
+
+    if (offer.type != "offer") {
+      if (onError) {
+        onError.onCallback("Invalid type " + offer.type + " passed");
+      }
+      return;
+    }
+
+    if (!offer.sdp) {
+      if (onError) {
+        onError.onCallback("SDP not provided to createAnswer");
+      }
+      return;
+    }
+
+    if (!constraints) {
+      constraints = "";
+    }
+    if (!provisional) {
+      provisional = false;
+    }
+
+    // TODO: Implement provisional answer & constraints.
+    this._queueOrRun({
+      func: this._pc.createAnswer,
+      args: ["", offer.sdp],
+      wait: true
+    });
+  },
+
+  setLocalDescription: function(desc, onSuccess, onError) {
+    if (this._onSetLocalDescriptionSuccess) {
+      if (onError) {
+        onError.onCallback("setLocalDescription already called");
+      }
+      return;
+    }
+
+    this._onSetLocalDescriptionSuccess = onSuccess;
+    this._onSetLocalDescriptionFailure = onError;
+
+    let type;
+    switch (desc.type) {
+      case "offer":
+        type = Ci.IPeerConnection.kActionOffer;
+        break;
+      case "answer":
+        type = Ci.IPeerConnection.kActionAnswer;
+        break;
+      default:
+        if (onError) {
+          onError.onCallback(
+            "Invalid type " + desc.type + " provided to setLocalDescription"
+          );
+          return;
+        }
+        break;
+    }
+
+    this._queueOrRun({
+      func: this._pc.setLocalDescription,
+      args: [type, desc.sdp],
+      wait: true
+    });
+  },
+
+  setRemoteDescription: function(desc, onSuccess, onError) {
+    if (this._onSetRemoteDescriptionSuccess) {
+      if (onError) {
+        onError.onCallback("setRemoteDescription already called");
+      }
+      return;
+    }
+
+    this._onSetRemoteDescriptionSuccess = onSuccess;
+    this._onSetRemoteDescriptionFailure = onError;
+
+    let type;
+    switch (desc.type) {
+      case "offer":
+        type = Ci.IPeerConnection.kActionOffer;
+        break;
+      case "answer":
+        type = Ci.IPeerConnection.kActionAnswer;
+        break;
+      default:
+        if (onError) {
+          onError.onCallback(
+            "Invalid type " + desc.type + " provided to setLocalDescription"
+          );
+          return;
+        }
+        break;
+    }
+
+    this._queueOrRun({
+      func: this._pc.setRemoteDescription,
+      args: [type, desc.sdp],
+      wait: true
+    });
+  },
+
+  updateIce: function(config, constraints, restart) {
+    return Cr.NS_ERROR_NOT_IMPLEMENTED;
+  },
+
+  addIceCandidate: function(cand) {
+    if (!cand) {
+      throw "Invalid candidate passed to addIceCandidate!";
+    }
+    if (!cand.candidate || !cand.sdpMid || !cand.sdpMLineIndex) {
+      throw "Invalid candidate passed to addIceCandidate!";
+    }
+
+    this._queueOrRun({
+      func: this._pc.addIceCandidate,
+      args: [cand.candidate, cand.sdpMid, cand.sdpMLineIndex],
+      wait: false
+    });
+  },
+
+  addStream: function(stream, constraints) {
+    // TODO: Implement constraints.
+    this._queueOrRun({
+      func: this._pc.addStream,
+      args: [stream],
+      wait: false
+    });
+  },
+
+  removeStream: function(stream) {
+    this._queueOrRun({
+      func: this._pc.removeStream,
+      args: [stream],
+      wait: false
+    });
+  },
+
+  close: function() {
+    this._queueOrRun({
+      func: this._pc.close,
+      args: [],
+      wait: false
+    });
+  }
+};
+
+// This is a seperate object because we don't want to expose it to DOM.
+function PeerConnectionObserver(dompc) {
+  this._dompc = dompc;
+}
+PeerConnectionObserver.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.IPeerConnectionObserver]),
+
+  onCreateOfferSuccess: function(offer) {
+    if (this._dompc._onCreateOfferSuccess) {
+      try {
+        this._dompc._onCreateOfferSuccess.onCallback({
+          type: "offer", sdp: offer,
+          __exposedProps__: { type: "rw", sdp: "rw" }
+        });
+      } catch(e) {}
+    }
+    this._dompc._executeNext();
+  },
+
+  onCreateOfferError: function(code) {
+    if (this._dompc._onCreateOfferFailure) {
+      try {
+        this._dompc._onCreateOfferFailure.onCallback(code);
+      } catch(e) {}
+    }
+    this._dompc._executeNext();
+  },
+
+  onCreateAnswerSuccess: function(answer) {
+    if (this._dompc._onCreateAnswerSuccess) {
+      try {
+        this._dompc._onCreateAnswerSuccess.onCallback({
+          type: "answer", sdp: answer,
+          __exposedProps__: { type: "rw", sdp: "rw" }
+        });
+      } catch(e) {}
+    }
+    this._dompc._executeNext();
+  },
+
+  onCreateAnswerError: function(code) {
+    if (this._dompc._onCreateAnswerFailure) {
+      try {
+        this._dompc._onCreateAnswerFailure.onCallback(code);
+      } catch(e) {}
+    }
+    this._dompc._executeNext();
+  },
+
+  onSetLocalDescriptionSuccess: function(code) {
+    if (this._dompc._onSetLocalDescriptionSuccess) {
+      try {
+        this._dompc._onSetLocalDescriptionSuccess.onCallback(code);
+      } catch(e) {}
+    }
+    this._dompc._executeNext();
+  },
+
+  onSetRemoteDescriptionSuccess: function(code) {
+    if (this._dompc._onSetRemoteDescriptionSuccess) {
+      try {
+        this._dompc._onSetRemoteDescriptionSuccess.onCallback(code);
+      } catch(e) {}
+    }
+    this._dompc._executeNext();
+  },
+
+  onSetLocalDescriptionError: function(code) {
+    if (this._dompc._onSetLocalDescriptionFailure) {
+      try {
+        this._dompc._onSetLocalDescriptionFailure.onCallback(code);
+      } catch(e) {}
+    }
+    this._dompc._executeNext();
+  },
+
+  onSetRemoteDescriptionError: function(code) {
+    if (this._dompc._onSetRemoteDescriptionFailure) {
+      this._dompc._onSetRemoteDescriptionFailure.onCallback(code);
+    }
+    this._dompc._executeNext();
+  },
+
+  onStateChange: function(state) {
+    if (state != Ci.IPeerConnectionObserver.kIceState) {
+      return;
+    }
+
+    let self = this;
+    let iceCb = function() {};
+    let iceGatherCb = function() {};
+    if (this._dompc.onicechange) {
+      iceCb = function(args) {
+        try {
+          self._dompc.onicechange(args);
+        } catch(e) {}
+      };
+    }
+    if (this._dompc.ongatheringchange) {
+      iceGatherCb = function(args) {
+        try {
+          self._dompc.ongatheringchange(args);
+        } catch(e) {}
+      };
+    }
+
+    switch (this._dompc._pc.iceState) {
+      case Ci.IPeerConnection.kIceGathering:
+        iceGatherCb("gathering");
+        break;
+      case Ci.IPeerConnection.kIceWaiting:
+        iceCb("starting");
+        this._dompc._executeNext();
+        break;
+      case Ci.IPeerConnection.kIceChecking:
+        iceCb("checking");
+        this._dompc._executeNext();
+        break;
+      case Ci.IPeerConnection.kIceConnected:
+        // ICE gathering complete.
+        iceCb("connected");
+        iceGatherCb("complete");
+        this._dompc._executeNext();
+        break;
+      case Ci.IPeerConnection.kIceFailed:
+        iceCb("failed");
+        break;
+      default:
+        // Unknown state!
+        break;
+    }
+  },
+
+  onAddStream: function(stream, type) {
+    if (this._dompc.onaddstream) {
+      try {
+        this._dompc.onaddstream.onCallback({
+          stream: stream, type: type,
+          __exposedProps__: { stream: "r", type: "r" }
+        });
+      } catch(e) {}
+    }
+    this._dompc._executeNext();
+  },
+
+  onRemoveStream: function(stream, type) {
+    if (this._dompc.onremovestream) {
+      try {
+        this._dompc.onremovestream.onCallback({
+          stream: stream, type: type,
+          __exposedProps__: { stream: "r", type: "r" }
+        });
+      } catch(e) {}
+    }
+    this._dompc._executeNext();
+  },
+
+  foundIceCandidate: function(cand) {
+    if (this._dompc.onicecandidate) {
+      try {
+        this._dompc.onicecandidate.onCallback({
+          candidate: cand,
+          __exposedProps__: { candidate: "rw" }
+        });
+      } catch(e) {}
+    }
+    this._dompc._executeNext();
+  }
+};
+
+let NSGetFactory = XPCOMUtils.generateNSGetFactory(
+  [IceCandidate, SessionDescription, PeerConnection]
+);
new file mode 100644
--- /dev/null
+++ b/dom/media/PeerConnection.manifest
@@ -0,0 +1,11 @@
+component {7cb2b368-b1ce-4560-acac-8e0dbda7d3d0} PeerConnection.js
+component {8c5dbd70-2c8e-4ecb-a5ad-2fc919099f01} PeerConnection.js
+component {5f21ffd9-b73f-4ba0-a685-56b4667aaf1c} PeerConnection.js
+
+contract @mozilla.org/dom/peerconnection;1 {7cb2b368-b1ce-4560-acac-8e0dbda7d3d0}
+contract @mozilla.org/dom/rtcicecandidate;1 {8c5dbd70-2c8e-4ecb-a5ad-2fc919099f01}
+contract @mozilla.org/dom/rtcsessiondescription;1 {5f21ffd9-b73f-4ba0-a685-56b4667aaf1c}
+
+category JavaScript-global-constructor mozRTCPeerConnection @mozilla.org/dom/peerconnection;1
+category JavaScript-global-constructor mozRTCIceCandidate @mozilla.org/dom/rtcicecandidate;1
+category JavaScript-global-constructor mozRTCSessionDescription @mozilla.org/dom/rtcsessiondescription;1
new file mode 100644
--- /dev/null
+++ b/dom/media/nsIDOMRTCPeerConnection.idl
@@ -0,0 +1,87 @@
+/* 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/. */
+
+#include "nsISupports.idl"
+#include "nsIDOMMediaStream.idl"
+
+[scriptable, function, uuid(eb9c563c-3b09-4565-9317-eca96ae0c538)]
+interface RTCPeerConnectionCallback : nsISupports
+{
+  void onCallback(in jsval value);
+};
+
+[scriptable, function, uuid(55546efd-287b-4460-8283-0592875b890f)]
+interface RTCPeerConnectionCallbackVoid : nsISupports
+{
+  void onCallback();
+};
+
+[scriptable, uuid(05d7375e-b024-4951-a570-c6642105ad35)]
+interface nsIDOMRTCSessionDescription : nsISupports
+{
+  attribute DOMString sdp;
+  attribute DOMString type;
+};
+
+[scriptable, uuid(df176474-e20a-4f42-a85b-b0414d634cf0)]
+interface nsIDOMRTCIceCandidate : nsISupports
+{
+  attribute DOMString candidate;
+  attribute DOMString sdpMid;
+  attribute unsigned short sdpMLineIndex;
+};
+
+/* See http://dev.w3.org/2011/webrtc/editor/webrtc.html */
+[scriptable, uuid(94628e70-e96f-4170-871c-f993a49f065a)]
+interface nsIDOMRTCPeerConnection : nsISupports
+{
+  void createOffer(in RTCPeerConnectionCallback successCallback,
+    [optional] in RTCPeerConnectionCallback failureCallback,
+    [optional] in jsval constraints);
+
+  void createAnswer(in nsIDOMRTCSessionDescription offer,
+    in RTCPeerConnectionCallback successCallback,
+    [optional] in RTCPeerConnectionCallback failureCallback,
+    [optional] in jsval constraints,
+    [optional] in bool createProvisionalAnswer);
+
+  void setLocalDescription(in nsIDOMRTCSessionDescription desc,
+    [optional] in RTCPeerConnectionCallback successCallback,
+    [optional] in RTCPeerConnectionCallback failureCallback);
+
+  void setRemoteDescription(in nsIDOMRTCSessionDescription desc,
+    [optional] in RTCPeerConnectionCallback successCallback,
+    [optional] in RTCPeerConnectionCallback failureCallback);
+
+  void updateIce([optional] in jsval configuration,
+    [optional] in jsval constraints,
+    [optional] in bool restart);
+
+  void addIceCandidate(in nsIDOMRTCIceCandidate candidate);
+
+  void addStream(in nsIDOMMediaStream stream,
+    [optional] in jsval constraints);
+
+  void removeStream(in nsIDOMMediaStream stream);
+  void close();
+
+  /* Readonly attributes */
+  readonly attribute DOMString iceState;
+  readonly attribute DOMString iceGatheringState;
+
+  readonly attribute DOMString readyState;
+  readonly attribute jsval localDescription;
+  readonly attribute jsval remoteDescription;
+
+  readonly attribute jsval localStreams; // MediaStream[]
+  readonly attribute jsval remoteStreams; // MediaStream[]
+
+  /* Event handlers. TODO: Use real EventTarget */
+  attribute RTCPeerConnectionCallback onaddstream;
+  attribute RTCPeerConnectionCallback onremovestream;
+  attribute RTCPeerConnectionCallback onicecandidate;
+  attribute RTCPeerConnectionCallback onstatechange;
+  attribute RTCPeerConnectionCallback ongatheringchange;
+  attribute RTCPeerConnectionCallback onicechange;
+};
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -536,17 +536,20 @@ var interfaceNamesInGlobalScope =
     "TCPSocket",
     "MozTimeManager",
     "MozNavigatorTime",
     "PermissionSettings",
     "DataErrorEvent",
     "DataChannel",
     "MozNetworkStatsManager",
     "MozNetworkStats",
-    "MozNetworkStatsData"
+    "MozNetworkStatsData",
+    "RTCSessionDescription",
+    "RTCIceCandidate",
+    "RTCPeerConnection"
   ]
 
 for (var i in SpecialPowers.Components.interfaces) {
   var s = i.toString();
   var name = null;
   if (s.indexOf("nsIDOM") == 0) {
     name = s.substring("nsIDOM".length);
   } else if (s.indexOf("nsI") == 0) {
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -168,16 +168,17 @@ pref("media.webm.enabled", true);
 #ifdef MOZ_DASH
 pref("media.dash.enabled", true);
 #endif
 #ifdef MOZ_GSTREAMER
 pref("media.h264.enabled", true);
 #endif
 #ifdef MOZ_WEBRTC
 pref("media.navigator.enabled", false);
+pref("media.peerconnection.enabled", false);
 #else
 #ifdef ANDROID
 pref("media.navigator.enabled", true);
 #endif
 #endif
 
 // Whether to enable Web Audio support
 pref("media.webaudio.enabled", false);