Bug 825703: Stun configuration from JS for PeerConnections (IP only) r=bz,jesup
authorJan-Ivar Bruaroey <jib@mozilla.com>
Wed, 23 Jan 2013 14:21:25 -0500
changeset 119727 6b5016ab9ebbb2ec884c4d865266446adebbc162
parent 119726 0eda7bbdfedf848d425d4aebaf256c4a443fda25
child 119728 f8f4c3163cd9b8807548d765c2e869613869c45b
push id24219
push userryanvm@gmail.com
push dateThu, 24 Jan 2013 17:36:06 +0000
treeherdermozilla-central@fa969919b1bb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, jesup
bugs825703
milestone21.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 825703: Stun configuration from JS for PeerConnections (IP only) r=bz,jesup
dom/media/PeerConnection.js
dom/media/bridge/IPeerConnection.idl
dom/media/tests/mochitest/Makefile.in
dom/media/tests/mochitest/test_peerConnection_bug825703.html
dom/webidl/RTCIceServer.webidl
dom/webidl/WebIDL.mk
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
media/webrtc/signaling/test/signaling_unittests.cpp
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -1,15 +1,15 @@
 /* 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;
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = 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_MANAGER_CONTRACT = "@mozilla.org/dom/peerconnectionmanager;1";
@@ -42,17 +42,17 @@ GlobalPCList.prototype = {
                                       Ci.nsIObserver,
                                       Ci.nsISupportsWeakReference,
                                       Ci.IPeerConnectionManager
                                     ]}),
 
   _xpcom_factory: {
     createInstance: function(outer, iid) {
       if (outer) {
-        throw Components.results.NS_ERROR_NO_AGGREGATION;
+        throw Cr.NS_ERROR_NO_AGGREGATION;
       }
       return _globalPCList.QueryInterface(iid);
     }
   },
 
   addPC: function(pc) {
     let winID = pc._winID;
     if (this._list[winID]) {
@@ -249,35 +249,41 @@ PeerConnection.prototype = {
 
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIDOMRTCPeerConnection,
     Ci.nsIDOMGlobalObjectConstructor,
     Ci.nsISupportsWeakReference
   ]),
 
   // Constructor is an explicit function, because of nsIDOMGlobalObjectConstructor.
-  constructor: function(win) {
+  constructor: function(win, rtcConfig) {
     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");
+      throw new Error("RTCPeerConnection constructor already called");
     }
+    if (!rtcConfig) {
+      // TODO(jib@mozilla.com): Hardcoded server pending Mozilla one. Bug 807494
+      rtcConfig = [{ url: "stun:23.21.150.121" }];
+    }
+    this._mustValidateRTCConfiguration(rtcConfig,
+        "RTCPeerConnection constructor passed invalid RTCConfiguration");
     if (_globalPCList._networkdown) {
-      throw new Error("Can't create RTPPeerConnections when the network is down");
+      throw new Error("Can't create RTCPeerConnections when the network is down");
     }
 
     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],
+      args: [this._observer, win, rtcConfig, 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.
@@ -317,16 +323,59 @@ PeerConnection.prototype = {
         this._executeNext();
       }
     } else {
       this._pending = false;
     }
   },
 
   /**
+   * An RTCConfiguration looks like this:
+   *
+   *   [ { url:"stun:23.21.150.121" },
+   *     { url:"turn:user@turn.example.org", credential:"myPassword"} ]
+   *
+   * We check for basic structure and well-formed stun/turn urls, but not
+   * validity of servers themselves, before passing along to C++.
+   * ErrorMsg is passed in to detail which array-entry failed, if any.
+   */
+  _mustValidateRTCConfiguration: function(rtcConfig, errorMsg) {
+    function isObject(obj) {
+      return obj && (typeof obj === "object");
+    }
+    function isArray(obj) {
+      return isObject(obj) &&
+        (Object.prototype.toString.call(obj) === "[object Array]");
+    }
+    function nicerNewURI(uriStr, errorMsg) {
+      let ios = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
+      try {
+        return ios.newURI(uriStr, null, null);
+      } catch (e if (e.result == Cr.NS_ERROR_MALFORMED_URI)) {
+        throw new Error(errorMsg + " - malformed URI: " + uriStr);
+      }
+    }
+    function mustValidateServer(server) {
+      let url = nicerNewURI(server.url, errorMsg);
+      if (!(url.scheme in { stun:1, stuns:1, turn:1 })) {
+        throw new Error (errorMsg + " - improper scheme: " + url.scheme);
+      }
+      if (server.credential && isObject(server.credential)) {
+        throw new Error (errorMsg + " - invalid credential");
+      }
+    }
+    if (!isArray(rtcConfig)) {
+      throw new Error (errorMsg);
+    }
+    for (let i=0; i < rtcConfig.length; i++) {
+      mustValidateServer (rtcConfig[i], errorMsg);
+    }
+  },
+
+  /**
    * Constraints look like this:
    *
    * {
    *   mandatory: {"foo": true, "bar": 10, "baz": "boo"},
    *   optional: [{"foo": true}, {"bar": 10}]
    * }
    *
    * We check for basic structure but not the validity of the constraints
@@ -476,21 +525,21 @@ PeerConnection.prototype = {
   },
 
   updateIce: function(config, constraints, restart) {
     return Cr.NS_ERROR_NOT_IMPLEMENTED;
   },
 
   addIceCandidate: function(cand) {
     if (!cand) {
-      throw "NULL candidate passed to addIceCandidate!";
+      throw new Error ("NULL candidate passed to addIceCandidate!");
     }
 
     if (!cand.candidate || !cand.sdpMLineIndex) {
-      throw "Invalid candidate passed to addIceCandidate!";
+      throw new Error ("Invalid candidate passed to addIceCandidate!");
     }
 
     this._queueOrRun({
       func: this._pc.addIceCandidate,
       args: [cand.candidate, cand.sdpMid || "", cand.sdpMLineIndex],
       wait: false
     });
   },
--- a/dom/media/bridge/IPeerConnection.idl
+++ b/dom/media/bridge/IPeerConnection.idl
@@ -60,17 +60,17 @@ interface IPeerConnectionObserver : nsIS
   /* When SDP is parsed and a candidate line is found this method is called.
    * It should hook back into the media transport to notify it of ICE candidates
    * listed in the SDP PeerConnectionImpl does not parse ICE candidates, just
    * pulls them out of the SDP.
    */
   void foundIceCandidate(in string candidate);
 };
 
