/* -*- 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 "Fetch.h" #include "FetchConsumer.h" #include "mozilla/dom/BlobBinding.h" #include "mozilla/dom/File.h" #include "mozilla/dom/FileBinding.h" #include "mozilla/dom/FileCreatorHelper.h" #include "mozilla/dom/PromiseNativeHandler.h" #include "mozilla/dom/WorkerCommon.h" #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/dom/WorkerRef.h" #include "mozilla/dom/WorkerRunnable.h" #include "mozilla/dom/WorkerScope.h" #include "mozilla/ipc/PBackgroundSharedTypes.h" #include "nsIInputStreamPump.h" #include "nsIThreadRetargetableRequest.h" #include "nsProxyRelease.h" // Undefine the macro of CreateFile to avoid FileCreatorHelper#CreateFile being // replaced by FileCreatorHelper#CreateFileW. #ifdef CreateFile #undef CreateFile #endif namespace mozilla { namespace dom { namespace { template class BeginConsumeBodyRunnable final : public Runnable { public: BeginConsumeBodyRunnable(FetchBodyConsumer* aConsumer, ThreadSafeWorkerRef* aWorkerRef) : Runnable("BeginConsumeBodyRunnable") , mFetchBodyConsumer(aConsumer) , mWorkerRef(aWorkerRef) { } NS_IMETHOD Run() override { mFetchBodyConsumer->BeginConsumeBodyMainThread(mWorkerRef); return NS_OK; } private: RefPtr> mFetchBodyConsumer; RefPtr mWorkerRef; }; /* * Called on successfully reading the complete stream. */ template class ContinueConsumeBodyRunnable final : public MainThreadWorkerRunnable { RefPtr> mFetchBodyConsumer; nsresult mStatus; uint32_t mLength; uint8_t* mResult; public: ContinueConsumeBodyRunnable(FetchBodyConsumer* aFetchBodyConsumer, WorkerPrivate* aWorkerPrivate, nsresult aStatus, uint32_t aLength, uint8_t* aResult) : MainThreadWorkerRunnable(aWorkerPrivate) , mFetchBodyConsumer(aFetchBodyConsumer) , mStatus(aStatus) , mLength(aLength) , mResult(aResult) { MOZ_ASSERT(NS_IsMainThread()); } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { mFetchBodyConsumer->ContinueConsumeBody(mStatus, mLength, mResult); return true; } }; // ControlRunnable used to complete the releasing of resources on the worker // thread when already shutting down. template class AbortConsumeBodyControlRunnable final : public MainThreadWorkerControlRunnable { RefPtr> mFetchBodyConsumer; public: AbortConsumeBodyControlRunnable(FetchBodyConsumer* aFetchBodyConsumer, WorkerPrivate* aWorkerPrivate) : MainThreadWorkerControlRunnable(aWorkerPrivate) , mFetchBodyConsumer(aFetchBodyConsumer) { MOZ_ASSERT(NS_IsMainThread()); } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { mFetchBodyConsumer->ContinueConsumeBody(NS_BINDING_ABORTED, 0, nullptr, true /* shutting down */); return true; } }; /* * In case of failure to create a stream pump or dispatch stream completion to * worker, ensure we cleanup properly. Thread agnostic. */ template class MOZ_STACK_CLASS AutoFailConsumeBody final { public: AutoFailConsumeBody(FetchBodyConsumer* aBodyConsumer, ThreadSafeWorkerRef* aWorkerRef) : mBodyConsumer(aBodyConsumer) , mWorkerRef(aWorkerRef) {} ~AutoFailConsumeBody() { AssertIsOnMainThread(); if (!mBodyConsumer) { return; } // Web Worker if (mWorkerRef) { RefPtr> r = new AbortConsumeBodyControlRunnable(mBodyConsumer, mWorkerRef->Private()); if (!r->Dispatch()) { MOZ_CRASH("We are going to leak"); } return; } // Main-thread mBodyConsumer->ContinueConsumeBody(NS_ERROR_FAILURE, 0, nullptr); } void DontFail() { mBodyConsumer = nullptr; } private: RefPtr> mBodyConsumer; RefPtr mWorkerRef; }; /* * Called on successfully reading the complete stream for Blob. */ template class ContinueConsumeBlobBodyRunnable final : public MainThreadWorkerRunnable { RefPtr> mFetchBodyConsumer; RefPtr mBlobImpl; public: ContinueConsumeBlobBodyRunnable(FetchBodyConsumer* aFetchBodyConsumer, WorkerPrivate* aWorkerPrivate, BlobImpl* aBlobImpl) : MainThreadWorkerRunnable(aWorkerPrivate) , mFetchBodyConsumer(aFetchBodyConsumer) , mBlobImpl(aBlobImpl) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mBlobImpl); } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { mFetchBodyConsumer->ContinueConsumeBlobBody(mBlobImpl); return true; } }; // ControlRunnable used to complete the releasing of resources on the worker // thread when already shutting down. template class AbortConsumeBlobBodyControlRunnable final : public MainThreadWorkerControlRunnable { RefPtr> mFetchBodyConsumer; public: AbortConsumeBlobBodyControlRunnable(FetchBodyConsumer* aFetchBodyConsumer, WorkerPrivate* aWorkerPrivate) : MainThreadWorkerControlRunnable(aWorkerPrivate) , mFetchBodyConsumer(aFetchBodyConsumer) { MOZ_ASSERT(NS_IsMainThread()); } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { mFetchBodyConsumer->ContinueConsumeBlobBody(nullptr, true /* shutting down */); return true; } }; template class ConsumeBodyDoneObserver final : public nsIStreamLoaderObserver , public MutableBlobStorageCallback { public: NS_DECL_THREADSAFE_ISUPPORTS ConsumeBodyDoneObserver(FetchBodyConsumer* aFetchBodyConsumer, ThreadSafeWorkerRef* aWorkerRef) : mFetchBodyConsumer(aFetchBodyConsumer) , mWorkerRef(aWorkerRef) { } NS_IMETHOD OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aCtxt, nsresult aStatus, uint32_t aResultLength, const uint8_t* aResult) override { MOZ_ASSERT(NS_IsMainThread()); // The loading is completed. Let's nullify the pump before continuing the // consuming of the body. mFetchBodyConsumer->NullifyConsumeBodyPump(); uint8_t* nonconstResult = const_cast(aResult); // Main-thread. if (!mWorkerRef) { mFetchBodyConsumer->ContinueConsumeBody(aStatus, aResultLength, nonconstResult); // FetchBody is responsible for data. return NS_SUCCESS_ADOPTED_DATA; } // Web Worker. { RefPtr> r = new ContinueConsumeBodyRunnable(mFetchBodyConsumer, mWorkerRef->Private(), aStatus, aResultLength, nonconstResult); if (r->Dispatch()) { // FetchBody is responsible for data. return NS_SUCCESS_ADOPTED_DATA; } } // The worker is shutting down. Let's use a control runnable to complete the // shutting down procedure. RefPtr> r = new AbortConsumeBodyControlRunnable(mFetchBodyConsumer, mWorkerRef->Private()); if (NS_WARN_IF(!r->Dispatch())) { return NS_ERROR_FAILURE; } // We haven't taken ownership of the data. return NS_OK; } virtual void BlobStoreCompleted(MutableBlobStorage* aBlobStorage, Blob* aBlob, nsresult aRv) override { // On error. if (NS_FAILED(aRv)) { OnStreamComplete(nullptr, nullptr, aRv, 0, nullptr); return; } // The loading is completed. Let's nullify the pump before continuing the // consuming of the body. mFetchBodyConsumer->NullifyConsumeBodyPump(); mFetchBodyConsumer->OnBlobResult(aBlob, mWorkerRef); } private: ~ConsumeBodyDoneObserver() = default; RefPtr> mFetchBodyConsumer; RefPtr mWorkerRef; }; template NS_IMPL_ADDREF(ConsumeBodyDoneObserver) template NS_IMPL_RELEASE(ConsumeBodyDoneObserver) template NS_INTERFACE_MAP_BEGIN(ConsumeBodyDoneObserver) NS_INTERFACE_MAP_ENTRY(nsIStreamLoaderObserver) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamLoaderObserver) NS_INTERFACE_MAP_END } // anonymous template /* static */ already_AddRefed FetchBodyConsumer::Create(nsIGlobalObject* aGlobal, nsIEventTarget* aMainThreadEventTarget, FetchBody* aBody, AbortSignalImpl* aSignalImpl, FetchConsumeType aType, ErrorResult& aRv) { MOZ_ASSERT(aBody); MOZ_ASSERT(aMainThreadEventTarget); nsCOMPtr bodyStream; aBody->DerivedClass()->GetBody(getter_AddRefs(bodyStream)); if (!bodyStream) { aRv = NS_NewCStringInputStream(getter_AddRefs(bodyStream), EmptyCString()); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } } RefPtr promise = Promise::Create(aGlobal, aRv); if (aRv.Failed()) { return nullptr; } RefPtr> consumer = new FetchBodyConsumer(aMainThreadEventTarget, aGlobal, aBody, bodyStream, promise, aType); RefPtr workerRef; if (!NS_IsMainThread()) { WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(workerPrivate); RefPtr strongWorkerRef = StrongWorkerRef::Create(workerPrivate, "FetchBodyConsumer", [consumer]() { consumer->ShutDownMainThreadConsuming(); }); if (NS_WARN_IF(!strongWorkerRef)) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } workerRef = new ThreadSafeWorkerRef(strongWorkerRef); } else { nsCOMPtr os = mozilla::services::GetObserverService(); if (NS_WARN_IF(!os)) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } aRv = os->AddObserver(consumer, DOM_WINDOW_DESTROYED_TOPIC, true); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } aRv = os->AddObserver(consumer, DOM_WINDOW_FROZEN_TOPIC, true); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } } nsCOMPtr r = new BeginConsumeBodyRunnable(consumer, workerRef); aRv = aMainThreadEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } if (aSignalImpl) { consumer->Follow(aSignalImpl); } return promise.forget(); } template void FetchBodyConsumer::ReleaseObject() { AssertIsOnTargetThread(); if (NS_IsMainThread()) { nsCOMPtr os = mozilla::services::GetObserverService(); if (os) { os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC); os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC); } } mGlobal = nullptr; #ifdef DEBUG mBody = nullptr; #endif Unfollow(); } template FetchBodyConsumer::FetchBodyConsumer(nsIEventTarget* aMainThreadEventTarget, nsIGlobalObject* aGlobalObject, FetchBody* aBody, nsIInputStream* aBodyStream, Promise* aPromise, FetchConsumeType aType) : mTargetThread(NS_GetCurrentThread()) , mMainThreadEventTarget(aMainThreadEventTarget) #ifdef DEBUG , mBody(aBody) #endif , mBodyStream(aBodyStream) , mBlobStorageType(MutableBlobStorage::eOnlyInMemory) , mBodyLocalPath(aBody ? aBody->BodyLocalPath() : VoidString()) , mGlobal(aGlobalObject) , mConsumeType(aType) , mConsumePromise(aPromise) , mBodyConsumed(false) , mShuttingDown(false) { MOZ_ASSERT(aMainThreadEventTarget); MOZ_ASSERT(aBody); MOZ_ASSERT(aBodyStream); MOZ_ASSERT(aPromise); const mozilla::UniquePtr& principalInfo = aBody->DerivedClass()->GetPrincipalInfo(); // We support temporary file for blobs only if the principal is known and // it's system or content not in private Browsing. if (principalInfo && (principalInfo->type() == mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo || (principalInfo->type() == mozilla::ipc::PrincipalInfo::TContentPrincipalInfo && principalInfo->get_ContentPrincipalInfo().attrs().mPrivateBrowsingId == 0))) { mBlobStorageType = MutableBlobStorage::eCouldBeInTemporaryFile; } mBodyMimeType = aBody->MimeType(); } template FetchBodyConsumer::~FetchBodyConsumer() { } template void FetchBodyConsumer::AssertIsOnTargetThread() const { MOZ_ASSERT(NS_GetCurrentThread() == mTargetThread); } namespace { template class FileCreationHandler final : public PromiseNativeHandler { public: NS_DECL_THREADSAFE_ISUPPORTS static void Create(Promise* aPromise, FetchBodyConsumer* aConsumer) { AssertIsOnMainThread(); MOZ_ASSERT(aPromise); RefPtr handler = new FileCreationHandler(aConsumer); aPromise->AppendNativeHandler(handler); } void ResolvedCallback(JSContext* aCx, JS::Handle aValue) override { AssertIsOnMainThread(); if (NS_WARN_IF(!aValue.isObject())) { mConsumer->OnBlobResult(nullptr); return; } RefPtr blob; if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Blob, &aValue.toObject(), blob)))) { mConsumer->OnBlobResult(nullptr); return; } mConsumer->OnBlobResult(blob); } void RejectedCallback(JSContext* aCx, JS::Handle aValue) override { AssertIsOnMainThread(); mConsumer->OnBlobResult(nullptr); } private: explicit FileCreationHandler(FetchBodyConsumer* aConsumer) : mConsumer(aConsumer) { AssertIsOnMainThread(); MOZ_ASSERT(aConsumer); } ~FileCreationHandler() = default; RefPtr> mConsumer; }; template NS_IMPL_ADDREF(FileCreationHandler) template NS_IMPL_RELEASE(FileCreationHandler) template NS_INTERFACE_MAP_BEGIN(FileCreationHandler) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END } // namespace template nsresult FetchBodyConsumer::GetBodyLocalFile(nsIFile** aFile) const { AssertIsOnMainThread(); if (!mBodyLocalPath.Length()) { return NS_OK; } nsresult rv; nsCOMPtr file = do_CreateInstance("@mozilla.org/file/local;1", &rv); if (NS_FAILED(rv)) { return rv; } rv = file->InitWithPath(mBodyLocalPath); NS_ENSURE_SUCCESS(rv, rv); bool exists; rv = file->Exists(&exists); NS_ENSURE_SUCCESS(rv, rv); if (!exists) { return NS_ERROR_FILE_NOT_FOUND; } bool isDir; rv = file->IsDirectory(&isDir); NS_ENSURE_SUCCESS(rv, rv); if (isDir) { return NS_ERROR_FILE_IS_DIRECTORY; } file.forget(aFile); return NS_OK; } /* * BeginConsumeBodyMainThread() will automatically reject the consume promise * and clean up on any failures, so there is no need for callers to do so, * reflected in a lack of error return code. */ template void FetchBodyConsumer::BeginConsumeBodyMainThread(ThreadSafeWorkerRef* aWorkerRef) { AssertIsOnMainThread(); AutoFailConsumeBody autoReject(this, aWorkerRef); if (mShuttingDown) { // We haven't started yet, but we have been terminated. AutoFailConsumeBody // will dispatch a runnable to release resources. return; } // If we're trying to consume a blob, and the request was for a local // file, then generate and return a File blob. if (mConsumeType == CONSUME_BLOB) { nsCOMPtr file; nsresult rv = GetBodyLocalFile(getter_AddRefs(file)); if (!NS_WARN_IF(NS_FAILED(rv)) && file) { ChromeFilePropertyBag bag; bag.mType = NS_ConvertUTF8toUTF16(mBodyMimeType); ErrorResult error; RefPtr promise = FileCreatorHelper::CreateFile(mGlobal, file, bag, true, error); if (NS_WARN_IF(error.Failed())) { return; } FileCreationHandler::Create(promise, this); autoReject.DontFail(); return; } } nsCOMPtr pump; nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump), mBodyStream.forget(), 0, 0, false, mMainThreadEventTarget); if (NS_WARN_IF(NS_FAILED(rv))) { return; } RefPtr> p = new ConsumeBodyDoneObserver(this, aWorkerRef); nsCOMPtr listener; if (mConsumeType == CONSUME_BLOB) { listener = new MutableBlobStreamListener(mBlobStorageType, nullptr, mBodyMimeType, p, mMainThreadEventTarget); } else { nsCOMPtr loader; rv = NS_NewStreamLoader(getter_AddRefs(loader), p); if (NS_WARN_IF(NS_FAILED(rv))) { return; } listener = loader; } rv = pump->AsyncRead(listener, nullptr); if (NS_WARN_IF(NS_FAILED(rv))) { return; } // Now that everything succeeded, we can assign the pump to a pointer that // stays alive for the lifetime of the FetchConsumer. mConsumeBodyPump = pump; // It is ok for retargeting to fail and reads to happen on the main thread. autoReject.DontFail(); // Try to retarget, otherwise fall back to main thread. nsCOMPtr rr = do_QueryInterface(pump); if (rr) { nsCOMPtr sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); rv = rr->RetargetDeliveryTo(sts); if (NS_FAILED(rv)) { NS_WARNING("Retargeting failed"); } } } /* * OnBlobResult() is called when a blob body is ready to be consumed (when its * network transfer completes in BeginConsumeBodyRunnable or its local File has * been wrapped by FileCreationHandler). The blob is sent to the target thread * and ContinueConsumeBody is called. */ template void FetchBodyConsumer::OnBlobResult(Blob* aBlob, ThreadSafeWorkerRef* aWorkerRef) { MOZ_ASSERT(aBlob); // Main-thread. if (!aWorkerRef) { ContinueConsumeBlobBody(aBlob->Impl()); return; } // Web Worker. { RefPtr> r = new ContinueConsumeBlobBodyRunnable(this, aWorkerRef->Private(), aBlob->Impl()); if (r->Dispatch()) { return; } } // The worker is shutting down. Let's use a control runnable to complete the // shutting down procedure. RefPtr> r = new AbortConsumeBlobBodyControlRunnable(this, aWorkerRef->Private()); Unused << NS_WARN_IF(!r->Dispatch()); } /* * ContinueConsumeBody() is to be called on the target thread whenever the * final result of the fetch is known. The fetch promise is resolved or * rejected based on whether the fetch succeeded, and the body can be * converted into the expected type of JS object. */ template void FetchBodyConsumer::ContinueConsumeBody(nsresult aStatus, uint32_t aResultLength, uint8_t* aResult, bool aShuttingDown) { AssertIsOnTargetThread(); // This makes sure that we free the data correctly. auto autoFree = mozilla::MakeScopeExit([&] { free(aResult); }); if (mBodyConsumed) { return; } mBodyConsumed = true; // Just a precaution to ensure ContinueConsumeBody is not called out of // sync with a body read. MOZ_ASSERT(mBody->BodyUsed()); MOZ_ASSERT(mConsumePromise); RefPtr localPromise = mConsumePromise.forget(); RefPtr> self = this; auto autoReleaseObject = mozilla::MakeScopeExit([self] { self->ReleaseObject(); }); if (aShuttingDown) { // If shutting down, we don't want to resolve any promise. return; } if (NS_WARN_IF(NS_FAILED(aStatus))) { localPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); } // Don't warn here since we warned above. if (NS_FAILED(aStatus)) { return; } // Finish successfully consuming body according to type. MOZ_ASSERT(aResult); AutoJSAPI jsapi; if (!jsapi.Init(mGlobal)) { localPromise->MaybeReject(NS_ERROR_UNEXPECTED); return; } JSContext* cx = jsapi.cx(); ErrorResult error; switch (mConsumeType) { case CONSUME_ARRAYBUFFER: { JS::Rooted arrayBuffer(cx); BodyUtil::ConsumeArrayBuffer(cx, &arrayBuffer, aResultLength, aResult, error); if (!error.Failed()) { JS::Rooted val(cx); val.setObjectOrNull(arrayBuffer); localPromise->MaybeResolve(cx, val); // ArrayBuffer takes over ownership. aResult = nullptr; } break; } case CONSUME_BLOB: { MOZ_CRASH("This should not happen."); break; } case CONSUME_FORMDATA: { nsCString data; data.Adopt(reinterpret_cast(aResult), aResultLength); aResult = nullptr; RefPtr fd = BodyUtil::ConsumeFormData(mGlobal, mBodyMimeType, data, error); if (!error.Failed()) { localPromise->MaybeResolve(fd); } break; } case CONSUME_TEXT: // fall through handles early exit. case CONSUME_JSON: { nsString decoded; if (NS_SUCCEEDED(BodyUtil::ConsumeText(aResultLength, aResult, decoded))) { if (mConsumeType == CONSUME_TEXT) { localPromise->MaybeResolve(decoded); } else { JS::Rooted json(cx); BodyUtil::ConsumeJson(cx, &json, decoded, error); if (!error.Failed()) { localPromise->MaybeResolve(cx, json); } } }; break; } default: MOZ_ASSERT_UNREACHABLE("Unexpected consume body type"); } error.WouldReportJSException(); if (error.Failed()) { localPromise->MaybeReject(error); } } template void FetchBodyConsumer::ContinueConsumeBlobBody(BlobImpl* aBlobImpl, bool aShuttingDown) { AssertIsOnTargetThread(); MOZ_ASSERT(mConsumeType == CONSUME_BLOB); if (mBodyConsumed) { return; } mBodyConsumed = true; // Just a precaution to ensure ContinueConsumeBody is not called out of // sync with a body read. MOZ_ASSERT(mBody->BodyUsed()); MOZ_ASSERT(mConsumePromise); RefPtr localPromise = mConsumePromise.forget(); if (!aShuttingDown) { RefPtr blob = dom::Blob::Create(mGlobal, aBlobImpl); MOZ_ASSERT(blob); localPromise->MaybeResolve(blob); } ReleaseObject(); } template void FetchBodyConsumer::ShutDownMainThreadConsuming() { if (!NS_IsMainThread()) { RefPtr> self = this; nsCOMPtr r = NS_NewRunnableFunction( "FetchBodyConsumer::ShutDownMainThreadConsuming", [self] () { self->ShutDownMainThreadConsuming(); }); mMainThreadEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL); return; } // We need this because maybe, mConsumeBodyPump has not been created yet. We // must be sure that we don't try to do it. mShuttingDown = true; if (mConsumeBodyPump) { mConsumeBodyPump->Cancel(NS_BINDING_ABORTED); mConsumeBodyPump = nullptr; } } template NS_IMETHODIMP FetchBodyConsumer::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { AssertIsOnMainThread(); MOZ_ASSERT((strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) || (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0)); nsCOMPtr window = do_QueryInterface(mGlobal); if (SameCOMIdentity(aSubject, window)) { ContinueConsumeBody(NS_BINDING_ABORTED, 0, nullptr); } return NS_OK; } template void FetchBodyConsumer::Abort() { AssertIsOnTargetThread(); ShutDownMainThreadConsuming(); ContinueConsumeBody(NS_ERROR_DOM_ABORT_ERR, 0, nullptr); } template NS_IMPL_ADDREF(FetchBodyConsumer) template NS_IMPL_RELEASE(FetchBodyConsumer) template NS_IMPL_QUERY_INTERFACE(FetchBodyConsumer, nsIObserver, nsISupportsWeakReference) } // namespace dom } // namespace mozilla