Bug 1197690 - Part2: Implement reconnect, r=smaug

--HG--
extra : rebase_source : b40f774b3af223910da37094b2f82bfc5b8b92d2
This commit is contained in:
Kershaw Chang 2016-08-02 19:11:00 +02:00
parent 165708a330
commit 4f5b2c7d11
29 changed files with 859 additions and 29 deletions

View File

@ -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>
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<PresentationConnection> 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<PresentationConnection> connection = aConnection;
mConnections.RemoveElement(connection);
}
already_AddRefed<PresentationConnection>
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<PresentationConnection> connection = mConnections[i];
if (!connection) {
// The connection was destroyed. Remove it from the list.
mConnections.RemoveElementAt(i);
continue;
}
if (connection->Equals(aWindowId, aId)) {
RefPtr<PresentationConnection> matchedConnection = connection.get();
return matchedConnection.forget();
}
}
return nullptr;
}
} // namespace dom
} // namespace mozilla

View File

@ -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<PresentationConnection>
FindConnection(uint64_t aWindowId,
const nsAString& aId,
const uint8_t aRole);
private:
friend class StaticAutoPtr<ControllerConnectionCollection>;
ControllerConnectionCollection();
virtual ~ControllerConnectionCollection();
static StaticAutoPtr<ControllerConnectionCollection> sSingleton;
nsTArray<WeakPtr<PresentationConnection>> mConnections;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_ControllerConnectionCollection_h

View File

@ -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<PresentationConnection> 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<nsIPresentationService> 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)

View File

@ -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<PresentationRequest> mRequest;
nsString mUrl;
nsString mSessionId;
RefPtr<Promise> 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<PresentationConnection> mConnection;
};
class PresentationResponderLoadingCallback final : public nsIWebProgressListener
, public nsSupportsWeakReference
{

View File

@ -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>
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<PresentationConnection> 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<AsyncEventDispatcher> 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,

View File

@ -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<PresentationConnection>
{
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<PresentationConnection>
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<PresentationConnectionList> mOwningConnectionList;

View File

@ -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<nsIDocShell> 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<PresentationConnection> connection =
PresentationConnection::Create(mOwner, aSessionId,
PresentationConnection::Create(mOwner, aSessionId, mUrl,
nsIPresentationService::ROLE_RECEIVER,
mConnectionList);
if (NS_WARN_IF(!connection)) {

View File

@ -54,6 +54,7 @@ private:
uint64_t mWindowId;
nsCOMPtr<nsPIDOMWindowInner> mOwner;
nsString mUrl;
RefPtr<Promise> mGetConnectionListPromise;
RefPtr<PresentationConnectionList> mConnectionList;
};

View File

@ -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<Promise>
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<nsIGlobalObject> global = do_QueryInterface(GetOwner());
if (NS_WARN_IF(!global)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
nsCOMPtr<nsIDocument> doc = GetOwner()->GetExtantDoc();
if (NS_WARN_IF(!doc)) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
RefPtr<Promise> 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<nsIRunnable> r =
NewRunnableMethod<nsString, RefPtr<Promise>>(
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<PresentationConnection> 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<nsIPresentationService> service =
do_GetService(PRESENTATION_SERVICE_CONTRACTID);
if(NS_WARN_IF(!service)) {
aPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
return;
}
nsCOMPtr<nsIPresentationServiceCallback> 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<Promise>
PresentationRequest::GetAvailability(ErrorResult& aRv)
{

View File

@ -36,6 +36,9 @@ public:
already_AddRefed<Promise> StartWithDevice(const nsAString& aDeviceId,
ErrorResult& aRv);
already_AddRefed<Promise> Reconnect(const nsAString& aPresentationId,
ErrorResult& aRv);
already_AddRefed<Promise> GetAvailability(ErrorResult& aRv);
IMPL_EVENT_HANDLER(connectionavailable);
@ -50,6 +53,9 @@ private:
bool Init();
void FindOrCreatePresentationConnection(const nsAString& aPresentationId,
Promise* aPromise);
nsString mUrl;
RefPtr<PresentationAvailability> mAvailability;
};

View File

@ -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<nsIPresentationDeviceManager> 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<nsIPresentationSessionRequest> 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<PresentationSessionInfo> 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<PresentationPresentingInfo*>(
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<nsIPresentationControlChannel> 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<PresentationSessionInfo> 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<PresentationSessionInfo> 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<PresentationControllingInfo*>(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<PresentationSessionInfo> info = GetSessionInfo(aSessionId, aRole);
if (NS_WARN_IF(!info)) {
return NS_ERROR_NOT_AVAILABLE;
}
return static_cast<PresentationControllingInfo*>(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,

View File

@ -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);

View File

@ -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

View File

@ -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.

View File

@ -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<nsIPresentationControlChannel> 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)
{

View File

@ -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<nsIServerSocket> mServerSocket;
nsCOMPtr<nsIPresentationServiceCallback> mReconnectCallback;
};
// Session info with presenting browsing context (receiver side) behaviors.

View File

@ -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)]

View File

@ -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);
};

View File

@ -9,6 +9,7 @@ interface nsIPresentationControlChannel;
%{C++
#define PRESENTATION_SESSION_REQUEST_TOPIC "presentation-session-request"
#define PRESENTATION_RECONNECT_REQUEST_TOPIC "presentation-reconnect-request"
%}
/*

View File

@ -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

View File

@ -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 {

View File

@ -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);
}

View File

@ -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<nsIPresentationSessionListener> 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,

View File

@ -62,14 +62,6 @@ private:
nsRefPtrHashtable<nsUint64HashKey,
nsIPresentationRespondingListener> mRespondingListeners;
RefPtr<PresentationResponderLoadingCallback> 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<nsUint64HashKey, nsString> mRespondingSessionIds;
nsDataHashtable<nsStringHashKey, uint64_t> mRespondingWindowIds;
nsRefPtrHashtable<nsStringHashKey,
PresentationContentSessionInfo> mSessionInfos;
};

View File

@ -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<PresentationService*>(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<PresentationService*>(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()
{

View File

@ -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;

View File

@ -32,6 +32,7 @@ EXPORTS.mozilla.dom += [
]
UNIFIED_SOURCES += [
'ControllerConnectionCollection.cpp',
'DCPresentationChannelDescription.cpp',
'ipc/PresentationBuilderChild.cpp',
'ipc/PresentationBuilderParent.cpp',

View File

@ -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".
*/

View File

@ -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<PresentationConnection> 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<PresentationConnection> reconnect(DOMString presentationId);
/*
* UA triggers device discovery mechanism periodically and monitor device
* availability.