gecko-dev/dom/script/ScriptLoadRequest.h

405 строки
13 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/. */
#ifndef mozilla_dom_ScriptLoadRequest_h
#define mozilla_dom_ScriptLoadRequest_h
#include "js/AllocPolicy.h"
#include "js/RootingAPI.h"
#include "js/TypeDecls.h"
#include "mozilla/Atomics.h"
#include "mozilla/Assertions.h"
#include "mozilla/CORSMode.h"
#include "mozilla/dom/SRIMetadata.h"
#include "mozilla/LinkedList.h"
#include "mozilla/Maybe.h"
#include "mozilla/PreloaderBase.h"
#include "mozilla/Utf8.h" // mozilla::Utf8Unit
#include "mozilla/Variant.h"
#include "mozilla/Vector.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIGlobalObject.h"
#include "nsIScriptElement.h"
#include "ScriptKind.h"
class nsICacheInfoChannel;
namespace JS {
class OffThreadToken;
}
namespace mozilla {
namespace dom {
class Element;
class ModuleLoadRequest;
class ScriptLoadRequestList;
/*
* Some options used when fetching script resources. This only loosely
* corresponds to HTML's "script fetch options".
*
* These are common to all modules in a module graph, and hence a single
* instance is shared by all ModuleLoadRequest objects in a graph.
*/
class ScriptFetchOptions {
~ScriptFetchOptions();
public:
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(ScriptFetchOptions)
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(ScriptFetchOptions)
ScriptFetchOptions(mozilla::CORSMode aCORSMode,
enum ReferrerPolicy aReferrerPolicy, Element* aElement,
nsIPrincipal* aTriggeringPrincipal,
nsIGlobalObject* aWebExtGlobal);
const mozilla::CORSMode mCORSMode;
const enum ReferrerPolicy mReferrerPolicy;
bool mIsPreload;
nsCOMPtr<Element> mElement;
nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
// Global that initiated this request, when using a WebExtension
// content-script.
nsCOMPtr<nsIGlobalObject> mWebExtGlobal;
};
/*
* A class that handles loading and evaluation of <script> elements.
*/
class ScriptLoadRequest
: public PreloaderBase,
private mozilla::LinkedListElement<ScriptLoadRequest> {
using super = LinkedListElement<ScriptLoadRequest>;
// Allow LinkedListElement<ScriptLoadRequest> to cast us to itself as needed.
friend class mozilla::LinkedListElement<ScriptLoadRequest>;
friend class ScriptLoadRequestList;
protected:
virtual ~ScriptLoadRequest();
public:
ScriptLoadRequest(ScriptKind aKind, nsIURI* aURI,
ScriptFetchOptions* aFetchOptions,
const SRIMetadata& aIntegrity, nsIURI* aReferrer);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ScriptLoadRequest)
// PreloaderBase
static void PrioritizeAsPreload(nsIChannel* aChannel);
virtual void PrioritizeAsPreload() override;
bool IsModuleRequest() const { return mKind == ScriptKind::eModule; }
ModuleLoadRequest* AsModuleRequest();
TimeStamp mOffThreadParseStartTime;
TimeStamp mOffThreadParseStopTime;
void FireScriptAvailable(nsresult aResult) {
bool isInlineClassicScript = mIsInline && !IsModuleRequest();
GetScriptElement()->ScriptAvailable(aResult, GetScriptElement(),
isInlineClassicScript, mURI, mLineNo);
}
void FireScriptEvaluated(nsresult aResult) {
GetScriptElement()->ScriptEvaluated(aResult, GetScriptElement(), mIsInline);
}
bool IsPreload() const {
MOZ_ASSERT_IF(mFetchOptions->mIsPreload, !GetScriptElement());
return mFetchOptions->mIsPreload;
}
virtual void Cancel();
bool IsCanceled() const { return mIsCanceled; }
virtual void SetReady();
JS::OffThreadToken** OffThreadTokenPtr() {
return mOffThreadToken ? &mOffThreadToken : nullptr;
}
bool IsTracking() const { return mIsTracking; }
void SetIsTracking() {
MOZ_ASSERT(!mIsTracking);
mIsTracking = true;
}
void BlockOnload(Document* aDocument);
void MaybeUnblockOnload();
enum class Progress : uint8_t {
eLoading, // Request either source or bytecode
eLoading_Source, // Explicitly Request source stream
eCompiling,
eFetchingImports,
eReady
};
bool IsReadyToRun() const { return mProgress == Progress::eReady; }
bool IsLoading() const {
return mProgress == Progress::eLoading ||
mProgress == Progress::eLoading_Source;
}
bool IsLoadingSource() const {
return mProgress == Progress::eLoading_Source;
}
bool InCompilingStage() const {
return mProgress == Progress::eCompiling ||
(IsReadyToRun() && mWasCompiledOMT);
}
// Type of data provided by the nsChannel.
enum class DataType : uint8_t { eUnknown, eTextSource, eBytecode };
bool IsUnknownDataType() const { return mDataType == DataType::eUnknown; }
bool IsTextSource() const { return mDataType == DataType::eTextSource; }
bool IsSource() const { return IsTextSource(); }
bool IsBytecode() const { return mDataType == DataType::eBytecode; }
void SetUnknownDataType();
void SetTextSource();
void SetBytecode();
// Use a vector backed by the JS allocator for script text so that contents
// can be transferred in constant time to the JS engine, not copied in linear
// time.
template <typename Unit>
using ScriptTextBuffer = Vector<Unit, 0, js::MallocAllocPolicy>;
bool IsUTF16Text() const {
return mScriptData->is<ScriptTextBuffer<char16_t>>();
}
bool IsUTF8Text() const {
return mScriptData->is<ScriptTextBuffer<Utf8Unit>>();
}
template <typename Unit>
const ScriptTextBuffer<Unit>& ScriptText() const {
MOZ_ASSERT(IsTextSource());
return mScriptData->as<ScriptTextBuffer<Unit>>();
}
template <typename Unit>
ScriptTextBuffer<Unit>& ScriptText() {
MOZ_ASSERT(IsTextSource());
return mScriptData->as<ScriptTextBuffer<Unit>>();
}
size_t ScriptTextLength() const {
MOZ_ASSERT(IsTextSource());
return IsUTF16Text() ? ScriptText<char16_t>().length()
: ScriptText<Utf8Unit>().length();
}
void ClearScriptText() {
MOZ_ASSERT(IsTextSource());
return IsUTF16Text() ? ScriptText<char16_t>().clearAndFree()
: ScriptText<Utf8Unit>().clearAndFree();
}
enum class ScriptMode : uint8_t {
eBlocking,
eDeferred,
eAsync,
eLinkPreload // this is a load initiated by <link rel="preload"
// as="script"> tag
};
void SetScriptMode(bool aDeferAttr, bool aAsyncAttr, bool aLinkPreload);
bool IsLinkPreloadScript() const {
return mScriptMode == ScriptMode::eLinkPreload;
}
bool IsBlockingScript() const { return mScriptMode == ScriptMode::eBlocking; }
bool IsDeferredScript() const { return mScriptMode == ScriptMode::eDeferred; }
bool IsAsyncScript() const { return mScriptMode == ScriptMode::eAsync; }
virtual bool IsTopLevel() const {
// Classic scripts are always top level.
return true;
}
mozilla::CORSMode CORSMode() const { return mFetchOptions->mCORSMode; }
enum ReferrerPolicy ReferrerPolicy() const {
return mFetchOptions->mReferrerPolicy;
}
nsIScriptElement* GetScriptElement() const;
nsIPrincipal* TriggeringPrincipal() const {
return mFetchOptions->mTriggeringPrincipal;
}
// This will return nullptr in most cases,
// unless this is a module being imported by a WebExtension content script.
// In that case it's the Sandbox global executing that code.
nsIGlobalObject* GetWebExtGlobal() const {
return mFetchOptions->mWebExtGlobal;
}
// Make this request a preload (speculative) request.
void SetIsPreloadRequest() {
MOZ_ASSERT(!GetScriptElement());
MOZ_ASSERT(!IsPreload());
mFetchOptions->mIsPreload = true;
}
// Make a preload request into an actual load request for the given element.
void SetIsLoadRequest(nsIScriptElement* aElement);
FromParser GetParserCreated() const {
nsIScriptElement* element = GetScriptElement();
if (!element) {
return NOT_FROM_PARSER;
}
return element->GetParserCreated();
}
void ClearScriptSource();
void SetScript(JSScript* aScript);
void MaybeCancelOffThreadScript();
void DropBytecodeCacheReferences();
using super::getNext;
using super::isInList;
const ScriptKind mKind; // Whether this is a classic script or a module
// script.
ScriptMode mScriptMode; // Whether this is a blocking, defer or async script.
Progress mProgress; // Are we still waiting for a load to complete?
DataType mDataType; // Does this contain Source or Bytecode?
bool mScriptFromHead; // Synchronous head script block loading of other non
// js/css content.
bool mIsInline; // Is the script inline or loaded?
bool mInDeferList; // True if we live in mDeferRequests.
bool mInAsyncList; // True if we live in mLoadingAsyncRequests or
// mLoadedAsyncRequests.
bool mIsNonAsyncScriptInserted; // True if we live in
// mNonAsyncExternalScriptInsertedRequests
bool mIsXSLT; // True if we live in mXSLTRequests.
bool mInCompilingList; // True if we are in mOffThreadCompilingRequests.
bool mIsCanceled; // True if we have been explicitly canceled.
bool mWasCompiledOMT; // True if the script has been compiled off main
// thread.
bool mIsTracking; // True if the script comes from a source on our
// tracking protection list.
RefPtr<ScriptFetchOptions> mFetchOptions;
JS::OffThreadToken* mOffThreadToken; // Off-thread parsing token.
Maybe<nsString> mSourceMapURL; // Holds source map url for loaded scripts
Atomic<Runnable*> mRunnable; // Runnable created when dispatching off thread
// compile. Tracked here so that it can be
// properly released during cancellation.
// Holds the top-level JSScript that corresponds to the current source, once
// it is parsed, and planned to be saved in the bytecode cache.
JS::Heap<JSScript*> mScript;
// Holds script source data for non-inline scripts.
Maybe<Variant<ScriptTextBuffer<char16_t>, ScriptTextBuffer<Utf8Unit>>>
mScriptData;
// The length of script source text, set when reading completes. This is used
// since mScriptData is cleared when the source is passed to the JS engine.
size_t mScriptTextLength;
// Holds the SRI serialized hash and the script bytecode for non-inline
// scripts.
mozilla::Vector<uint8_t> mScriptBytecode;
uint32_t mBytecodeOffset; // Offset of the bytecode in mScriptBytecode
const nsCOMPtr<nsIURI> mURI;
nsCOMPtr<nsIPrincipal> mOriginPrincipal;
nsAutoCString
mURL; // Keep the URI's filename alive during off thread parsing.
int32_t mLineNo;
const SRIMetadata mIntegrity;
const nsCOMPtr<nsIURI> mReferrer;
// Non-null if there is a document that this request is blocking from loading.
RefPtr<Document> mLoadBlockedDocument;
// Holds the Cache information, which is used to register the bytecode
// on the cache entry, such that we can load it the next time.
nsCOMPtr<nsICacheInfoChannel> mCacheInfo;
// The base URL used for resolving relative module imports.
nsCOMPtr<nsIURI> mBaseURL;
// For preload requests, we defer reporting errors to the console until the
// request is used.
nsresult mUnreportedPreloadError;
};
class ScriptLoadRequestList : private mozilla::LinkedList<ScriptLoadRequest> {
using super = mozilla::LinkedList<ScriptLoadRequest>;
public:
~ScriptLoadRequestList();
void CancelRequestsAndClear();
#ifdef DEBUG
bool Contains(ScriptLoadRequest* aElem) const;
#endif // DEBUG
using super::getFirst;
using super::isEmpty;
void AppendElement(ScriptLoadRequest* aElem) {
MOZ_ASSERT(!aElem->isInList());
NS_ADDREF(aElem);
insertBack(aElem);
}
already_AddRefed<ScriptLoadRequest> Steal(ScriptLoadRequest* aElem) {
aElem->removeFrom(*this);
return dont_AddRef(aElem);
}
already_AddRefed<ScriptLoadRequest> StealFirst() {
MOZ_ASSERT(!isEmpty());
return Steal(getFirst());
}
void Remove(ScriptLoadRequest* aElem) {
aElem->removeFrom(*this);
NS_RELEASE(aElem);
}
};
inline void ImplCycleCollectionUnlink(ScriptLoadRequestList& aField) {
while (!aField.isEmpty()) {
RefPtr<ScriptLoadRequest> first = aField.StealFirst();
}
}
inline void ImplCycleCollectionTraverse(
nsCycleCollectionTraversalCallback& aCallback,
ScriptLoadRequestList& aField, const char* aName, uint32_t aFlags) {
for (ScriptLoadRequest* request = aField.getFirst(); request;
request = request->getNext()) {
CycleCollectionNoteChild(aCallback, request, aName, aFlags);
}
}
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_ScriptLoadRequest_h