diff options
Diffstat (limited to 'dom/serviceworkers/FetchEventOpProxyChild.cpp')
-rw-r--r-- | dom/serviceworkers/FetchEventOpProxyChild.cpp | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/dom/serviceworkers/FetchEventOpProxyChild.cpp b/dom/serviceworkers/FetchEventOpProxyChild.cpp new file mode 100644 index 0000000000..8e437356ec --- /dev/null +++ b/dom/serviceworkers/FetchEventOpProxyChild.cpp @@ -0,0 +1,280 @@ +/* -*- 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 "FetchEventOpProxyChild.h" + +#include <utility> + +#include "mozilla/dom/FetchTypes.h" +#include "mozilla/dom/ServiceWorkerOpPromise.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsThreadUtils.h" + +#include "mozilla/Assertions.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/InternalRequest.h" +#include "mozilla/dom/InternalResponse.h" +#include "mozilla/dom/RemoteWorkerChild.h" +#include "mozilla/dom/RemoteWorkerService.h" +#include "mozilla/dom/ServiceWorkerOp.h" +#include "mozilla/dom/WorkerCommon.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/IPCStreamUtils.h" + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +namespace { + +nsresult GetIPCSynthesizeResponseArgs( + ChildToParentSynthesizeResponseArgs* aIPCArgs, + SynthesizeResponseArgs&& aArgs) { + MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread()); + + auto [internalResponse, closure, timeStamps] = std::move(aArgs); + + aIPCArgs->closure() = std::move(closure); + aIPCArgs->timeStamps() = std::move(timeStamps); + + PBackgroundChild* bgChild = BackgroundChild::GetOrCreateForCurrentThread(); + + if (NS_WARN_IF(!bgChild)) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + internalResponse->ToChildToParentInternalResponse( + &aIPCArgs->internalResponse(), bgChild); + return NS_OK; +} + +} // anonymous namespace + +void FetchEventOpProxyChild::Initialize( + const ParentToChildServiceWorkerFetchEventOpArgs& aArgs) { + MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread()); + MOZ_ASSERT(!mOp); + + mInternalRequest = + MakeSafeRefPtr<InternalRequest>(aArgs.common().internalRequest()); + + if (aArgs.common().preloadNavigation()) { + // We use synchronous task dispatch here to make sure that if the preload + // response arrived before we dispatch the fetch event, then the JS preload + // response promise will get resolved immediately. + mPreloadResponseAvailablePromise = + MakeRefPtr<FetchEventPreloadResponseAvailablePromise::Private>( + __func__); + mPreloadResponseAvailablePromise->UseSynchronousTaskDispatch(__func__); + if (aArgs.preloadResponse().isSome()) { + mPreloadResponseAvailablePromise->Resolve( + InternalResponse::FromIPC(aArgs.preloadResponse().ref()), __func__); + } + + mPreloadResponseTimingPromise = + MakeRefPtr<FetchEventPreloadResponseTimingPromise::Private>(__func__); + mPreloadResponseTimingPromise->UseSynchronousTaskDispatch(__func__); + if (aArgs.preloadResponseTiming().isSome()) { + mPreloadResponseTimingPromise->Resolve( + aArgs.preloadResponseTiming().ref(), __func__); + } + + mPreloadResponseEndPromise = + MakeRefPtr<FetchEventPreloadResponseEndPromise::Private>(__func__); + mPreloadResponseEndPromise->UseSynchronousTaskDispatch(__func__); + if (aArgs.preloadResponseEndArgs().isSome()) { + mPreloadResponseEndPromise->Resolve(aArgs.preloadResponseEndArgs().ref(), + __func__); + } + } + + RemoteWorkerChild* manager = static_cast<RemoteWorkerChild*>(Manager()); + MOZ_ASSERT(manager); + + RefPtr<FetchEventOpProxyChild> self = this; + + auto callback = [self](const ServiceWorkerOpResult& aResult) { + // FetchEventOp could finish before NavigationPreload fetch finishes. + // If NavigationPreload is available in FetchEvent, caching FetchEventOp + // result until RecvPreloadResponseEnd is called, such that the preload + // response could be completed. + if (self->mPreloadResponseEndPromise && + !self->mPreloadResponseEndPromise->IsResolved() && + self->mPreloadResponseAvailablePromise->IsResolved()) { + self->mCachedOpResult = Some(aResult); + return; + } + if (!self->CanSend()) { + return; + } + + if (NS_WARN_IF(aResult.type() == ServiceWorkerOpResult::Tnsresult)) { + Unused << self->Send__delete__(self, aResult.get_nsresult()); + return; + } + + MOZ_ASSERT(aResult.type() == + ServiceWorkerOpResult::TServiceWorkerFetchEventOpResult); + + Unused << self->Send__delete__(self, aResult); + }; + + RefPtr<FetchEventOp> op = ServiceWorkerOp::Create(aArgs, std::move(callback)) + .template downcast<FetchEventOp>(); + + MOZ_ASSERT(op); + + op->SetActor(this); + mOp = op; + + op->GetRespondWithPromise() + ->Then(GetCurrentSerialEventTarget(), __func__, + [self = std::move(self)]( + FetchEventRespondWithPromise::ResolveOrRejectValue&& aResult) { + self->mRespondWithPromiseRequestHolder.Complete(); + + if (NS_WARN_IF(aResult.IsReject())) { + MOZ_ASSERT(NS_FAILED(aResult.RejectValue().status())); + + Unused << self->SendRespondWith(aResult.RejectValue()); + return; + } + + auto& result = aResult.ResolveValue(); + + if (result.is<SynthesizeResponseArgs>()) { + ChildToParentSynthesizeResponseArgs ipcArgs; + nsresult rv = GetIPCSynthesizeResponseArgs( + &ipcArgs, result.extract<SynthesizeResponseArgs>()); + + if (NS_WARN_IF(NS_FAILED(rv))) { + Unused << self->SendRespondWith( + CancelInterceptionArgs(rv, ipcArgs.timeStamps())); + return; + } + + Unused << self->SendRespondWith(ipcArgs); + } else if (result.is<ResetInterceptionArgs>()) { + Unused << self->SendRespondWith( + result.extract<ResetInterceptionArgs>()); + } else { + Unused << self->SendRespondWith( + result.extract<CancelInterceptionArgs>()); + } + }) + ->Track(mRespondWithPromiseRequestHolder); + + manager->MaybeStartOp(std::move(op)); +} + +SafeRefPtr<InternalRequest> FetchEventOpProxyChild::ExtractInternalRequest() { + MOZ_ASSERT(IsCurrentThreadRunningWorker()); + MOZ_ASSERT(mInternalRequest); + + return std::move(mInternalRequest); +} + +RefPtr<FetchEventPreloadResponseAvailablePromise> +FetchEventOpProxyChild::GetPreloadResponseAvailablePromise() { + return mPreloadResponseAvailablePromise; +} + +RefPtr<FetchEventPreloadResponseTimingPromise> +FetchEventOpProxyChild::GetPreloadResponseTimingPromise() { + return mPreloadResponseTimingPromise; +} + +RefPtr<FetchEventPreloadResponseEndPromise> +FetchEventOpProxyChild::GetPreloadResponseEndPromise() { + return mPreloadResponseEndPromise; +} + +mozilla::ipc::IPCResult FetchEventOpProxyChild::RecvPreloadResponse( + ParentToChildInternalResponse&& aResponse) { + // Receiving this message implies that navigation preload is enabled, so + // Initialize() should have created this promise. + MOZ_ASSERT(mPreloadResponseAvailablePromise); + + mPreloadResponseAvailablePromise->Resolve( + InternalResponse::FromIPC(aResponse), __func__); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult FetchEventOpProxyChild::RecvPreloadResponseTiming( + ResponseTiming&& aTiming) { + // Receiving this message implies that navigation preload is enabled, so + // Initialize() should have created this promise. + MOZ_ASSERT(mPreloadResponseTimingPromise); + + mPreloadResponseTimingPromise->Resolve(std::move(aTiming), __func__); + return IPC_OK(); +} + +mozilla::ipc::IPCResult FetchEventOpProxyChild::RecvPreloadResponseEnd( + ResponseEndArgs&& aArgs) { + // Receiving this message implies that navigation preload is enabled, so + // Initialize() should have created this promise. + MOZ_ASSERT(mPreloadResponseEndPromise); + + mPreloadResponseEndPromise->Resolve(std::move(aArgs), __func__); + // If mCachedOpResult is not nothing, it means FetchEventOp had already done + // and the operation result is cached. Continue closing IPC here. + if (mCachedOpResult.isNothing()) { + return IPC_OK(); + } + + if (!CanSend()) { + return IPC_OK(); + } + + if (NS_WARN_IF(mCachedOpResult.ref().type() == + ServiceWorkerOpResult::Tnsresult)) { + Unused << Send__delete__(this, mCachedOpResult.ref().get_nsresult()); + return IPC_OK(); + } + + MOZ_ASSERT(mCachedOpResult.ref().type() == + ServiceWorkerOpResult::TServiceWorkerFetchEventOpResult); + + Unused << Send__delete__(this, mCachedOpResult.ref()); + + return IPC_OK(); +} + +void FetchEventOpProxyChild::ActorDestroy(ActorDestroyReason) { + Unused << NS_WARN_IF(mRespondWithPromiseRequestHolder.Exists()); + mRespondWithPromiseRequestHolder.DisconnectIfExists(); + + // If mPreloadResponseAvailablePromise exists, navigation preloading response + // will not be valid anymore since it is too late to respond to the + // FetchEvent. Resolve the preload response promise with + // NS_ERROR_DOM_ABORT_ERR. + if (mPreloadResponseAvailablePromise) { + mPreloadResponseAvailablePromise->Resolve( + InternalResponse::NetworkError(NS_ERROR_DOM_ABORT_ERR), __func__); + } + + if (mPreloadResponseTimingPromise) { + mPreloadResponseTimingPromise->Resolve(ResponseTiming(), __func__); + } + + if (mPreloadResponseEndPromise) { + ResponseEndArgs args(FetchDriverObserver::eAborted); + mPreloadResponseEndPromise->Resolve(args, __func__); + } + + mOp->RevokeActor(this); + mOp = nullptr; +} + +} // namespace dom +} // namespace mozilla |