diff --git a/dom/presentation/DCPresentationChannelDescription.cpp b/dom/presentation/DCPresentationChannelDescription.cpp new file mode 100644 index 0000000000000..a904dfe3f4ce1 --- /dev/null +++ b/dom/presentation/DCPresentationChannelDescription.cpp @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "DCPresentationChannelDescription.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_ISUPPORTS(DCPresentationChannelDescription, + nsIPresentationChannelDescription) + +NS_IMETHODIMP +DCPresentationChannelDescription::GetType(uint8_t* aRetVal) +{ + if (NS_WARN_IF(!aRetVal)) { + return NS_ERROR_INVALID_POINTER; + } + + *aRetVal = nsIPresentationChannelDescription::TYPE_DATACHANNEL; + return NS_OK; +} + +NS_IMETHODIMP +DCPresentationChannelDescription::GetTcpAddress(nsIArray** aRetVal) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +DCPresentationChannelDescription::GetTcpPort(uint16_t* aRetVal) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +DCPresentationChannelDescription::GetDataChannelSDP(nsAString& aDataChannelSDP) +{ + aDataChannelSDP = mSDP; + return NS_OK; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/presentation/DCPresentationChannelDescription.h b/dom/presentation/DCPresentationChannelDescription.h new file mode 100644 index 0000000000000..63a058f9a9c8d --- /dev/null +++ b/dom/presentation/DCPresentationChannelDescription.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#ifndef mozilla_dom_DCPresentationChannelDescription_h +#define mozilla_dom_DCPresentationChannelDescription_h + +#include "nsIPresentationControlChannel.h" +#include "nsString.h" + +namespace mozilla { +namespace dom { + +// PresentationChannelDescription for Data Channel +class DCPresentationChannelDescription final : public nsIPresentationChannelDescription +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPRESENTATIONCHANNELDESCRIPTION + + explicit DCPresentationChannelDescription(const nsAString& aSDP) + : mSDP(aSDP) + { + } + +private: + virtual ~DCPresentationChannelDescription() = default; + + nsString mSDP; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_DCPresentationChannelDescription_h diff --git a/dom/presentation/PresentationDataChannelSessionTransport.js b/dom/presentation/PresentationDataChannelSessionTransport.js index 5da306d7e3e17..1adbad694dc53 100644 --- a/dom/presentation/PresentationDataChannelSessionTransport.js +++ b/dom/presentation/PresentationDataChannelSessionTransport.js @@ -49,11 +49,10 @@ PresentationTransportBuilder.prototype = { classID: PRESENTATIONTRANSPORTBUILDER_CID, contractID: PRESENTATIONTRANSPORTBUILDER_CONTRACTID, QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDataChannelSessionTransportBuilder, - Ci.nsIPresentationControlChannelListener, Ci.nsITimerCallback]), - buildDataChannelTransport: function(aRole, aWindow, aControlChannel, aListener) { - if (!aRole || !aWindow || !aControlChannel || !aListener) { + buildDataChannelTransport: function(aRole, aWindow, aListener) { + if (!aRole || !aWindow || !aListener) { log("buildDataChannelTransport with illegal parameters"); throw Cr.NS_ERROR_ILLEGAL_VALUE; } @@ -66,23 +65,21 @@ PresentationTransportBuilder.prototype = { log("buildDataChannelTransport with role " + aRole); this._role = aRole; this._window = aWindow; - this._controlChannel = aControlChannel.QueryInterface(Ci.nsIPresentationControlChannel); - this._controlChannel.listener = this; this._listener = aListener.QueryInterface(Ci.nsIPresentationSessionTransportBuilderListener); // TODO bug 1227053 set iceServers from |nsIPresentationDevice| this._peerConnection = new this._window.RTCPeerConnection(); - // |this._controlChannel == null| will throw since the control channel is + // |this._listener == null| will throw since the control channel is // abnormally closed. this._peerConnection.onicecandidate = aEvent => aEvent.candidate && - this._controlChannel.sendIceCandidate(JSON.stringify(aEvent.candidate)); + this._listener.sendIceCandidate(JSON.stringify(aEvent.candidate)); this._peerConnection.onnegotiationneeded = () => { log("onnegotiationneeded with role " + this._role); this._peerConnection.createOffer() .then(aOffer => this._peerConnection.setLocalDescription(aOffer)) - .then(() => this._controlChannel + .then(() => this._listener .sendOffer(new PresentationDataChannelDescription(this._peerConnection.localDescription))) .catch(e => this._reportError(e)); } @@ -169,11 +166,6 @@ PresentationTransportBuilder.prototype = { this._role = null; this._window = null; - if (this._controlChannel) { - this._controlChannel.close(aReason); - this._controlChannel = null; - } - this._listener = null; this._sessionTransport = null; @@ -202,7 +194,7 @@ PresentationTransportBuilder.prototype = { .then(aAnswer => this._peerConnection.setLocalDescription(aAnswer)) .then(() => { this._isControlChannelNeeded = false; - this._controlChannel + this._listener .sendAnswer(new PresentationDataChannelDescription(this._peerConnection.localDescription)) }).catch(e => this._reportError(e)); }, @@ -229,10 +221,6 @@ PresentationTransportBuilder.prototype = { this._peerConnection.addIceCandidate(candidate).catch(e => this._reportError(e)); }, - notifyOpened: function() { - log("notifyOpened, should be opened beforehand"); - }, - notifyClosed: function(aReason) { log("notifyClosed reason: " + aReason); @@ -241,7 +229,6 @@ PresentationTransportBuilder.prototype = { } else if (this._isControlChannelNeeded) { this._cleanup(Cr.NS_ERROR_FAILURE); } - this._controlChannel = null; }, }; diff --git a/dom/presentation/PresentationService.cpp b/dom/presentation/PresentationService.cpp index 9bda51417ab5e..2d57ef70de5d4 100644 --- a/dom/presentation/PresentationService.cpp +++ b/dom/presentation/PresentationService.cpp @@ -453,7 +453,7 @@ PresentationService::StartSession(const nsAString& aUrl, NS_ConvertUTF16toUTF8 utf8DeviceId(aDeviceId); bool hasMore; - while(NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore){ + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr isupports; rv = enumerator->GetNext(getter_AddRefs(isupports)); @@ -618,6 +618,25 @@ PresentationService::UnregisterSessionListener(const nsAString& aSessionId, return NS_OK; } +nsresult +PresentationService::RegisterTransportBuilder(const nsAString& aSessionId, + uint8_t aRole, + nsIPresentationSessionTransportBuilder* aBuilder) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aBuilder); + MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || + aRole == nsIPresentationService::ROLE_RECEIVER); + + RefPtr info = GetSessionInfo(aSessionId, aRole); + if (NS_WARN_IF(!info)) { + return NS_ERROR_NOT_AVAILABLE; + } + + info->SetBuilder(aBuilder); + return NS_OK; +} + NS_IMETHODIMP PresentationService::RegisterRespondingListener( uint64_t aWindowId, @@ -683,6 +702,23 @@ PresentationService::NotifyReceiverReady(const nsAString& aSessionId, return static_cast(info.get())->NotifyResponderReady(); } +nsresult +PresentationService::NotifyTransportClosed(const nsAString& aSessionId, + uint8_t aRole, + nsresult aReason) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aSessionId.IsEmpty()); + MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || + aRole == nsIPresentationService::ROLE_RECEIVER); + + RefPtr info = GetSessionInfo(aSessionId, aRole); + if (NS_WARN_IF(!info)) { + return NS_ERROR_NOT_AVAILABLE; + } + + return info->NotifyTransportClosed(aReason); +} + NS_IMETHODIMP PresentationService::UntrackSessionInfo(const nsAString& aSessionId, uint8_t aRole) diff --git a/dom/presentation/PresentationService.h b/dom/presentation/PresentationService.h index 5870d03dbf557..3ebb53f74578c 100644 --- a/dom/presentation/PresentationService.h +++ b/dom/presentation/PresentationService.h @@ -15,6 +15,7 @@ class nsIPresentationSessionRequest; class nsIURI; +class nsIPresentationSessionTransportBuilder; namespace mozilla { namespace dom { @@ -54,6 +55,10 @@ class PresentationService final : public nsIPresentationService const uint8_t aRole, base::ProcessId aProcessId); + nsresult RegisterTransportBuilder(const nsAString& aSessionId, + uint8_t aRole, + nsIPresentationSessionTransportBuilder* aBuilder); + private: friend class PresentationDeviceRequest; diff --git a/dom/presentation/PresentationServiceBase.h b/dom/presentation/PresentationServiceBase.h index 42408450f2bb2..d460b08535a4f 100644 --- a/dom/presentation/PresentationServiceBase.h +++ b/dom/presentation/PresentationServiceBase.h @@ -7,6 +7,7 @@ #ifndef mozilla_dom_PresentationServiceBase_h #define mozilla_dom_PresentationServiceBase_h +#include "nsClassHashtable.h" #include "nsRefPtrHashtable.h" #include "nsTArray.h" diff --git a/dom/presentation/PresentationSessionInfo.cpp b/dom/presentation/PresentationSessionInfo.cpp index b7e7f9d16a7cf..6603357871447 100644 --- a/dom/presentation/PresentationSessionInfo.cpp +++ b/dom/presentation/PresentationSessionInfo.cpp @@ -237,7 +237,7 @@ PresentationSessionInfo::Shutdown(nsresult aReason) mIsResponderReady = false; - mBuilder = nullptr; + SetBuilder(nullptr); } nsresult @@ -350,6 +350,11 @@ PresentationSessionInfo::NotifyTransportReady() mIsTransportReady = true; + // Established RTCDataChannel implies responder is ready. + if (mTransportType == nsIPresentationChannelDescription::TYPE_DATACHANNEL) { + mIsResponderReady = true; + } + // At sender side, session might not be ready at this point (waiting for // receiver's answer). Yet at receiver side, session must be ready at this // point since the data transport channel is created after the receiver page @@ -413,6 +418,13 @@ PresentationSessionInfo::NotifyData(const nsACString& aData) NS_IMETHODIMP PresentationSessionInfo::OnSessionTransport(nsIPresentationSessionTransport* transport) { + SetBuilder(nullptr); + + // The session transport is managed by content process + if (!transport) { + return NS_OK; + } + mTransport = transport; nsresult rv = mTransport->SetCallback(this); @@ -430,9 +442,34 @@ PresentationSessionInfo::OnSessionTransport(nsIPresentationSessionTransport* tra NS_IMETHODIMP PresentationSessionInfo::OnError(nsresult reason) { + SetBuilder(nullptr); return ReplyError(reason); } +NS_IMETHODIMP +PresentationSessionInfo::SendOffer(nsIPresentationChannelDescription* aOffer) +{ + return mControlChannel->SendOffer(aOffer); +} + +NS_IMETHODIMP +PresentationSessionInfo::SendAnswer(nsIPresentationChannelDescription* aAnswer) +{ + return mControlChannel->SendAnswer(aAnswer); +} + +NS_IMETHODIMP +PresentationSessionInfo::SendIceCandidate(const nsAString& candidate) +{ + return mControlChannel->SendIceCandidate(candidate); +} + +NS_IMETHODIMP +PresentationSessionInfo::Close(nsresult reason) +{ + return mControlChannel->Close(reason); +} + /** * Implementation of PresentationControllingInfo * @@ -577,13 +614,6 @@ PresentationControllingInfo::GetAddress() return NS_OK; } -NS_IMETHODIMP -PresentationControllingInfo::OnIceCandidate(const nsAString& aCandidate) -{ - MOZ_ASSERT(false, "Should not receive ICE candidates."); - return NS_ERROR_FAILURE; -} - nsresult PresentationControllingInfo::OnGetAddress(const nsACString& aAddress) { @@ -602,6 +632,23 @@ PresentationControllingInfo::OnGetAddress(const nsACString& aAddress) } // nsIPresentationControlChannelListener +NS_IMETHODIMP +PresentationControllingInfo::OnIceCandidate(const nsAString& aCandidate) +{ + if (mTransportType != nsIPresentationChannelDescription::TYPE_DATACHANNEL) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr + builder = do_QueryInterface(mBuilder); + + if (NS_WARN_IF(!builder)) { + return NS_ERROR_FAILURE; + } + + return builder->OnIceCandidate(aCandidate); +} + NS_IMETHODIMP PresentationControllingInfo::OnOffer(nsIPresentationChannelDescription* aDescription) { @@ -612,6 +659,17 @@ PresentationControllingInfo::OnOffer(nsIPresentationChannelDescription* aDescrip NS_IMETHODIMP PresentationControllingInfo::OnAnswer(nsIPresentationChannelDescription* aDescription) { + if (mTransportType == nsIPresentationChannelDescription::TYPE_DATACHANNEL) { + nsCOMPtr + builder = do_QueryInterface(mBuilder); + + if (NS_WARN_IF(!builder)) { + return NS_ERROR_FAILURE; + } + + return builder->OnAnswer(aDescription); + } + mIsResponderReady = true; // Close the control channel since it's no longer needed. @@ -639,21 +697,48 @@ PresentationControllingInfo::NotifyOpened() return GetAddress(); } - nsCOMPtr builder = - do_CreateInstance("@mozilla.org/presentation/datachanneltransportbuilder;1"); - - if (NS_WARN_IF(!builder)) { - return NS_ERROR_NOT_AVAILABLE; + nsPIDOMWindowInner* window = nullptr; + /** + * Generally transport is maintained by the chrome process. However, data + * channel should be live with the DOM , which implies RTCDataChannel in an OOP + * page should be establish in the content process. + * + * In OOP data channel transport case, |mBuilder| is hooked when the content + * process is ready to build a data channel transport, trigger by: + * 1. PresentationIPCService::StartSession (sender) + * 2. PresentationIPCService::NotifyReceiverReady (receiver). + * + * In this case, |mBuilder| would be an object of |PresentationBuilderParent| + * and set previously. Therefore, |BuildDataChannelTransport| triggers an IPC + * call to make content process establish a RTCDataChannel transport. + */ + // in-process case + if (!mBuilder) { + nsCOMPtr builder = + do_CreateInstance("@mozilla.org/presentation/datachanneltransportbuilder;1"); + if (NS_WARN_IF(!builder)) { + return NS_ERROR_NOT_AVAILABLE; + } + SetBuilder(builder); + // OOP window would be set from content process + window = GetWindow(); } - - mBuilder = builder; + // OOP case mTransportType = nsIPresentationChannelDescription::TYPE_DATACHANNEL; - return builder->BuildDataChannelTransport(nsIPresentationService::ROLE_CONTROLLER, - GetWindow(), - mControlChannel, + nsCOMPtr + dataChannelBuilder(do_QueryInterface(mBuilder)); + if (NS_WARN_IF(!dataChannelBuilder)) { + return NS_ERROR_NOT_AVAILABLE; + } + nsresult rv = dataChannelBuilder-> + BuildDataChannelTransport(nsIPresentationService::ROLE_CONTROLLER, + window, this); - + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; } NS_IMETHODIMP @@ -661,6 +746,14 @@ PresentationControllingInfo::NotifyClosed(nsresult aReason) { MOZ_ASSERT(NS_IsMainThread()); + if (mTransportType == nsIPresentationChannelDescription::TYPE_DATACHANNEL) { + nsCOMPtr + builder = do_QueryInterface(mBuilder); + if (builder) { + NS_WARN_IF(NS_FAILED(builder->NotifyClosed(aReason))); + } + } + // Unset control channel here so it won't try to re-close it in potential // subsequent |Shutdown| calls. SetControlChannel(nullptr); @@ -784,6 +877,7 @@ PresentationPresentingInfo::Shutdown(nsresult aReason) mDevice = nullptr; mLoadingCallback = nullptr; mRequesterDescription = nullptr; + mPendingCandidates.Clear(); mPromise = nullptr; } @@ -797,6 +891,11 @@ PresentationPresentingInfo::OnSessionTransport(nsIPresentationSessionTransport* return rv; } + // The session transport is managed by content process + if (!transport) { + return NS_OK; + } + // send answer for TCP session transport if (mTransportType == nsIPresentationChannelDescription::TYPE_TCP) { // Prepare and send the answer. @@ -823,10 +922,26 @@ PresentationPresentingInfo::OnSessionTransport(nsIPresentationSessionTransport* return NS_OK; } +// Delegate the pending offer and ICE candidates to builder. NS_IMETHODIMP -PresentationPresentingInfo::OnError(nsresult reason) +PresentationPresentingInfo::FlushPendingEvents(nsIPresentationDataChannelSessionTransportBuilder* builder) { - return PresentationSessionInfo::OnError(reason); + if (NS_WARN_IF(!builder)) { + return NS_ERROR_FAILURE; + } + + mHasFlushPendingEvents = true; + + if (mRequesterDescription) { + builder->OnOffer(mRequesterDescription); + } + mRequesterDescription = nullptr; + + for (size_t i = 0; i < mPendingCandidates.Length(); ++i) { + builder->OnIceCandidate(mPendingCandidates[i]); + } + mPendingCandidates.Clear(); + return NS_OK; } nsresult @@ -849,7 +964,7 @@ PresentationPresentingInfo::InitTransportAndSendAnswer() return NS_ERROR_NOT_AVAILABLE; } - mBuilder = builder; + SetBuilder(builder); mTransportType = nsIPresentationChannelDescription::TYPE_TCP; return builder->BuildTCPReceiverTransport(mRequesterDescription, this); } @@ -858,31 +973,57 @@ PresentationPresentingInfo::InitTransportAndSendAnswer() if (!Preferences::GetBool("dom.presentation.session_transport.data_channel.enable")) { return NS_ERROR_NOT_IMPLEMENTED; } - nsCOMPtr builder = - do_CreateInstance("@mozilla.org/presentation/datachanneltransportbuilder;1"); + nsPIDOMWindowInner* window = nullptr; + + /** + * Generally transport is maintained by the chrome process. However, data + * channel should be live with the DOM , which implies RTCDataChannel in an OOP + * page should be establish in the content process. + * + * In OOP data channel transport case, |mBuilder| is hooked when the content + * process is ready to build a data channel transport, trigger by: + * 1. PresentationIPCService::StartSession (sender) + * 2. PresentationIPCService::NotifyReceiverReady (receiver). + * + * In this case, |mBuilder| would be an object of |PresentationBuilderParent| + * and set previously. Therefore, |BuildDataChannelTransport| triggers an IPC + * call to make content process establish a RTCDataChannel transport. + */ + // in-process case + if (!mBuilder) { + nsCOMPtr builder = + do_CreateInstance("@mozilla.org/presentation/datachanneltransportbuilder;1"); + + if (NS_WARN_IF(!builder)) { + return NS_ERROR_NOT_AVAILABLE; + } - if (NS_WARN_IF(!builder)) { - return NS_ERROR_NOT_AVAILABLE; - } + SetBuilder(builder); - mBuilder = builder; + // OOP window would be set from content process + window = GetWindow(); + } mTransportType = nsIPresentationChannelDescription::TYPE_DATACHANNEL; - rv = builder->BuildDataChannelTransport(nsIPresentationService::ROLE_RECEIVER, - GetWindow(), - mControlChannel, - this); + + nsCOMPtr + dataChannelBuilder(do_QueryInterface(mBuilder)); + if (NS_WARN_IF(!dataChannelBuilder)) { + return NS_ERROR_NOT_AVAILABLE; + } + rv = dataChannelBuilder-> + BuildDataChannelTransport(nsIPresentationService::ROLE_RECEIVER, + window, + this); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - // delegate |onOffer| to builder - nsCOMPtr listener(do_QueryInterface(builder)); - if (NS_WARN_IF(!listener)) { - return NS_ERROR_NOT_AVAILABLE; + rv = this->FlushPendingEvents(dataChannelBuilder); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; } - - return listener->OnOffer(mRequesterDescription); + return NS_OK; } MOZ_ASSERT(false, "Unknown nsIPresentationChannelDescription type!"); @@ -943,6 +1084,10 @@ PresentationPresentingInfo::NotifyResponderReady() NS_IMETHODIMP PresentationPresentingInfo::OnOffer(nsIPresentationChannelDescription* aDescription) { + if (NS_WARN_IF(mHasFlushPendingEvents)) { + return ReplyError(NS_ERROR_DOM_OPERATION_ERR); + } + if (NS_WARN_IF(!aDescription)) { return ReplyError(NS_ERROR_DOM_OPERATION_ERR); } @@ -971,8 +1116,19 @@ PresentationPresentingInfo::OnAnswer(nsIPresentationChannelDescription* aDescrip NS_IMETHODIMP PresentationPresentingInfo::OnIceCandidate(const nsAString& aCandidate) { - MOZ_ASSERT(false, "Should not receive ICE candidates."); - return NS_ERROR_FAILURE; + if (!mBuilder && !mHasFlushPendingEvents) { + mPendingCandidates.AppendElement(nsString(aCandidate)); + return NS_OK; + } + + if (NS_WARN_IF(!mBuilder && mHasFlushPendingEvents)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr + builder = do_QueryInterface(mBuilder); + + return builder->OnIceCandidate(aCandidate); } NS_IMETHODIMP @@ -987,6 +1143,14 @@ PresentationPresentingInfo::NotifyClosed(nsresult aReason) { MOZ_ASSERT(NS_IsMainThread()); + if (mTransportType == nsIPresentationChannelDescription::TYPE_DATACHANNEL) { + nsCOMPtr + builder = do_QueryInterface(mBuilder); + if (builder) { + NS_WARN_IF(NS_FAILED(builder->NotifyClosed(aReason))); + } + } + // Unset control channel here so it won't try to re-close it in potential // subsequent |Shutdown| calls. SetControlChannel(nullptr); diff --git a/dom/presentation/PresentationSessionInfo.h b/dom/presentation/PresentationSessionInfo.h index f7ac2d24c4673..7b115bf7761a4 100644 --- a/dom/presentation/PresentationSessionInfo.h +++ b/dom/presentation/PresentationSessionInfo.h @@ -77,6 +77,11 @@ class PresentationSessionInfo : public nsIPresentationSessionTransportCallback mDevice = aDevice; } + void SetBuilder(nsIPresentationSessionTransportBuilder* aBuilder) + { + mBuilder = aBuilder; + } + already_AddRefed GetDevice() const { nsCOMPtr device = mDevice; @@ -114,7 +119,10 @@ class PresentationSessionInfo : public nsIPresentationSessionTransportCallback nsresult ReplySuccess(); - virtual bool IsSessionReady() = 0; + bool IsSessionReady() + { + return mIsResponderReady && mIsTransportReady; + } virtual nsresult UntrackFromService(); @@ -186,18 +194,6 @@ class PresentationControllingInfo final : public PresentationSessionInfo nsresult OnGetAddress(const nsACString& aAddress); nsCOMPtr mServerSocket; - -protected: - bool IsSessionReady() override - { - if (mTransportType == nsIPresentationChannelDescription::TYPE_TCP) { - return mIsResponderReady && mIsTransportReady; - } else if (mTransportType == nsIPresentationChannelDescription::TYPE_DATACHANNEL) { - // Established RTCDataChannel implies responder is ready. - return mIsTransportReady; - } - return false; - } }; // Session info with presenting browsing context (receiver side) behaviors. @@ -208,7 +204,6 @@ class PresentationPresentingInfo final : public PresentationSessionInfo public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIPRESENTATIONCONTROLCHANNELLISTENER - NS_DECL_NSIPRESENTATIONSESSIONTRANSPORTBUILDERLISTENER NS_DECL_NSITIMERCALLBACK PresentationPresentingInfo(const nsAString& aUrl, @@ -226,6 +221,8 @@ class PresentationPresentingInfo final : public PresentationSessionInfo nsresult NotifyResponderReady(); + NS_IMETHODIMP OnSessionTransport(nsIPresentationSessionTransport* transport) override; + void ResolvedCallback(JSContext* aCx, JS::Handle aValue) override; void RejectedCallback(JSContext* aCx, JS::Handle aValue) override; @@ -250,20 +247,19 @@ class PresentationPresentingInfo final : public PresentationSessionInfo nsresult UntrackFromService() override; + NS_IMETHODIMP + FlushPendingEvents(nsIPresentationDataChannelSessionTransportBuilder* builder); + + bool mHasFlushPendingEvents = false; RefPtr mLoadingCallback; nsCOMPtr mTimer; nsCOMPtr mRequesterDescription; + nsTArray mPendingCandidates; RefPtr mPromise; // The content parent communicating with the content process which the OOP // receiver page belongs to. nsCOMPtr mContentParent; - -protected: - bool IsSessionReady() override - { - return mIsResponderReady && mIsTransportReady; - } }; } // namespace dom diff --git a/dom/presentation/PresentationTCPSessionTransport.cpp b/dom/presentation/PresentationTCPSessionTransport.cpp index a2cd9b3a0b61c..4e1ec8955255b 100644 --- a/dom/presentation/PresentationTCPSessionTransport.cpp +++ b/dom/presentation/PresentationTCPSessionTransport.cpp @@ -68,12 +68,13 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(PresentationTCPSessionTransport) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PresentationTCPSessionTransport) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPresentationSessionTransport) + NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback) NS_INTERFACE_MAP_ENTRY(nsIPresentationSessionTransport) - NS_INTERFACE_MAP_ENTRY(nsITransportEventSink) + NS_INTERFACE_MAP_ENTRY(nsIPresentationSessionTransportBuilder) NS_INTERFACE_MAP_ENTRY(nsIPresentationTCPSessionTransportBuilder) - NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback) - NS_INTERFACE_MAP_ENTRY(nsIStreamListener) NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) + NS_INTERFACE_MAP_ENTRY(nsIStreamListener) + NS_INTERFACE_MAP_ENTRY(nsITransportEventSink) NS_INTERFACE_MAP_END PresentationTCPSessionTransport::PresentationTCPSessionTransport() diff --git a/dom/presentation/interfaces/nsIPresentationControlChannel.idl b/dom/presentation/interfaces/nsIPresentationControlChannel.idl index 5883470c30ea7..767b3f1e8965e 100644 --- a/dom/presentation/interfaces/nsIPresentationControlChannel.idl +++ b/dom/presentation/interfaces/nsIPresentationControlChannel.idl @@ -54,7 +54,8 @@ interface nsIPresentationControlChannelListener: nsISupports void onIceCandidate(in DOMString candidate); /* - * The callback for notifying channel opened. + * The callback for notifying channel opened. This should be async called + * after nsIPresentationDevice::establishControlChannel. */ void notifyOpened(); diff --git a/dom/presentation/interfaces/nsIPresentationService.idl b/dom/presentation/interfaces/nsIPresentationService.idl index 44d51ed3ef7ed..a995186403272 100644 --- a/dom/presentation/interfaces/nsIPresentationService.idl +++ b/dom/presentation/interfaces/nsIPresentationService.idl @@ -168,6 +168,17 @@ interface nsIPresentationService : nsISupports void notifyReceiverReady(in DOMString sessionId, [optional] in unsigned long long windowId); + /* + * Notify the transport is closed + * + * @param sessionId: An ID to identify presentation session. + * @param role: Identify the function called by controller or receiver. + * @param reason: the error message. NS_OK indicates it is closed normally. + */ + void NotifyTransportClosed(in DOMString sessionId, + in uint8_t role, + in nsresult reason); + /* * Untrack the relevant info about the presentation session if there's any. * diff --git a/dom/presentation/interfaces/nsIPresentationSessionTransportBuilder.idl b/dom/presentation/interfaces/nsIPresentationSessionTransportBuilder.idl index 1418eb9096087..feb4e93f729fd 100644 --- a/dom/presentation/interfaces/nsIPresentationSessionTransportBuilder.idl +++ b/dom/presentation/interfaces/nsIPresentationSessionTransportBuilder.idl @@ -15,8 +15,12 @@ interface nsIPresentationSessionTransportBuilderListener : nsISupports { // Should set |transport.callback| in |onSessionTransport|. void onSessionTransport(in nsIPresentationSessionTransport transport); - void onError(in nsresult reason); + + void sendOffer(in nsIPresentationChannelDescription offer); + void sendAnswer(in nsIPresentationChannelDescription answer); + void sendIceCandidate(in DOMString candidate); + void close(in nsresult reason); }; [scriptable, uuid(2fdbe67d-80f9-48dc-8237-5bef8fa19801)] @@ -55,8 +59,13 @@ interface nsIPresentationDataChannelSessionTransportBuilder : nsIPresentationSes * |aControlChannel| should be called before calling * |buildDataChannelTransport|. */ - void buildDataChannelTransport(in uint8_t aType, + void buildDataChannelTransport(in uint8_t aRole, in mozIDOMWindow aWindow, - in nsIPresentationControlChannel aControlChannel, in nsIPresentationSessionTransportBuilderListener aListener); + + // Bug 1275150 - Merge TCP builder with the following APIs + void onOffer(in nsIPresentationChannelDescription offer); + void onAnswer(in nsIPresentationChannelDescription answer); + void onIceCandidate(in DOMString candidate); + void notifyClosed(in nsresult reason); }; diff --git a/dom/presentation/ipc/PPresentation.ipdl b/dom/presentation/ipc/PPresentation.ipdl index 174990d56056a..4314b534c12c3 100644 --- a/dom/presentation/ipc/PPresentation.ipdl +++ b/dom/presentation/ipc/PPresentation.ipdl @@ -6,6 +6,7 @@ include protocol PContent; include protocol PPresentationRequest; +include protocol PPresentationBuilder; include InputStreamParams; @@ -52,6 +53,7 @@ union PresentationIPCRequest sync protocol PPresentation { manager PContent; + manages PPresentationBuilder; manages PPresentationRequest; child: @@ -62,6 +64,8 @@ child: async NotifyMessage(nsString aSessionId, nsCString aData); async NotifySessionConnect(uint64_t aWindowId, nsString aSessionId); + async PPresentationBuilder(nsString aSessionId, uint8_t aRole); + parent: async __delete__(); @@ -77,6 +81,7 @@ parent: async PPresentationRequest(PresentationIPCRequest aRequest); async NotifyReceiverReady(nsString aSessionId, uint64_t aWindowId); + async NotifyTransportClosed(nsString aSessionId, uint8_t aRole, nsresult aReason); }; } // namespace dom diff --git a/dom/presentation/ipc/PPresentationBuilder.ipdl b/dom/presentation/ipc/PPresentationBuilder.ipdl new file mode 100644 index 0000000000000..e32b02e8f3189 --- /dev/null +++ b/dom/presentation/ipc/PPresentationBuilder.ipdl @@ -0,0 +1,34 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 protocol PPresentation; + +namespace mozilla { +namespace dom { + +async protocol PPresentationBuilder +{ + manager PPresentation; + +parent: + async SendOffer(nsString aSDP); + async SendAnswer(nsString aSDP); + async SendIceCandidate(nsString aCandidate); + async Close(nsresult aReason); + + async OnSessionTransport(); + async OnSessionTransportError(nsresult aReason); + +child: + async OnOffer(nsString aSDP); + async OnAnswer(nsString aSDP); + async OnIceCandidate(nsString aCandidate); + + async __delete__(); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/presentation/ipc/PresentationBuilderChild.cpp b/dom/presentation/ipc/PresentationBuilderChild.cpp new file mode 100644 index 0000000000000..0a309dce6f139 --- /dev/null +++ b/dom/presentation/ipc/PresentationBuilderChild.cpp @@ -0,0 +1,177 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 "DCPresentationChannelDescription.h" +#include "PresentationBuilderChild.h" +#include "PresentationIPCService.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_ISUPPORTS(PresentationBuilderChild, + nsIPresentationSessionTransportBuilderListener) + +PresentationBuilderChild::PresentationBuilderChild(const nsString& aSessionId, + uint8_t aRole) + : mSessionId(aSessionId) + , mRole(aRole) +{ +} + +nsresult PresentationBuilderChild::Init() +{ + mBuilder = do_CreateInstance("@mozilla.org/presentation/datachanneltransportbuilder;1"); + if (NS_WARN_IF(!mBuilder)) { + return NS_ERROR_NOT_AVAILABLE; + } + + uint64_t windowId = 0; + + nsCOMPtr service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (NS_WARN_IF(NS_FAILED(service->GetWindowIdBySessionId(mSessionId, &windowId)))) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsPIDOMWindowInner* window = nsGlobalWindow::GetInnerWindowWithId(windowId)->AsInner(); + if (NS_WARN_IF(!window)) { + return NS_ERROR_NOT_AVAILABLE; + } + + return mBuilder->BuildDataChannelTransport(mRole, window, this); +} + +void +PresentationBuilderChild::ActorDestroy(ActorDestroyReason aWhy) +{ + mBuilder = nullptr; + mActorDestroyed = true; +} + +bool +PresentationBuilderChild::RecvOnOffer(const nsString& aSDP) +{ + if (NS_WARN_IF(!mBuilder)) { + return false; + } + RefPtr description = + new DCPresentationChannelDescription(aSDP); + + if (NS_WARN_IF(NS_FAILED(mBuilder->OnOffer(description)))) { + return false; + } + return true; +} + +bool +PresentationBuilderChild::RecvOnAnswer(const nsString& aSDP) +{ + if (NS_WARN_IF(!mBuilder)) { + return false; + } + RefPtr description = + new DCPresentationChannelDescription(aSDP); + + if (NS_WARN_IF(NS_FAILED(mBuilder->OnAnswer(description)))) { + return false; + } + return true; +} + +bool +PresentationBuilderChild::RecvOnIceCandidate(const nsString& aCandidate) +{ + if (NS_WARN_IF(NS_FAILED(mBuilder->OnIceCandidate(aCandidate)))) { + return false; + } + return true; +} + +// nsPresentationSessionTransportBuilderListener +NS_IMETHODIMP +PresentationBuilderChild::OnSessionTransport(nsIPresentationSessionTransport* aTransport) +{ + if (NS_WARN_IF(mActorDestroyed || !SendOnSessionTransport())){ + return NS_ERROR_FAILURE; + } + + nsCOMPtr service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + NS_WARN_IF(!service); + if (service) { + NS_WARN_IF(NS_FAILED(static_cast(service.get())-> + NotifySessionTransport(mSessionId, mRole, aTransport))); + } + mBuilder = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +PresentationBuilderChild::OnError(nsresult reason) +{ + mBuilder = nullptr; + + if (NS_WARN_IF(mActorDestroyed || !SendOnSessionTransportError(reason))){ + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +PresentationBuilderChild::SendOffer(nsIPresentationChannelDescription* aOffer) +{ + nsAutoString SDP; + nsresult rv = aOffer->GetDataChannelSDP(SDP); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (NS_WARN_IF(mActorDestroyed || !SendSendOffer(SDP))){ + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +PresentationBuilderChild::SendAnswer(nsIPresentationChannelDescription* aAnswer) +{ + nsAutoString SDP; + nsresult rv = aAnswer->GetDataChannelSDP(SDP); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (NS_WARN_IF(mActorDestroyed || !SendSendAnswer(SDP))){ + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +PresentationBuilderChild::SendIceCandidate(const nsAString& candidate) +{ + if (NS_WARN_IF(mActorDestroyed || !SendSendIceCandidate(nsString(candidate)))) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +PresentationBuilderChild::Close(nsresult reason) +{ + if (NS_WARN_IF(mActorDestroyed || !SendClose(reason))) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +} // namespace dom +} // namespace mozilla + diff --git a/dom/presentation/ipc/PresentationBuilderChild.h b/dom/presentation/ipc/PresentationBuilderChild.h new file mode 100644 index 0000000000000..5ada53ab7e868 --- /dev/null +++ b/dom/presentation/ipc/PresentationBuilderChild.h @@ -0,0 +1,48 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_PresentationBuilderChild_h +#define mozilla_dom_PresentationBuilderChild_h + +#include "mozilla/dom/PPresentationBuilderChild.h" +#include "nsIPresentationSessionTransportBuilder.h" + +namespace mozilla { +namespace dom { + +class PresentationBuilderChild final: public PPresentationBuilderChild + , public nsIPresentationSessionTransportBuilderListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPRESENTATIONSESSIONTRANSPORTBUILDERLISTENER + + explicit PresentationBuilderChild(const nsString& aSessionId, + uint8_t aRole); + + nsresult Init(); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + virtual bool RecvOnOffer(const nsString& aSDP) override; + + virtual bool RecvOnAnswer(const nsString& aSDP) override; + + virtual bool RecvOnIceCandidate(const nsString& aCandidate) override; + +private: + virtual ~PresentationBuilderChild() = default; + + nsString mSessionId; + uint8_t mRole; + bool mActorDestroyed = false; + nsCOMPtr mBuilder; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_PresentationBuilderChild_h diff --git a/dom/presentation/ipc/PresentationBuilderParent.cpp b/dom/presentation/ipc/PresentationBuilderParent.cpp new file mode 100644 index 0000000000000..de8e9759ce2c8 --- /dev/null +++ b/dom/presentation/ipc/PresentationBuilderParent.cpp @@ -0,0 +1,177 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 "DCPresentationChannelDescription.h" +#include "PresentationBuilderParent.h" +#include "PresentationSessionInfo.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_ISUPPORTS(PresentationBuilderParent, + nsIPresentationSessionTransportBuilder, + nsIPresentationDataChannelSessionTransportBuilder) + +PresentationBuilderParent::PresentationBuilderParent(PresentationParent* aParent) + : mParent(aParent) +{ + MOZ_COUNT_CTOR(PresentationBuilderParent); +} + +PresentationBuilderParent::~PresentationBuilderParent() +{ + MOZ_COUNT_DTOR(PresentationBuilderParent); + + if (mNeedDestroyActor) { + NS_WARN_IF(!Send__delete__(this)); + } +} + +NS_IMETHODIMP +PresentationBuilderParent::BuildDataChannelTransport( + uint8_t aRole, + mozIDOMWindow* aWindow, /* unused */ + nsIPresentationSessionTransportBuilderListener* aListener) +{ + mBuilderListener = aListener; + + RefPtr info = static_cast(aListener); + if (NS_WARN_IF(!mParent->SendPPresentationBuilderConstructor(this, + nsString(info->GetSessionId()), + aRole))) { + return NS_ERROR_FAILURE; + } + mNeedDestroyActor = true; + mParent = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +PresentationBuilderParent::OnIceCandidate(const nsAString& aCandidate) +{ + if (NS_WARN_IF(!SendOnIceCandidate(nsString(aCandidate)))){ + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +PresentationBuilderParent::OnOffer(nsIPresentationChannelDescription* aDescription) +{ + nsAutoString SDP; + nsresult rv = aDescription->GetDataChannelSDP(SDP); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (NS_WARN_IF(!SendOnOffer(SDP))){ + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +PresentationBuilderParent::OnAnswer(nsIPresentationChannelDescription* aDescription) +{ + nsAutoString SDP; + nsresult rv = aDescription->GetDataChannelSDP(SDP); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (NS_WARN_IF(!SendOnAnswer(SDP))){ + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +PresentationBuilderParent::NotifyClosed(nsresult aReason) +{ + return NS_OK; +} + +void +PresentationBuilderParent::ActorDestroy(ActorDestroyReason aWhy) +{ + mNeedDestroyActor = false; + mParent = nullptr; + mBuilderListener = nullptr; +} + +bool +PresentationBuilderParent::RecvSendOffer(const nsString& aSDP) +{ + RefPtr description = + new DCPresentationChannelDescription(aSDP); + if (NS_WARN_IF(!mBuilderListener || + NS_FAILED(mBuilderListener->SendOffer(description)))) { + return false; + } + return true; +} + +bool +PresentationBuilderParent::RecvSendAnswer(const nsString& aSDP) +{ + RefPtr description = + new DCPresentationChannelDescription(aSDP); + if (NS_WARN_IF(!mBuilderListener || + NS_FAILED(mBuilderListener->SendAnswer(description)))) { + return false; + } + return true; +} + +bool +PresentationBuilderParent::RecvSendIceCandidate(const nsString& aCandidate) +{ + if (NS_WARN_IF(!mBuilderListener || + NS_FAILED(mBuilderListener->SendIceCandidate(aCandidate)))) { + return false; + } + return true; +} + +bool +PresentationBuilderParent::RecvClose(const nsresult& aReason) +{ + if (NS_WARN_IF(!mBuilderListener || + NS_FAILED(mBuilderListener->Close(aReason)))) { + return false; + } + return true; +} + +// Delegate to nsIPresentationSessionTransportBuilderListener +bool +PresentationBuilderParent::RecvOnSessionTransport() +{ + // To avoid releasing |this| in this method + NS_DispatchToMainThread(NS_NewRunnableFunction([this]() -> void { + NS_WARN_IF(!mBuilderListener || + NS_FAILED(mBuilderListener->OnSessionTransport(nullptr))); + })); + + nsCOMPtr + callback(do_QueryInterface(mBuilderListener)); + + callback->NotifyTransportReady(); + return true; +} + +bool +PresentationBuilderParent::RecvOnSessionTransportError(const nsresult& aReason) +{ + if (NS_WARN_IF(!mBuilderListener || + NS_FAILED(mBuilderListener->OnError(aReason)))) { + return false; + } + return true; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/presentation/ipc/PresentationBuilderParent.h b/dom/presentation/ipc/PresentationBuilderParent.h new file mode 100644 index 0000000000000..89b5c054c8f8f --- /dev/null +++ b/dom/presentation/ipc/PresentationBuilderParent.h @@ -0,0 +1,51 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_PresentationBuilderParent_h__ +#define mozilla_dom_PresentationBuilderParent_h__ + +#include "mozilla/dom/PPresentationBuilderParent.h" +#include "PresentationParent.h" +#include "nsIPresentationSessionTransportBuilder.h" + +namespace mozilla { +namespace dom { + +class PresentationBuilderParent final: public PPresentationBuilderParent + , public nsIPresentationDataChannelSessionTransportBuilder +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPRESENTATIONSESSIONTRANSPORTBUILDER + NS_DECL_NSIPRESENTATIONDATACHANNELSESSIONTRANSPORTBUILDER + + explicit PresentationBuilderParent(PresentationParent* aParent); + + virtual bool RecvSendOffer(const nsString& aSDP) override; + + virtual bool RecvSendAnswer(const nsString& aSDP) override; + + virtual bool RecvSendIceCandidate(const nsString& aCandidate) override; + + virtual bool RecvClose(const nsresult& aReason) override; + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + virtual bool RecvOnSessionTransport() override; + + virtual bool RecvOnSessionTransportError(const nsresult& aReason) override; + +private: + virtual ~PresentationBuilderParent(); + bool mNeedDestroyActor = false; + RefPtr mParent; + nsCOMPtr mBuilderListener; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_PresentationBuilderParent_h__ diff --git a/dom/presentation/ipc/PresentationChild.cpp b/dom/presentation/ipc/PresentationChild.cpp index abb700238c6d9..a5b5b615fbcdc 100644 --- a/dom/presentation/ipc/PresentationChild.cpp +++ b/dom/presentation/ipc/PresentationChild.cpp @@ -4,7 +4,9 @@ * 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 "DCPresentationChannelDescription.h" #include "mozilla/StaticPtr.h" +#include "PresentationBuilderChild.h" #include "PresentationChild.h" #include "PresentationIPCService.h" #include "nsThreadUtils.h" @@ -57,6 +59,35 @@ PresentationChild::DeallocPPresentationRequestChild(PPresentationRequestChild* a return true; } +bool PresentationChild::RecvPPresentationBuilderConstructor( + PPresentationBuilderChild* aActor, + const nsString& aSessionId, + const uint8_t& aRole) +{ + // Child will build the session transport + PresentationBuilderChild* actor = static_cast(aActor); + return NS_WARN_IF(NS_FAILED(actor->Init())) ? false : true; +} + +PPresentationBuilderChild* +PresentationChild::AllocPPresentationBuilderChild(const nsString& aSessionId, + const uint8_t& aRole) +{ + RefPtr actor + = new PresentationBuilderChild(aSessionId, aRole); + + return actor.forget().take(); +} + +bool +PresentationChild::DeallocPPresentationBuilderChild(PPresentationBuilderChild* aActor) +{ + RefPtr actor = + dont_AddRef(static_cast(aActor)); + return true; +} + + bool PresentationChild::RecvNotifyAvailableChange(const bool& aAvailable) { diff --git a/dom/presentation/ipc/PresentationChild.h b/dom/presentation/ipc/PresentationChild.h index 5ba8ac6b8f32d..93128d6faf728 100644 --- a/dom/presentation/ipc/PresentationChild.h +++ b/dom/presentation/ipc/PresentationChild.h @@ -7,6 +7,7 @@ #ifndef mozilla_dom_PresentationChild_h #define mozilla_dom_PresentationChild_h +#include "mozilla/dom/PPresentationBuilderChild.h" #include "mozilla/dom/PPresentationChild.h" #include "mozilla/dom/PPresentationRequestChild.h" @@ -31,6 +32,16 @@ class PresentationChild final : public PPresentationChild virtual bool DeallocPPresentationRequestChild(PPresentationRequestChild* aActor) override; + bool RecvPPresentationBuilderConstructor(PPresentationBuilderChild* aActor, + const nsString& aSessionId, + const uint8_t& aRole) override; + + virtual PPresentationBuilderChild* + AllocPPresentationBuilderChild(const nsString& aSessionId, const uint8_t& aRole) override; + + virtual bool + DeallocPPresentationBuilderChild(PPresentationBuilderChild* aActor) override; + virtual bool RecvNotifyAvailableChange(const bool& aAvailable) override; @@ -50,7 +61,7 @@ class PresentationChild final : public PPresentationChild private: virtual ~PresentationChild(); - bool mActorDestroyed; + bool mActorDestroyed = false; RefPtr mService; }; @@ -70,7 +81,7 @@ class PresentationRequestChild final : public PPresentationRequestChild private: virtual ~PresentationRequestChild(); - bool mActorDestroyed; + bool mActorDestroyed = false; nsCOMPtr mCallback; }; diff --git a/dom/presentation/ipc/PresentationContentSessionInfo.cpp b/dom/presentation/ipc/PresentationContentSessionInfo.cpp new file mode 100644 index 0000000000000..72a4b63f30a3f --- /dev/null +++ b/dom/presentation/ipc/PresentationContentSessionInfo.cpp @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsServiceManagerUtils.h" +#include "PresentationContentSessionInfo.h" +#include "PresentationIPCService.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_ISUPPORTS(PresentationContentSessionInfo, + nsIPresentationSessionTransportCallback); + +nsresult +PresentationContentSessionInfo::Init() { + if (NS_WARN_IF(NS_FAILED(mTransport->SetCallback(this)))) { + return NS_ERROR_NOT_AVAILABLE; + } + if (NS_WARN_IF(NS_FAILED(mTransport->EnableDataNotification()))) { + return NS_ERROR_NOT_AVAILABLE; + } + return NS_OK; +} + +nsresult +PresentationContentSessionInfo::Send(const nsAString& aData) +{ + return mTransport->Send(aData); +} + +nsresult +PresentationContentSessionInfo::Close(nsresult aReason) +{ + return mTransport->Close(aReason); +} + +// nsIPresentationSessionTransportCallback +NS_IMETHODIMP +PresentationContentSessionInfo::NotifyTransportReady() +{ + // do nothing since |onSessionTransport| implies this + return NS_OK; +} + +NS_IMETHODIMP +PresentationContentSessionInfo::NotifyTransportClosed(nsresult aReason) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // Nullify |mTransport| here so it won't try to re-close |mTransport| in + // potential subsequent |Shutdown| calls. + mTransport = nullptr; + nsCOMPtr service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + return NS_ERROR_NOT_AVAILABLE; + } + return static_cast(service.get())-> + NotifyTransportClosed(mSessionId, mRole, aReason); +} + +NS_IMETHODIMP +PresentationContentSessionInfo::NotifyData(const nsACString& aData) +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + return NS_ERROR_NOT_AVAILABLE; + } + return static_cast(service.get())-> + NotifyMessage(mSessionId, aData); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/presentation/ipc/PresentationContentSessionInfo.h b/dom/presentation/ipc/PresentationContentSessionInfo.h new file mode 100644 index 0000000000000..d3848dc37fdaf --- /dev/null +++ b/dom/presentation/ipc/PresentationContentSessionInfo.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_PresentationContentSessionInfo_h +#define mozilla_dom_PresentationContentSessionInfo_h + +#include "nsCOMPtr.h" +#include "nsIPresentationSessionTransport.h" + +namespace mozilla { +namespace dom { + +/** + * PresentationContentSessionInfo manages nsIPresentationSessionTransport and + * delegates the callbacks to PresentationIPCService. Only lives in content + * process. + */ +class PresentationContentSessionInfo final : public nsIPresentationSessionTransportCallback +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPRESENTATIONSESSIONTRANSPORTCALLBACK + + PresentationContentSessionInfo(const nsAString& aSessionId, + uint8_t aRole, + nsIPresentationSessionTransport* aTransport) + : mSessionId(aSessionId) + , mRole(aRole) + , mTransport(aTransport) + { + MOZ_ASSERT(XRE_IsContentProcess()); + MOZ_ASSERT(!aSessionId.IsEmpty()); + MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || + aRole == nsIPresentationService::ROLE_RECEIVER); + MOZ_ASSERT(aTransport); + } + + nsresult Init(); + + nsresult Send(const nsAString& aData); + + nsresult Close(nsresult aReason); + +private: + virtual ~PresentationContentSessionInfo() {} + + nsString mSessionId; + uint8_t mRole; + nsCOMPtr mTransport; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_PresentationContentSessionInfo_h diff --git a/dom/presentation/ipc/PresentationIPCService.cpp b/dom/presentation/ipc/PresentationIPCService.cpp index 56e77c9aee6b2..36e43cb7a2a92 100644 --- a/dom/presentation/ipc/PresentationIPCService.cpp +++ b/dom/presentation/ipc/PresentationIPCService.cpp @@ -11,6 +11,7 @@ #include "nsIPresentationListener.h" #include "PresentationCallbacks.h" #include "PresentationChild.h" +#include "PresentationContentSessionInfo.h" #include "PresentationIPCService.h" using namespace mozilla; @@ -44,6 +45,7 @@ PresentationIPCService::~PresentationIPCService() mAvailabilityListeners.Clear(); mSessionListeners.Clear(); + mSessionInfos.Clear(); sPresentationChild = nullptr; } @@ -74,6 +76,12 @@ PresentationIPCService::SendSessionMessage(const nsAString& aSessionId, MOZ_ASSERT(!aSessionId.IsEmpty()); MOZ_ASSERT(!aData.IsEmpty()); + RefPtr info; + // data channel session transport is maintained by content process + if (mSessionInfos.Get(aSessionId, getter_AddRefs(info))) { + return info->Send(aData); + } + return SendRequest(nullptr, SendSessionMessageRequest(nsString(aSessionId), aRole, nsString(aData))); @@ -86,9 +94,20 @@ PresentationIPCService::CloseSession(const nsAString& aSessionId, { MOZ_ASSERT(!aSessionId.IsEmpty()); - return SendRequest(nullptr, CloseSessionRequest(nsString(aSessionId), - aRole, - aClosedReason)); + nsresult rv = SendRequest(nullptr, CloseSessionRequest(nsString(aSessionId), + aRole, + aClosedReason)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + RefPtr info; + // data channel session transport is maintained by content process + if (mSessionInfos.Get(aSessionId, getter_AddRefs(info))) { + return info->Close(NS_OK); + } + + return NS_OK; } NS_IMETHODIMP @@ -97,7 +116,18 @@ PresentationIPCService::TerminateSession(const nsAString& aSessionId, { MOZ_ASSERT(!aSessionId.IsEmpty()); - return SendRequest(nullptr, TerminateSessionRequest(nsString(aSessionId), aRole)); + nsresult rv = SendRequest(nullptr, TerminateSessionRequest(nsString(aSessionId), aRole)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + RefPtr info; + // data channel session transport is maintained by content process + if (mSessionInfos.Get(aSessionId, getter_AddRefs(info))) { + return info->Close(NS_OK); + } + + return NS_OK; } nsresult @@ -192,6 +222,21 @@ PresentationIPCService::UnregisterRespondingListener(uint64_t aWindowId) return NS_OK; } +nsresult +PresentationIPCService::NotifySessionTransport(const nsString& aSessionId, + const uint8_t& aRole, + nsIPresentationSessionTransport* aTransport) +{ + RefPtr info = + new PresentationContentSessionInfo(aSessionId, aRole, aTransport); + + if (NS_WARN_IF(NS_FAILED(info->Init()))) { + return NS_ERROR_NOT_AVAILABLE; + } + mSessionInfos.Put(aSessionId, info); + return NS_OK; +} + NS_IMETHODIMP PresentationIPCService::GetWindowIdBySessionId(const nsAString& aSessionId, uint64_t* aWindowId) @@ -212,6 +257,7 @@ PresentationIPCService::NotifySessionStateChange(const nsAString& aSessionId, return listener->NotifyStateChange(aSessionId, aState, aReason); } +// Only used for OOP RTCDataChannel session transport case. nsresult PresentationIPCService::NotifyMessage(const nsAString& aSessionId, const nsACString& aData) @@ -224,6 +270,19 @@ PresentationIPCService::NotifyMessage(const nsAString& aSessionId, return listener->NotifyMessage(aSessionId, aData); } +// Only used for OOP RTCDataChannel session transport case. +nsresult +PresentationIPCService::NotifyTransportClosed(const nsAString& aSessionId, + uint8_t aRole, + nsresult aReason) +{ + if (NS_WARN_IF(!mSessionInfos.Contains(aSessionId))) { + return NS_ERROR_NOT_AVAILABLE; + } + NS_WARN_IF(!sPresentationChild->SendNotifyTransportClosed(nsString(aSessionId), aRole, aReason)); + return NS_OK; +} + nsresult PresentationIPCService::NotifySessionConnect(uint64_t aWindowId, const nsAString& aSessionId) @@ -284,6 +343,10 @@ PresentationIPCService::UntrackSessionInfo(const nsAString& aSessionId, { // Remove the OOP responding info (if it has never been used). RemoveRespondingSessionId(aSessionId); + if (mSessionInfos.Contains(aSessionId)) { + mSessionInfos.Remove(aSessionId); + } + return NS_OK; } diff --git a/dom/presentation/ipc/PresentationIPCService.h b/dom/presentation/ipc/PresentationIPCService.h index e2da780989adc..c77928cc5264d 100644 --- a/dom/presentation/ipc/PresentationIPCService.h +++ b/dom/presentation/ipc/PresentationIPCService.h @@ -8,6 +8,7 @@ #define mozilla_dom_PresentationIPCService_h #include "mozilla/dom/PresentationServiceBase.h" +#include "nsIPresentationSessionTransport.h" #include "nsIPresentationService.h" #include "nsTObserverArray.h" @@ -17,6 +18,7 @@ namespace mozilla { namespace dom { class PresentationIPCRequest; +class PresentationContentSessionInfo; class PresentationResponderLoadingCallback; class PresentationIPCService final : public nsIPresentationService @@ -45,14 +47,31 @@ class PresentationIPCService final : public nsIPresentationService nsresult MonitorResponderLoading(const nsAString& aSessionId, nsIDocShell* aDocShell); + nsresult NotifySessionTransport(const nsString& aSessionId, + const uint8_t& aRole, + nsIPresentationSessionTransport* transport); + private: virtual ~PresentationIPCService(); nsresult SendRequest(nsIPresentationServiceCallback* aCallback, const PresentationIPCRequest& aRequest); nsTObserverArray > mAvailabilityListeners; - nsRefPtrHashtable mSessionListeners; + nsRefPtrHashtable mSessionListeners; + nsRefPtrHashtable mRespondingListeners; RefPtr mCallback; + + // Store the mapping between the window ID of the OOP page (in this process) + // and the ID of the responding session. It's used for an OOP receiver page + // to retrieve the correspondent session ID. Besides, also keep the mapping + // between the responding session ID and the window ID to help look up the + // window ID. + nsClassHashtable mRespondingSessionIds; + nsDataHashtable mRespondingWindowIds; + nsRefPtrHashtable mSessionInfos; }; } // namespace dom diff --git a/dom/presentation/ipc/PresentationParent.cpp b/dom/presentation/ipc/PresentationParent.cpp index 9691300ec66f5..ae583085e1a76 100644 --- a/dom/presentation/ipc/PresentationParent.cpp +++ b/dom/presentation/ipc/PresentationParent.cpp @@ -4,11 +4,14 @@ * 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 "DCPresentationChannelDescription.h" #include "mozilla/ipc/InputStreamUtils.h" #include "nsIPresentationDeviceManager.h" #include "nsServiceManagerUtils.h" +#include "PresentationBuilderParent.h" #include "PresentationParent.h" #include "PresentationService.h" +#include "PresentationSessionInfo.h" using namespace mozilla::dom; @@ -22,7 +25,6 @@ NS_IMPL_ISUPPORTS(PresentationParent, nsIPresentationRespondingListener) PresentationParent::PresentationParent() - : mActorDestroyed(false) { MOZ_COUNT_CTOR(PresentationParent); } @@ -112,6 +114,21 @@ PresentationParent::DeallocPPresentationRequestParent( return true; } +PPresentationBuilderParent* +PresentationParent::AllocPPresentationBuilderParent(const nsString& aSessionId, + const uint8_t& aRole) +{ + NS_NOTREACHED("We should never be manually allocating AllocPPresentationBuilderParent actors"); + return nullptr; +} + +bool +PresentationParent::DeallocPPresentationBuilderParent( + PPresentationBuilderParent* aActor) +{ + return true; +} + bool PresentationParent::Recv__delete__() { @@ -189,6 +206,19 @@ PresentationParent::RecvUnregisterRespondingHandler(const uint64_t& aWindowId) return true; } +bool +PresentationParent::RegisterTransportBuilder(const nsString& aSessionId, + const uint8_t& aRole) +{ + MOZ_ASSERT(mService); + + nsCOMPtr builder = + new PresentationBuilderParent(this); + NS_WARN_IF(NS_FAILED(static_cast(mService.get())-> + RegisterTransportBuilder(aSessionId, aRole, builder))); + return true; +} + NS_IMETHODIMP PresentationParent::NotifyAvailableChange(bool aAvailable) { @@ -240,11 +270,22 @@ PresentationParent::RecvNotifyReceiverReady(const nsString& aSessionId, { MOZ_ASSERT(mService); - // Set window ID to 0 since the window is from content process. + RegisterTransportBuilder(aSessionId, nsIPresentationService::ROLE_RECEIVER); NS_WARN_IF(NS_FAILED(mService->NotifyReceiverReady(aSessionId, aWindowId))); return true; } +bool +PresentationParent::RecvNotifyTransportClosed(const nsString& aSessionId, + const uint8_t& aRole, + const nsresult& aReason) +{ + MOZ_ASSERT(mService); + + NS_WARN_IF(NS_FAILED(mService->NotifyTransportClosed(aSessionId, aRole, aReason))); + return true; +} + /* * Implementation of PresentationRequestParent */ @@ -252,8 +293,7 @@ PresentationParent::RecvNotifyReceiverReady(const nsString& aSessionId, NS_IMPL_ISUPPORTS(PresentationRequestParent, nsIPresentationServiceCallback) PresentationRequestParent::PresentationRequestParent(nsIPresentationService* aService) - : mActorDestroyed(false) - , mService(aService) + : mService(aService) { MOZ_COUNT_CTOR(PresentationRequestParent); } @@ -274,8 +314,8 @@ nsresult PresentationRequestParent::DoRequest(const StartSessionRequest& aRequest) { MOZ_ASSERT(mService); - - // Set window ID to 0 since the window is from content process. + mNeedRegisterBuilder = true; + mSessionId = aRequest.sessionId(); return mService->StartSession(aRequest.url(), aRequest.sessionId(), aRequest.origin(), aRequest.deviceId(), aRequest.windowId(), this); @@ -345,6 +385,13 @@ PresentationRequestParent::DoRequest(const TerminateSessionRequest& aRequest) NS_IMETHODIMP PresentationRequestParent::NotifySuccess() { + if (mNeedRegisterBuilder) { + RefPtr parent = static_cast(Manager()); + NS_WARN_IF(!parent->RegisterTransportBuilder( + mSessionId, + nsIPresentationService::ROLE_CONTROLLER)); + } + return SendResponse(NS_OK); } diff --git a/dom/presentation/ipc/PresentationParent.h b/dom/presentation/ipc/PresentationParent.h index 29e00de75c954..f0e27473edc6d 100644 --- a/dom/presentation/ipc/PresentationParent.h +++ b/dom/presentation/ipc/PresentationParent.h @@ -7,10 +7,12 @@ #ifndef mozilla_dom_PresentationParent_h__ #define mozilla_dom_PresentationParent_h__ +#include "mozilla/dom/PPresentationBuilderParent.h" #include "mozilla/dom/PPresentationParent.h" #include "mozilla/dom/PPresentationRequestParent.h" #include "nsIPresentationListener.h" #include "nsIPresentationService.h" +#include "nsIPresentationSessionTransportBuilder.h" namespace mozilla { namespace dom { @@ -30,6 +32,8 @@ class PresentationParent final : public PPresentationParent bool Init(); + bool RegisterTransportBuilder(const nsString& aSessionId, const uint8_t& aRole); + virtual void ActorDestroy(ActorDestroyReason aWhy) override; virtual bool @@ -42,6 +46,14 @@ class PresentationParent final : public PPresentationParent virtual bool DeallocPPresentationRequestParent(PPresentationRequestParent* aActor) override; + virtual PPresentationBuilderParent* + AllocPPresentationBuilderParent(const nsString& aSessionId, + const uint8_t& aRole) override; + + virtual bool + DeallocPPresentationBuilderParent( + PPresentationBuilderParent* aActor) override; + virtual bool Recv__delete__() override; virtual bool RecvRegisterAvailabilityHandler() override; @@ -61,10 +73,14 @@ class PresentationParent final : public PPresentationParent virtual bool RecvNotifyReceiverReady(const nsString& aSessionId, const uint64_t& aWindowId) override; + virtual bool RecvNotifyTransportClosed(const nsString& aSessionId, + const uint8_t& aRole, + const nsresult& aReason) override; + private: virtual ~PresentationParent(); - bool mActorDestroyed; + bool mActorDestroyed = false; nsCOMPtr mService; nsTArray mSessionIdsAtController; nsTArray mSessionIdsAtReceiver; @@ -97,7 +113,9 @@ class PresentationRequestParent final : public PPresentationRequestParent nsresult DoRequest(const TerminateSessionRequest& aRequest); - bool mActorDestroyed; + bool mActorDestroyed = false; + bool mNeedRegisterBuilder = false; + nsString mSessionId; nsCOMPtr mService; }; diff --git a/dom/presentation/moz.build b/dom/presentation/moz.build index 9e90d1595207e..ffbe96c20988d 100644 --- a/dom/presentation/moz.build +++ b/dom/presentation/moz.build @@ -11,6 +11,9 @@ MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini'] MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini'] EXPORTS.mozilla.dom += [ + 'DCPresentationChannelDescription.h', + 'ipc/PresentationBuilderChild.h', + 'ipc/PresentationBuilderParent.h', 'ipc/PresentationChild.h', 'ipc/PresentationIPCService.h', 'ipc/PresentationParent.h', @@ -29,7 +32,11 @@ EXPORTS.mozilla.dom += [ ] UNIFIED_SOURCES += [ + 'DCPresentationChannelDescription.cpp', + 'ipc/PresentationBuilderChild.cpp', + 'ipc/PresentationBuilderParent.cpp', 'ipc/PresentationChild.cpp', + 'ipc/PresentationContentSessionInfo.cpp', 'ipc/PresentationIPCService.cpp', 'ipc/PresentationParent.cpp', 'Presentation.cpp', @@ -66,6 +73,7 @@ EXTRA_JS_MODULES += [ IPDL_SOURCES += [ 'ipc/PPresentation.ipdl', + 'ipc/PPresentationBuilder.ipdl', 'ipc/PPresentationRequest.ipdl' ] diff --git a/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js b/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js index 21d16746f4685..5523b902aafe5 100644 --- a/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js +++ b/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js @@ -39,6 +39,7 @@ function registerMockedFactory(contractId, mockedClassId, mockedFactory) { function registerOriginalFactory(contractId, mockedClassId, mockedFactory, originalClassId, originalFactory) { if (originalFactory) { + var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); registrar.unregisterFactory(mockedClassId, mockedFactory); registrar.registerFactory(originalClassId, "", contractId, originalFactory); } @@ -110,42 +111,35 @@ const mockedControlChannel = { return this._listener; }, sendOffer: function(offer) { - var isValid = false; - try { - var addresses = offer.tcpAddress; - if (addresses.length > 0) { - for (var i = 0; i < addresses.length; i++) { - // Ensure CString addresses are used. Otherwise, an error will be thrown. - addresses.queryElementAt(i, Ci.nsISupportsCString); - } + sendAsyncMessage('offer-sent', this._isValidSDP(offer)); + }, + sendAnswer: function(answer) { + sendAsyncMessage('answer-sent', this._isValidSDP(answer)); - isValid = true; - } - } catch (e) { - isValid = false; + if (answer.type == Ci.nsIPresentationChannelDescription.TYPE_TCP) { + this._listener.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportReady(); } - - sendAsyncMessage('offer-sent', isValid); }, - sendAnswer: function(answer) { + _isValidSDP: function(aSDP) { var isValid = false; - try { - var addresses = answer.tcpAddress; - if (addresses.length > 0) { - for (var i = 0; i < addresses.length; i++) { - // Ensure CString addresses are used. Otherwise, an error will be thrown. - addresses.queryElementAt(i, Ci.nsISupportsCString); + if (aSDP.type == Ci.nsIPresentationChannelDescription.TYPE_TCP) { + try { + var addresses = aSDP.tcpAddress; + if (addresses.length > 0) { + for (var i = 0; i < addresses.length; i++) { + // Ensure CString addresses are used. Otherwise, an error will be thrown. + addresses.queryElementAt(i, Ci.nsISupportsCString); + } + + isValid = true; } - - isValid = true; + } catch (e) { + isValid = false; } - } catch (e) { - isValid = false; + } else if (aSDP.type == Ci.nsIPresentationChannelDescription.TYPE_DATACHANNEL) { + isValid = (aSDP.dataChannelSDP == "test-sdp"); } - - sendAsyncMessage('answer-sent', isValid); - - this._listener.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportReady(); + return isValid; }, close: function(reason) { sendAsyncMessage('control-channel-closed', reason); @@ -256,7 +250,8 @@ const mockedSessionTransport = { }, 0); }, // in-process case - buildDataChannelTransport: function(role, window, controlChannel, listener) { + buildDataChannelTransport: function(role, window, listener) { + dump("PresentationSessionChromeScript: build data channel transport\n"); this._listener = listener; this._role = role; diff --git a/dom/presentation/tests/mochitest/PresentationSessionFrameScript.js b/dom/presentation/tests/mochitest/PresentationSessionFrameScript.js new file mode 100644 index 0000000000000..f1fc5e2f47f66 --- /dev/null +++ b/dom/presentation/tests/mochitest/PresentationSessionFrameScript.js @@ -0,0 +1,258 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function loadPrivilegedScriptTest() { + /** + * The script is loaded as + * (a) a privileged script in content process for dc_sender.html + * (b) a frame script in the remote iframe process for dc_receiver_oop.html + * |type port == "undefined"| indicates the script is load by + * |loadPrivilegedScript| which is the first case. + */ + function sendMessage(type, data) { + if (typeof port == "undefined") { + sendAsyncMessage(type, {'data': data}); + } else { + port.postMessage({'type': type, + 'data': data + }); + } + } + + if (typeof port != "undefined") { + /** + * When the script is loaded by |loadPrivilegedScript|, these APIs + * are exposed to this script. + */ + port.onmessage = (e) => { + var type = e.data['type']; + if (!handlers.hasOwnProperty(type)) { + return; + } + var args = [e]; + handlers[type].forEach(handler => handler.apply(null, args)); + }; + var handlers = {}; + addMessageListener = function(message, handler) { + if (handlers.hasOwnProperty(message)) { + handlers[message].push(handler); + } else { + handlers[message] = [handler]; + } + }; + removeMessageListener = function(message, handler) { + if (!handler || !handlers.hasOwnProperty(message)) { + return; + } + var index = handlers[message].indexOf(handler); + if (index != -1) { + handlers[message].splice(index, 1); + } + }; + } + + const { classes: Cc, interfaces: Ci, manager: Cm, utils: Cu, results: Cr } = Components; + + const mockedChannelDescription = { + QueryInterface : function (iid) { + const interfaces = [Ci.nsIPresentationChannelDescription]; + + if (!interfaces.some(v => iid.equals(v))) { + throw Cr.NS_ERROR_NO_INTERFACE; + } + return this; + }, + get type() { + if (Services.prefs.getBoolPref("dom.presentation.session_transport.data_channel.enable")) { + return Ci.nsIPresentationChannelDescription.TYPE_DATACHANNEL; + } + return Ci.nsIPresentationChannelDescription.TYPE_TCP; + }, + get dataChannelSDP() { + return "test-sdp"; + } + }; + + function setTimeout(callback, delay) { + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.initWithCallback({ notify: callback }, + delay, + Ci.nsITimer.TYPE_ONE_SHOT); + return timer; + } + + const mockedSessionTransport = { + QueryInterface : function (iid) { + const interfaces = [Ci.nsIPresentationSessionTransport, + Ci.nsIPresentationDataChannelSessionTransportBuilder, + Ci.nsIFactory]; + + if (!interfaces.some(v => iid.equals(v))) { + throw Cr.NS_ERROR_NO_INTERFACE; + } + return this; + }, + createInstance: function(aOuter, aIID) { + if (aOuter) { + throw Components.results.NS_ERROR_NO_AGGREGATION; + } + return this.QueryInterface(aIID); + }, + set callback(callback) { + this._callback = callback; + }, + get callback() { + return this._callback; + }, + /* OOP case */ + buildDataChannelTransport: function(role, window, listener) { + dump("PresentationSessionFrameScript: build data channel transport\n"); + this._listener = listener; + this._role = role; + + var hasNavigator = window ? (typeof window.navigator != "undefined") : false; + sendMessage('check-navigator', hasNavigator); + + if (this._role == Ci.nsIPresentationService.ROLE_CONTROLLER) { + this._listener.sendOffer(mockedChannelDescription); + } + }, + + enableDataNotification: function() { + sendMessage('data-transport-notification-enabled'); + }, + send: function(data) { + sendMessage('message-sent', data); + }, + close: function(reason) { + sendMessage('data-transport-closed', reason); + this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportClosed(reason); + this._callback = null; + }, + simulateTransportReady: function() { + this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportReady(); + }, + simulateIncomingMessage: function(message) { + this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyData(message); + }, + onOffer: function(aOffer) { + this._listener.sendAnswer(mockedChannelDescription); + this._onSessionTransport(); + }, + onAnswer: function(aAnswer) { + this._onSessionTransport(); + }, + _onSessionTransport: function() { + setTimeout(()=>{ + this._listener.onSessionTransport(this); + this.simulateTransportReady(); + this._listener = null; + }, 0); + } + }; + + + function tearDown() { + mockedSessionTransport.callback = null; + + /* Register original factories. */ + for (var data in originalFactoryData) { + registerOriginalFactory(data.contractId, data.mockedClassId, + data.mockedFactory, data.originalClassId, + data.originalFactory); + } + sendMessage("teardown-complete"); + } + + + function registerMockedFactory(contractId, mockedClassId, mockedFactory) { + var originalClassId, originalFactory; + var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); + + if (!registrar.isCIDRegistered(mockedClassId)) { + try { + originalClassId = registrar.contractIDToCID(contractId); + originalFactory = Cm.getClassObject(Cc[contractId], Ci.nsIFactory); + } catch (ex) { + originalClassId = ""; + originalFactory = null; + } + if (originalFactory) { + registrar.unregisterFactory(originalClassId, originalFactory); + } + registrar.registerFactory(mockedClassId, "", contractId, mockedFactory); + } + + return { contractId: contractId, + mockedClassId: mockedClassId, + mockedFactory: mockedFactory, + originalClassId: originalClassId, + originalFactory: originalFactory }; + } + + function registerOriginalFactory(contractId, mockedClassId, mockedFactory, originalClassId, originalFactory) { + if (originalFactory) { + var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); + registrar.unregisterFactory(mockedClassId, mockedFactory); + registrar.registerFactory(originalClassId, "", contractId, originalFactory); + } + } + + /* Register mocked factories. */ + const originalFactoryData = []; + const uuidGenerator = Cc["@mozilla.org/uuid-generator;1"] + .getService(Ci.nsIUUIDGenerator); + originalFactoryData.push(registerMockedFactory("@mozilla.org/presentation/datachanneltransportbuilder;1", + uuidGenerator.generateUUID(), + mockedSessionTransport)); + + addMessageListener('trigger-incoming-message', function(event) { + mockedSessionTransport.simulateIncomingMessage(event.data.data); + }); + addMessageListener('teardown', ()=>tearDown()); +} + +// Exposed to the caller of |loadPrivilegedScript| +var contentScript = { + handlers: {}, + addMessageListener: function(message, handler) { + if (this.handlers.hasOwnProperty(message)) { + this.handlers[message].push(handler); + } else { + this.handlers[message] = [handler]; + } + }, + removeMessageListener: function(message, handler) { + if (!handler || !this.handlers.hasOwnProperty(message)) { + return; + } + var index = this.handlers[message].indexOf(handler); + if (index != -1) { + this.handlers[message].splice(index, 1); + } + }, + sendAsyncMessage: function(message, data) { + port.postMessage({'type': message, + 'data': data + }); + } +} + +if (!SpecialPowers.isMainProcess()) { + var port; + try { + port = SpecialPowers.loadPrivilegedScript(loadPrivilegedScriptTest.toSource()); + } catch (e) { + ok(false, "loadPrivilegedScript shoulde not throw" + e); + } + + port.onmessage = (e) => { + var type = e.data['type']; + if (!contentScript.handlers.hasOwnProperty(type)) { + return; + } + var args = [e.data['data']]; + contentScript.handlers[type].forEach(handler => handler.apply(null, args)); + }; +} diff --git a/dom/presentation/tests/mochitest/mochitest.ini b/dom/presentation/tests/mochitest/mochitest.ini index cfe732b337692..6fe7eede5cfdc 100644 --- a/dom/presentation/tests/mochitest/mochitest.ini +++ b/dom/presentation/tests/mochitest/mochitest.ini @@ -2,6 +2,7 @@ support-files = PresentationDeviceInfoChromeScript.js PresentationSessionChromeScript.js + PresentationSessionFrameScript.js PresentationSessionChromeScript1UA.js file_presentation_1ua_receiver.html file_presentation_non_receiver_inner_iframe.html @@ -13,9 +14,10 @@ support-files = test_presentation_1ua_connection_wentaway.js [test_presentation_dc_sender.html] -skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785 [test_presentation_dc_receiver.html] skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785 +[test_presentation_dc_receiver_oop.html] +skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785 [test_presentation_1ua_sender_and_receiver.html] skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785 [test_presentation_1ua_sender_and_receiver_oop.html] diff --git a/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver.html b/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver.html index f6eb6c493ea7c..b3fa37b917c5a 100644 --- a/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver.html +++ b/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver.html @@ -215,6 +215,8 @@ {type: "browser", allow: true, context: document}, ], () => { SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true], + /* Mocked TCP session transport builder in the test */ + ["dom.presentation.session_transport.data_channel.enable", false], ["dom.presentation.test.enabled", true], ["dom.presentation.test.stage", 0], ["dom.mozBrowserFramesEnabled", true]]}, diff --git a/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver_oop.html b/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver_oop.html index 089d5ed6d6aa9..5474133dca72b 100644 --- a/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver_oop.html +++ b/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver_oop.html @@ -220,6 +220,8 @@ {type: "browser", allow: true, context: document}, ], () => { SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true], + /* Mocked TCP session transport builder in the test */ + ["dom.presentation.session_transport.data_channel.enable", false], ["dom.presentation.test.enabled", true], ["dom.presentation.test.stage", 0], ["dom.mozBrowserFramesEnabled", true], diff --git a/dom/presentation/tests/mochitest/test_presentation_datachannel_sessiontransport.html b/dom/presentation/tests/mochitest/test_presentation_datachannel_sessiontransport.html index fd61521baccc9..918edc13526a9 100644 --- a/dom/presentation/tests/mochitest/test_presentation_datachannel_sessiontransport.html +++ b/dom/presentation/tests/mochitest/test_presentation_datachannel_sessiontransport.html @@ -26,8 +26,6 @@ var serverBuilder; var clientTransport; var serverTransport; -var clientControlChannel; -var serverControlChannel; const clientMessage = "Client Message"; const serverMessage = "Server Message"; @@ -36,36 +34,6 @@ const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm"); const { Services } = Cu.import("resource://gre/modules/Services.jsm"); -function TestControlChannel() { - this._listener = null; -} - -TestControlChannel.prototype = { - QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel]), - set listener(aListener) { - this._listener = aListener; - }, - get listener() { - return this._listener; - }, - sendOffer: function(aOffer) { - setTimeout(()=>this._remote.listener.onOffer(aOffer), 0); - }, - sendAnswer: function(aAnswer) { - setTimeout(()=>this._remote.listener.onAnswer(aAnswer), 0); - }, - sendIceCandidate: function(aCandidate) { - setTimeout(()=>this._remote.listener.onIceCandidate(aCandidate), 0); - }, - close: function(aReason) { - setTimeout(()=>this._listener.notifyClosed(aReason), 0); - setTimeout(()=>this._remote.listener.notifyClosed(aReason), 0); - }, - set remote(aRemote) { - this._remote = aRemote; - }, -}; - var isClientReady = false; var isServerReady = false; var isClientClosed = false; @@ -131,7 +99,26 @@ }, onError: function(aError) { ok(false, "client's builder reports error " + aError); - } + }, + sendOffer: function(aOffer) { + setTimeout(()=>this._remoteBuilder.onOffer(aOffer), 0); + }, + sendAnswer: function(aAnswer) { + setTimeout(()=>this._remoteBuilder.onAnswer(aAnswer), 0); + }, + sendIceCandidate: function(aCandidate) { + setTimeout(()=>this._remoteBuilder.onIceCandidate(aCandidate), 0); + }, + close: function(aReason) { + setTimeout(()=>this._localBuilder.notifyClosed(aReason), 0); + setTimeout(()=>this._remoteBuilder.notifyClosed(aReason), 0); + }, + set remoteBuilder(aRemoteBuilder) { + this._remoteBuilder = aRemoteBuilder; + }, + set localBuilder(aLocalBuilder) { + this._localBuilder = aLocalBuilder; + }, } const serverListener = { @@ -144,7 +131,26 @@ }, onError: function(aError) { ok(false, "server's builder reports error " + aError); - } + }, + sendOffer: function(aOffer) { + setTimeout(()=>this._remoteBuilder.onOffer(aOffer), 0); + }, + sendAnswer: function(aAnswer) { + setTimeout(()=>this._remoteBuilder.onAnswer(aAnswer), 0); + }, + sendIceCandidate: function(aCandidate) { + setTimeout(()=>this._remoteBuilder.onIceCandidate(aCandidate), 0); + }, + close: function(aReason) { + setTimeout(()=>this._localBuilder.notifyClosed(aReason), 0); + setTimeout(()=>this._remoteBuilder.notifyClosed(aReason), 0); + }, + set remoteBuilder(aRemoteBuilder) { + this._remoteBuilder = aRemoteBuilder; + }, + set localBuilder(aLocalBuilder) { + this._localBuilder = aLocalBuilder; + }, } function testBuilder() { @@ -152,26 +158,24 @@ gResolve = aResolve; gReject = aReject; - clientControlChannel = new TestControlChannel(); - serverControlChannel = new TestControlChannel(); - clientControlChannel.remote = serverControlChannel; - serverControlChannel.remote = clientControlChannel; - clientBuilder = Cc["@mozilla.org/presentation/datachanneltransportbuilder;1"] .createInstance(Ci.nsIPresentationDataChannelSessionTransportBuilder); serverBuilder = Cc["@mozilla.org/presentation/datachanneltransportbuilder;1"] .createInstance(Ci.nsIPresentationDataChannelSessionTransportBuilder); + clientListener.localBuilder = clientBuilder; + clientListener.remoteBuilder = serverBuilder; + serverListener.localBuilder = serverBuilder; + serverListener.remoteBuilder = clientBuilder; + clientBuilder .buildDataChannelTransport(Ci.nsIPresentationService.ROLE_CONTROLLER, window, - clientControlChannel, clientListener); serverBuilder .buildDataChannelTransport(Ci.nsIPresentationService.ROLE_RECEIVER, window, - serverControlChannel, serverListener); }); } diff --git a/dom/presentation/tests/mochitest/test_presentation_dc_receiver_oop.html b/dom/presentation/tests/mochitest/test_presentation_dc_receiver_oop.html new file mode 100644 index 0000000000000..69d5465b15edc --- /dev/null +++ b/dom/presentation/tests/mochitest/test_presentation_dc_receiver_oop.html @@ -0,0 +1,211 @@ + + + + + + Test for B2G PresentationConnection API at receiver side (OOP) + + + + + +Test B2G PresentationConnection API at receiver side (OOP) +

