fune/js/loader/ScriptLoadRequest.cpp
Yulia Startsev d2fa4ee51a Bug 1798667 - clear cache when finished with service worker scripts; r=asuth
I was over eager in introducing the strong pointers to WorkerLoadContext. It turns out that
previously when we were cycle collecting our ScriptLoadRequests, we were also cleaning up everything
related to WorkerLoadContext. Also, in an attempt to fix the cancellation for workers, We
accidentally stopped cleaning up the reference to the cache creator. This patch does the following:

1) cleans up the cache creator reference whenever we finish with a cache load handler. This is done
by calling Fail appropriately. This solves both bug 1798667 (we no longer reach NEW_URI if we fail)
and 1781295 (we no longer leak memory because things were not cleaned up properly).
2) Does no remove the back reference to WorkerLoadContext from the LoadRequest. It turns out that
this is sometimes necessary. There is a chance that we will accidently try to access the
WorkerLoadContext after cancellation. This actually causes problems later on in the module code.

Differential Revision: https://phabricator.services.mozilla.com/D161288
2022-11-04 15:04:27 +00:00

302 lines
9 KiB
C++

/* -*- 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 "ScriptLoadRequest.h"
#include "GeckoProfiler.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/ScriptLoadContext.h"
#include "mozilla/dom/WorkerLoadContext.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/Unused.h"
#include "mozilla/Utf8.h" // mozilla::Utf8Unit
#include "js/OffThreadScriptCompilation.h"
#include "js/SourceText.h"
#include "ModuleLoadRequest.h"
#include "nsContentUtils.h"
#include "nsICacheInfoChannel.h"
#include "nsIClassOfService.h"
#include "nsISupportsPriority.h"
using JS::SourceText;
namespace JS::loader {
//////////////////////////////////////////////////////////////
// ScriptFetchOptions
//////////////////////////////////////////////////////////////
NS_IMPL_CYCLE_COLLECTION(ScriptFetchOptions, mTriggeringPrincipal, mElement)
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(ScriptFetchOptions, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(ScriptFetchOptions, Release)
ScriptFetchOptions::ScriptFetchOptions(
mozilla::CORSMode aCORSMode, mozilla::dom::ReferrerPolicy aReferrerPolicy,
nsIPrincipal* aTriggeringPrincipal, mozilla::dom::Element* aElement)
: mCORSMode(aCORSMode),
mReferrerPolicy(aReferrerPolicy),
mTriggeringPrincipal(aTriggeringPrincipal),
mElement(aElement) {}
ScriptFetchOptions::~ScriptFetchOptions() = default;
//////////////////////////////////////////////////////////////
// ScriptLoadRequest
//////////////////////////////////////////////////////////////
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptLoadRequest)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptLoadRequest)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptLoadRequest)
NS_IMPL_CYCLE_COLLECTION_CLASS(ScriptLoadRequest)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ScriptLoadRequest)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchOptions, mCacheInfo, mLoadContext)
tmp->mScriptForBytecodeEncoding = nullptr;
tmp->DropBytecodeCacheReferences();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ScriptLoadRequest)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchOptions, mCacheInfo, mLoadContext)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ScriptLoadRequest)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScriptForBytecodeEncoding)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
ScriptLoadRequest::ScriptLoadRequest(ScriptKind aKind, nsIURI* aURI,
ScriptFetchOptions* aFetchOptions,
const SRIMetadata& aIntegrity,
nsIURI* aReferrer,
LoadContextBase* aContext)
: mKind(aKind),
mState(State::Fetching),
mFetchSourceOnly(false),
mDataType(DataType::eUnknown),
mFetchOptions(aFetchOptions),
mIntegrity(aIntegrity),
mReferrer(aReferrer),
mScriptTextLength(0),
mScriptBytecode(),
mBytecodeOffset(0),
mURI(aURI),
mLoadContext(aContext) {
MOZ_ASSERT(mFetchOptions);
if (mLoadContext) {
mLoadContext->SetRequest(this);
}
}
ScriptLoadRequest::~ScriptLoadRequest() {
if (IsMarkedForBytecodeEncoding()) {
DropBytecodeCacheReferences();
}
mLoadContext = nullptr;
DropJSObjects(this);
}
void ScriptLoadRequest::SetReady() {
MOZ_ASSERT(!IsReadyToRun());
mState = State::Ready;
}
void ScriptLoadRequest::Cancel() {
mState = State::Canceled;
if (HasScriptLoadContext()) {
GetScriptLoadContext()->MaybeCancelOffThreadScript();
}
if (HasWorkerLoadContext()) {
// The back reference needs to be cleared for workers, as there is no CC.
// However, we don't want to remove our pointer to the worker load
// context as it is used to determine load failure information.
GetWorkerLoadContext()->mRequest = nullptr;
}
}
void ScriptLoadRequest::DropBytecodeCacheReferences() {
mCacheInfo = nullptr;
DropJSObjects(this);
}
bool ScriptLoadRequest::HasScriptLoadContext() const {
return HasLoadContext() && mLoadContext->IsWindowContext();
}
bool ScriptLoadRequest::HasWorkerLoadContext() const {
return HasLoadContext() && mLoadContext->IsWorkerContext();
}
mozilla::dom::ScriptLoadContext* ScriptLoadRequest::GetScriptLoadContext() {
MOZ_ASSERT(mLoadContext);
return mLoadContext->AsWindowContext();
}
mozilla::loader::ComponentLoadContext*
ScriptLoadRequest::GetComponentLoadContext() {
MOZ_ASSERT(mLoadContext);
return mLoadContext->AsComponentContext();
}
mozilla::dom::WorkerLoadContext* ScriptLoadRequest::GetWorkerLoadContext() {
MOZ_ASSERT(mLoadContext);
return mLoadContext->AsWorkerContext();
}
already_AddRefed<mozilla::dom::WorkerLoadContext>
ScriptLoadRequest::StealWorkerLoadContext() {
MOZ_ASSERT(mLoadContext);
RefPtr<mozilla::dom::WorkerLoadContext> workerContext =
mLoadContext->AsWorkerContext();
// Break cycle.
mLoadContext->mRequest = nullptr;
mLoadContext = nullptr;
return workerContext.forget();
}
ModuleLoadRequest* ScriptLoadRequest::AsModuleRequest() {
MOZ_ASSERT(IsModuleRequest());
return static_cast<ModuleLoadRequest*>(this);
}
const ModuleLoadRequest* ScriptLoadRequest::AsModuleRequest() const {
MOZ_ASSERT(IsModuleRequest());
return static_cast<const ModuleLoadRequest*>(this);
}
void ScriptLoadRequest::SetBytecode() {
MOZ_ASSERT(IsUnknownDataType());
mDataType = DataType::eBytecode;
}
bool ScriptLoadRequest::IsUTF8ParsingEnabled() {
if (HasLoadContext()) {
if (mLoadContext->IsWindowContext()) {
return mozilla::StaticPrefs::
dom_script_loader_external_scripts_utf8_parsing_enabled();
}
if (mLoadContext->IsWorkerContext()) {
return mozilla::StaticPrefs::
dom_worker_script_loader_utf8_parsing_enabled();
}
}
return false;
}
void ScriptLoadRequest::ClearScriptSource() {
if (IsTextSource()) {
ClearScriptText();
}
}
void ScriptLoadRequest::MarkForBytecodeEncoding(JSScript* aScript) {
MOZ_ASSERT(!IsModuleRequest());
MOZ_ASSERT(!IsMarkedForBytecodeEncoding());
mScriptForBytecodeEncoding = aScript;
HoldJSObjects(this);
}
bool ScriptLoadRequest::IsMarkedForBytecodeEncoding() const {
if (IsModuleRequest()) {
return AsModuleRequest()->IsModuleMarkedForBytecodeEncoding();
}
return !!mScriptForBytecodeEncoding;
}
nsresult ScriptLoadRequest::GetScriptSource(JSContext* aCx,
MaybeSourceText* aMaybeSource) {
// If there's no script text, we try to get it from the element
if (HasScriptLoadContext() && GetScriptLoadContext()->mIsInline) {
nsAutoString inlineData;
GetScriptLoadContext()->GetScriptElement()->GetScriptText(inlineData);
size_t nbytes = inlineData.Length() * sizeof(char16_t);
JS::UniqueTwoByteChars chars(
static_cast<char16_t*>(JS_malloc(aCx, nbytes)));
if (!chars) {
return NS_ERROR_OUT_OF_MEMORY;
}
memcpy(chars.get(), inlineData.get(), nbytes);
SourceText<char16_t> srcBuf;
if (!srcBuf.init(aCx, std::move(chars), inlineData.Length())) {
return NS_ERROR_OUT_OF_MEMORY;
}
aMaybeSource->construct<SourceText<char16_t>>(std::move(srcBuf));
return NS_OK;
}
size_t length = ScriptTextLength();
if (IsUTF16Text()) {
JS::UniqueTwoByteChars chars;
chars.reset(ScriptText<char16_t>().extractOrCopyRawBuffer());
if (!chars) {
JS_ReportOutOfMemory(aCx);
return NS_ERROR_OUT_OF_MEMORY;
}
SourceText<char16_t> srcBuf;
if (!srcBuf.init(aCx, std::move(chars), length)) {
return NS_ERROR_OUT_OF_MEMORY;
}
aMaybeSource->construct<SourceText<char16_t>>(std::move(srcBuf));
return NS_OK;
}
MOZ_ASSERT(IsUTF8Text());
UniquePtr<Utf8Unit[], JS::FreePolicy> chars;
chars.reset(ScriptText<Utf8Unit>().extractOrCopyRawBuffer());
if (!chars) {
JS_ReportOutOfMemory(aCx);
return NS_ERROR_OUT_OF_MEMORY;
}
SourceText<Utf8Unit> srcBuf;
if (!srcBuf.init(aCx, std::move(chars), length)) {
return NS_ERROR_OUT_OF_MEMORY;
}
aMaybeSource->construct<SourceText<Utf8Unit>>(std::move(srcBuf));
return NS_OK;
}
//////////////////////////////////////////////////////////////
// ScriptLoadRequestList
//////////////////////////////////////////////////////////////
ScriptLoadRequestList::~ScriptLoadRequestList() { CancelRequestsAndClear(); }
void ScriptLoadRequestList::CancelRequestsAndClear() {
while (!isEmpty()) {
RefPtr<ScriptLoadRequest> first = StealFirst();
first->Cancel();
// And just let it go out of scope and die.
}
}
#ifdef DEBUG
bool ScriptLoadRequestList::Contains(ScriptLoadRequest* aElem) const {
for (const ScriptLoadRequest* req = getFirst(); req; req = req->getNext()) {
if (req == aElem) {
return true;
}
}
return false;
}
#endif // DEBUG
} // namespace JS::loader