diff --git a/dom/presentation/ControllerConnectionCollection.cpp b/dom/presentation/ControllerConnectionCollection.cpp new file mode 100644 index 000000000000..7d3ffe68450c --- /dev/null +++ b/dom/presentation/ControllerConnectionCollection.cpp @@ -0,0 +1,116 @@ +/* -*- 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 "ControllerConnectionCollection.h" + +#include "mozilla/ClearOnShutdown.h" +#include "nsIPresentationService.h" +#include "PresentationConnection.h" + +namespace mozilla { +namespace dom { + +/* static */ +StaticAutoPtr +ControllerConnectionCollection::sSingleton; + +/* static */ ControllerConnectionCollection* +ControllerConnectionCollection::GetSingleton() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!sSingleton) { + sSingleton = new ControllerConnectionCollection(); + ClearOnShutdown(&sSingleton); + } + + return sSingleton; +} + +ControllerConnectionCollection::ControllerConnectionCollection() +{ + MOZ_COUNT_CTOR(ControllerConnectionCollection); +} + +ControllerConnectionCollection::~ControllerConnectionCollection() +{ + MOZ_COUNT_DTOR(ControllerConnectionCollection); +} + +void +ControllerConnectionCollection::AddConnection( + PresentationConnection* aConnection, + const uint8_t aRole) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (aRole != nsIPresentationService::ROLE_CONTROLLER) { + MOZ_ASSERT(false, "This is allowed only to be called at controller side."); + return; + } + + if (!aConnection) { + return; + } + + WeakPtr connection = aConnection; + if (mConnections.Contains(connection)) { + return; + } + + mConnections.AppendElement(connection); +} + +void +ControllerConnectionCollection::RemoveConnection( + PresentationConnection* aConnection, + const uint8_t aRole) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (aRole != nsIPresentationService::ROLE_CONTROLLER) { + MOZ_ASSERT(false, "This is allowed only to be called at controller side."); + return; + } + + if (!aConnection) { + return; + } + + WeakPtr connection = aConnection; + mConnections.RemoveElement(connection); +} + +already_AddRefed +ControllerConnectionCollection::FindConnection( + uint64_t aWindowId, + const nsAString& aId, + const uint8_t aRole) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (aRole != nsIPresentationService::ROLE_CONTROLLER) { + MOZ_ASSERT(false, "This is allowed only to be called at controller side."); + return nullptr; + } + + // Loop backwards to allow removing elements in the loop. + for (int i = mConnections.Length() - 1; i >= 0; --i) { + WeakPtr connection = mConnections[i]; + if (!connection) { + // The connection was destroyed. Remove it from the list. + mConnections.RemoveElementAt(i); + continue; + } + + if (connection->Equals(aWindowId, aId)) { + RefPtr matchedConnection = connection.get(); + return matchedConnection.forget(); + } + } + + return nullptr; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/presentation/ControllerConnectionCollection.h b/dom/presentation/ControllerConnectionCollection.h new file mode 100644 index 000000000000..c5300fe309f9 --- /dev/null +++ b/dom/presentation/ControllerConnectionCollection.h @@ -0,0 +1,49 @@ +/* -*- 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_ControllerConnectionCollection_h +#define mozilla_dom_ControllerConnectionCollection_h + +#include "mozilla/StaticPtr.h" +#include "mozilla/WeakPtr.h" +#include "nsString.h" +#include "nsTArray.h" + +namespace mozilla { +namespace dom { + +class PresentationConnection; + +class ControllerConnectionCollection final +{ +public: + static ControllerConnectionCollection* GetSingleton(); + + void AddConnection(PresentationConnection* aConnection, + const uint8_t aRole); + + void RemoveConnection(PresentationConnection* aConnection, + const uint8_t aRole); + + already_AddRefed + FindConnection(uint64_t aWindowId, + const nsAString& aId, + const uint8_t aRole); + +private: + friend class StaticAutoPtr; + + ControllerConnectionCollection(); + virtual ~ControllerConnectionCollection(); + + static StaticAutoPtr sSingleton; + nsTArray> mConnections; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_ControllerConnectionCollection_h diff --git a/dom/presentation/PresentationCallbacks.cpp b/dom/presentation/PresentationCallbacks.cpp index 23d6d50f7f9f..39bf7a5c36dc 100644 --- a/dom/presentation/PresentationCallbacks.cpp +++ b/dom/presentation/PresentationCallbacks.cpp @@ -13,6 +13,7 @@ #include "PresentationCallbacks.h" #include "PresentationRequest.h" #include "PresentationConnection.h" +#include "nsThreadUtils.h" using namespace mozilla; using namespace mozilla::dom; @@ -28,6 +29,7 @@ PresentationRequesterCallback::PresentationRequesterCallback(PresentationRequest const nsAString& aSessionId, Promise* aPromise) : mRequest(aRequest) + , mUrl(aUrl) , mSessionId(aSessionId) , mPromise(aPromise) { @@ -46,10 +48,8 @@ PresentationRequesterCallback::NotifySuccess() { MOZ_ASSERT(NS_IsMainThread()); - // At the sender side, this function must get called after the transport - // channel is ready. So we simply set the connection state as connected. RefPtr connection = - PresentationConnection::Create(mRequest->GetOwner(), mSessionId, + PresentationConnection::Create(mRequest->GetOwner(), mSessionId, mUrl, nsIPresentationService::ROLE_CONTROLLER); if (NS_WARN_IF(!connection)) { mPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR); @@ -74,6 +74,74 @@ PresentationRequesterCallback::NotifyError(nsresult aError) * Implementation of PresentationRequesterCallback */ +NS_IMPL_ISUPPORTS_INHERITED0(PresentationReconnectCallback, + PresentationRequesterCallback) + +PresentationReconnectCallback::PresentationReconnectCallback( + PresentationRequest* aRequest, + const nsAString& aUrl, + const nsAString& aSessionId, + Promise* aPromise, + PresentationConnection* aConnection) + : PresentationRequesterCallback(aRequest, aUrl, aSessionId, aPromise) + , mConnection(aConnection) +{ +} + +PresentationReconnectCallback::~PresentationReconnectCallback() +{ +} + +NS_IMETHODIMP +PresentationReconnectCallback::NotifySuccess() +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = NS_OK; + // We found a matched connection with the same window ID, URL, and + // the session ID. Resolve the promise with this connection and dispatch + // the event. + if (mConnection) { + mPromise->MaybeResolve(mConnection); + rv = mRequest->DispatchConnectionAvailableEvent(mConnection); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { + // Use |PresentationRequesterCallback::NotifySuccess| to create a new + // connection since we don't find one that can be reused. + rv = PresentationRequesterCallback::NotifySuccess(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = service->UpdateWindowIdBySessionId(mSessionId, + mRequest->GetOwner()->WindowID()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + nsString sessionId = nsString(mSessionId); + return NS_DispatchToMainThread( + NS_NewRunnableFunction([sessionId, service]() -> void { + service->BuildTransport(sessionId, + nsIPresentationService::ROLE_CONTROLLER); + })); +} + +NS_IMETHODIMP +PresentationReconnectCallback::NotifyError(nsresult aError) +{ + return PresentationRequesterCallback::NotifyError(aError); +} + NS_IMPL_ISUPPORTS(PresentationResponderLoadingCallback, nsIWebProgressListener, nsISupportsWeakReference) diff --git a/dom/presentation/PresentationCallbacks.h b/dom/presentation/PresentationCallbacks.h index 565fab354c4c..08321b2feaef 100644 --- a/dom/presentation/PresentationCallbacks.h +++ b/dom/presentation/PresentationCallbacks.h @@ -20,10 +20,11 @@ class nsIWebProgress; namespace mozilla { namespace dom { +class PresentationConnection; class PresentationRequest; class Promise; -class PresentationRequesterCallback final : public nsIPresentationServiceCallback +class PresentationRequesterCallback : public nsIPresentationServiceCallback { public: NS_DECL_ISUPPORTS @@ -34,14 +35,33 @@ public: const nsAString& aSessionId, Promise* aPromise); -private: - ~PresentationRequesterCallback(); +protected: + virtual ~PresentationRequesterCallback(); RefPtr mRequest; + nsString mUrl; nsString mSessionId; RefPtr mPromise; }; +class PresentationReconnectCallback final : public PresentationRequesterCallback +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIPRESENTATIONSERVICECALLBACK + + PresentationReconnectCallback(PresentationRequest* aRequest, + const nsAString& aUrl, + const nsAString& aSessionId, + Promise* aPromise, + PresentationConnection* aConnection); + +private: + virtual ~PresentationReconnectCallback(); + + RefPtr mConnection; +}; + class PresentationResponderLoadingCallback final : public nsIWebProgressListener , public nsSupportsWeakReference { diff --git a/dom/presentation/PresentationConnection.cpp b/dom/presentation/PresentationConnection.cpp index 345533071fcd..96a8910d6272 100644 --- a/dom/presentation/PresentationConnection.cpp +++ b/dom/presentation/PresentationConnection.cpp @@ -6,6 +6,7 @@ #include "PresentationConnection.h" +#include "ControllerConnectionCollection.h" #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/dom/DOMException.h" #include "mozilla/dom/MessageEvent.h" @@ -43,10 +44,12 @@ NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) PresentationConnection::PresentationConnection(nsPIDOMWindowInner* aWindow, const nsAString& aId, + const nsAString& aUrl, const uint8_t aRole, PresentationConnectionList* aList) : DOMEventTargetHelper(aWindow) , mId(aId) + , mUrl(aUrl) , mState(PresentationConnectionState::Connecting) , mOwningConnectionList(aList) { @@ -62,14 +65,24 @@ PresentationConnection::PresentationConnection(nsPIDOMWindowInner* aWindow, /* static */ already_AddRefed PresentationConnection::Create(nsPIDOMWindowInner* aWindow, const nsAString& aId, + const nsAString& aUrl, const uint8_t aRole, PresentationConnectionList* aList) { MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || aRole == nsIPresentationService::ROLE_RECEIVER); RefPtr connection = - new PresentationConnection(aWindow, aId, aRole, aList); - return NS_WARN_IF(!connection->Init()) ? nullptr : connection.forget(); + new PresentationConnection(aWindow, aId, aUrl, aRole, aList); + if (NS_WARN_IF(!connection->Init())) { + return nullptr; + } + + if (aRole == nsIPresentationService::ROLE_CONTROLLER) { + ControllerConnectionCollection::GetSingleton()->AddConnection(connection, + aRole); + } + + return connection.forget(); } bool @@ -112,6 +125,11 @@ PresentationConnection::Shutdown() rv = RemoveFromLoadGroup(); NS_WARN_IF(NS_FAILED(rv)); + + if (mRole == nsIPresentationService::ROLE_CONTROLLER) { + ControllerConnectionCollection::GetSingleton()->RemoveConnection(this, + mRole); + } } /* virtual */ void @@ -134,6 +152,12 @@ PresentationConnection::GetId(nsAString& aId) const aId = mId; } +void +PresentationConnection::GetUrl(nsAString& aUrl) const +{ + aUrl = mUrl; +} + PresentationConnectionState PresentationConnection::State() const { @@ -203,6 +227,15 @@ PresentationConnection::Terminate(ErrorResult& aRv) NS_WARN_IF(NS_FAILED(service->TerminateSession(mId, mRole))); } +bool +PresentationConnection::Equals(uint64_t aWindowId, + const nsAString& aId) +{ + return GetOwner() && + aWindowId == GetOwner()->WindowID() && + mId.Equals(aId); +} + NS_IMETHODIMP PresentationConnection::NotifyStateChange(const nsAString& aSessionId, uint16_t aState, @@ -252,6 +285,8 @@ nsresult PresentationConnection::ProcessStateChanged(nsresult aReason) { switch (mState) { + case PresentationConnectionState::Connecting: + return NS_OK; case PresentationConnectionState::Connected: { RefPtr asyncDispatcher = new AsyncEventDispatcher(this, NS_LITERAL_STRING("connect"), false); @@ -333,6 +368,14 @@ PresentationConnection::NotifyMessage(const nsAString& aSessionId, return DispatchMessageEvent(jsData); } +NS_IMETHODIMP +PresentationConnection::NotifyReplaced() +{ + return NotifyStateChange(mId, + nsIPresentationSessionListener::STATE_CLOSED, + NS_OK); +} + nsresult PresentationConnection::DispatchConnectionClosedEvent( PresentationConnectionClosedReason aReason, diff --git a/dom/presentation/PresentationConnection.h b/dom/presentation/PresentationConnection.h index fce1acf63625..b8ca76c00280 100644 --- a/dom/presentation/PresentationConnection.h +++ b/dom/presentation/PresentationConnection.h @@ -8,6 +8,7 @@ #define mozilla_dom_PresentationConnection_h #include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/WeakPtr.h" #include "mozilla/dom/PresentationConnectionBinding.h" #include "mozilla/dom/PresentationConnectionClosedEventBinding.h" #include "nsIPresentationListener.h" @@ -22,6 +23,7 @@ class PresentationConnectionList; class PresentationConnection final : public DOMEventTargetHelper , public nsIPresentationSessionListener , public nsIRequest + , public SupportsWeakPtr { public: NS_DECL_ISUPPORTS_INHERITED @@ -29,10 +31,12 @@ public: DOMEventTargetHelper) NS_DECL_NSIPRESENTATIONSESSIONLISTENER NS_DECL_NSIREQUEST + MOZ_DECLARE_WEAKREFERENCE_TYPENAME(PresentationConnection) static already_AddRefed Create(nsPIDOMWindowInner* aWindow, const nsAString& aId, + const nsAString& aUrl, const uint8_t aRole, PresentationConnectionList* aList = nullptr); @@ -44,6 +48,8 @@ public: // WebIDL (public APIs) void GetId(nsAString& aId) const; + void GetUrl(nsAString& aUrl) const; + PresentationConnectionState State() const; void Send(const nsAString& aData, @@ -53,6 +59,9 @@ public: void Terminate(ErrorResult& aRv); + bool + Equals(uint64_t aWindowId, const nsAString& aId); + IMPL_EVENT_HANDLER(connect); IMPL_EVENT_HANDLER(close); IMPL_EVENT_HANDLER(terminate); @@ -61,6 +70,7 @@ public: private: PresentationConnection(nsPIDOMWindowInner* aWindow, const nsAString& aId, + const nsAString& aUrl, const uint8_t aRole, PresentationConnectionList* aList); @@ -84,6 +94,7 @@ private: nsresult RemoveFromLoadGroup(); nsString mId; + nsString mUrl; uint8_t mRole; PresentationConnectionState mState; RefPtr mOwningConnectionList; diff --git a/dom/presentation/PresentationReceiver.cpp b/dom/presentation/PresentationReceiver.cpp index d19864f59c5d..1e58edb9061c 100644 --- a/dom/presentation/PresentationReceiver.cpp +++ b/dom/presentation/PresentationReceiver.cpp @@ -8,6 +8,7 @@ #include "mozilla/dom/PresentationReceiverBinding.h" #include "mozilla/dom/Promise.h" +#include "nsContentUtils.h" #include "nsIPresentationService.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" @@ -56,7 +57,11 @@ PresentationReceiver::Init() } mWindowId = mOwner->WindowID(); - return true; + nsCOMPtr docShell = mOwner->GetDocShell(); + MOZ_ASSERT(docShell); + + nsContentUtils::GetPresentationURL(docShell, mUrl); + return !mUrl.IsEmpty(); } void PresentationReceiver::Shutdown() @@ -96,7 +101,7 @@ PresentationReceiver::NotifySessionConnect(uint64_t aWindowId, } RefPtr connection = - PresentationConnection::Create(mOwner, aSessionId, + PresentationConnection::Create(mOwner, aSessionId, mUrl, nsIPresentationService::ROLE_RECEIVER, mConnectionList); if (NS_WARN_IF(!connection)) { diff --git a/dom/presentation/PresentationReceiver.h b/dom/presentation/PresentationReceiver.h index f77dcfd4e5ae..5647f1194045 100644 --- a/dom/presentation/PresentationReceiver.h +++ b/dom/presentation/PresentationReceiver.h @@ -54,6 +54,7 @@ private: uint64_t mWindowId; nsCOMPtr mOwner; + nsString mUrl; RefPtr mGetConnectionListPromise; RefPtr mConnectionList; }; diff --git a/dom/presentation/PresentationRequest.cpp b/dom/presentation/PresentationRequest.cpp index 4eec65bcb537..f9ed659a11fa 100644 --- a/dom/presentation/PresentationRequest.cpp +++ b/dom/presentation/PresentationRequest.cpp @@ -6,6 +6,7 @@ #include "PresentationRequest.h" +#include "ControllerConnectionCollection.h" #include "mozilla/dom/PresentationRequestBinding.h" #include "mozilla/dom/PresentationConnectionAvailableEvent.h" #include "mozilla/dom/Promise.h" @@ -152,6 +153,118 @@ PresentationRequest::StartWithDevice(const nsAString& aDeviceId, return promise.forget(); } +already_AddRefed +PresentationRequest::Reconnect(const nsAString& aPresentationId, + ErrorResult& aRv) +{ + // TODO: Before starting to reconnect, we have to run the prohibits + // mixed security contexts algorithm first. This will be implemented + // in bug 1254488. + + nsCOMPtr global = do_QueryInterface(GetOwner()); + if (NS_WARN_IF(!global)) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + nsCOMPtr doc = GetOwner()->GetExtantDoc(); + if (NS_WARN_IF(!doc)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + RefPtr promise = Promise::Create(global, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) { + promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); + return promise.forget(); + } + + nsString presentationId = nsString(aPresentationId); + nsCOMPtr r = + NewRunnableMethod>( + this, + &PresentationRequest::FindOrCreatePresentationConnection, + presentationId, + promise); + + if (NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(r)))) { + promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR); + } + + return promise.forget(); +} + +void +PresentationRequest::FindOrCreatePresentationConnection( + const nsAString& aPresentationId, + Promise* aPromise) +{ + MOZ_ASSERT(aPromise); + + if (NS_WARN_IF(!GetOwner())) { + aPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR); + return; + } + + RefPtr connection = + ControllerConnectionCollection::GetSingleton()->FindConnection( + GetOwner()->WindowID(), + aPresentationId, + nsIPresentationService::ROLE_CONTROLLER); + + if (connection) { + nsAutoString url; + connection->GetUrl(url); + if (url.Equals(mUrl)) { + switch (connection->State()) { + case PresentationConnectionState::Closed: + // We found the matched connection. + break; + case PresentationConnectionState::Connecting: + case PresentationConnectionState::Connected: + aPromise->MaybeResolve(connection); + return; + case PresentationConnectionState::Terminated: + // A terminated connection cannot be reused. + connection = nullptr; + break; + default: + MOZ_CRASH("Unknown presentation session state."); + return; + } + } else { + connection = nullptr; + } + } + + nsCOMPtr service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if(NS_WARN_IF(!service)) { + aPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR); + return; + } + + nsCOMPtr callback = + new PresentationReconnectCallback(this, + mUrl, + aPresentationId, + aPromise, + connection); + + nsresult rv = + service->ReconnectSession(mUrl, + aPresentationId, + nsIPresentationService::ROLE_CONTROLLER, + callback); + if (NS_WARN_IF(NS_FAILED(rv))) { + aPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR); + } +} + already_AddRefed PresentationRequest::GetAvailability(ErrorResult& aRv) { diff --git a/dom/presentation/PresentationRequest.h b/dom/presentation/PresentationRequest.h index 0278c76617dc..72dbe40fef79 100644 --- a/dom/presentation/PresentationRequest.h +++ b/dom/presentation/PresentationRequest.h @@ -36,6 +36,9 @@ public: already_AddRefed StartWithDevice(const nsAString& aDeviceId, ErrorResult& aRv); + already_AddRefed Reconnect(const nsAString& aPresentationId, + ErrorResult& aRv); + already_AddRefed GetAvailability(ErrorResult& aRv); IMPL_EVENT_HANDLER(connectionavailable); @@ -50,6 +53,9 @@ private: bool Init(); + void FindOrCreatePresentationConnection(const nsAString& aPresentationId, + Promise* aPromise); + nsString mUrl; RefPtr mAvailability; }; diff --git a/dom/presentation/PresentationService.cpp b/dom/presentation/PresentationService.cpp index 72acab69e399..499a449b3d94 100644 --- a/dom/presentation/PresentationService.cpp +++ b/dom/presentation/PresentationService.cpp @@ -223,6 +223,10 @@ PresentationService::Init() if (NS_WARN_IF(NS_FAILED(rv))) { return false; } + rv = obs->AddObserver(this, PRESENTATION_RECONNECT_REQUEST_TOPIC, false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } nsCOMPtr deviceManager = do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID); @@ -258,6 +262,13 @@ PresentationService::Observe(nsISupports* aSubject, } return HandleTerminateRequest(request); + } else if (!strcmp(aTopic, PRESENTATION_RECONNECT_REQUEST_TOPIC)) { + nsCOMPtr request(do_QueryInterface(aSubject)); + if (NS_WARN_IF(!request)) { + return NS_ERROR_FAILURE; + } + + return HandleReconnectRequest(request); } else if (!strcmp(aTopic, "profile-after-change")) { // It's expected since we add and entry to |kLayoutCategories| in // |nsLayoutModule.cpp| to launch this service earlier. @@ -285,6 +296,7 @@ PresentationService::HandleShutdown() obs->RemoveObserver(this, PRESENTATION_DEVICE_CHANGE_TOPIC); obs->RemoveObserver(this, PRESENTATION_SESSION_REQUEST_TOPIC); obs->RemoveObserver(this, PRESENTATION_TERMINATE_REQUEST_TOPIC); + obs->RemoveObserver(this, PRESENTATION_RECONNECT_REQUEST_TOPIC); } } @@ -366,12 +378,18 @@ PresentationService::HandleSessionRequest(nsIPresentationSessionRequest* aReques // Create or reuse session info. RefPtr info = GetSessionInfo(sessionId, nsIPresentationService::ROLE_RECEIVER); - if (NS_WARN_IF(info)) { - // TODO Bug 1195605. Update here after session join/resume becomes supported. - ctrlChannel->Disconnect(NS_ERROR_DOM_OPERATION_ERR); - return NS_ERROR_DOM_ABORT_ERR; + + // This is the case for reconnecting a session. + // Update the control channel and device of the session info. + // Call |NotifyResponderReady| to indicate the receiver page is already there. + if (info) { + info->SetControlChannel(ctrlChannel); + info->SetDevice(device); + return static_cast( + info.get())->NotifyResponderReady(); } + // This is the case for a new session. info = new PresentationPresentingInfo(url, sessionId, device); rv = info->Init(ctrlChannel); if (NS_WARN_IF(NS_FAILED(rv))) { @@ -452,6 +470,53 @@ PresentationService::HandleTerminateRequest(nsIPresentationTerminateRequest* aRe return info->OnTerminate(ctrlChannel); } +nsresult +PresentationService::HandleReconnectRequest(nsIPresentationSessionRequest* aRequest) +{ + nsCOMPtr ctrlChannel; + nsresult rv = aRequest->GetControlChannel(getter_AddRefs(ctrlChannel)); + if (NS_WARN_IF(NS_FAILED(rv) || !ctrlChannel)) { + return rv; + } + + nsAutoString sessionId; + rv = aRequest->GetPresentationId(sessionId); + if (NS_WARN_IF(NS_FAILED(rv))) { + ctrlChannel->Disconnect(rv); + return rv; + } + + uint64_t windowId; + rv = GetWindowIdBySessionIdInternal(sessionId, &windowId); + if (NS_WARN_IF(NS_FAILED(rv))) { + ctrlChannel->Disconnect(rv); + return rv; + } + + RefPtr info = + GetSessionInfo(sessionId, nsIPresentationService::ROLE_RECEIVER); + if (NS_WARN_IF(!info)) { + // Cannot reconnect non-existed session + ctrlChannel->Disconnect(NS_ERROR_DOM_OPERATION_ERR); + return NS_ERROR_DOM_ABORT_ERR; + } + + nsAutoString url; + rv = aRequest->GetUrl(url); + if (NS_WARN_IF(NS_FAILED(rv))) { + ctrlChannel->Disconnect(rv); + return rv; + } + + // Make sure the url is the same as the previous one. + if (NS_WARN_IF(!info->GetUrl().Equals(url))) { + ctrlChannel->Disconnect(rv); + return rv; + } + + return HandleSessionRequest(aRequest); +} + void PresentationService::NotifyAvailableChange(bool aIsAvailable) { @@ -642,6 +707,57 @@ PresentationService::TerminateSession(const nsAString& aSessionId, return info->Close(NS_OK, nsIPresentationSessionListener::STATE_TERMINATED); } +NS_IMETHODIMP +PresentationService::ReconnectSession(const nsAString& aUrl, + const nsAString& aSessionId, + uint8_t aRole, + nsIPresentationServiceCallback* aCallback) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aSessionId.IsEmpty()); + MOZ_ASSERT(aCallback); + + if (aRole != nsIPresentationService::ROLE_CONTROLLER) { + MOZ_ASSERT(false, "Only controller can call ReconnectSession."); + return NS_ERROR_INVALID_ARG; + } + + if (NS_WARN_IF(!aCallback)) { + return NS_ERROR_INVALID_ARG; + } + + RefPtr info = GetSessionInfo(aSessionId, aRole); + if (NS_WARN_IF(!info)) { + return aCallback->NotifyError(NS_ERROR_DOM_NOT_FOUND_ERR); + } + + if (NS_WARN_IF(!info->GetUrl().Equals(aUrl))) { + return aCallback->NotifyError(NS_ERROR_DOM_NOT_FOUND_ERR); + } + + return static_cast(info.get())->Reconnect(aCallback); +} + +NS_IMETHODIMP +PresentationService::BuildTransport(const nsAString& aSessionId, + uint8_t aRole) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aSessionId.IsEmpty()); + + if (aRole != nsIPresentationService::ROLE_CONTROLLER) { + MOZ_ASSERT(false, "Only controller can call BuildTransport."); + return NS_ERROR_INVALID_ARG; + } + + RefPtr info = GetSessionInfo(aSessionId, aRole); + if (NS_WARN_IF(!info)) { + return NS_ERROR_NOT_AVAILABLE; + } + + return static_cast(info.get())->BuildTransport(); +} + NS_IMETHODIMP PresentationService::RegisterAvailabilityListener(nsIPresentationAvailabilityListener* aListener) { @@ -848,6 +964,13 @@ PresentationService::GetWindowIdBySessionId(const nsAString& aSessionId, return GetWindowIdBySessionIdInternal(aSessionId, aWindowId); } +NS_IMETHODIMP +PresentationService::UpdateWindowIdBySessionId(const nsAString& aSessionId, + const uint64_t aWindowId) +{ + return UpdateWindowIdBySessionIdInternal(aSessionId, aWindowId); +} + bool PresentationService::IsSessionAccessible(const nsAString& aSessionId, const uint8_t aRole, diff --git a/dom/presentation/PresentationService.h b/dom/presentation/PresentationService.h index 913b0e2dfe23..602017be5ef6 100644 --- a/dom/presentation/PresentationService.h +++ b/dom/presentation/PresentationService.h @@ -68,6 +68,7 @@ private: nsresult HandleDeviceChange(); nsresult HandleSessionRequest(nsIPresentationSessionRequest* aRequest); nsresult HandleTerminateRequest(nsIPresentationTerminateRequest* aRequest); + nsresult HandleReconnectRequest(nsIPresentationSessionRequest* aRequest); void NotifyAvailableChange(bool aIsAvailable); bool IsAppInstalled(nsIURI* aUri); diff --git a/dom/presentation/PresentationServiceBase.cpp b/dom/presentation/PresentationServiceBase.cpp index 7c0674a23a0e..d5eb24c22446 100644 --- a/dom/presentation/PresentationServiceBase.cpp +++ b/dom/presentation/PresentationServiceBase.cpp @@ -36,6 +36,8 @@ PresentationServiceBase::GetWindowIdBySessionIdInternal( const nsAString& aSessionId, uint64_t* aWindowId) { + MOZ_ASSERT(NS_IsMainThread()); + if (mRespondingWindowIds.Get(aSessionId, aWindowId)) { return NS_OK; } @@ -46,6 +48,8 @@ void PresentationServiceBase::AddRespondingSessionId(uint64_t aWindowId, const nsAString& aSessionId) { + MOZ_ASSERT(NS_IsMainThread()); + if (NS_WARN_IF(aWindowId == 0)) { return; } @@ -63,6 +67,8 @@ PresentationServiceBase::AddRespondingSessionId(uint64_t aWindowId, void PresentationServiceBase::RemoveRespondingSessionId(const nsAString& aSessionId) { + MOZ_ASSERT(NS_IsMainThread()); + uint64_t windowId = 0; if (mRespondingWindowIds.Get(aSessionId, &windowId)) { mRespondingWindowIds.Remove(aSessionId); @@ -76,5 +82,17 @@ PresentationServiceBase::RemoveRespondingSessionId(const nsAString& aSessionId) } } +nsresult +PresentationServiceBase::UpdateWindowIdBySessionIdInternal( + const nsAString& aSessionId, + const uint64_t aWindowId) +{ + MOZ_ASSERT(NS_IsMainThread()); + + RemoveRespondingSessionId(aSessionId); + AddRespondingSessionId(aWindowId, aSessionId); + return NS_OK; +} + } // namespace dom } // namespace mozilla diff --git a/dom/presentation/PresentationServiceBase.h b/dom/presentation/PresentationServiceBase.h index d460b08535a4..962b12f1a17b 100644 --- a/dom/presentation/PresentationServiceBase.h +++ b/dom/presentation/PresentationServiceBase.h @@ -37,6 +37,8 @@ protected: nsresult GetWindowIdBySessionIdInternal(const nsAString& aSessionId, uint64_t* aWindowId); void AddRespondingSessionId(uint64_t aWindowId, const nsAString& aSessionId); void RemoveRespondingSessionId(const nsAString& aSessionId); + nsresult UpdateWindowIdBySessionIdInternal(const nsAString& aSessionId, + const uint64_t aWindowId); // Store the responding listener based on the window ID of the (in-process or // OOP) receiver page. diff --git a/dom/presentation/PresentationSessionInfo.cpp b/dom/presentation/PresentationSessionInfo.cpp index 02065a05ebf9..9590fe245b1b 100644 --- a/dom/presentation/PresentationSessionInfo.cpp +++ b/dom/presentation/PresentationSessionInfo.cpp @@ -244,6 +244,10 @@ PresentationSessionInfo::Shutdown(nsresult aReason) nsresult PresentationSessionInfo::SetListener(nsIPresentationSessionListener* aListener) { + if (mListener && aListener) { + NS_WARN_IF(NS_FAILED(mListener->NotifyReplaced())); + } + mListener = aListener; if (mListener) { @@ -425,7 +429,8 @@ PresentationSessionInfo::NotifyTransportClosed(nsresult aReason) // potential subsequent |Shutdown| calls. mTransport = nullptr; - if (NS_WARN_IF(!IsSessionReady())) { + if (NS_WARN_IF(!IsSessionReady() && + mState == nsIPresentationSessionListener::STATE_CONNECTING)) { // It happens before the session is ready. Reply the callback. return ReplyError(NS_ERROR_DOM_OPERATION_ERR); } @@ -744,6 +749,10 @@ PresentationControllingInfo::NotifyConnected() switch (mState) { case nsIPresentationSessionListener::STATE_CONNECTING: { + nsresult rv = mControlChannel->Launch(GetSessionId(), GetUrl()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } Unused << NS_WARN_IF(NS_FAILED(BuildTransport())); break; } @@ -758,14 +767,27 @@ PresentationControllingInfo::NotifyConnected() return NS_OK; } +NS_IMETHODIMP +PresentationControllingInfo::NotifyReconnected() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mReconnectCallback); + + if (NS_WARN_IF(mState == nsIPresentationSessionListener::STATE_TERMINATED)) { + return NS_ERROR_FAILURE; + } + + SetStateWithReason(nsIPresentationSessionListener::STATE_CONNECTING, NS_OK); + return mReconnectCallback->NotifySuccess(); +} + nsresult PresentationControllingInfo::BuildTransport() { MOZ_ASSERT(NS_IsMainThread()); - nsresult rv = mControlChannel->Launch(GetSessionId(), GetUrl()); - if (NS_FAILED(rv)) { - return rv; + if (mState != nsIPresentationSessionListener::STATE_CONNECTING) { + return NS_OK; } if (!Preferences::GetBool("dom.presentation.session_transport.data_channel.enable")) { @@ -807,7 +829,7 @@ PresentationControllingInfo::BuildTransport() if (NS_WARN_IF(!dataChannelBuilder)) { return NS_ERROR_NOT_AVAILABLE; } - rv = dataChannelBuilder-> + nsresult rv = dataChannelBuilder-> BuildDataChannelTransport(nsIPresentationService::ROLE_CONTROLLER, window, this); @@ -893,6 +915,41 @@ PresentationControllingInfo::OnStopListening(nsIServerSocket* aServerSocket, return NS_OK; } +nsresult +PresentationControllingInfo::Reconnect(nsIPresentationServiceCallback* aCallback) +{ + if (!aCallback) { + return NS_ERROR_INVALID_ARG; + } + + mReconnectCallback = aCallback; + + if (NS_WARN_IF(mState == nsIPresentationSessionListener::STATE_TERMINATED)) { + return mReconnectCallback->NotifyError(NS_ERROR_DOM_INVALID_STATE_ERR); + } + + nsresult rv = NS_OK; + if (!mControlChannel) { + nsCOMPtr ctrlChannel; + rv = mDevice->EstablishControlChannel(getter_AddRefs(ctrlChannel)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return mReconnectCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR); + } + + rv = Init(ctrlChannel); + if (NS_WARN_IF(NS_FAILED(rv))) { + return mReconnectCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR); + } + } + + rv = mControlChannel->Reconnect(mSessionId, GetUrl()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return mReconnectCallback->NotifyError(rv); + } + + return NS_OK; +} + /** * Implementation of PresentationPresentingInfo * @@ -1217,6 +1274,13 @@ PresentationPresentingInfo::NotifyConnected() return NS_OK; } +NS_IMETHODIMP +PresentationPresentingInfo::NotifyReconnected() +{ + MOZ_ASSERT(false, "NotifyReconnected should not be called at receiver side."); + return NS_OK; +} + NS_IMETHODIMP PresentationPresentingInfo::NotifyDisconnected(nsresult aReason) { diff --git a/dom/presentation/PresentationSessionInfo.h b/dom/presentation/PresentationSessionInfo.h index 3a38c9fd6a29..701db302040c 100644 --- a/dom/presentation/PresentationSessionInfo.h +++ b/dom/presentation/PresentationSessionInfo.h @@ -186,6 +186,10 @@ public: nsresult Init(nsIPresentationControlChannel* aControlChannel) override; + nsresult Reconnect(nsIPresentationServiceCallback* aCallback); + + nsresult BuildTransport(); + private: ~PresentationControllingInfo() { @@ -198,9 +202,8 @@ private: nsresult OnGetAddress(const nsACString& aAddress); - nsresult BuildTransport(); - nsCOMPtr mServerSocket; + nsCOMPtr mReconnectCallback; }; // Session info with presenting browsing context (receiver side) behaviors. diff --git a/dom/presentation/interfaces/nsIPresentationListener.idl b/dom/presentation/interfaces/nsIPresentationListener.idl index 63ff6d6aca36..a8f568500a4c 100644 --- a/dom/presentation/interfaces/nsIPresentationListener.idl +++ b/dom/presentation/interfaces/nsIPresentationListener.idl @@ -33,6 +33,11 @@ interface nsIPresentationSessionListener : nsISupports */ void notifyMessage(in DOMString sessionId, in ACString data); + + /* + * Called when this listener is replaced by another one. + */ + void notifyReplaced(); }; [scriptable, uuid(27f101d7-9ed1-429e-b4f8-43b00e8e111c)] diff --git a/dom/presentation/interfaces/nsIPresentationService.idl b/dom/presentation/interfaces/nsIPresentationService.idl index a99518640327..375e6e4c1204 100644 --- a/dom/presentation/interfaces/nsIPresentationService.idl +++ b/dom/presentation/interfaces/nsIPresentationService.idl @@ -98,6 +98,21 @@ interface nsIPresentationService : nsISupports void terminateSession(in DOMString sessionId, in uint8_t role); + /* + * Reconnect the session. + * + * @param url: The url of presenting page. + * @param sessionId: An ID to identify presentation session. + * @param role: Identify the function called by controller or receiver. + * @param callback: NotifySuccess() is called when a control channel + * is opened successfully. + * Otherwise, NotifyError() is called with a error message. + */ + void reconnectSession(in DOMString url, + in DOMString sessionId, + in uint8_t role, + in nsIPresentationServiceCallback callback); + /* * Register an availability listener. Must be called from the main thread. * @@ -191,4 +206,23 @@ interface nsIPresentationService : nsISupports * The windowId for building RTCDataChannel session transport */ unsigned long long getWindowIdBySessionId(in DOMString sessionId); + + /* + * Update the mapping of the session ID and window ID. + * + * @param sessionId: An ID to identify presentation session. + * @param windowId: The inner window ID associated with the presentation + * session. + */ + void updateWindowIdBySessionId(in DOMString sessionId, + in unsigned long long windowId); + + /* + * To build the session transport. + * NOTE: This function should be only called at controller side. + * + * @param sessionId: An ID to identify presentation session. + * @param role: Identify the function called by controller or receiver. + */ + void buildTransport(in DOMString sessionId, in uint8_t role); }; diff --git a/dom/presentation/interfaces/nsIPresentationSessionRequest.idl b/dom/presentation/interfaces/nsIPresentationSessionRequest.idl index 473b260a1af8..45a0e314cb22 100644 --- a/dom/presentation/interfaces/nsIPresentationSessionRequest.idl +++ b/dom/presentation/interfaces/nsIPresentationSessionRequest.idl @@ -9,6 +9,7 @@ interface nsIPresentationControlChannel; %{C++ #define PRESENTATION_SESSION_REQUEST_TOPIC "presentation-session-request" +#define PRESENTATION_RECONNECT_REQUEST_TOPIC "presentation-reconnect-request" %} /* diff --git a/dom/presentation/ipc/PPresentation.ipdl b/dom/presentation/ipc/PPresentation.ipdl index 4314b534c12c..7adff62d7a9a 100644 --- a/dom/presentation/ipc/PPresentation.ipdl +++ b/dom/presentation/ipc/PPresentation.ipdl @@ -42,12 +42,27 @@ struct TerminateSessionRequest uint8_t role; }; +struct ReconnectSessionRequest +{ + nsString url; + nsString sessionId; + uint8_t role; +}; + +struct BuildTransportRequest +{ + nsString sessionId; + uint8_t role; +}; + union PresentationIPCRequest { StartSessionRequest; SendSessionMessageRequest; CloseSessionRequest; TerminateSessionRequest; + ReconnectSessionRequest; + BuildTransportRequest; }; sync protocol PPresentation diff --git a/dom/presentation/ipc/PresentationBuilderChild.cpp b/dom/presentation/ipc/PresentationBuilderChild.cpp index 0a309dce6f13..1fb69935e0ae 100644 --- a/dom/presentation/ipc/PresentationBuilderChild.cpp +++ b/dom/presentation/ipc/PresentationBuilderChild.cpp @@ -5,8 +5,11 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "DCPresentationChannelDescription.h" +#include "nsComponentManagerUtils.h" +#include "nsGlobalWindow.h" #include "PresentationBuilderChild.h" #include "PresentationIPCService.h" +#include "nsServiceManagerUtils.h" namespace mozilla { namespace dom { diff --git a/dom/presentation/ipc/PresentationContentSessionInfo.cpp b/dom/presentation/ipc/PresentationContentSessionInfo.cpp index 72a4b63f30a3..7bcd620bde55 100644 --- a/dom/presentation/ipc/PresentationContentSessionInfo.cpp +++ b/dom/presentation/ipc/PresentationContentSessionInfo.cpp @@ -28,12 +28,20 @@ PresentationContentSessionInfo::Init() { nsresult PresentationContentSessionInfo::Send(const nsAString& aData) { + if (!mTransport) { + return NS_ERROR_NOT_AVAILABLE; + } + return mTransport->Send(aData); } nsresult PresentationContentSessionInfo::Close(nsresult aReason) { + if (!mTransport) { + return NS_ERROR_NOT_AVAILABLE; + } + return mTransport->Close(aReason); } diff --git a/dom/presentation/ipc/PresentationIPCService.cpp b/dom/presentation/ipc/PresentationIPCService.cpp index 7b162ce24f8e..d6eecaf43b65 100644 --- a/dom/presentation/ipc/PresentationIPCService.cpp +++ b/dom/presentation/ipc/PresentationIPCService.cpp @@ -131,6 +131,39 @@ PresentationIPCService::TerminateSession(const nsAString& aSessionId, return NS_OK; } +NS_IMETHODIMP +PresentationIPCService::ReconnectSession(const nsAString& aUrl, + const nsAString& aSessionId, + uint8_t aRole, + nsIPresentationServiceCallback* aCallback) +{ + MOZ_ASSERT(!aSessionId.IsEmpty()); + + if (aRole != nsIPresentationService::ROLE_CONTROLLER) { + MOZ_ASSERT(false, "Only controller can call ReconnectSession."); + return NS_ERROR_INVALID_ARG; + } + + return SendRequest(aCallback, ReconnectSessionRequest(nsString(aUrl), + nsString(aSessionId), + aRole)); +} + +NS_IMETHODIMP +PresentationIPCService::BuildTransport(const nsAString& aSessionId, + uint8_t aRole) +{ + MOZ_ASSERT(!aSessionId.IsEmpty()); + + if (aRole != nsIPresentationService::ROLE_CONTROLLER) { + MOZ_ASSERT(false, "Only controller can call ReconnectSession."); + return NS_ERROR_INVALID_ARG; + } + + return SendRequest(nullptr, BuildTransportRequest(nsString(aSessionId), + aRole)); +} + nsresult PresentationIPCService::SendRequest(nsIPresentationServiceCallback* aCallback, const PresentationIPCRequest& aRequest) @@ -176,6 +209,13 @@ PresentationIPCService::RegisterSessionListener(const nsAString& aSessionId, MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aListener); + nsCOMPtr listener; + if (mSessionListeners.Get(aSessionId, getter_AddRefs(listener))) { + NS_WARN_IF(NS_FAILED(listener->NotifyReplaced())); + mSessionListeners.Put(aSessionId, aListener); + return NS_OK; + } + mSessionListeners.Put(aSessionId, aListener); if (sPresentationChild) { NS_WARN_IF(!sPresentationChild->SendRegisterSessionHandler(nsString(aSessionId), aRole)); @@ -245,6 +285,13 @@ PresentationIPCService::GetWindowIdBySessionId(const nsAString& aSessionId, return GetWindowIdBySessionIdInternal(aSessionId, aWindowId); } +NS_IMETHODIMP +PresentationIPCService::UpdateWindowIdBySessionId(const nsAString& aSessionId, + const uint64_t aWindowId) +{ + return UpdateWindowIdBySessionIdInternal(aSessionId, aWindowId); +} + nsresult PresentationIPCService::NotifySessionStateChange(const nsAString& aSessionId, uint16_t aState, diff --git a/dom/presentation/ipc/PresentationIPCService.h b/dom/presentation/ipc/PresentationIPCService.h index c77928cc5264..3eb29fb0f350 100644 --- a/dom/presentation/ipc/PresentationIPCService.h +++ b/dom/presentation/ipc/PresentationIPCService.h @@ -62,14 +62,6 @@ private: 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; }; diff --git a/dom/presentation/ipc/PresentationParent.cpp b/dom/presentation/ipc/PresentationParent.cpp index ae583085e1a7..4f7181ee049e 100644 --- a/dom/presentation/ipc/PresentationParent.cpp +++ b/dom/presentation/ipc/PresentationParent.cpp @@ -89,6 +89,12 @@ PresentationParent::RecvPPresentationRequestConstructor( case PresentationIPCRequest::TTerminateSessionRequest: rv = actor->DoRequest(aRequest.get_TerminateSessionRequest()); break; + case PresentationIPCRequest::TReconnectSessionRequest: + rv = actor->DoRequest(aRequest.get_ReconnectSessionRequest()); + break; + case PresentationIPCRequest::TBuildTransportRequest: + rv = actor->DoRequest(aRequest.get_BuildTransportRequest()); + break; default: MOZ_CRASH("Unknown PresentationIPCRequest type"); } @@ -242,6 +248,14 @@ PresentationParent::NotifyStateChange(const nsAString& aSessionId, return NS_OK; } +NS_IMETHODIMP +PresentationParent::NotifyReplaced() +{ + // Do nothing here, since |PresentationIPCService::RegisterSessionListener| + // already dealt with this in content process. + return NS_OK; +} + NS_IMETHODIMP PresentationParent::NotifyMessage(const nsAString& aSessionId, const nsACString& aData) @@ -382,6 +396,48 @@ PresentationRequestParent::DoRequest(const TerminateSessionRequest& aRequest) return NotifySuccess(); } +nsresult +PresentationRequestParent::DoRequest(const ReconnectSessionRequest& aRequest) +{ + MOZ_ASSERT(mService); + + // Validate the accessibility (primarily for receiver side) so that a + // compromised child process can't fake the ID. + if (NS_WARN_IF(!static_cast(mService.get())-> + IsSessionAccessible(aRequest.sessionId(), aRequest.role(), OtherPid()))) { + + // NOTE: Return NS_ERROR_DOM_NOT_FOUND_ERR here to match the spec. + // https://w3c.github.io/presentation-api/#reconnecting-to-a-presentation + return NotifyError(NS_ERROR_DOM_NOT_FOUND_ERR); + } + + mNeedRegisterBuilder = true; + mSessionId = aRequest.sessionId(); + return mService->ReconnectSession(aRequest.url(), + aRequest.sessionId(), + aRequest.role(), + this); +} + +nsresult +PresentationRequestParent::DoRequest(const BuildTransportRequest& aRequest) +{ + MOZ_ASSERT(mService); + + // Validate the accessibility (primarily for receiver side) so that a + // compromised child process can't fake the ID. + if (NS_WARN_IF(!static_cast(mService.get())-> + IsSessionAccessible(aRequest.sessionId(), aRequest.role(), OtherPid()))) { + return NotifyError(NS_ERROR_DOM_SECURITY_ERR); + } + + nsresult rv = mService->BuildTransport(aRequest.sessionId(), aRequest.role()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NotifyError(rv); + } + return NotifySuccess(); +} + NS_IMETHODIMP PresentationRequestParent::NotifySuccess() { diff --git a/dom/presentation/ipc/PresentationParent.h b/dom/presentation/ipc/PresentationParent.h index f0e27473edc6..e7958ef4eecf 100644 --- a/dom/presentation/ipc/PresentationParent.h +++ b/dom/presentation/ipc/PresentationParent.h @@ -113,6 +113,10 @@ private: nsresult DoRequest(const TerminateSessionRequest& aRequest); + nsresult DoRequest(const ReconnectSessionRequest& aRequest); + + nsresult DoRequest(const BuildTransportRequest& aRequest); + bool mActorDestroyed = false; bool mNeedRegisterBuilder = false; nsString mSessionId; diff --git a/dom/presentation/moz.build b/dom/presentation/moz.build index df2b50807761..0e95446ff44f 100644 --- a/dom/presentation/moz.build +++ b/dom/presentation/moz.build @@ -32,6 +32,7 @@ EXPORTS.mozilla.dom += [ ] UNIFIED_SOURCES += [ + 'ControllerConnectionCollection.cpp', 'DCPresentationChannelDescription.cpp', 'ipc/PresentationBuilderChild.cpp', 'ipc/PresentationBuilderParent.cpp', diff --git a/dom/webidl/PresentationConnection.webidl b/dom/webidl/PresentationConnection.webidl index 7492237d5231..2be5119c1265 100644 --- a/dom/webidl/PresentationConnection.webidl +++ b/dom/webidl/PresentationConnection.webidl @@ -29,6 +29,11 @@ interface PresentationConnection : EventTarget { [Constant] readonly attribute DOMString id; + /* + * Specifies the connection's presentation URL. + */ + readonly attribute DOMString url; + /* * @value "connected", "closed", or "terminated". */ diff --git a/dom/webidl/PresentationRequest.webidl b/dom/webidl/PresentationRequest.webidl index d54c855575ac..83b9e4c58542 100644 --- a/dom/webidl/PresentationRequest.webidl +++ b/dom/webidl/PresentationRequest.webidl @@ -24,10 +24,26 @@ interface PresentationRequest : EventTarget { * - "AbortError": User dismiss/cancel the device prompt box. * - "NetworkError": Failed to establish the control channel or data channel. * - "TimeoutError": Presenting page takes too long to load. + * - "SecurityError": This operation is insecure. */ [Throws] Promise start(); + /* + * A requesting page can use reconnect(presentationId) to reopen a + * non-terminated presentation connection. + * + * The promise is resolved when a new presentation connection is created. + * The connection state is "connecting". + * + * The promise may be rejected duo to one of the following reasons: + * - "OperationError": Unexpected error occurs. + * - "NotFoundError": Can not find a presentation connection with the presentationId. + * - "SecurityError": This operation is insecure. + */ + [Throws] + Promise reconnect(DOMString presentationId); + /* * UA triggers device discovery mechanism periodically and monitor device * availability.