+ +

+
+
+
diff --git a/dom/presentation/tests/mochitest/test_presentation_dc_sender.html b/dom/presentation/tests/mochitest/test_presentation_dc_sender.html
index 4322c2cbbfe49..6961fbf1a56f1 100644
--- a/dom/presentation/tests/mochitest/test_presentation_dc_sender.html
+++ b/dom/presentation/tests/mochitest/test_presentation_dc_sender.html
@@ -7,6 +7,7 @@
   Test for B2G Presentation API at sender side
   
   
+  
 
 
 Test for B2G Presentation API at sender side
@@ -15,6 +16,7 @@
 'use strict';
 
 var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var frameScript = SpecialPowers.isMainProcess() ? gScript : contentScript;
 var request;
 var connection;
 
@@ -65,15 +67,15 @@
       info("The control channel is closed. " + aReason);
     });
 
-    gScript.addMessageListener('check-navigator', function checknavigatorHandler(aSuccess) {
-      gScript.removeMessageListener('check-navigator', checknavigatorHandler);
+    frameScript.addMessageListener('check-navigator', function checknavigatorHandler(aSuccess) {
+      frameScript.removeMessageListener('check-navigator', checknavigatorHandler);
       ok(aSuccess, "buildDataChannel get correct window object");
     });
 
     gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
       gScript.removeMessageListener('offer-sent', offerSentHandler);
       ok(aIsValid, "A valid offer is sent out.");
-      gScript.sendAsyncMessage('trigger-incoming-transport');
+      gScript.sendAsyncMessage('trigger-incoming-answer');
     });
 
     gScript.addMessageListener('answer-received', function answerReceivedHandler() {
@@ -81,14 +83,13 @@
       info("An answer is received.");
     });
 