-[scriptable, uuid(c86903c7-0a2e-42b4-903c-1518f03b9ba5)]
+[scriptable, uuid(cc8327f5-66f4-42f4-820d-9a9db0474b6e)]
 interface IPeerConnection : nsISupports
 {
   const unsigned long kHintAudio = 0x00000001;
   const unsigned long kHintVideo = 0x00000002;
 
   const long kActionNone = -1;
   const long kActionOffer = 0;
   const long kActionAnswer = 1;
@@ -83,17 +83,18 @@ interface IPeerConnection : nsISupports
   const long kIceFailed = 4;
 
   /* for 'type' in DataChannelInit dictionary */
   const unsigned short kDataChannelReliable = 0;
   const unsigned short kDataChannelPartialReliableRexmit = 1;
   const unsigned short kDataChannelPartialReliableTimed = 2;
 
   /* Must be called first. Observer events will be dispatched on the thread provided */
-  void initialize(in IPeerConnectionObserver observer, in nsIDOMWindow window,
+  [implicit_jscontext] void initialize(in IPeerConnectionObserver observer, in nsIDOMWindow window,
+                  [optional] in jsval iceServers,
                   [optional] in nsIThread thread);
 
   /* JSEP calls */
   [implicit_jscontext] void createOffer(in jsval constraints);
   [implicit_jscontext] void createAnswer(in jsval constraints);
   void setLocalDescription(in long action, in string sdp);
   void setRemoteDescription(in long action, in string sdp);
 
--- a/dom/media/tests/mochitest/Makefile.in
+++ b/dom/media/tests/mochitest/Makefile.in
@@ -15,16 +15,17 @@ MOCHITEST_FILES = \
   test_getUserMedia_basicAudio.html \
   test_getUserMedia_basicVideo.html \
   test_getUserMedia_basicVideoAudio.html \
   test_peerConnection_basicAudio.html \
   test_peerConnection_basicAudioVideo.html \
   test_peerConnection_basicAudioVideoCombined.html \
   test_peerConnection_basicVideo.html \
   test_peerConnection_bug827843.html \
+  test_peerConnection_bug825703.html \
   head.js \
   mediaStreamPlayback.js \
   pc.js \
   $(NULL)
 
 # The following tests are leaking and cannot be run by default yet
 ifdef MOZ_WEBRTC_LEAKING_TESTS
 MOCHITEST_FILES += \
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_bug825703.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=825703
+-->
+<head>
+  <meta charset="utf-8">
+    <title>Bug 825703: RTCConfiguration valid/invalid permutations</title>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+    <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="application/javascript" src="head.js"></script>
+  </meta>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=825703">RTCConfiguration valid/invalid permutations</a>
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+runTest(function () {
+  var pc;
+  var pcs;
+  var exception = null;
+  try { pcs = new mozRTCPeerConnection(); } catch (e) { exception = e; }
+  ok(!exception, "mozRTCPeerConnection() succeeds");
+  exception = null;
+  try { pc = new mozRTCPeerConnection(1); } catch (e) { exception = e; }
+  ok(exception, "mozRTCPeerConnection(1) throws");
+  exception = null;
+  try { pc = new mozRTCPeerConnection({}); } catch (e) { exception = e; }
+  ok(exception, "mozRTCPeerConnection({}) throws");
+  exception = null;
+  try { pcs = new mozRTCPeerConnection([]); } catch (e) { exception = e; }
+  ok(!exception, "mozRTCPeerConnection([]) succeeds");
+  exception = null;
+  try { pc = new mozRTCPeerConnection([{}]); } catch (e) { exception = e; }
+  ok(exception, "mozRTCPeerConnection([{}]) throws");
+  exception = null;
+  try { pc = new mozRTCPeerConnection([{ url:"" }]); } catch (e) { exception = e; }
+  ok(exception, "mozRTCPeerConnection([{ url:\"\" }]) throws");
+  exception = null;
+  try { pc = new mozRTCPeerConnection([{ url:"http:0.0.0.0" }]); } catch (e) { exception = e; }
+  ok(exception, "mozRTCPeerConnection([{ url:\"http:0.0.0.0\" }]) throws");
+  exception = null;
+  try { pcs = new mozRTCPeerConnection([{ url:"stun:0.0.0.0" }, { url:"stuns:x.net", foo:"" }, { url:"turn:user@x.org:42", credential:"p" }]); } catch (e) { exception = e; }
+  ok(!exception, "mozRTCPeerConnection([{ url:\"stun:0.0.0.0\" }, { url:\"stuns:x.net\", foo:\"\" }, { url:\"turn:user@x.org:42\", credential:\"p\" }]) succeeds");
+  exception = null;
+  try { pc = new mozRTCPeerConnection([{ url:"stun:0.0.0.0", credential:{}}]); } catch (e) { exception = e; }
+  ok(exception, "mozRTCPeerConnection([{ url:\"stun:0.0.0.0\", credential:{}}]) throws");
+  pc = null;
+  pcs = null;
+  SimpleTest.finish();
+}, true);
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/webidl/RTCIceServer.webidl
@@ -0,0 +1,10 @@
+/* -*- 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/.
+ */
+
+dictionary RTCIceServer {
+    DOMString  url;
+    DOMString? credential = null;
+};
--- a/dom/webidl/WebIDL.mk
+++ b/dom/webidl/WebIDL.mk
@@ -186,16 +186,17 @@ ifdef MOZ_WEBGL
 webidl_files += \
   WebGLRenderingContext.webidl \
   $(NULL)
 endif
 
 ifdef MOZ_WEBRTC
 webidl_files += \
   MediaStreamList.webidl \
+  RTCIceServer.webidl \
   $(NULL)
 endif
 
 ifdef MOZ_B2G_RIL
 webidl_files += \
   USSDReceivedEvent.webidl \
   $(NULL)
 endif
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -21,36 +21,45 @@
 
 #include "nsNetCID.h"
 #include "nsIProperty.h"
 #include "nsIPropertyBag2.h"
 #include "nsIServiceManager.h"
 #include "nsISimpleEnumerator.h"
 #include "nsServiceManagerUtils.h"
 #include "nsISocketTransportService.h"
-
+#include "nsIConsoleService.h"
 #include "nsThreadUtils.h"
 #include "nsProxyRelease.h"
 
 #include "runnable_utils.h"
 #include "PeerConnectionCtx.h"
 #include "PeerConnectionImpl.h"
-
 #include "nsPIDOMWindow.h"
 #include "nsDOMDataChannel.h"
 #ifdef MOZILLA_INTERNAL_API
+#include "nsContentUtils.h"
+#include "nsDOMJSUtils.h"
+#include "nsIScriptError.h"
+#include "nsPrintfCString.h"
+#include "nsURLHelper.h"
+#include "nsNetUtil.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/RTCIceServerBinding.h"
 #include "MediaStreamList.h"
 #include "nsIScriptGlobalObject.h"
 #include "jsapi.h"
 #endif
 
 #ifndef USE_FAKE_MEDIA_STREAMS
 #include "MediaSegment.h"
 #endif
 
+#define ICE_PARSING "In RTCConfiguration passed to RTCPeerConnection constructor"
+
 using namespace mozilla;
 using namespace mozilla::dom;
 
 namespace mozilla {
   class DataChannel;
 }
 
 class nsIDOMDataChannel;
@@ -286,39 +295,155 @@ PeerConnectionImpl::CreateRemoteSourceSt
 
   nsRefPtr<RemoteSourceStreamInfo> remote;
   remote = new RemoteSourceStreamInfo(comstream);
   *aInfo = remote;
 
   return NS_OK;
 }
 
+#ifdef MOZILLA_INTERNAL_API
+static void
+Warn(JSContext* aCx, const nsCString& aMsg) {
+  CSFLogErrorS(logTag, "Warning: " << aMsg.get());
+  nsIScriptContext* sc = GetScriptContextFromJSContext(aCx);
+  if (sc) {
+    nsCOMPtr<nsIDocument> doc;
+    doc = nsContentUtils::GetDocumentFromScriptContext(sc);
+    if (doc) {
+      // Passing in line-# 1 hides peerconnection.js (shows document instead)
+      nsContentUtils::ReportToConsoleNonLocalized(NS_ConvertUTF8toUTF16 (aMsg),
+          nsIScriptError::warningFlag, logTag, doc, nullptr, EmptyString(), 1);
+    }
+  }
+}
+#endif
+
+/**
+ * In JS, an RTCConfiguration looks like this:
+ *
+ *    [ { url:"stun:23.21.150.121" },
+ *      { url:"turn:user@turn.example.org", credential:"myPassword"} ]
+ *
+ * This function converts an already-validated jsval that looks like the above
+ * into an RTCConfiguration object.
+ */
+nsresult
+PeerConnectionImpl::ConvertRTCConfiguration(const JS::Value& aSrc,
+                                            RTCConfiguration *aDst,
+                                            JSContext* aCx)
+{
+#ifdef MOZILLA_INTERNAL_API
+  if (!aSrc.isObject()) {
+    return NS_ERROR_FAILURE;
+  }
+  JSObject& servers = aSrc.toObject();
+  JSAutoCompartment ac(aCx, &servers);
+  uint32_t len;
+  if (!(IsArrayLike(aCx, &servers) && JS_GetArrayLength(aCx, &servers, &len))) {
+    return NS_ERROR_FAILURE;
+  }
+  for (uint32_t i = 0; i < len; i++) {
+    nsresult rv;
+    RTCIceServer server;
+    {
+      JS::Value v;
+      if (!(JS_GetElement(aCx, &servers, i, &v) && server.Init(aCx, nullptr, v))) {
+        return NS_ERROR_FAILURE;
+      }
+    }
+    if (!server.mUrl.WasPassed()) {
+      return NS_ERROR_FAILURE;
+    }
+    nsRefPtr<nsIURI> url;
+    rv = NS_NewURI(getter_AddRefs(url), server.mUrl.Value());
+    NS_ENSURE_SUCCESS(rv, rv);
+    bool isStun = false, isStuns = false, isTurn = false, isTurns = false;
+    url->SchemeIs("stun", &isStun);
+    url->SchemeIs("stuns", &isStuns);
+    url->SchemeIs("turn", &isTurn);
+    url->SchemeIs("turns", &isTurns);
+    if (!(isStun || isStuns || isTurn || isTurns)) {
+      return NS_ERROR_FAILURE;
+    }
+    nsAutoCString spec;
+    rv = url->GetSpec(spec);
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (!server.mCredential.IsEmpty()) {
+      // TODO(jib@mozilla.com): Support username, credentials & TURN servers
+      Warn(aCx, nsPrintfCString(ICE_PARSING
+          ": Credentials not yet implemented. Omitting \"%s\"", spec.get()));
+      continue;
+    }
+    if (isTurn || isTurns) {
+      Warn(aCx, nsPrintfCString(ICE_PARSING
+          ": TURN servers not yet supported. Treating as STUN: \"%s\"", spec.get()));
+    }
+    // TODO(jib@mozilla.com): Revisit once nsURI supports host and port on STUN
+    int32_t port;
+    nsAutoCString host;
+    {
+      uint32_t hostPos;
+      int32_t hostLen;
+      nsAutoCString path;
+      rv = url->GetPath(path);
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = net_GetAuthURLParser()->ParseAuthority(path.get(), path.Length(),
+                                                  nullptr,  nullptr,
+                                                  nullptr,  nullptr,
+                                                  &hostPos,  &hostLen, &port);
+      NS_ENSURE_SUCCESS(rv, rv);
+      if (!hostLen) {
+        return NS_ERROR_FAILURE;
+      }
+      path.Mid(host, hostPos, hostLen);
+    }
+    if (port == -1)
+      port = (isStuns || isTurns)? 5349 : 3478;
+    if (!aDst->addServer(host.get(), port)) {
+      Warn(aCx, nsPrintfCString(ICE_PARSING
+          ": FQDN not yet implemented (only IP-#s). Omitting \"%s\"", spec.get()));
+    }
+  }
+#endif
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 PeerConnectionImpl::Initialize(IPeerConnectionObserver* aObserver,
                                nsIDOMWindow* aWindow,
-                               nsIThread* aThread) {
+                               const JS::Value &aRTCConfiguration,
+                               nsIThread* aThread,
+                               JSContext* aCx)
+{
+  return Initialize(aObserver, aWindow, nullptr, &aRTCConfiguration, aThread, aCx);
+}
+
+nsresult
+PeerConnectionImpl::Initialize(IPeerConnectionObserver* aObserver,
+                               nsIDOMWindow* aWindow,
+                               const RTCConfiguration* aConfiguration,
+                               const JS::Value* aRTCConfiguration,
+                               nsIThread* aThread,
+                               JSContext* aCx)
+{
 #ifdef MOZILLA_INTERNAL_API
   MOZ_ASSERT(NS_IsMainThread());
 #endif
   MOZ_ASSERT(aObserver);
   MOZ_ASSERT(aThread);
+  mThread = aThread;
 
   mPCObserver = do_GetWeakReference(aObserver);
 
   nsresult res;
-
 #ifdef MOZILLA_INTERNAL_API
   // This code interferes with the C++ unit test startup code.
   nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &res);
   NS_ENSURE_SUCCESS(res, res);
-#endif
-
-  mThread = aThread;
-
-#ifdef MOZILLA_INTERNAL_API
   // Currently no standalone unit tests for DataChannel,
   // which is the user of mWindow
   MOZ_ASSERT(aWindow);
   mWindow = do_QueryInterface(aWindow);
   NS_ENSURE_STATE(mWindow);
 #endif
 
   res = PeerConnectionCtx::InitializeGlobal(mThread);
@@ -333,17 +458,24 @@ PeerConnectionImpl::Initialize(IPeerConn
     return NS_ERROR_FAILURE;
   }
 
   // Connect ICE slots.
   mMedia->SignalIceGatheringCompleted.connect(this, &PeerConnectionImpl::IceGatheringCompleted);
   mMedia->SignalIceCompleted.connect(this, &PeerConnectionImpl::IceCompleted);
 
   // Initialize the media object.
-  res = mMedia->Init();
+  if (aRTCConfiguration) {
+    RTCConfiguration ic;
+    res = ConvertRTCConfiguration(*aRTCConfiguration, &ic, aCx);
+    NS_ENSURE_SUCCESS(res, res);
+    res = mMedia->Init(ic.getServers());
+  } else {
+    res = mMedia->Init(aConfiguration->getServers());
+  }
   if (NS_FAILED(res)) {
     CSFLogErrorS(logTag, __FUNCTION__ << ": Couldn't initialize media object");
     return res;
   }
 
   // Generate a random handle
   unsigned char handle_bin[8];
   PK11_GenerateRandom(handle_bin, sizeof(handle_bin));
@@ -610,34 +742,34 @@ PeerConnectionImpl::NotifyDataChannel(al
  *
  * Optional constraints are ordered, and hence in an array. This function
  * converts a jsval that looks like the above into a MediaConstraints object.
  */
 nsresult
 PeerConnectionImpl::ConvertConstraints(
   const JS::Value& aConstraints, MediaConstraints* aObj, JSContext* aCx)
 {
-  jsval mandatory, optional;
+  JS::Value mandatory, optional;
   JSObject& constraints = aConstraints.toObject();
 
   // Mandatory constraints.  Note that we only care if the constraint array exists
   if (!JS_GetProperty(aCx, &constraints, "mandatory", &mandatory)) {
     return NS_ERROR_FAILURE;
   }
   if (!mandatory.isNullOrUndefined()) {
     if (!mandatory.isObject()) {
       return NS_ERROR_FAILURE;
     }
 
     JSObject* opts = JSVAL_TO_OBJECT(mandatory);
     JS::AutoIdArray mandatoryOpts(aCx, JS_Enumerate(aCx, opts));
 
     // Iterate over each property.
     for (size_t i = 0; i < mandatoryOpts.length(); i++) {
-      jsval option, optionName;
+      JS::Value option, optionName;
       if (!JS_GetPropertyById(aCx, opts, mandatoryOpts[i], &option) ||
           !JS_IdToValue(aCx, mandatoryOpts[i], &optionName) ||
           // We only support boolean constraints for now.
           !JSVAL_IS_BOOLEAN(option)) {
         return NS_ERROR_FAILURE;
       }
       JSString* optionNameString = JS_ValueToString(aCx, optionName);
       if (!optionNameString) {
@@ -658,18 +790,18 @@ PeerConnectionImpl::ConvertConstraints(
     }
 
     JSObject* opts = JSVAL_TO_OBJECT(optional);
     uint32_t length;
     if (!JS_IsArrayObject(aCx, opts) ||
         !JS_GetArrayLength(aCx, opts, &length)) {
       return NS_ERROR_FAILURE;
     }
-    for (size_t i = 0; i < length; i++) {
-      jsval val;
+    for (uint32_t i = 0; i < length; i++) {
+      JS::Value val;
       if (!JS_GetElement(aCx, opts, i, &val) ||
           !val.isObject()) {
         return NS_ERROR_FAILURE;
       }
       // Extract name & value and store.
       // FIXME: MediaConstraints does not support optional constraints?
     }
   }
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -41,32 +41,52 @@ namespace mozilla {
 #endif
 
 using namespace mozilla;
 
 namespace sipcc {
 
 class PeerConnectionWrapper;
 
-struct ConstraintInfo {
+struct ConstraintInfo
+{
   std::string  value;
   bool         mandatory;
 };
 typedef std::map<std::string, ConstraintInfo> constraints_map;
 
-class MediaConstraints {
+class MediaConstraints
+{
 public:
   void setBooleanConstraint(const std::string& constraint, bool enabled, bool mandatory);
 
   void buildArray(cc_media_constraints_t** constraintarray);
 
 private:
   constraints_map  mConstraints;
 };
 
+class RTCConfiguration
+{
+public:
+  bool addServer(const std::string& addr, uint16_t port)
+  {
+    NrIceStunServer* server(NrIceStunServer::Create(addr, port));
+    if (!server) {
+      return false;
+    }
+    addServer(*server);
+    return true;
+  }
+  void addServer(const NrIceStunServer& server) { mServers.push_back (server); }
+  const std::vector<NrIceStunServer>& getServers() const { return mServers; }
+private:
+  std::vector<NrIceStunServer> mServers;
+};
+
 class PeerConnectionWrapper;
 
 // Enter an API call and check that the state is OK,
 // the PC isn't closed, etc.
 #define PC_AUTO_ENTER_API_CALL(assert_ice_ready) \
     do { \
       /* do/while prevents res from conflicting with locals */    \
       nsresult res = CheckApiState(assert_ice_ready);             \
@@ -74,17 +94,18 @@ class PeerConnectionWrapper;
     } while(0)
 #define PC_AUTO_ENTER_API_CALL_NO_CHECK() CheckThread()
 
 
 class PeerConnectionImpl MOZ_FINAL : public IPeerConnection,
 #ifdef MOZILLA_INTERNAL_API
                                      public mozilla::DataChannelConnection::DataConnectionListener,
 #endif
-                                     public sigslot::has_slots<> {
+                                     public sigslot::has_slots<>
+{
 public:
   PeerConnectionImpl();
   ~PeerConnectionImpl();
 
   enum ReadyState {
     kNew,
     kNegotiating,
     kActive,
@@ -112,16 +133,18 @@ public:
     kRoleOfferer,
     kRoleAnswerer
   };
 
   NS_DECL_ISUPPORTS
   NS_DECL_IPEERCONNECTION
 
   static PeerConnectionImpl* CreatePeerConnection();
+  static nsresult ConvertRTCConfiguration(const JS::Value& aSrc,
+    RTCConfiguration *aDst, JSContext* aCx);
   static nsresult ConvertConstraints(
     const JS::Value& aConstraints, MediaConstraints* aObj, JSContext* aCx);
   static nsresult MakeMediaStream(uint32_t aHint, nsIDOMMediaStream** aStream);
 
   Role GetRole() const {
     PC_AUTO_ENTER_API_CALL_NO_CHECK();
     return mRole;
   }
@@ -179,25 +202,38 @@ public:
   // Create a fake media stream
   nsresult CreateFakeMediaStream(uint32_t hint, nsIDOMMediaStream** retval);
 
   nsPIDOMWindow* GetWindow() const {
     PC_AUTO_ENTER_API_CALL_NO_CHECK();
     return mWindow;
   }
 
+  // Initialize PeerConnection from an RTCConfiguration object.
+  nsresult Initialize(IPeerConnectionObserver* aObserver,
+                      nsIDOMWindow* aWindow,
+                      const RTCConfiguration& aConfiguration,
+                      nsIThread* aThread) {
+    return Initialize(aObserver, aWindow, &aConfiguration, nullptr, aThread, nullptr);
+  }
+
   // Validate constraints and construct a MediaConstraints object
   // from a JS::Value.
   NS_IMETHODIMP CreateOffer(MediaConstraints& aConstraints);
   NS_IMETHODIMP CreateAnswer(MediaConstraints& aConstraints);
 
 private:
   PeerConnectionImpl(const PeerConnectionImpl&rhs);
   PeerConnectionImpl& operator=(PeerConnectionImpl);
-
+  nsresult Initialize(IPeerConnectionObserver* aObserver,
+                      nsIDOMWindow* aWindow,
+                      const RTCConfiguration* aConfiguration,
+                      const JS::Value* aRTCConfiguration,
+                      nsIThread* aThread,
+                      JSContext* aCx);
   NS_IMETHODIMP CreateOfferInt(MediaConstraints& constraints);
   NS_IMETHODIMP CreateAnswerInt(MediaConstraints& constraints);
 
   nsresult CloseInt(bool aIsSynchronous);
   void ChangeReadyState(ReadyState aReadyState);
   nsresult CheckApiState(bool assert_ice_ready) const;
   void CheckThread() const {
     NS_ABORT_IF_FALSE(CheckThreadInt(), "Wrong thread");
@@ -267,17 +303,18 @@ private:
 public:
   //these are temporary until the DataChannel Listen/Connect API is removed
   unsigned short listenPort;
   unsigned short connectPort;
   char *connectStr; // XXX ownership/free
 };
 
 // This is what is returned when you acquire on a handle
-class PeerConnectionWrapper {
+class PeerConnectionWrapper
+{
  public:
   PeerConnectionWrapper(const std::string& handle);
 
   PeerConnectionImpl *impl() { return impl_; }
 
  private:
   nsRefPtr<PeerConnectionImpl> impl_;
 };
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -63,43 +63,29 @@ PeerConnectionImpl* PeerConnectionImpl::
   PeerConnectionImpl *pc = new PeerConnectionImpl();
 
   CSFLogDebugS(logTag, "Created PeerConnection: " << static_cast<void*>(pc));
 
   return pc;
 }
 
 
-nsresult PeerConnectionMedia::Init()
+nsresult PeerConnectionMedia::Init(const std::vector<NrIceStunServer>& stun_servers)
 {
   // TODO(ekr@rtfm.com): need some way to set not offerer later
   // Looks like a bug in the NrIceCtx API.
   mIceCtx = NrIceCtx::Create("PC:" + mParent->GetHandle(), true);
   if(!mIceCtx) {
     CSFLogErrorS(logTag, __FUNCTION__ << ": Failed to create Ice Context");
     return NS_ERROR_FAILURE;
   }
-
-  // Temporarily hardwire the ICE server.
-  // TODO(ekr@rtfm.com): Remove this when we have ICE configuration
-  // settings.
-  std::vector<NrIceStunServer> stun_servers;
-  ScopedDeletePtr<NrIceStunServer> server(NrIceStunServer::Create(
-      std::string((char *)"216.93.246.14"), 3478));
-  MOZ_ASSERT(server);
-  if (!server) {
-    CSFLogErrorS(logTag, __FUNCTION__ << ": Could not parse STUN server string");
-    return NS_ERROR_FAILURE;
+  nsresult rv = mIceCtx->SetStunServers(stun_servers);
+  if (NS_FAILED(rv)) {
+    return rv;
   }
-    
-  stun_servers.push_back(*server);
-  nsresult rv = mIceCtx->SetStunServers(stun_servers);
-  if (NS_FAILED(rv))
-    return rv;
-
   mIceCtx->SignalGatheringCompleted.connect(this,
                                             &PeerConnectionMedia::IceGatheringCompleted);
   mIceCtx->SignalCompleted.connect(this,
                                    &PeerConnectionMedia::IceCompleted);
 
   // Create three streams to start with.
   // One each for audio, video and DataChannel
   // TODO: this will be re-visited
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
@@ -231,17 +231,17 @@ class PeerConnectionMedia : public sigsl
       : mParent(parent),
       mLocalSourceStreamsLock(PR_NewLock()),
       mIceCtx(NULL) {}
 
   ~PeerConnectionMedia() {
     PR_DestroyLock(mLocalSourceStreamsLock);
   }
 
-  nsresult Init();
+  nsresult Init(const std::vector<mozilla::NrIceStunServer>& stun_servers);
 
   // WARNING: This destroys the object!
   void SelfDestruct();
 
   mozilla::RefPtr<mozilla::NrIceCtx> ice_ctx() const { return mIceCtx; }
 
   mozilla::RefPtr<mozilla::NrIceMediaStream> ice_media_stream(size_t i) const {
     // TODO(ekr@rtfm.com): If someone asks for a value that doesn't exist,
--- a/media/webrtc/signaling/test/signaling_unittests.cpp
+++ b/media/webrtc/signaling/test/signaling_unittests.cpp
@@ -23,16 +23,17 @@ using namespace std;
 #include "FakeMediaStreams.h"
 #include "FakeMediaStreamsImpl.h"
 #include "PeerConnectionImpl.h"
 #include "PeerConnectionCtx.h"
 #include "runnable_utils.h"
 #include "nsStaticComponents.h"
 #include "nsIDOMRTCPeerConnection.h"
 #include "nsWeakReference.h"
+#include "nricectx.h"
 
 #include "mtransport_test_utils.h"
 MtransportTestUtils *test_utils;
 
 
 
 static int kDefaultTimeout = 5000;
 
@@ -500,17 +501,19 @@ class SignalingAgent {
     ASSERT_TRUE(found > 0);
 
     pc = sipcc::PeerConnectionImpl::CreatePeerConnection();
     ASSERT_TRUE(pc);
 
     pObserver = new TestObserver(pc);
     ASSERT_TRUE(pObserver);
 
-    ASSERT_EQ(pc->Initialize(pObserver, nullptr, thread), NS_OK);
+    sipcc::RTCConfiguration cfg;
+    cfg.addServer("23.21.150.121", 3478);
+    ASSERT_EQ(pc->Initialize(pObserver, nullptr, cfg, thread), NS_OK);
 
   }
 
   void Init(nsCOMPtr<nsIThread> thread)
   {
     thread->Dispatch(
       WrapRunnable(this, &SignalingAgent::Init_m, thread),
       NS_DISPATCH_SYNC);