/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ServiceWorkerEvents.h" #include "ServiceWorkerClient.h" #include "nsINetworkInterceptController.h" #include "nsIOutputStream.h" #include "nsContentUtils.h" #include "nsComponentManagerUtils.h" #include "nsServiceManagerUtils.h" #include "nsStreamUtils.h" #include "nsNetCID.h" #include "nsSerializationHelper.h" #include "mozilla/dom/FetchEventBinding.h" #include "mozilla/dom/PromiseNativeHandler.h" #include "mozilla/dom/Request.h" #include "mozilla/dom/Response.h" #include "mozilla/dom/WorkerScope.h" #include "mozilla/dom/workers/bindings/ServiceWorker.h" using namespace mozilla::dom; BEGIN_WORKERS_NAMESPACE FetchEvent::FetchEvent(EventTarget* aOwner) : Event(aOwner, nullptr, nullptr) , mIsReload(false) , mWaitToRespond(false) { } FetchEvent::~FetchEvent() { } void FetchEvent::PostInit(nsMainThreadPtrHandle& aChannel, nsMainThreadPtrHandle& aServiceWorker, nsAutoPtr& aClientInfo) { mChannel = aChannel; mServiceWorker = aServiceWorker; mClientInfo = aClientInfo; } /*static*/ already_AddRefed FetchEvent::Constructor(const GlobalObject& aGlobal, const nsAString& aType, const FetchEventInit& aOptions, ErrorResult& aRv) { nsRefPtr owner = do_QueryObject(aGlobal.GetAsSupports()); MOZ_ASSERT(owner); nsRefPtr e = new FetchEvent(owner); bool trusted = e->Init(owner); e->InitEvent(aType, aOptions.mBubbles, aOptions.mCancelable); e->SetTrusted(trusted); e->mRequest = aOptions.mRequest.WasPassed() ? &aOptions.mRequest.Value() : nullptr; e->mIsReload = aOptions.mIsReload.WasPassed() ? aOptions.mIsReload.Value() : false; e->mClient = aOptions.mClient.WasPassed() ? &aOptions.mClient.Value() : nullptr; return e.forget(); } namespace { class CancelChannelRunnable MOZ_FINAL : public nsRunnable { nsMainThreadPtrHandle mChannel; public: explicit CancelChannelRunnable(nsMainThreadPtrHandle& aChannel) : mChannel(aChannel) { } NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); nsresult rv = mChannel->Cancel(); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } }; class FinishResponse MOZ_FINAL : public nsRunnable { nsMainThreadPtrHandle mChannel; nsRefPtr mInternalResponse; public: FinishResponse(nsMainThreadPtrHandle& aChannel, InternalResponse* aInternalResponse) : mChannel(aChannel) , mInternalResponse(aInternalResponse) { } NS_IMETHOD Run() { AssertIsOnMainThread(); nsCOMPtr infoObj; nsresult rv = NS_DeserializeObject(mInternalResponse->GetSecurityInfo(), getter_AddRefs(infoObj)); if (NS_SUCCEEDED(rv)) { rv = mChannel->SetSecurityInfo(infoObj); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } mChannel->SynthesizeStatus(mInternalResponse->GetStatus(), mInternalResponse->GetStatusText()); nsAutoTArray entries; mInternalResponse->Headers()->GetEntries(entries); for (uint32_t i = 0; i < entries.Length(); ++i) { mChannel->SynthesizeHeader(entries[i].mName, entries[i].mValue); } rv = mChannel->FinishSynthesizedResponse(); NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to finish synthesized response"); return rv; } }; class RespondWithHandler MOZ_FINAL : public PromiseNativeHandler { nsMainThreadPtrHandle mInterceptedChannel; nsMainThreadPtrHandle mServiceWorker; public: RespondWithHandler(nsMainThreadPtrHandle& aChannel, nsMainThreadPtrHandle& aServiceWorker) : mInterceptedChannel(aChannel) , mServiceWorker(aServiceWorker) { } void ResolvedCallback(JSContext* aCx, JS::Handle aValue) MOZ_OVERRIDE; void RejectedCallback(JSContext* aCx, JS::Handle aValue) MOZ_OVERRIDE; void CancelRequest(); }; struct RespondWithClosure { nsMainThreadPtrHandle mInterceptedChannel; nsRefPtr mInternalResponse; RespondWithClosure(nsMainThreadPtrHandle& aChannel, InternalResponse* aInternalResponse) : mInterceptedChannel(aChannel) , mInternalResponse(aInternalResponse) { } }; void RespondWithCopyComplete(void* aClosure, nsresult aStatus) { nsAutoPtr data(static_cast(aClosure)); nsCOMPtr event; if (NS_SUCCEEDED(aStatus)) { event = new FinishResponse(data->mInterceptedChannel, data->mInternalResponse); } else { event = new CancelChannelRunnable(data->mInterceptedChannel); } MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(event))); } class MOZ_STACK_CLASS AutoCancel { nsRefPtr mOwner; public: explicit AutoCancel(RespondWithHandler* aOwner) : mOwner(aOwner) { } ~AutoCancel() { if (mOwner) { mOwner->CancelRequest(); } } void Reset() { mOwner = nullptr; } }; void RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle aValue) { AutoCancel autoCancel(this); if (!aValue.isObject()) { return; } nsRefPtr response; nsresult rv = UNWRAP_OBJECT(Response, &aValue.toObject(), response); if (NS_FAILED(rv)) { return; } // FIXME(nsm) Bug 1136200 deal with opaque and no-cors (fetch spec 4.2.2.2). if (response->Type() == ResponseType::Error) { return; } if (NS_WARN_IF(response->BodyUsed())) { return; } nsCOMPtr body; response->GetBody(getter_AddRefs(body)); if (NS_WARN_IF(!body)) { return; } response->SetBodyUsed(); nsCOMPtr responseBody; rv = mInterceptedChannel->GetResponseBody(getter_AddRefs(responseBody)); if (NS_WARN_IF(NS_FAILED(rv))) { return; } nsRefPtr ir = response->GetInternalResponse(); if (NS_WARN_IF(!ir)) { return; } nsAutoPtr closure(new RespondWithClosure(mInterceptedChannel, ir)); nsCOMPtr stsThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); if (NS_WARN_IF(!stsThread)) { return; } rv = NS_AsyncCopy(body, responseBody, stsThread, NS_ASYNCCOPY_VIA_READSEGMENTS, 4096, RespondWithCopyComplete, closure.forget()); if (NS_WARN_IF(NS_FAILED(rv))) { return; } autoCancel.Reset(); } void RespondWithHandler::RejectedCallback(JSContext* aCx, JS::Handle aValue) { CancelRequest(); } void RespondWithHandler::CancelRequest() { nsCOMPtr runnable = new CancelChannelRunnable(mInterceptedChannel); NS_DispatchToMainThread(runnable); } } // anonymous namespace void FetchEvent::RespondWith(Promise& aPromise, ErrorResult& aRv) { if (mWaitToRespond) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } mWaitToRespond = true; nsRefPtr handler = new RespondWithHandler(mChannel, mServiceWorker); aPromise.AppendNativeHandler(handler); } already_AddRefed FetchEvent::GetClient() { if (!mClient) { if (!mClientInfo) { return nullptr; } mClient = new ServiceWorkerClient(GetParentObject(), *mClientInfo); } nsRefPtr client = mClient; return client.forget(); } NS_IMPL_ADDREF_INHERITED(FetchEvent, Event) NS_IMPL_RELEASE_INHERITED(FetchEvent, Event) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FetchEvent) NS_INTERFACE_MAP_END_INHERITING(Event) NS_IMPL_CYCLE_COLLECTION_INHERITED(FetchEvent, Event, mRequest, mClient) ExtendableEvent::ExtendableEvent(EventTarget* aOwner) : Event(aOwner, nullptr, nullptr) { } void ExtendableEvent::WaitUntil(Promise& aPromise) { MOZ_ASSERT(!NS_IsMainThread()); // Only first caller counts. if (EventPhase() == AT_TARGET && !mPromise) { mPromise = &aPromise; } } NS_IMPL_ADDREF_INHERITED(ExtendableEvent, Event) NS_IMPL_RELEASE_INHERITED(ExtendableEvent, Event) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ExtendableEvent) NS_INTERFACE_MAP_END_INHERITING(Event) NS_IMPL_CYCLE_COLLECTION_INHERITED(ExtendableEvent, Event, mPromise) InstallEvent::InstallEvent(EventTarget* aOwner) : ExtendableEvent(aOwner) , mActivateImmediately(false) { } NS_IMPL_ADDREF_INHERITED(InstallEvent, ExtendableEvent) NS_IMPL_RELEASE_INHERITED(InstallEvent, ExtendableEvent) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(InstallEvent) NS_INTERFACE_MAP_END_INHERITING(ExtendableEvent) NS_IMPL_CYCLE_COLLECTION_INHERITED(InstallEvent, ExtendableEvent, mActiveWorker) END_WORKERS_NAMESPACE