summaryrefslogtreecommitdiffstats
path: root/dom/script/ScriptLoader.h
diff options
context:
space:
mode:
Diffstat (limited to 'dom/script/ScriptLoader.h')
-rw-r--r--dom/script/ScriptLoader.h669
1 files changed, 669 insertions, 0 deletions
diff --git a/dom/script/ScriptLoader.h b/dom/script/ScriptLoader.h
new file mode 100644
index 000000000..e6b75bf3b
--- /dev/null
+++ b/dom/script/ScriptLoader.h
@@ -0,0 +1,669 @@
+/* -*- 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_ScriptLoader_h
+#define mozilla_dom_ScriptLoader_h
+
+#include "nsCOMPtr.h"
+#include "nsRefPtrHashtable.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsIScriptElement.h"
+#include "nsCOMArray.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsTArray.h"
+#include "nsAutoPtr.h"
+#include "nsIDocument.h"
+#include "nsIIncrementalStreamLoader.h"
+#include "nsURIHashKey.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/dom/SRIMetadata.h"
+#include "mozilla/dom/SRICheck.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/net/ReferrerPolicy.h"
+#include "mozilla/Vector.h"
+
+class nsIURI;
+
+namespace JS {
+ class SourceBufferHolder;
+} // namespace JS
+
+namespace mozilla {
+namespace dom {
+
+class AutoJSAPI;
+class ModuleLoadRequest;
+class ModuleScript;
+class ScriptLoadRequestList;
+
+//////////////////////////////////////////////////////////////
+// Per-request data structure
+//////////////////////////////////////////////////////////////
+
+enum class ScriptKind {
+ Classic,
+ Module
+};
+
+class ScriptLoadRequest : public nsISupports,
+ private mozilla::LinkedListElement<ScriptLoadRequest>
+{
+ typedef LinkedListElement<ScriptLoadRequest> super;
+
+ // 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,
+ nsIScriptElement* aElement,
+ uint32_t aVersion,
+ mozilla::CORSMode aCORSMode,
+ const mozilla::dom::SRIMetadata &aIntegrity)
+ : mKind(aKind),
+ mElement(aElement),
+ mProgress(Progress::Loading),
+ mIsInline(true),
+ mHasSourceMapURL(false),
+ mIsDefer(false),
+ mIsAsync(false),
+ mIsNonAsyncScriptInserted(false),
+ mIsXSLT(false),
+ mIsCanceled(false),
+ mWasCompiledOMT(false),
+ mOffThreadToken(nullptr),
+ mScriptTextBuf(nullptr),
+ mScriptTextLength(0),
+ mJSVersion(aVersion),
+ mLineNo(1),
+ mCORSMode(aCORSMode),
+ mIntegrity(aIntegrity),
+ mReferrerPolicy(mozilla::net::RP_Default)
+ {
+ }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(ScriptLoadRequest)
+
+ bool IsModuleRequest() const
+ {
+ return mKind == ScriptKind::Module;
+ }
+
+ ModuleLoadRequest* AsModuleRequest();
+
+ void FireScriptAvailable(nsresult aResult)
+ {
+ mElement->ScriptAvailable(aResult, mElement, mIsInline, mURI, mLineNo);
+ }
+ void FireScriptEvaluated(nsresult aResult)
+ {
+ mElement->ScriptEvaluated(aResult, mElement, mIsInline);
+ }
+
+ bool IsPreload()
+ {
+ return mElement == nullptr;
+ }
+
+ virtual void Cancel();
+
+ bool IsCanceled() const
+ {
+ return mIsCanceled;
+ }
+
+ virtual void SetReady();
+
+ void** OffThreadTokenPtr()
+ {
+ return mOffThreadToken ? &mOffThreadToken : nullptr;
+ }
+
+ enum class Progress {
+ Loading,
+ Compiling,
+ FetchingImports,
+ Ready
+ };
+ bool IsReadyToRun() const {
+ return mProgress == Progress::Ready;
+ }
+ bool IsLoading() const {
+ return mProgress == Progress::Loading;
+ }
+ bool InCompilingStage() const {
+ return mProgress == Progress::Compiling ||
+ (IsReadyToRun() && mWasCompiledOMT);
+ }
+
+ void MaybeCancelOffThreadScript();
+
+ using super::getNext;
+ using super::isInList;
+
+ const ScriptKind mKind;
+ nsCOMPtr<nsIScriptElement> mElement;
+ Progress mProgress; // Are we still waiting for a load to complete?
+ bool mIsInline; // Is the script inline or loaded?
+ bool mHasSourceMapURL; // Does the HTTP header have a source map url?
+ bool mIsDefer; // True if we live in mDeferRequests.
+ bool mIsAsync; // 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 mIsCanceled; // True if we have been explicitly canceled.
+ bool mWasCompiledOMT; // True if the script has been compiled off main thread.
+ void* mOffThreadToken; // Off-thread parsing token.
+ nsString mSourceMapURL; // Holds source map url for loaded scripts
+ char16_t* mScriptTextBuf; // Holds script text for non-inline scripts. Don't
+ size_t mScriptTextLength; // use nsString so we can give ownership to jsapi.
+ uint32_t mJSVersion;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIPrincipal> mOriginPrincipal;
+ nsAutoCString mURL; // Keep the URI's filename alive during off thread parsing.
+ int32_t mLineNo;
+ const mozilla::CORSMode mCORSMode;
+ const mozilla::dom::SRIMetadata mIntegrity;
+ mozilla::net::ReferrerPolicy mReferrerPolicy;
+};
+
+class ScriptLoadRequestList : private mozilla::LinkedList<ScriptLoadRequest>
+{
+ typedef mozilla::LinkedList<ScriptLoadRequest> super;
+
+public:
+ ~ScriptLoadRequestList();
+
+ void Clear();
+
+#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);
+ }
+
+ MOZ_MUST_USE
+ already_AddRefed<ScriptLoadRequest> Steal(ScriptLoadRequest* aElem)
+ {
+ aElem->removeFrom(*this);
+ return dont_AddRef(aElem);
+ }
+
+ MOZ_MUST_USE
+ already_AddRefed<ScriptLoadRequest> StealFirst()
+ {
+ MOZ_ASSERT(!isEmpty());
+ return Steal(getFirst());
+ }
+
+ void Remove(ScriptLoadRequest* aElem)
+ {
+ aElem->removeFrom(*this);
+ NS_RELEASE(aElem);
+ }
+};
+class ScriptLoadHandler;
+
+//////////////////////////////////////////////////////////////
+// Script loader implementation
+//////////////////////////////////////////////////////////////
+
+class ScriptLoader final : public nsISupports
+{
+ class MOZ_STACK_CLASS AutoCurrentScriptUpdater
+ {
+ public:
+ AutoCurrentScriptUpdater(ScriptLoader* aScriptLoader,
+ nsIScriptElement* aCurrentScript)
+ : mOldScript(aScriptLoader->mCurrentScript)
+ , mScriptLoader(aScriptLoader)
+ {
+ mScriptLoader->mCurrentScript = aCurrentScript;
+ }
+ ~AutoCurrentScriptUpdater()
+ {
+ mScriptLoader->mCurrentScript.swap(mOldScript);
+ }
+ private:
+ nsCOMPtr<nsIScriptElement> mOldScript;
+ ScriptLoader* mScriptLoader;
+ };
+
+ friend class ModuleLoadRequest;
+ friend class ScriptRequestProcessor;
+ friend class ScriptLoadHandler;
+ friend class AutoCurrentScriptUpdater;
+
+public:
+ explicit ScriptLoader(nsIDocument* aDocument);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(ScriptLoader)
+
+ /**
+ * The loader maintains a weak reference to the document with
+ * which it is initialized. This call forces the reference to
+ * be dropped.
+ */
+ void DropDocumentReference()
+ {
+ mDocument = nullptr;
+ }
+
+ /**
+ * Add an observer for all scripts loaded through this loader.
+ *
+ * @param aObserver observer for all script processing.
+ */
+ nsresult AddObserver(nsIScriptLoaderObserver* aObserver)
+ {
+ return mObservers.AppendObject(aObserver) ? NS_OK :
+ NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ /**
+ * Remove an observer.
+ *
+ * @param aObserver observer to be removed
+ */
+ void RemoveObserver(nsIScriptLoaderObserver* aObserver)
+ {
+ mObservers.RemoveObject(aObserver);
+ }
+
+ /**
+ * Process a script element. This will include both loading the
+ * source of the element if it is not inline and evaluating
+ * the script itself.
+ *
+ * If the script is an inline script that can be executed immediately
+ * (i.e. there are no other scripts pending) then ScriptAvailable
+ * and ScriptEvaluated will be called before the function returns.
+ *
+ * If true is returned the script could not be executed immediately.
+ * In this case ScriptAvailable is guaranteed to be called at a later
+ * point (as well as possibly ScriptEvaluated).
+ *
+ * @param aElement The element representing the script to be loaded and
+ * evaluated.
+ */
+ bool ProcessScriptElement(nsIScriptElement* aElement);
+
+ /**
+ * Gets the currently executing script. This is useful if you want to
+ * generate a unique key based on the currently executing script.
+ */
+ nsIScriptElement* GetCurrentScript()
+ {
+ return mCurrentScript;
+ }
+
+ nsIScriptElement* GetCurrentParserInsertedScript()
+ {
+ return mCurrentParserInsertedScript;
+ }
+
+ /**
+ * Whether the loader is enabled or not.
+ * When disabled, processing of new script elements is disabled.
+ * Any call to ProcessScriptElement() will return false. Note that
+ * this DOES NOT disable currently loading or executing scripts.
+ */
+ bool GetEnabled()
+ {
+ return mEnabled;
+ }
+ void SetEnabled(bool aEnabled)
+ {
+ if (!mEnabled && aEnabled) {
+ ProcessPendingRequestsAsync();
+ }
+ mEnabled = aEnabled;
+ }
+
+ /**
+ * Add/remove a blocker for parser-blocking scripts (and XSLT
+ * scripts). Blockers will stop such scripts from executing, but not from
+ * loading.
+ */
+ void AddParserBlockingScriptExecutionBlocker()
+ {
+ ++mParserBlockingBlockerCount;
+ }
+ void RemoveParserBlockingScriptExecutionBlocker()
+ {
+ if (!--mParserBlockingBlockerCount && ReadyToExecuteScripts()) {
+ ProcessPendingRequestsAsync();
+ }
+ }
+
+ /**
+ * Add/remove a blocker for execution of all scripts. Blockers will stop
+ * scripts from executing, but not from loading.
+ */
+ void AddExecuteBlocker()
+ {
+ ++mBlockerCount;
+ }
+ void RemoveExecuteBlocker()
+ {
+ MOZ_ASSERT(mBlockerCount);
+ if (!--mBlockerCount) {
+ ProcessPendingRequestsAsync();
+ }
+ }
+
+ /**
+ * Convert the given buffer to a UTF-16 string.
+ * @param aChannel Channel corresponding to the data. May be null.
+ * @param aData The data to convert
+ * @param aLength Length of the data
+ * @param aHintCharset Hint for the character set (e.g., from a charset
+ * attribute). May be the empty string.
+ * @param aDocument Document which the data is loaded for. Must not be
+ * null.
+ * @param aBufOut [out] char16_t array allocated by ConvertToUTF16 and
+ * containing data converted to unicode. Caller must
+ * js_free() this data when no longer needed.
+ * @param aLengthOut [out] Length of array returned in aBufOut in number
+ * of char16_t code units.
+ */
+ static nsresult ConvertToUTF16(nsIChannel* aChannel, const uint8_t* aData,
+ uint32_t aLength,
+ const nsAString& aHintCharset,
+ nsIDocument* aDocument,
+ char16_t*& aBufOut, size_t& aLengthOut);
+
+ /**
+ * Handle the completion of a stream. This is called by the
+ * ScriptLoadHandler object which observes the IncrementalStreamLoader
+ * loading the script.
+ */
+ nsresult OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
+ nsISupports* aContext,
+ nsresult aChannelStatus,
+ nsresult aSRIStatus,
+ mozilla::Vector<char16_t> &aString,
+ mozilla::dom::SRICheckDataVerifier* aSRIDataVerifier);
+
+ /**
+ * Processes any pending requests that are ready for processing.
+ */
+ void ProcessPendingRequests();
+
+ /**
+ * Starts deferring deferred scripts and puts them in the mDeferredRequests
+ * queue instead.
+ */
+ void BeginDeferringScripts()
+ {
+ mDeferEnabled = true;
+ if (mDocument) {
+ mDocument->BlockOnload();
+ }
+ }
+
+ /**
+ * Notifies the script loader that parsing is done. If aTerminated is true,
+ * this will drop any pending scripts that haven't run yet. Otherwise, it
+ * will stops deferring scripts and immediately processes the
+ * mDeferredRequests queue.
+ *
+ * WARNING: This function will synchronously execute content scripts, so be
+ * prepared that the world might change around you.
+ */
+ void ParsingComplete(bool aTerminated);
+
+ /**
+ * Returns the number of pending scripts, deferred or not.
+ */
+ uint32_t HasPendingOrCurrentScripts()
+ {
+ return mCurrentScript || mParserBlockingRequest;
+ }
+
+ /**
+ * Adds aURI to the preload list and starts loading it.
+ *
+ * @param aURI The URI of the external script.
+ * @param aCharset The charset parameter for the script.
+ * @param aType The type parameter for the script.
+ * @param aCrossOrigin The crossorigin attribute for the script.
+ * Void if not present.
+ * @param aIntegrity The expect hash url, if avail, of the request
+ * @param aScriptFromHead Whether or not the script was a child of head
+ */
+ virtual void PreloadURI(nsIURI *aURI, const nsAString &aCharset,
+ const nsAString &aType,
+ const nsAString &aCrossOrigin,
+ const nsAString& aIntegrity,
+ bool aScriptFromHead,
+ const mozilla::net::ReferrerPolicy aReferrerPolicy);
+
+ /**
+ * Process a request that was deferred so that the script could be compiled
+ * off thread.
+ */
+ nsresult ProcessOffThreadRequest(ScriptLoadRequest *aRequest);
+
+ bool AddPendingChildLoader(ScriptLoader* aChild) {
+ return mPendingChildLoaders.AppendElement(aChild) != nullptr;
+ }
+
+private:
+ virtual ~ScriptLoader();
+
+ ScriptLoadRequest* CreateLoadRequest(
+ ScriptKind aKind,
+ nsIScriptElement* aElement,
+ uint32_t aVersion,
+ mozilla::CORSMode aCORSMode,
+ const mozilla::dom::SRIMetadata &aIntegrity);
+
+ /**
+ * Unblocks the creator parser of the parser-blocking scripts.
+ */
+ void UnblockParser(ScriptLoadRequest* aParserBlockingRequest);
+
+ /**
+ * Asynchronously resumes the creator parser of the parser-blocking scripts.
+ */
+ void ContinueParserAsync(ScriptLoadRequest* aParserBlockingRequest);
+
+
+ /**
+ * Helper function to check the content policy for a given request.
+ */
+ static nsresult CheckContentPolicy(nsIDocument* aDocument,
+ nsISupports *aContext,
+ nsIURI *aURI,
+ const nsAString &aType,
+ bool aIsPreLoad);
+
+ /**
+ * Start a load for aRequest's URI.
+ */
+ nsresult StartLoad(ScriptLoadRequest *aRequest, const nsAString &aType,
+ bool aScriptFromHead);
+
+ /**
+ * Process any pending requests asynchronously (i.e. off an event) if there
+ * are any. Note that this is a no-op if there aren't any currently pending
+ * requests.
+ *
+ * This function is virtual to allow cross-library calls to SetEnabled()
+ */
+ virtual void ProcessPendingRequestsAsync();
+
+ /**
+ * If true, the loader is ready to execute parser-blocking scripts, and so are
+ * all its ancestors. If the loader itself is ready but some ancestor is not,
+ * this function will add an execute blocker and ask the ancestor to remove it
+ * once it becomes ready.
+ */
+ bool ReadyToExecuteParserBlockingScripts();
+
+ /**
+ * Return whether just this loader is ready to execute parser-blocking
+ * scripts.
+ */
+ bool SelfReadyToExecuteParserBlockingScripts()
+ {
+ return ReadyToExecuteScripts() && !mParserBlockingBlockerCount;
+ }
+
+ /**
+ * Return whether this loader is ready to execute scripts in general.
+ */
+ bool ReadyToExecuteScripts()
+ {
+ return mEnabled && !mBlockerCount;
+ }
+
+ nsresult AttemptAsyncScriptCompile(ScriptLoadRequest* aRequest);
+ nsresult ProcessRequest(ScriptLoadRequest* aRequest);
+ nsresult CompileOffThreadOrProcessRequest(ScriptLoadRequest* aRequest);
+ void FireScriptAvailable(nsresult aResult,
+ ScriptLoadRequest* aRequest);
+ void FireScriptEvaluated(nsresult aResult,
+ ScriptLoadRequest* aRequest);
+ nsresult EvaluateScript(ScriptLoadRequest* aRequest);
+
+ already_AddRefed<nsIScriptGlobalObject> GetScriptGlobalObject();
+ nsresult FillCompileOptionsForRequest(const mozilla::dom::AutoJSAPI& jsapi,
+ ScriptLoadRequest* aRequest,
+ JS::Handle<JSObject*> aScopeChain,
+ JS::CompileOptions* aOptions);
+
+ uint32_t NumberOfProcessors();
+ nsresult PrepareLoadedRequest(ScriptLoadRequest* aRequest,
+ nsIIncrementalStreamLoader* aLoader,
+ nsresult aStatus,
+ mozilla::Vector<char16_t> &aString);
+
+ void AddDeferRequest(ScriptLoadRequest* aRequest);
+ bool MaybeRemovedDeferRequests();
+
+ void MaybeMoveToLoadedList(ScriptLoadRequest* aRequest);
+
+ JS::SourceBufferHolder GetScriptSource(ScriptLoadRequest* aRequest,
+ nsAutoString& inlineData);
+
+ bool ModuleScriptsEnabled();
+
+ void SetModuleFetchStarted(ModuleLoadRequest *aRequest);
+ void SetModuleFetchFinishedAndResumeWaitingRequests(ModuleLoadRequest *aRequest,
+ nsresult aResult);
+
+ bool IsFetchingModule(ModuleLoadRequest *aRequest) const;
+
+ bool ModuleMapContainsModule(ModuleLoadRequest *aRequest) const;
+ RefPtr<mozilla::GenericPromise> WaitForModuleFetch(ModuleLoadRequest *aRequest);
+ ModuleScript* GetFetchedModule(nsIURI* aURL) const;
+
+ friend bool
+ HostResolveImportedModule(JSContext* aCx, unsigned argc, JS::Value* vp);
+
+ nsresult CreateModuleScript(ModuleLoadRequest* aRequest);
+ nsresult ProcessFetchedModuleSource(ModuleLoadRequest* aRequest);
+ void CheckModuleDependenciesLoaded(ModuleLoadRequest* aRequest);
+ void ProcessLoadedModuleTree(ModuleLoadRequest* aRequest);
+ bool InstantiateModuleTree(ModuleLoadRequest* aRequest);
+ void StartFetchingModuleDependencies(ModuleLoadRequest* aRequest);
+
+ RefPtr<mozilla::GenericPromise>
+ StartFetchingModuleAndDependencies(ModuleLoadRequest* aRequest, nsIURI* aURI);
+
+ nsIDocument* mDocument; // [WEAK]
+ nsCOMArray<nsIScriptLoaderObserver> mObservers;
+ ScriptLoadRequestList mNonAsyncExternalScriptInsertedRequests;
+ // mLoadingAsyncRequests holds async requests while they're loading; when they
+ // have been loaded they are moved to mLoadedAsyncRequests.
+ ScriptLoadRequestList mLoadingAsyncRequests;
+ ScriptLoadRequestList mLoadedAsyncRequests;
+ ScriptLoadRequestList mDeferRequests;
+ ScriptLoadRequestList mXSLTRequests;
+ RefPtr<ScriptLoadRequest> mParserBlockingRequest;
+
+ // In mRequests, the additional information here is stored by the element.
+ struct PreloadInfo {
+ RefPtr<ScriptLoadRequest> mRequest;
+ nsString mCharset;
+ };
+
+ friend void ImplCycleCollectionUnlink(ScriptLoader::PreloadInfo& aField);
+ friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+ ScriptLoader::PreloadInfo& aField,
+ const char* aName, uint32_t aFlags);
+
+ struct PreloadRequestComparator {
+ bool Equals(const PreloadInfo &aPi, ScriptLoadRequest * const &aRequest)
+ const
+ {
+ return aRequest == aPi.mRequest;
+ }
+ };
+ struct PreloadURIComparator {
+ bool Equals(const PreloadInfo &aPi, nsIURI * const &aURI) const;
+ };
+ nsTArray<PreloadInfo> mPreloads;
+
+ nsCOMPtr<nsIScriptElement> mCurrentScript;
+ nsCOMPtr<nsIScriptElement> mCurrentParserInsertedScript;
+ nsTArray< RefPtr<ScriptLoader> > mPendingChildLoaders;
+ uint32_t mParserBlockingBlockerCount;
+ uint32_t mBlockerCount;
+ uint32_t mNumberOfProcessors;
+ bool mEnabled;
+ bool mDeferEnabled;
+ bool mDocumentParsingDone;
+ bool mBlockingDOMContentLoaded;
+
+ // Module map
+ nsRefPtrHashtable<nsURIHashKey, mozilla::GenericPromise::Private> mFetchingModules;
+ nsRefPtrHashtable<nsURIHashKey, ModuleScript> mFetchedModules;
+
+ nsCOMPtr<nsIConsoleReportCollector> mReporter;
+};
+
+class nsAutoScriptLoaderDisabler
+{
+public:
+ explicit nsAutoScriptLoaderDisabler(nsIDocument* aDoc)
+ {
+ mLoader = aDoc->ScriptLoader();
+ mWasEnabled = mLoader->GetEnabled();
+ if (mWasEnabled) {
+ mLoader->SetEnabled(false);
+ }
+ }
+
+ ~nsAutoScriptLoaderDisabler()
+ {
+ if (mWasEnabled) {
+ mLoader->SetEnabled(true);
+ }
+ }
+
+ bool mWasEnabled;
+ RefPtr<ScriptLoader> mLoader;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif //mozilla_dom_ScriptLoader_h