-    gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
-      gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
+    frameScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
+      frameScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
       info("Data transport channel is initialized.");
-      gScript.sendAsyncMessage('trigger-incoming-answer');
     });
 
-    gScript.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
-      gScript.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
+    frameScript.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
+      frameScript.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
       info("Data notification is enabled for data transport channel.");
     });
 
@@ -132,8 +133,8 @@
   return new Promise(function(aResolve, aReject) {
     const outgoingMessage = "test outgoing message";
 
-    gScript.addMessageListener('message-sent', function messageSentHandler(aMessage) {
-      gScript.removeMessageListener('message-sent', messageSentHandler);
+    frameScript.addMessageListener('message-sent', function messageSentHandler(aMessage) {
+      frameScript.removeMessageListener('message-sent', messageSentHandler);
       is(aMessage, outgoingMessage, "The message is sent out.");
       aResolve();
     });
@@ -152,14 +153,14 @@
       aResolve();
     });
 
-    gScript.sendAsyncMessage('trigger-incoming-message', incomingMessage);
+    frameScript.sendAsyncMessage('trigger-incoming-message', incomingMessage);
   });
 }
 
 function testTerminateConnection() {
   return new Promise(function(aResolve, aReject) {
-    gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
-      gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+    frameScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+      frameScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
       info("The data transport is closed. " + aReason);
     });
 
@@ -195,7 +196,6 @@
   then(teardown);
 }
 
-SimpleTest.expectAssertions(0, 5);
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPermissions([
   {type: 'presentation-device-manage', allow: false, context: document},
diff --git a/dom/presentation/tests/mochitest/test_presentation_sender_startWithDevice.html b/dom/presentation/tests/mochitest/test_presentation_sender_startWithDevice.html
index b4e1faf03864a..be05f415c7ec6 100644
--- a/dom/presentation/tests/mochitest/test_presentation_sender_startWithDevice.html
+++ b/dom/presentation/tests/mochitest/test_presentation_sender_startWithDevice.html
@@ -165,6 +165,7 @@
   {type: 'presentation', allow: true, context: document},
 ], function() {
   SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
+                                      ["dom.presentation.session_transport.data_channel.enable", false],
                                       ["dom.presentation.test.enabled", true],
                                       ["dom.presentation.test.stage", 0]]},
                             runTests);