diff options
Diffstat (limited to 'dom/presentation/PresentationConnection.cpp')
-rw-r--r-- | dom/presentation/PresentationConnection.cpp | 757 |
1 files changed, 757 insertions, 0 deletions
diff --git a/dom/presentation/PresentationConnection.cpp b/dom/presentation/PresentationConnection.cpp new file mode 100644 index 0000000000..8d944f1e78 --- /dev/null +++ b/dom/presentation/PresentationConnection.cpp @@ -0,0 +1,757 @@ +/* -*- 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/. */ + +#include "PresentationConnection.h" + +#include "ControllerConnectionCollection.h" +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/MessageEvent.h" +#include "mozilla/dom/MessageEventBinding.h" +#include "mozilla/dom/PresentationConnectionCloseEvent.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "nsContentUtils.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIPresentationService.h" +#include "nsServiceManagerUtils.h" +#include "nsStringStream.h" +#include "PresentationConnectionList.h" +#include "PresentationLog.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_CLASS(PresentationConnection) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PresentationConnection, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwningConnectionList) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PresentationConnection, + DOMEventTargetHelper) + tmp->Shutdown(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwningConnectionList) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_ADDREF_INHERITED(PresentationConnection, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(PresentationConnection, DOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PresentationConnection) + NS_INTERFACE_MAP_ENTRY(nsIPresentationSessionListener) + NS_INTERFACE_MAP_ENTRY(nsIRequest) +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), + mBinaryType(PresentationConnectionBinaryType::Arraybuffer) { + MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || + aRole == nsIPresentationService::ROLE_RECEIVER); + mRole = aRole; +} + +/* virtual */ PresentationConnection::~PresentationConnection() = default; + +/* 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, 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 PresentationConnection::Init() { + if (NS_WARN_IF(mId.IsEmpty())) { + return false; + } + + nsCOMPtr<nsIPresentationService> service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + return false; + } + + nsresult rv = service->RegisterSessionListener(mId, mRole, this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + rv = AddIntoLoadGroup(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + return true; +} + +void PresentationConnection::Shutdown() { + PRES_DEBUG("connection shutdown:id[%s], role[%d]\n", + NS_ConvertUTF16toUTF8(mId).get(), mRole); + + nsCOMPtr<nsIPresentationService> service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + return; + } + + DebugOnly<nsresult> rv = service->UnregisterSessionListener(mId, mRole); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "UnregisterSessionListener failed"); + + DebugOnly<nsresult> rv2 = RemoveFromLoadGroup(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv2), "RemoveFromLoadGroup failed"); + + if (mRole == nsIPresentationService::ROLE_CONTROLLER) { + ControllerConnectionCollection::GetSingleton()->RemoveConnection(this, + mRole); + } +} + +/* virtual */ +void PresentationConnection::DisconnectFromOwner() { + Unused << NS_WARN_IF(NS_FAILED(ProcessConnectionWentAway())); + DOMEventTargetHelper::DisconnectFromOwner(); +} + +/* virtual */ +JSObject* PresentationConnection::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return PresentationConnection_Binding::Wrap(aCx, this, aGivenProto); +} + +void PresentationConnection::GetId(nsAString& aId) const { + if (nsContentUtils::ShouldResistFingerprinting()) { + aId.Truncate(); + return; + } + + aId = mId; +} + +void PresentationConnection::GetUrl(nsAString& aUrl) const { + if (nsContentUtils::ShouldResistFingerprinting()) { + aUrl.Truncate(); + return; + } + + aUrl = mUrl; +} + +PresentationConnectionState PresentationConnection::State() const { + if (nsContentUtils::ShouldResistFingerprinting()) { + return PresentationConnectionState::Terminated; + } + + return mState; +} + +PresentationConnectionBinaryType PresentationConnection::BinaryType() const { + if (nsContentUtils::ShouldResistFingerprinting()) { + return PresentationConnectionBinaryType::Blob; + } + + return mBinaryType; +} + +void PresentationConnection::SetBinaryType( + PresentationConnectionBinaryType aType) { + if (nsContentUtils::ShouldResistFingerprinting()) { + return; + } + + mBinaryType = aType; +} + +void PresentationConnection::Send(const nsAString& aData, ErrorResult& aRv) { + if (nsContentUtils::ShouldResistFingerprinting()) { + return; + } + + // Sending is not allowed if the session is not connected. + if (NS_WARN_IF(mState != PresentationConnectionState::Connected)) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + nsCOMPtr<nsIPresentationService> service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + AsyncCloseConnectionWithErrorMsg( + u"Unable to send message due to an internal error."_ns); + return; + } + + nsresult rv = service->SendSessionMessage(mId, mRole, aData); + if (NS_WARN_IF(NS_FAILED(rv))) { + const uint32_t kMaxMessageLength = 256; + nsAutoString data(Substring(aData, 0, kMaxMessageLength)); + + AsyncCloseConnectionWithErrorMsg(u"Unable to send message: \""_ns + data + + u"\""_ns); + } +} + +void PresentationConnection::Send(Blob& aData, ErrorResult& aRv) { + if (nsContentUtils::ShouldResistFingerprinting()) { + return; + } + + if (NS_WARN_IF(mState != PresentationConnectionState::Connected)) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + nsCOMPtr<nsIPresentationService> service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + AsyncCloseConnectionWithErrorMsg( + u"Unable to send message due to an internal error."_ns); + return; + } + + nsresult rv = service->SendSessionBlob(mId, mRole, &aData); + if (NS_WARN_IF(NS_FAILED(rv))) { + AsyncCloseConnectionWithErrorMsg( + u"Unable to send binary message for Blob message."_ns); + } +} + +void PresentationConnection::Send(const ArrayBuffer& aData, ErrorResult& aRv) { + if (nsContentUtils::ShouldResistFingerprinting()) { + return; + } + + if (NS_WARN_IF(mState != PresentationConnectionState::Connected)) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + nsCOMPtr<nsIPresentationService> service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + AsyncCloseConnectionWithErrorMsg( + u"Unable to send message due to an internal error."_ns); + return; + } + + aData.ComputeState(); + + static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required"); + + uint32_t length = aData.Length(); + char* data = reinterpret_cast<char*>(aData.Data()); + nsDependentCSubstring msgString(data, length); + + nsresult rv = service->SendSessionBinaryMsg(mId, mRole, msgString); + if (NS_WARN_IF(NS_FAILED(rv))) { + AsyncCloseConnectionWithErrorMsg(nsLiteralString( + u"Unable to send binary message for ArrayBuffer message.")); + } +} + +void PresentationConnection::Send(const ArrayBufferView& aData, + ErrorResult& aRv) { + if (nsContentUtils::ShouldResistFingerprinting()) { + return; + } + + if (NS_WARN_IF(mState != PresentationConnectionState::Connected)) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + nsCOMPtr<nsIPresentationService> service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + AsyncCloseConnectionWithErrorMsg( + u"Unable to send message due to an internal error."_ns); + return; + } + + aData.ComputeState(); + + static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required"); + + uint32_t length = aData.Length(); + char* data = reinterpret_cast<char*>(aData.Data()); + nsDependentCSubstring msgString(data, length); + + nsresult rv = service->SendSessionBinaryMsg(mId, mRole, msgString); + if (NS_WARN_IF(NS_FAILED(rv))) { + AsyncCloseConnectionWithErrorMsg(nsLiteralString( + u"Unable to send binary message for ArrayBufferView message.")); + } +} + +void PresentationConnection::Close(ErrorResult& aRv) { + if (nsContentUtils::ShouldResistFingerprinting()) { + return; + } + + // It only works when the state is CONNECTED or CONNECTING. + if (NS_WARN_IF(mState != PresentationConnectionState::Connected && + mState != PresentationConnectionState::Connecting)) { + return; + } + + nsCOMPtr<nsIPresentationService> service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + aRv.Throw(NS_ERROR_DOM_OPERATION_ERR); + return; + } + + Unused << NS_WARN_IF(NS_FAILED(service->CloseSession( + mId, mRole, nsIPresentationService::CLOSED_REASON_CLOSED))); +} + +void PresentationConnection::Terminate(ErrorResult& aRv) { + if (nsContentUtils::ShouldResistFingerprinting()) { + return; + } + + // It only works when the state is CONNECTED. + if (NS_WARN_IF(mState != PresentationConnectionState::Connected)) { + return; + } + + nsCOMPtr<nsIPresentationService> service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + aRv.Throw(NS_ERROR_DOM_OPERATION_ERR); + return; + } + + Unused << 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, nsresult aReason) { + PRES_DEBUG("connection state change:id[%s], state[%" PRIx32 + "], reason[%" PRIx32 "], role[%d]\n", + NS_ConvertUTF16toUTF8(aSessionId).get(), aState, + static_cast<uint32_t>(aReason), mRole); + + if (!aSessionId.Equals(mId)) { + return NS_ERROR_INVALID_ARG; + } + + // A terminated connection should always remain in terminated. + if (mState == PresentationConnectionState::Terminated) { + return NS_OK; + } + + PresentationConnectionState state; + switch (aState) { + case nsIPresentationSessionListener::STATE_CONNECTING: + state = PresentationConnectionState::Connecting; + break; + case nsIPresentationSessionListener::STATE_CONNECTED: + state = PresentationConnectionState::Connected; + break; + case nsIPresentationSessionListener::STATE_CLOSED: + state = PresentationConnectionState::Closed; + break; + case nsIPresentationSessionListener::STATE_TERMINATED: + state = PresentationConnectionState::Terminated; + break; + default: + NS_WARNING("Unknown presentation session state."); + return NS_ERROR_INVALID_ARG; + } + + if (mState == state) { + return NS_OK; + } + mState = state; + + nsresult rv = ProcessStateChanged(aReason); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (mOwningConnectionList) { + mOwningConnectionList->NotifyStateChange(aSessionId, this); + } + + return NS_OK; +} + +nsresult PresentationConnection::ProcessStateChanged(nsresult aReason) { + switch (mState) { + case PresentationConnectionState::Connecting: + return NS_OK; + case PresentationConnectionState::Connected: { + if (nsContentUtils::ShouldResistFingerprinting()) { + return NS_OK; + } + + RefPtr<AsyncEventDispatcher> asyncDispatcher = + new AsyncEventDispatcher(this, u"connect"_ns, CanBubble::eNo); + return asyncDispatcher->PostDOMEvent(); + } + case PresentationConnectionState::Closed: { + PresentationConnectionClosedReason reason = + PresentationConnectionClosedReason::Closed; + + nsString errorMsg; + if (NS_FAILED(aReason)) { + reason = PresentationConnectionClosedReason::Error; + nsCString name, message; + + // If aReason is not a DOM error, use error name as message. + if (NS_FAILED( + NS_GetNameAndMessageForDOMNSResult(aReason, name, message))) { + GetErrorName(aReason, message); + message.InsertLiteral("Internal error: ", 0); + } + CopyUTF8toUTF16(message, errorMsg); + } + + Unused << NS_WARN_IF( + NS_FAILED(DispatchConnectionCloseEvent(reason, errorMsg))); + + return RemoveFromLoadGroup(); + } + case PresentationConnectionState::Terminated: { + if (!nsContentUtils::ShouldResistFingerprinting()) { + // Ensure onterminate event is fired. + RefPtr<AsyncEventDispatcher> asyncDispatcher = + new AsyncEventDispatcher(this, u"terminate"_ns, CanBubble::eNo); + Unused << NS_WARN_IF(NS_FAILED(asyncDispatcher->PostDOMEvent())); + } + + nsCOMPtr<nsIPresentationService> service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = service->UnregisterSessionListener(mId, mRole); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return RemoveFromLoadGroup(); + } + default: + MOZ_CRASH("Unknown presentation session state."); + return NS_ERROR_INVALID_ARG; + } +} + +NS_IMETHODIMP +PresentationConnection::NotifyMessage(const nsAString& aSessionId, + const nsACString& aData, bool aIsBinary) { + PRES_DEBUG("connection %s:id[%s], data[%s], role[%d]\n", __func__, + NS_ConvertUTF16toUTF8(aSessionId).get(), + nsPromiseFlatCString(aData).get(), mRole); + + if (!aSessionId.Equals(mId)) { + return NS_ERROR_INVALID_ARG; + } + + // No message should be expected when the session is not connected. + if (NS_WARN_IF(mState != PresentationConnectionState::Connected)) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + if (NS_WARN_IF(NS_FAILED(DoReceiveMessage(aData, aIsBinary)))) { + AsyncCloseConnectionWithErrorMsg(u"Unable to receive a message."_ns); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult PresentationConnection::DoReceiveMessage(const nsACString& aData, + bool aIsBinary) { + if (nsContentUtils::ShouldResistFingerprinting()) { + return NS_OK; + } + + // Transform the data. + AutoJSAPI jsapi; + if (!jsapi.Init(GetOwner())) { + return NS_ERROR_FAILURE; + } + JSContext* cx = jsapi.cx(); + JS::Rooted<JS::Value> jsData(cx); + + nsresult rv; + if (aIsBinary) { + if (mBinaryType == PresentationConnectionBinaryType::Blob) { + RefPtr<Blob> blob = + Blob::CreateStringBlob(GetOwnerGlobal(), aData, u""_ns); + if (NS_WARN_IF(!blob)) { + return NS_ERROR_FAILURE; + } + + if (!ToJSValue(cx, blob, &jsData)) { + return NS_ERROR_FAILURE; + } + } else if (mBinaryType == PresentationConnectionBinaryType::Arraybuffer) { + JS::Rooted<JSObject*> arrayBuf(cx); + rv = nsContentUtils::CreateArrayBuffer(cx, aData, arrayBuf.address()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + jsData.setObject(*arrayBuf); + } else { + MOZ_CRASH("Unknown binary type!"); + } + } else { + NS_ConvertUTF8toUTF16 utf16Data(aData); + if (NS_WARN_IF(!ToJSValue(cx, utf16Data, &jsData))) { + return NS_ERROR_FAILURE; + } + } + + return DispatchMessageEvent(jsData); +} + +nsresult PresentationConnection::DispatchConnectionCloseEvent( + PresentationConnectionClosedReason aReason, const nsAString& aMessage, + bool aDispatchNow) { + if (nsContentUtils::ShouldResistFingerprinting()) { + return NS_OK; + } + + if (mState != PresentationConnectionState::Closed) { + MOZ_ASSERT(false, "The connection state should be closed."); + return NS_ERROR_FAILURE; + } + + PresentationConnectionCloseEventInit init; + init.mReason = aReason; + init.mMessage = aMessage; + + RefPtr<PresentationConnectionCloseEvent> closedEvent = + PresentationConnectionCloseEvent::Constructor(this, u"close"_ns, init); + closedEvent->SetTrusted(true); + + if (aDispatchNow) { + ErrorResult rv; + DispatchEvent(*closedEvent, rv); + return rv.StealNSResult(); + } + + RefPtr<AsyncEventDispatcher> asyncDispatcher = + new AsyncEventDispatcher(this, closedEvent); + return asyncDispatcher->PostDOMEvent(); +} + +nsresult PresentationConnection::DispatchMessageEvent( + JS::Handle<JS::Value> aData) { + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner()); + if (NS_WARN_IF(!global)) { + return NS_ERROR_NOT_AVAILABLE; + } + + // Get the origin. + nsAutoString origin; + nsresult rv = nsContentUtils::GetUTFOrigin(global->PrincipalOrNull(), origin); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + RefPtr<MessageEvent> messageEvent = new MessageEvent(this, nullptr, nullptr); + + messageEvent->InitMessageEvent( + nullptr, u"message"_ns, CanBubble::eNo, Cancelable::eNo, aData, origin, + u""_ns, nullptr, Sequence<OwningNonNull<MessagePort>>()); + messageEvent->SetTrusted(true); + + RefPtr<AsyncEventDispatcher> asyncDispatcher = + new AsyncEventDispatcher(this, messageEvent); + return asyncDispatcher->PostDOMEvent(); +} + +nsresult PresentationConnection::ProcessConnectionWentAway() { + if (mState != PresentationConnectionState::Connected && + mState != PresentationConnectionState::Connecting) { + // If the state is not connected or connecting, do not need to + // close the session. + return NS_OK; + } + + mState = PresentationConnectionState::Terminated; + + nsCOMPtr<nsIPresentationService> service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + return NS_ERROR_NOT_AVAILABLE; + } + + return service->CloseSession(mId, mRole, + nsIPresentationService::CLOSED_REASON_WENTAWAY); +} + +NS_IMETHODIMP +PresentationConnection::GetName(nsACString& aResult) { + aResult.AssignLiteral("about:presentation-connection"); + return NS_OK; +} + +NS_IMETHODIMP +PresentationConnection::IsPending(bool* aRetval) { + *aRetval = true; + return NS_OK; +} + +NS_IMETHODIMP +PresentationConnection::GetStatus(nsresult* aStatus) { + *aStatus = NS_OK; + return NS_OK; +} + +NS_IMETHODIMP +PresentationConnection::Cancel(nsresult aStatus) { + nsCOMPtr<nsIRunnable> event = NewRunnableMethod( + "dom::PresentationConnection::ProcessConnectionWentAway", this, + &PresentationConnection::ProcessConnectionWentAway); + return NS_DispatchToCurrentThread(event); +} +NS_IMETHODIMP +PresentationConnection::Suspend(void) { return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP +PresentationConnection::Resume(void) { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +PresentationConnection::GetLoadGroup(nsILoadGroup** aLoadGroup) { + *aLoadGroup = nullptr; + + nsCOMPtr<Document> doc = GetOwner() ? GetOwner()->GetExtantDoc() : nullptr; + if (!doc) { + return NS_ERROR_FAILURE; + } + + *aLoadGroup = doc->GetDocumentLoadGroup().take(); + return NS_OK; +} + +NS_IMETHODIMP +PresentationConnection::SetLoadGroup(nsILoadGroup* aLoadGroup) { + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +PresentationConnection::GetLoadFlags(nsLoadFlags* aLoadFlags) { + *aLoadFlags = nsIRequest::LOAD_BACKGROUND; + return NS_OK; +} + +NS_IMETHODIMP +PresentationConnection::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; } + +NS_IMETHODIMP +PresentationConnection::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { + return GetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +PresentationConnection::SetTRRMode(nsIRequest::TRRMode aTRRMode) { + return SetTRRModeImpl(aTRRMode); +} + +nsresult PresentationConnection::AddIntoLoadGroup() { + // Avoid adding to loadgroup multiple times + if (mWeakLoadGroup) { + return NS_OK; + } + + nsCOMPtr<nsILoadGroup> loadGroup; + nsresult rv = GetLoadGroup(getter_AddRefs(loadGroup)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = loadGroup->AddRequest(this, nullptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mWeakLoadGroup = do_GetWeakReference(loadGroup); + return NS_OK; +} + +nsresult PresentationConnection::RemoveFromLoadGroup() { + if (!mWeakLoadGroup) { + return NS_OK; + } + + nsCOMPtr<nsILoadGroup> loadGroup = do_QueryReferent(mWeakLoadGroup); + if (loadGroup) { + mWeakLoadGroup = nullptr; + return loadGroup->RemoveRequest(this, nullptr, NS_OK); + } + + return NS_OK; +} + +void PresentationConnection::AsyncCloseConnectionWithErrorMsg( + const nsAString& aMessage) { + if (mState == PresentationConnectionState::Terminated) { + return; + } + + nsString message = nsString(aMessage); + RefPtr<PresentationConnection> self = this; + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "dom::PresentationConnection::AsyncCloseConnectionWithErrorMsg", + [self, message]() -> void { + // Set |mState| to |PresentationConnectionState::Closed| here to avoid + // calling |ProcessStateChanged|. + self->mState = PresentationConnectionState::Closed; + + // Make sure dispatching the event and closing the connection are + // invoked at the same time by setting |aDispatchNow| to true. + Unused << NS_WARN_IF(NS_FAILED(self->DispatchConnectionCloseEvent( + PresentationConnectionClosedReason::Error, message, true))); + + nsCOMPtr<nsIPresentationService> service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + return; + } + + Unused << NS_WARN_IF(NS_FAILED(service->CloseSession( + self->mId, self->mRole, + nsIPresentationService::CLOSED_REASON_ERROR))); + }); + + Unused << NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(r))); +} + +} // namespace dom +} // namespace mozilla |