From f90c9929ee2415d07cbf2d28f9f71b0502ce88d5 Mon Sep 17 00:00:00 2001 From: Anant Narayanan Date: Thu, 25 Oct 2012 12:24:30 -0700 Subject: [PATCH] Bug 802694: Pass along constraints from PC JS module to PCImpl; r=ekr,jesup --- dom/media/PeerConnection.js | 113 ++++++++-------- dom/media/bridge/IPeerConnection.idl | 7 +- .../src/peerconnection/PeerConnectionImpl.cpp | 126 ++++++++++++++---- .../src/peerconnection/PeerConnectionImpl.h | 9 +- 4 files changed, 164 insertions(+), 91 deletions(-) diff --git a/dom/media/PeerConnection.js b/dom/media/PeerConnection.js index d072018c2a43..a31c8ec28968 100644 --- a/dom/media/PeerConnection.js +++ b/dom/media/PeerConnection.js @@ -230,22 +230,51 @@ PeerConnection.prototype = { } }, + /** + * 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 + * themselves before passing them along to C++. + */ + _validateConstraints: function(constraints) { + function isObject(obj) { + return obj && (typeof obj === "object"); + } + function isArray(obj) { + return isObject(obj) && + (Object.prototype.toString.call(obj) === "[object Array]"); + } + + if (!isObject(constraints)) { + return false; + } + if (constraints.mandatory && !isObject(constraints.mandatory)) { + return false; + } + if (constraints.optional && !isArray(constraints.optional)) { + return false; + } + + return true; + }, + createOffer: function(onSuccess, onError, constraints) { if (this._onCreateOfferSuccess) { - if (onError) { - onError.onCallback("createOffer already called"); - } - return; + throw new Error("createOffer already called"); + } + + if (!this._validateConstraints(constraints)) { + throw new Error("createOffer passed invalid constraints"); } this._onCreateOfferSuccess = onSuccess; this._onCreateOfferFailure = onError; - // TODO: Implement constraints/hints. - if (!constraints) { - constraints = ""; - } - this._queueOrRun({ func: this._pc.createOffer, args: [constraints], @@ -255,56 +284,39 @@ PeerConnection.prototype = { createAnswer: function(onSuccess, onError, constraints, provisional) { if (this._onCreateAnswerSuccess) { - if (onError) { - try { - onError.onCallback("createAnswer already called"); - } catch(e) {} - } - return; + throw new Error("createAnswer already called"); } if (!this.remoteDescription) { - if (onError) { - try { - onError.onCallback("setRemoteDescription not called"); - } catch(e) {} - } + throw new Error("setRemoteDescription not called"); } if (this.remoteDescription.type != "offer") { - if (onError) { - try { - onError.onCallback("No outstanding offer"); - } catch(e) {} - } + throw new Error("No outstanding offer"); + } + + if (!this._validateConstraints(constraints)) { + throw new Error("createAnswer passed invalid constraints"); } this._onCreateAnswerSuccess = onSuccess; this._onCreateAnswerFailure = onError; - if (!constraints) { - constraints = ""; - } if (!provisional) { provisional = false; } - // TODO: Implement provisional answer & constraints. + // TODO: Implement provisional answer. this._queueOrRun({ func: this._pc.createAnswer, - args: ["", this.remoteDescription.sdp], + args: [constraints], wait: true }); }, setLocalDescription: function(desc, onSuccess, onError) { if (this._onSetLocalDescriptionSuccess) { - if (onError) { - try { - onError.onCallback("setLocalDescription already called"); - } catch(e) {} - } - return; + throw new Error("setLocalDescription already called"); } this._onSetLocalDescriptionSuccess = onSuccess; @@ -319,14 +331,9 @@ PeerConnection.prototype = { type = Ci.IPeerConnection.kActionAnswer; break; default: - if (onError) { - try { - onError.onCallback( - "Invalid type " + desc.type + " provided to setLocalDescription" - ); - } catch(e) {} - return; - } + throw new Error( + "Invalid type " + desc.type + " provided to setLocalDescription" + ); break; } @@ -339,12 +346,7 @@ PeerConnection.prototype = { setRemoteDescription: function(desc, onSuccess, onError) { if (this._onSetRemoteDescriptionSuccess) { - if (onError) { - try { - onError.onCallback("setRemoteDescription already called"); - } catch(e) {} - } - return; + throw new Error("setRemoteDescription already called"); } this._onSetRemoteDescriptionSuccess = onSuccess; @@ -359,14 +361,9 @@ PeerConnection.prototype = { type = Ci.IPeerConnection.kActionAnswer; break; default: - if (onError) { - try { - onError.onCallback( - "Invalid type " + desc.type + " provided to setRemoteDescription" - ); - } catch(e) {} - return; - } + throw new Error( + "Invalid type " + desc.type + " provided to setRemoteDescription" + ); break; } diff --git a/dom/media/bridge/IPeerConnection.idl b/dom/media/bridge/IPeerConnection.idl index 7abd42e453b0..be2bc50801ea 100644 --- a/dom/media/bridge/IPeerConnection.idl +++ b/dom/media/bridge/IPeerConnection.idl @@ -1,5 +1,6 @@ #include "nsIThread.idl" #include "nsIDOMWindow.idl" +#include "nsIPropertyBag2.idl" interface nsIDOMMediaStream; interface nsIDOMDataChannel; @@ -51,7 +52,7 @@ interface IPeerConnectionObserver : nsISupports void foundIceCandidate(in string candidate); }; -[scriptable, uuid(cb3f0048-1009-11e2-b822-87ee49eface7)] +[scriptable, uuid(f6819246-f5af-40f2-ab82-e166d5da7ba0)] interface IPeerConnection : nsISupports { const unsigned long kHintAudio = 0x00000001; @@ -73,8 +74,8 @@ interface IPeerConnection : nsISupports [optional] in nsIThread thread); /* JSEP calls */ - void createOffer(in string hints); - void createAnswer(in string hints, in string offer); + [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); diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp index 727ee7332ef5..7629e3e32542 100644 --- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp @@ -15,14 +15,19 @@ #include "cpr_string.h" #include "cpr_stdlib.h" +#include "jsapi.h" #include "nspr.h" #include "nss.h" #include "pk11pub.h" #include "nsNetCID.h" +#include "nsIProperty.h" +#include "nsIPropertyBag2.h" #include "nsIServiceManager.h" +#include "nsISimpleEnumerator.h" #include "nsServiceManagerUtils.h" #include "nsISocketTransportService.h" + #include "nsThreadUtils.h" #include "nsProxyRelease.h" @@ -730,24 +735,91 @@ PeerConnectionImpl::NotifyDataChannel(mozilla::DataChannel *aChannel) #endif } -/* - * the Constraints UI IDL work is being done. The CreateOffer below is the one - * currently called by the signaling unit tests. +/** + * Constraints look like this: + * + * { + * "mandatory": {"foo":"hello", "bar": false, "baz": 10}, + * "optional": [{"hello":"foo"}, {"baz": false}] + * } + * + * Optional constraints are ordered, and hence in an array. This function + * converts a jsval that looks like the above into a MediaConstraints object. */ -NS_IMETHODIMP -PeerConnectionImpl::CreateOffer(const char* constraints) { - MOZ_ASSERT(constraints); +nsresult +PeerConnectionImpl::ConvertConstraints( + const JS::Value& aConstraints, MediaConstraints* aObj, JSContext* aCx) +{ + size_t i; + jsval mandatory, optional; + JSObject& constraints = aConstraints.toObject(); + + // Mandatory constraints. + if (JS_GetProperty(aCx, &constraints, "mandatory", &mandatory)) { + if (JSVAL_IS_PRIMITIVE(mandatory) && mandatory.isObject() && !JSVAL_IS_NULL(mandatory)) { + JSObject* opts = JSVAL_TO_OBJECT(mandatory); + JS::AutoIdArray mandatoryOpts(aCx, JS_Enumerate(aCx, opts)); + + // Iterate over each property. + for (i = 0; i < mandatoryOpts.length(); i++) { + jsval option, optionName; + if (JS_GetPropertyById(aCx, opts, mandatoryOpts[i], &option)) { + if (JS_IdToValue(aCx, mandatoryOpts[i], &optionName)) { + // We only support boolean constraints for now. + if (JSVAL_IS_BOOLEAN(option)) { + JSString* optionNameString = JS_ValueToString(aCx, optionName); + NS_ConvertUTF16toUTF8 stringVal(JS_GetStringCharsZ(aCx, optionNameString)); + aObj->setBooleanConstraint(stringVal.get(), JSVAL_TO_BOOLEAN(option), true); + } + } + } + } + } + } + + // Optional constraints. + if (JS_GetProperty(aCx, &constraints, "optional", &optional)) { + if (JSVAL_IS_PRIMITIVE(optional) && optional.isObject() && !JSVAL_IS_NULL(optional)) { + JSObject* opts = JSVAL_TO_OBJECT(optional); + if (JS_IsArrayObject(aCx, opts)) { + uint32_t length; + if (!JS_GetArrayLength(aCx, opts, &length)) { + return NS_ERROR_FAILURE; + } + for (i = 0; i < length; i++) { + jsval val; + JS_GetElement(aCx, opts, i, &val); + if (JSVAL_IS_PRIMITIVE(val)) { + // Extract name & value and store. + // FIXME: MediaConstraints does not support optional constraints? + } + } + } + } + } - CheckIceState(); - mRole = kRoleOfferer; // TODO(ekr@rtfm.com): Interrogate SIPCC here? - MediaConstraints aconstraints; - CreateOffer(aconstraints); return NS_OK; } NS_IMETHODIMP -PeerConnectionImpl::CreateOffer(MediaConstraints& constraints) { +PeerConnectionImpl::CreateOffer(const JS::Value& aConstraints, JSContext* aCx) +{ + CheckIceState(); + mRole = kRoleOfferer; // TODO(ekr@rtfm.com): Interrogate SIPCC here? + MediaConstraints* cs = new MediaConstraints(); + nsresult rv = ConvertConstraints(aConstraints, cs, aCx); + if (rv != NS_OK) { + return rv; + } + + return CreateOffer(*cs); +} + +// Used by unit tests and the IDL CreateOffer. +NS_IMETHODIMP +PeerConnectionImpl::CreateOffer(MediaConstraints& constraints) +{ cc_media_constraints_t* cc_constraints = nullptr; constraints.buildArray(&cc_constraints); @@ -755,27 +827,25 @@ PeerConnectionImpl::CreateOffer(MediaConstraints& constraints) { return NS_OK; } -/* - * the Constraints UI IDL work is being done. The CreateAnswer below is the one - * currently called by the signaling unit tests. - * - * The aOffer parameter needs to be removed here and in the PeerConnection IDL - */ - NS_IMETHODIMP -PeerConnectionImpl::CreateAnswer(const char* constraints, const char* aOffer) { - MOZ_ASSERT(constraints); - +PeerConnectionImpl::CreateAnswer(const JS::Value& aConstraints, JSContext* aCx) +{ CheckIceState(); mRole = kRoleAnswerer; // TODO(ekr@rtfm.com): Interrogate SIPCC here? - MediaConstraints aconstraints; - CreateAnswer(aconstraints); + + MediaConstraints* cs = new MediaConstraints(); + nsresult rv = ConvertConstraints(aConstraints, cs, aCx); + if (rv != NS_OK) { + return rv; + } + + CreateAnswer(*cs); return NS_OK; } NS_IMETHODIMP -PeerConnectionImpl::CreateAnswer(MediaConstraints& constraints) { - +PeerConnectionImpl::CreateAnswer(MediaConstraints& constraints) +{ cc_media_constraints_t* cc_constraints = nullptr; constraints.buildArray(&cc_constraints); @@ -784,7 +854,8 @@ PeerConnectionImpl::CreateAnswer(MediaConstraints& constraints) { } NS_IMETHODIMP -PeerConnectionImpl::SetLocalDescription(int32_t aAction, const char* aSDP) { +PeerConnectionImpl::SetLocalDescription(int32_t aAction, const char* aSDP) +{ if (!aSDP) { CSFLogError(logTag, "%s - aSDP is NULL", __FUNCTION__); return NS_ERROR_FAILURE; @@ -797,7 +868,8 @@ PeerConnectionImpl::SetLocalDescription(int32_t aAction, const char* aSDP) { } NS_IMETHODIMP -PeerConnectionImpl::SetRemoteDescription(int32_t action, const char* aSDP) { +PeerConnectionImpl::SetRemoteDescription(int32_t action, const char* aSDP) +{ if (!aSDP) { CSFLogError(logTag, "%s - aSDP is NULL", __FUNCTION__); return NS_ERROR_FAILURE; diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h index 5b0bab82b358..154e17ff874c 100644 --- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h @@ -396,9 +396,12 @@ public: nsPIDOMWindow* GetWindow() const { return mWindow; } - NS_IMETHODIMP CreateOffer(MediaConstraints& constraints); - - NS_IMETHODIMP CreateAnswer(MediaConstraints& constraints); + // Validate constraints and construct a MediaConstraints object + // from a JS::Value. + nsresult ConvertConstraints( + const JS::Value& aConstraints, MediaConstraints* aObj, JSContext* aCx); + NS_IMETHODIMP CreateOffer(MediaConstraints& aConstraints); + NS_IMETHODIMP CreateAnswer(MediaConstraints& aConstraints); private: PeerConnectionImpl(const PeerConnectionImpl&rhs);