summaryrefslogtreecommitdiffstats
path: root/dom/script
diff options
context:
space:
mode:
Diffstat (limited to 'dom/script')
-rw-r--r--dom/script/LoadedScript.cpp201
-rw-r--r--dom/script/LoadedScript.h119
-rw-r--r--dom/script/ModuleLoadRequest.cpp218
-rw-r--r--dom/script/ModuleLoadRequest.h122
-rw-r--r--dom/script/ScriptDecoding.h96
-rw-r--r--dom/script/ScriptElement.cpp119
-rw-r--r--dom/script/ScriptElement.h54
-rw-r--r--dom/script/ScriptKind.h18
-rw-r--r--dom/script/ScriptLoadHandler.cpp472
-rw-r--r--dom/script/ScriptLoadHandler.h117
-rw-r--r--dom/script/ScriptLoadRequest.cpp319
-rw-r--r--dom/script/ScriptLoadRequest.h409
-rw-r--r--dom/script/ScriptLoader.cpp4343
-rw-r--r--dom/script/ScriptLoader.h780
-rw-r--r--dom/script/ScriptSettings.cpp841
-rw-r--r--dom/script/ScriptSettings.h527
-rw-r--r--dom/script/ScriptTrace.cpp33
-rw-r--r--dom/script/ScriptTrace.h47
-rw-r--r--dom/script/moz.build48
-rw-r--r--dom/script/nsIScriptElement.cpp53
-rw-r--r--dom/script/nsIScriptElement.h343
-rw-r--r--dom/script/nsIScriptLoaderObserver.idl47
22 files changed, 9326 insertions, 0 deletions
diff --git a/dom/script/LoadedScript.cpp b/dom/script/LoadedScript.cpp
new file mode 100644
index 0000000000..35fec0dd25
--- /dev/null
+++ b/dom/script/LoadedScript.cpp
@@ -0,0 +1,201 @@
+/* -*- 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 "LoadedScript.h"
+
+#include "mozilla/HoldDropJSObjects.h"
+
+#include "jsfriendapi.h"
+#include "js/Modules.h" // JS::{Get,Set}ModulePrivate
+#include "ScriptLoader.h"
+
+namespace mozilla {
+namespace dom {
+
+//////////////////////////////////////////////////////////////
+// LoadedScript
+//////////////////////////////////////////////////////////////
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LoadedScript)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(LoadedScript)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(LoadedScript)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchOptions)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mBaseURL)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(LoadedScript)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchOptions)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(LoadedScript)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(LoadedScript)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(LoadedScript)
+
+LoadedScript::LoadedScript(ScriptKind aKind, ScriptFetchOptions* aFetchOptions,
+ nsIURI* aBaseURL)
+ : mKind(aKind), mFetchOptions(aFetchOptions), mBaseURL(aBaseURL) {
+ MOZ_ASSERT(mFetchOptions);
+ MOZ_ASSERT(mBaseURL);
+}
+
+LoadedScript::~LoadedScript() { DropJSObjects(this); }
+
+void LoadedScript::AssociateWithScript(JSScript* aScript) {
+ // Set a JSScript's private value to point to this object. The JS engine will
+ // increment our reference count by calling HostAddRefTopLevelScript(). This
+ // is decremented by HostReleaseTopLevelScript() below when the JSScript dies.
+
+ MOZ_ASSERT(JS::GetScriptPrivate(aScript).isUndefined());
+ JS::SetScriptPrivate(aScript, JS::PrivateValue(this));
+}
+
+inline void CheckModuleScriptPrivate(LoadedScript* script,
+ const JS::Value& aPrivate) {
+#ifdef DEBUG
+ if (script->IsModuleScript()) {
+ JSObject* module = script->AsModuleScript()->mModuleRecord.unbarrieredGet();
+ MOZ_ASSERT_IF(module, JS::GetModulePrivate(module) == aPrivate);
+ }
+#endif
+}
+
+void HostAddRefTopLevelScript(const JS::Value& aPrivate) {
+ // Increment the reference count of a LoadedScript object that is now pointed
+ // to by a JSScript. The reference count is decremented by
+ // HostReleaseTopLevelScript() below.
+
+ auto script = static_cast<LoadedScript*>(aPrivate.toPrivate());
+ CheckModuleScriptPrivate(script, aPrivate);
+ script->AddRef();
+}
+
+void HostReleaseTopLevelScript(const JS::Value& aPrivate) {
+ // Decrement the reference count of a LoadedScript object that was pointed to
+ // by a JSScript. The reference count was originally incremented by
+ // HostAddRefTopLevelScript() above.
+
+ auto script = static_cast<LoadedScript*>(aPrivate.toPrivate());
+ CheckModuleScriptPrivate(script, aPrivate);
+ script->Release();
+}
+
+//////////////////////////////////////////////////////////////
+// EventScript
+//////////////////////////////////////////////////////////////
+
+EventScript::EventScript(ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL)
+ : LoadedScript(ScriptKind::eEvent, aFetchOptions, aBaseURL) {}
+
+//////////////////////////////////////////////////////////////
+// ClassicScript
+//////////////////////////////////////////////////////////////
+
+ClassicScript::ClassicScript(ScriptFetchOptions* aFetchOptions,
+ nsIURI* aBaseURL)
+ : LoadedScript(ScriptKind::eClassic, aFetchOptions, aBaseURL) {}
+
+//////////////////////////////////////////////////////////////
+// ModuleScript
+//////////////////////////////////////////////////////////////
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleScript)
+NS_INTERFACE_MAP_END_INHERITING(LoadedScript)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ModuleScript)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ModuleScript, LoadedScript)
+ tmp->UnlinkModuleRecord();
+ tmp->mParseError.setUndefined();
+ tmp->mErrorToRethrow.setUndefined();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ModuleScript, LoadedScript)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ModuleScript, LoadedScript)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mModuleRecord)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mParseError)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mErrorToRethrow)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_ADDREF_INHERITED(ModuleScript, LoadedScript)
+NS_IMPL_RELEASE_INHERITED(ModuleScript, LoadedScript)
+
+ModuleScript::ModuleScript(ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL)
+ : LoadedScript(ScriptKind::eModule, aFetchOptions, aBaseURL),
+ mDebuggerDataInitialized(false) {
+ MOZ_ASSERT(!ModuleRecord());
+ MOZ_ASSERT(!HasParseError());
+ MOZ_ASSERT(!HasErrorToRethrow());
+}
+
+void ModuleScript::UnlinkModuleRecord() {
+ // Remove the module record's pointer to this object if present and
+ // decrement our reference count. The reference is added by
+ // SetModuleRecord() below.
+ if (mModuleRecord) {
+ MOZ_ASSERT(JS::GetModulePrivate(mModuleRecord).toPrivate() == this);
+ JS::SetModulePrivate(mModuleRecord, JS::UndefinedValue());
+ mModuleRecord = nullptr;
+ }
+}
+
+ModuleScript::~ModuleScript() {
+ // The object may be destroyed without being unlinked first.
+ UnlinkModuleRecord();
+}
+
+void ModuleScript::SetModuleRecord(JS::Handle<JSObject*> aModuleRecord) {
+ MOZ_ASSERT(!mModuleRecord);
+ MOZ_ASSERT_IF(IsModuleScript(), !AsModuleScript()->HasParseError());
+ MOZ_ASSERT_IF(IsModuleScript(), !AsModuleScript()->HasErrorToRethrow());
+
+ mModuleRecord = aModuleRecord;
+
+ // Make module's host defined field point to this object. The JS engine will
+ // increment our reference count by calling HostAddRefTopLevelScript(). This
+ // is decremented when the field is cleared in UnlinkModuleRecord() above or
+ // when the module record dies.
+ MOZ_ASSERT(JS::GetModulePrivate(mModuleRecord).isUndefined());
+ JS::SetModulePrivate(mModuleRecord, JS::PrivateValue(this));
+
+ HoldJSObjects(this);
+}
+
+void ModuleScript::SetParseError(const JS::Value& aError) {
+ MOZ_ASSERT(!aError.isUndefined());
+ MOZ_ASSERT(!HasParseError());
+ MOZ_ASSERT(!HasErrorToRethrow());
+
+ UnlinkModuleRecord();
+ mParseError = aError;
+ HoldJSObjects(this);
+}
+
+void ModuleScript::SetErrorToRethrow(const JS::Value& aError) {
+ MOZ_ASSERT(!aError.isUndefined());
+
+ // This is only called after SetModuleRecord() or SetParseError() so we don't
+ // need to call HoldJSObjects() here.
+ MOZ_ASSERT(ModuleRecord() || HasParseError());
+
+ mErrorToRethrow = aError;
+}
+
+void ModuleScript::SetDebuggerDataInitialized() {
+ MOZ_ASSERT(ModuleRecord());
+ MOZ_ASSERT(!mDebuggerDataInitialized);
+
+ mDebuggerDataInitialized = true;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/script/LoadedScript.h b/dom/script/LoadedScript.h
new file mode 100644
index 0000000000..76d512ab68
--- /dev/null
+++ b/dom/script/LoadedScript.h
@@ -0,0 +1,119 @@
+/* -*- 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_LoadedScript_h
+#define mozilla_dom_LoadedScript_h
+
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "jsapi.h"
+#include "ScriptLoadRequest.h"
+
+class nsIURI;
+
+namespace mozilla {
+namespace dom {
+
+class ScriptLoader;
+
+void HostAddRefTopLevelScript(const JS::Value& aPrivate);
+void HostReleaseTopLevelScript(const JS::Value& aPrivate);
+
+class ClassicScript;
+class ModuleScript;
+class EventScript;
+
+class LoadedScript : public nsISupports {
+ ScriptKind mKind;
+ RefPtr<ScriptFetchOptions> mFetchOptions;
+ nsCOMPtr<nsIURI> mBaseURL;
+
+ protected:
+ LoadedScript(ScriptKind aKind, ScriptFetchOptions* aFetchOptions,
+ nsIURI* aBaseURL);
+
+ virtual ~LoadedScript();
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(LoadedScript)
+
+ bool IsModuleScript() const { return mKind == ScriptKind::eModule; }
+ bool IsEventScript() const { return mKind == ScriptKind::eEvent; }
+
+ inline ClassicScript* AsClassicScript();
+ inline ModuleScript* AsModuleScript();
+ inline EventScript* AsEventScript();
+
+ ScriptFetchOptions* GetFetchOptions() const { return mFetchOptions; }
+ nsIURI* BaseURL() const { return mBaseURL; }
+
+ void AssociateWithScript(JSScript* aScript);
+};
+
+class ClassicScript final : public LoadedScript {
+ ~ClassicScript() = default;
+
+ public:
+ ClassicScript(ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL);
+};
+
+class EventScript final : public LoadedScript {
+ ~EventScript() = default;
+
+ public:
+ EventScript(ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL);
+};
+
+// A single module script. May be used to satisfy multiple load requests.
+
+class ModuleScript final : public LoadedScript {
+ JS::Heap<JSObject*> mModuleRecord;
+ JS::Heap<JS::Value> mParseError;
+ JS::Heap<JS::Value> mErrorToRethrow;
+ bool mDebuggerDataInitialized;
+
+ ~ModuleScript();
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(ModuleScript,
+ LoadedScript)
+
+ ModuleScript(ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL);
+
+ void SetModuleRecord(JS::Handle<JSObject*> aModuleRecord);
+ void SetParseError(const JS::Value& aError);
+ void SetErrorToRethrow(const JS::Value& aError);
+ void SetDebuggerDataInitialized();
+
+ JSObject* ModuleRecord() const { return mModuleRecord; }
+
+ JS::Value ParseError() const { return mParseError; }
+ JS::Value ErrorToRethrow() const { return mErrorToRethrow; }
+ bool HasParseError() const { return !mParseError.isUndefined(); }
+ bool HasErrorToRethrow() const { return !mErrorToRethrow.isUndefined(); }
+ bool DebuggerDataInitialized() const { return mDebuggerDataInitialized; }
+
+ void UnlinkModuleRecord();
+
+ friend void CheckModuleScriptPrivate(LoadedScript*, const JS::Value&);
+};
+
+ClassicScript* LoadedScript::AsClassicScript() {
+ MOZ_ASSERT(!IsModuleScript());
+ return static_cast<ClassicScript*>(this);
+}
+
+ModuleScript* LoadedScript::AsModuleScript() {
+ MOZ_ASSERT(IsModuleScript());
+ return static_cast<ModuleScript*>(this);
+}
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_LoadedScript_h
diff --git a/dom/script/ModuleLoadRequest.cpp b/dom/script/ModuleLoadRequest.cpp
new file mode 100644
index 0000000000..c2771e6d46
--- /dev/null
+++ b/dom/script/ModuleLoadRequest.cpp
@@ -0,0 +1,218 @@
+/* -*- 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 "ModuleLoadRequest.h"
+
+#include "mozilla/HoldDropJSObjects.h"
+
+#include "LoadedScript.h"
+#include "ScriptLoader.h"
+
+namespace mozilla {
+namespace dom {
+
+#undef LOG
+#define LOG(args) \
+ MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleLoadRequest)
+NS_INTERFACE_MAP_END_INHERITING(ScriptLoadRequest)
+
+NS_IMPL_CYCLE_COLLECTION_MULTI_ZONE_JSHOLDER_CLASS(ModuleLoadRequest)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ModuleLoadRequest,
+ ScriptLoadRequest)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoader, mModuleScript, mImports)
+ tmp->ClearDynamicImport();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ModuleLoadRequest,
+ ScriptLoadRequest)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoader, mModuleScript, mImports)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ModuleLoadRequest,
+ ScriptLoadRequest)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mDynamicReferencingPrivate)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mDynamicSpecifier)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mDynamicPromise)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_ADDREF_INHERITED(ModuleLoadRequest, ScriptLoadRequest)
+NS_IMPL_RELEASE_INHERITED(ModuleLoadRequest, ScriptLoadRequest)
+
+static VisitedURLSet* NewVisitedSetForTopLevelImport(nsIURI* aURI) {
+ auto set = new VisitedURLSet();
+ set->PutEntry(aURI);
+ return set;
+}
+
+/* static */
+ModuleLoadRequest* ModuleLoadRequest::CreateTopLevel(
+ nsIURI* aURI, ScriptFetchOptions* aFetchOptions,
+ const SRIMetadata& aIntegrity, nsIURI* aReferrer, ScriptLoader* aLoader) {
+ return new ModuleLoadRequest(aURI, aFetchOptions, aIntegrity, aReferrer,
+ true, /* is top level */
+ false, /* is dynamic import */
+ aLoader, NewVisitedSetForTopLevelImport(aURI));
+}
+
+/* static */
+ModuleLoadRequest* ModuleLoadRequest::CreateStaticImport(
+ nsIURI* aURI, ModuleLoadRequest* aParent) {
+ auto request =
+ new ModuleLoadRequest(aURI, aParent->mFetchOptions, SRIMetadata(),
+ aParent->mURI, false, /* is top level */
+ false, /* is dynamic import */
+ aParent->mLoader, aParent->mVisitedSet);
+
+ request->mIsInline = false;
+ request->mScriptMode = aParent->mScriptMode;
+
+ return request;
+}
+
+/* static */
+ModuleLoadRequest* ModuleLoadRequest::CreateDynamicImport(
+ nsIURI* aURI, ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL,
+ ScriptLoader* aLoader, JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSString*> aSpecifier, JS::Handle<JSObject*> aPromise) {
+ MOZ_ASSERT(aSpecifier);
+ MOZ_ASSERT(aPromise);
+
+ auto request = new ModuleLoadRequest(
+ aURI, aFetchOptions, SRIMetadata(), aBaseURL, true, /* is top level */
+ true, /* is dynamic import */
+ aLoader, NewVisitedSetForTopLevelImport(aURI));
+
+ request->mIsInline = false;
+ request->mScriptMode = ScriptMode::eAsync;
+ request->mDynamicReferencingPrivate = aReferencingPrivate;
+ request->mDynamicSpecifier = aSpecifier;
+ request->mDynamicPromise = aPromise;
+
+ HoldJSObjects(request);
+
+ return request;
+}
+
+ModuleLoadRequest::ModuleLoadRequest(
+ nsIURI* aURI, ScriptFetchOptions* aFetchOptions,
+ const SRIMetadata& aIntegrity, nsIURI* aReferrer, bool aIsTopLevel,
+ bool aIsDynamicImport, ScriptLoader* aLoader, VisitedURLSet* aVisitedSet)
+ : ScriptLoadRequest(ScriptKind::eModule, aURI, aFetchOptions, aIntegrity,
+ aReferrer),
+ mIsTopLevel(aIsTopLevel),
+ mIsDynamicImport(aIsDynamicImport),
+ mLoader(aLoader),
+ mVisitedSet(aVisitedSet) {}
+
+void ModuleLoadRequest::Cancel() {
+ ScriptLoadRequest::Cancel();
+ mModuleScript = nullptr;
+ mProgress = ScriptLoadRequest::Progress::eReady;
+ CancelImports();
+ mReady.RejectIfExists(NS_ERROR_DOM_ABORT_ERR, __func__);
+}
+
+void ModuleLoadRequest::CancelImports() {
+ for (size_t i = 0; i < mImports.Length(); i++) {
+ mImports[i]->Cancel();
+ }
+}
+
+void ModuleLoadRequest::SetReady() {
+ // Mark a module as ready to execute. This means that this module and all it
+ // dependencies have had their source loaded, parsed as a module and the
+ // modules instantiated.
+ //
+ // The mReady promise is used to ensure that when all dependencies of a module
+ // have become ready, DependenciesLoaded is called on that module
+ // request. This is set up in StartFetchingModuleDependencies.
+
+#ifdef DEBUG
+ for (size_t i = 0; i < mImports.Length(); i++) {
+ MOZ_ASSERT(mImports[i]->IsReadyToRun());
+ }
+#endif
+
+ ScriptLoadRequest::SetReady();
+ mReady.ResolveIfExists(true, __func__);
+}
+
+void ModuleLoadRequest::ModuleLoaded() {
+ // A module that was found to be marked as fetching in the module map has now
+ // been loaded.
+
+ LOG(("ScriptLoadRequest (%p): Module loaded", this));
+
+ mModuleScript = mLoader->GetFetchedModule(mURI);
+ if (!mModuleScript || mModuleScript->HasParseError()) {
+ ModuleErrored();
+ return;
+ }
+
+ mLoader->StartFetchingModuleDependencies(this);
+}
+
+void ModuleLoadRequest::ModuleErrored() {
+ if (IsCanceled()) {
+ return;
+ }
+
+ LOG(("ScriptLoadRequest (%p): Module errored", this));
+
+ mLoader->CheckModuleDependenciesLoaded(this);
+ MOZ_ASSERT(!mModuleScript || mModuleScript->HasParseError());
+
+ CancelImports();
+ SetReady();
+ LoadFinished();
+}
+
+void ModuleLoadRequest::DependenciesLoaded() {
+ if (IsCanceled()) {
+ return;
+ }
+
+ // The module and all of its dependencies have been successfully fetched and
+ // compiled.
+
+ LOG(("ScriptLoadRequest (%p): Module dependencies loaded", this));
+
+ MOZ_ASSERT(mModuleScript);
+
+ mLoader->CheckModuleDependenciesLoaded(this);
+ SetReady();
+ LoadFinished();
+}
+
+void ModuleLoadRequest::LoadFailed() {
+ // We failed to load the source text or an error occurred unrelated to the
+ // content of the module (e.g. OOM).
+
+ LOG(("ScriptLoadRequest (%p): Module load failed", this));
+
+ MOZ_ASSERT(!mModuleScript);
+
+ Cancel();
+ LoadFinished();
+}
+
+void ModuleLoadRequest::LoadFinished() {
+ mLoader->ProcessLoadedModuleTree(this);
+
+ mLoader = nullptr;
+}
+
+void ModuleLoadRequest::ClearDynamicImport() {
+ mDynamicReferencingPrivate = JS::UndefinedValue();
+ mDynamicSpecifier = nullptr;
+ mDynamicPromise = nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/script/ModuleLoadRequest.h b/dom/script/ModuleLoadRequest.h
new file mode 100644
index 0000000000..8faff57cac
--- /dev/null
+++ b/dom/script/ModuleLoadRequest.h
@@ -0,0 +1,122 @@
+/* -*- 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_ModuleLoadRequest_h
+#define mozilla_dom_ModuleLoadRequest_h
+
+#include "ScriptLoadRequest.h"
+#include "js/RootingAPI.h"
+#include "js/Value.h"
+#include "nsURIHashKey.h"
+#include "mozilla/MozPromise.h"
+#include "nsTHashtable.h"
+
+namespace mozilla {
+namespace dom {
+
+class ModuleScript;
+class ScriptLoader;
+
+// A reference counted set of URLs we have visited in the process of loading a
+// module graph.
+class VisitedURLSet : public nsTHashtable<nsURIHashKey> {
+ NS_INLINE_DECL_REFCOUNTING(VisitedURLSet)
+
+ private:
+ ~VisitedURLSet() = default;
+};
+
+// A load request for a module, created for every top level module script and
+// every module import. Load request can share an ModuleScript if there are
+// multiple imports of the same module.
+
+class ModuleLoadRequest final : public ScriptLoadRequest {
+ ~ModuleLoadRequest() = default;
+
+ ModuleLoadRequest(const ModuleLoadRequest& aOther) = delete;
+ ModuleLoadRequest(ModuleLoadRequest&& aOther) = delete;
+
+ ModuleLoadRequest(nsIURI* aURI, ScriptFetchOptions* aFetchOptions,
+ const SRIMetadata& aIntegrity, nsIURI* aReferrer,
+ bool aIsTopLevel, bool aIsDynamicImport,
+ ScriptLoader* aLoader, VisitedURLSet* aVisitedSet);
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(ModuleLoadRequest,
+ ScriptLoadRequest)
+
+ // Create a top-level module load request.
+ static ModuleLoadRequest* CreateTopLevel(nsIURI* aURI,
+ ScriptFetchOptions* aFetchOptions,
+ const SRIMetadata& aIntegrity,
+ nsIURI* aReferrer,
+ ScriptLoader* aLoader);
+
+ // Create a module load request for a static module import.
+ static ModuleLoadRequest* CreateStaticImport(nsIURI* aURI,
+ ModuleLoadRequest* aParent);
+
+ // Create a module load request for dynamic module import.
+ static ModuleLoadRequest* CreateDynamicImport(
+ nsIURI* aURI, ScriptFetchOptions* aFetchOptions, nsIURI* aBaseURL,
+ ScriptLoader* aLoader, JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSString*> aSpecifier, JS::Handle<JSObject*> aPromise);
+
+ bool IsTopLevel() const override { return mIsTopLevel; }
+
+ bool IsDynamicImport() const { return mIsDynamicImport; }
+
+ void SetReady() override;
+ void Cancel() override;
+ void ClearDynamicImport();
+
+ void ModuleLoaded();
+ void ModuleErrored();
+ void DependenciesLoaded();
+ void LoadFailed();
+
+ private:
+ void LoadFinished();
+ void CancelImports();
+
+ public:
+ // Is this a request for a top level module script or an import?
+ const bool mIsTopLevel;
+
+ // Is this the top level request for a dynamic module import?
+ const bool mIsDynamicImport;
+
+ // Pointer to the script loader, used to trigger actions when the module load
+ // finishes.
+ RefPtr<ScriptLoader> mLoader;
+
+ // Set to a module script object after a successful load or nullptr on
+ // failure.
+ RefPtr<ModuleScript> mModuleScript;
+
+ // A promise that is completed on successful load of this module and all of
+ // its dependencies, indicating that the module is ready for instantiation and
+ // evaluation.
+ MozPromiseHolder<GenericPromise> mReady;
+
+ // Array of imported modules.
+ nsTArray<RefPtr<ModuleLoadRequest>> mImports;
+
+ // Set of module URLs visited while fetching the module graph this request is
+ // part of.
+ RefPtr<VisitedURLSet> mVisitedSet;
+
+ // For dynamic imports, the details to pass to FinishDynamicImport.
+ JS::Heap<JS::Value> mDynamicReferencingPrivate;
+ JS::Heap<JSString*> mDynamicSpecifier;
+ JS::Heap<JSObject*> mDynamicPromise;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ModuleLoadRequest_h
diff --git a/dom/script/ScriptDecoding.h b/dom/script/ScriptDecoding.h
new file mode 100644
index 0000000000..a54d120240
--- /dev/null
+++ b/dom/script/ScriptDecoding.h
@@ -0,0 +1,96 @@
+/* -*- 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/. */
+
+/* Script decoding templates for processing byte data as UTF-8 or UTF-16. */
+
+#ifndef mozilla_dom_ScriptDecoding_h
+#define mozilla_dom_ScriptDecoding_h
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/CheckedInt.h" // mozilla::CheckedInt
+#include "mozilla/Encoding.h" // mozilla::Decoder
+#include "mozilla/Span.h" // mozilla::Span
+#include "mozilla/Tuple.h" // mozilla::Tie
+#include "mozilla/UniquePtr.h" // mozilla::UniquePtr
+#include "mozilla/Unused.h" // mozilla::Unused
+
+#include <stddef.h> // size_t
+#include <stdint.h> // uint8_t, uint32_t
+#include <type_traits> // std::is_same
+
+namespace mozilla {
+namespace dom {
+
+template <typename Unit>
+struct ScriptDecoding {
+ static_assert(std::is_same<Unit, char16_t>::value ||
+ std::is_same<Unit, Utf8Unit>::value,
+ "must be either UTF-8 or UTF-16");
+};
+
+template <>
+struct ScriptDecoding<char16_t> {
+ static CheckedInt<size_t> MaxBufferLength(const UniquePtr<Decoder>& aDecoder,
+ uint32_t aByteLength) {
+ return aDecoder->MaxUTF16BufferLength(aByteLength);
+ }
+
+ static size_t DecodeInto(const UniquePtr<Decoder>& aDecoder,
+ const Span<const uint8_t>& aSrc,
+ Span<char16_t> aDest, bool aEndOfSource) {
+ uint32_t result;
+ size_t read;
+ size_t written;
+ bool hadErrors;
+ Tie(result, read, written, hadErrors) =
+ aDecoder->DecodeToUTF16(aSrc, aDest, aEndOfSource);
+ MOZ_ASSERT(result == kInputEmpty);
+ MOZ_ASSERT(read == aSrc.Length());
+ MOZ_ASSERT(written <= aDest.Length());
+ Unused << hadErrors;
+
+ return written;
+ }
+};
+
+template <>
+struct ScriptDecoding<Utf8Unit> {
+ static CheckedInt<size_t> MaxBufferLength(const UniquePtr<Decoder>& aDecoder,
+ uint32_t aByteLength) {
+ return aDecoder->MaxUTF8BufferLength(aByteLength);
+ }
+
+ static size_t DecodeInto(const UniquePtr<Decoder>& aDecoder,
+ const Span<const uint8_t>& aSrc,
+ Span<Utf8Unit> aDest, bool aEndOfSource) {
+ uint32_t result;
+ size_t read;
+ size_t written;
+ bool hadErrors;
+ // Until C++ char8_t happens, our decoder APIs deal in |uint8_t| while
+ // |Utf8Unit| internally deals with |char|, so there's inevitable impedance
+ // mismatch between |aDest| as |Utf8Unit| and |AsWritableBytes(aDest)| as
+ // |Span<uint8_t>|. :-( The written memory will be interpreted through
+ // |char Utf8Unit::mValue| which is *permissible* because any object's
+ // memory can be interpreted as |char|. Unfortunately, until
+ // twos-complement is mandated, we have to play fast and loose and *hope*
+ // interpreting memory storing |uint8_t| as |char| will pick up the desired
+ // wrapped-around value. ¯\_(ツ)_/¯
+ Tie(result, read, written, hadErrors) =
+ aDecoder->DecodeToUTF8(aSrc, AsWritableBytes(aDest), aEndOfSource);
+ MOZ_ASSERT(result == kInputEmpty);
+ MOZ_ASSERT(read == aSrc.Length());
+ MOZ_ASSERT(written <= aDest.Length());
+ Unused << hadErrors;
+
+ return written;
+ }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ScriptDecoding_h
diff --git a/dom/script/ScriptElement.cpp b/dom/script/ScriptElement.cpp
new file mode 100644
index 0000000000..66c86fc9a3
--- /dev/null
+++ b/dom/script/ScriptElement.cpp
@@ -0,0 +1,119 @@
+/* -*- 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 "ScriptElement.h"
+#include "ScriptLoader.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "nsContentUtils.h"
+#include "nsPresContext.h"
+#include "nsIParser.h"
+#include "nsGkAtoms.h"
+#include "nsContentSink.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMETHODIMP
+ScriptElement::ScriptAvailable(nsresult aResult, nsIScriptElement* aElement,
+ bool aIsInlineClassicScript, nsIURI* aURI,
+ int32_t aLineNo) {
+ if (!aIsInlineClassicScript && NS_FAILED(aResult)) {
+ nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
+ if (parser) {
+ parser->IncrementScriptNestingLevel();
+ }
+ nsresult rv = FireErrorEvent();
+ if (parser) {
+ parser->DecrementScriptNestingLevel();
+ }
+ return rv;
+ }
+ return NS_OK;
+}
+
+/* virtual */
+nsresult ScriptElement::FireErrorEvent() {
+ nsCOMPtr<nsIContent> cont = do_QueryInterface((nsIScriptElement*)this);
+
+ return nsContentUtils::DispatchTrustedEvent(
+ cont->OwnerDoc(), cont, u"error"_ns, CanBubble::eNo, Cancelable::eNo);
+}
+
+NS_IMETHODIMP
+ScriptElement::ScriptEvaluated(nsresult aResult, nsIScriptElement* aElement,
+ bool aIsInline) {
+ nsresult rv = NS_OK;
+ if (!aIsInline) {
+ nsCOMPtr<nsIContent> cont = do_QueryInterface((nsIScriptElement*)this);
+
+ RefPtr<nsPresContext> presContext =
+ nsContentUtils::GetContextForContent(cont);
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ EventMessage message = NS_SUCCEEDED(aResult) ? eLoad : eLoadError;
+ WidgetEvent event(true, message);
+ // Load event doesn't bubble.
+ event.mFlags.mBubbles = (message != eLoad);
+
+ EventDispatcher::Dispatch(cont, presContext, &event, nullptr, &status);
+ }
+
+ return rv;
+}
+
+void ScriptElement::CharacterDataChanged(nsIContent* aContent,
+ const CharacterDataChangeInfo&) {
+ MaybeProcessScript();
+}
+
+void ScriptElement::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ MaybeProcessScript();
+}
+
+void ScriptElement::ContentAppended(nsIContent* aFirstNewContent) {
+ MaybeProcessScript();
+}
+
+void ScriptElement::ContentInserted(nsIContent* aChild) {
+ MaybeProcessScript();
+}
+
+bool ScriptElement::MaybeProcessScript() {
+ nsCOMPtr<nsIContent> cont = do_QueryInterface((nsIScriptElement*)this);
+
+ NS_ASSERTION(cont->DebugGetSlots()->mMutationObservers.Contains(this),
+ "You forgot to add self as observer");
+
+ if (mAlreadyStarted || !mDoneAddingChildren || !cont->GetComposedDoc() ||
+ mMalformed || !HasScriptContent()) {
+ return false;
+ }
+
+ Document* ownerDoc = cont->OwnerDoc();
+ FreezeExecutionAttrs(ownerDoc);
+
+ mAlreadyStarted = true;
+
+ nsCOMPtr<nsIParser> parser = ((nsIScriptElement*)this)->GetCreatorParser();
+ if (parser) {
+ nsCOMPtr<nsIContentSink> sink = parser->GetContentSink();
+ if (sink) {
+ nsCOMPtr<Document> parserDoc = do_QueryInterface(sink->GetTarget());
+ if (ownerDoc != parserDoc) {
+ // Willful violation of HTML5 as of 2010-12-01
+ return false;
+ }
+ }
+ }
+
+ RefPtr<ScriptLoader> loader = ownerDoc->ScriptLoader();
+ return loader->ProcessScriptElement(this);
+}
diff --git a/dom/script/ScriptElement.h b/dom/script/ScriptElement.h
new file mode 100644
index 0000000000..6f0e69f7bd
--- /dev/null
+++ b/dom/script/ScriptElement.h
@@ -0,0 +1,54 @@
+/* -*- 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_ScriptElement_h
+#define mozilla_dom_ScriptElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsIScriptLoaderObserver.h"
+#include "nsIScriptElement.h"
+#include "nsStubMutationObserver.h"
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * Baseclass useful for script elements (such as <xhtml:script> and
+ * <svg:script>). Currently the class assumes that only the 'src'
+ * attribute and the children of the class affect what script to execute.
+ */
+
+class ScriptElement : public nsIScriptElement, public nsStubMutationObserver {
+ public:
+ // nsIScriptLoaderObserver
+ NS_DECL_NSISCRIPTLOADEROBSERVER
+
+ // nsIMutationObserver
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+
+ explicit ScriptElement(FromParser aFromParser)
+ : nsIScriptElement(aFromParser) {}
+
+ virtual nsresult FireErrorEvent() override;
+
+ protected:
+ // Internal methods
+
+ /**
+ * Check if this element contains any script, linked or inline
+ */
+ virtual bool HasScriptContent() = 0;
+
+ virtual bool MaybeProcessScript() override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ScriptElement_h
diff --git a/dom/script/ScriptKind.h b/dom/script/ScriptKind.h
new file mode 100644
index 0000000000..7ae6c6c465
--- /dev/null
+++ b/dom/script/ScriptKind.h
@@ -0,0 +1,18 @@
+/* -*- 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_ScriptKind_h
+#define mozilla_dom_ScriptKind_h
+
+namespace mozilla {
+namespace dom {
+
+enum class ScriptKind { eClassic, eModule, eEvent };
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/script/ScriptLoadHandler.cpp b/dom/script/ScriptLoadHandler.cpp
new file mode 100644
index 0000000000..2d5f2bfbf6
--- /dev/null
+++ b/dom/script/ScriptLoadHandler.cpp
@@ -0,0 +1,472 @@
+/* -*- 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 "ScriptLoadHandler.h"
+
+#include <stdlib.h>
+#include <utility>
+#include "ScriptLoader.h"
+#include "ScriptTrace.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/Logging.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/Tuple.h"
+#include "mozilla/Utf8.h"
+#include "mozilla/Vector.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/SRICheck.h"
+#include "mozilla/dom/ScriptDecoding.h"
+#include "mozilla/dom/ScriptLoadRequest.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsDebug.h"
+#include "nsICacheInfoChannel.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIRequest.h"
+#include "nsIScriptElement.h"
+#include "nsIURI.h"
+#include "nsJSUtils.h"
+#include "nsMimeTypes.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace dom {
+
+#undef LOG
+#define LOG(args) \
+ MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
+
+#define LOG_ENABLED() \
+ MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug)
+
+ScriptLoadHandler::ScriptLoadHandler(
+ ScriptLoader* aScriptLoader, ScriptLoadRequest* aRequest,
+ UniquePtr<SRICheckDataVerifier>&& aSRIDataVerifier)
+ : mScriptLoader(aScriptLoader),
+ mRequest(aRequest),
+ mSRIDataVerifier(std::move(aSRIDataVerifier)),
+ mSRIStatus(NS_OK),
+ mDecoder() {
+ MOZ_ASSERT(mRequest->IsUnknownDataType());
+ MOZ_ASSERT(mRequest->IsLoading());
+}
+
+ScriptLoadHandler::~ScriptLoadHandler() = default;
+
+NS_IMPL_ISUPPORTS(ScriptLoadHandler, nsIIncrementalStreamLoaderObserver)
+
+template <typename Unit>
+nsresult ScriptLoadHandler::DecodeRawDataHelper(const uint8_t* aData,
+ uint32_t aDataLength,
+ bool aEndOfStream) {
+ CheckedInt<size_t> needed =
+ ScriptDecoding<Unit>::MaxBufferLength(mDecoder, aDataLength);
+ if (!needed.isValid()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Reference to the script source buffer which we will update.
+ ScriptLoadRequest::ScriptTextBuffer<Unit>& scriptText =
+ mRequest->ScriptText<Unit>();
+
+ uint32_t haveRead = scriptText.length();
+
+ CheckedInt<uint32_t> capacity = haveRead;
+ capacity += needed.value();
+
+ if (!capacity.isValid() || !scriptText.resize(capacity.value())) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ size_t written = ScriptDecoding<Unit>::DecodeInto(
+ mDecoder, Span(aData, aDataLength),
+ Span(scriptText.begin() + haveRead, needed.value()), aEndOfStream);
+ MOZ_ASSERT(written <= needed.value());
+
+ haveRead += written;
+ MOZ_ASSERT(haveRead <= capacity.value(),
+ "mDecoder produced more data than expected");
+ MOZ_ALWAYS_TRUE(scriptText.resize(haveRead));
+ mRequest->mScriptTextLength = scriptText.length();
+
+ return NS_OK;
+}
+
+nsresult ScriptLoadHandler::DecodeRawData(const uint8_t* aData,
+ uint32_t aDataLength,
+ bool aEndOfStream) {
+ if (mRequest->IsUTF16Text()) {
+ return DecodeRawDataHelper<char16_t>(aData, aDataLength, aEndOfStream);
+ }
+
+ return DecodeRawDataHelper<Utf8Unit>(aData, aDataLength, aEndOfStream);
+}
+
+NS_IMETHODIMP
+ScriptLoadHandler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
+ nsISupports* aContext,
+ uint32_t aDataLength, const uint8_t* aData,
+ uint32_t* aConsumedLength) {
+ nsCOMPtr<nsIRequest> channelRequest;
+ aLoader->GetRequest(getter_AddRefs(channelRequest));
+
+ if (!mPreloadStartNotified) {
+ mPreloadStartNotified = true;
+ mRequest->NotifyStart(channelRequest);
+ }
+
+ if (mRequest->IsCanceled()) {
+ // If request cancelled, ignore any incoming data.
+ *aConsumedLength = aDataLength;
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+ if (mRequest->IsUnknownDataType()) {
+ rv = EnsureKnownDataType(aLoader);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mRequest->IsTextSource()) {
+ if (!EnsureDecoder(aLoader, aData, aDataLength,
+ /* aEndOfStream = */ false)) {
+ return NS_OK;
+ }
+
+ // Below we will/shall consume entire data chunk.
+ *aConsumedLength = aDataLength;
+
+ // Decoder has already been initialized. -- trying to decode all loaded
+ // bytes.
+ rv = DecodeRawData(aData, aDataLength, /* aEndOfStream = */ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If SRI is required for this load, appending new bytes to the hash.
+ if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
+ mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
+ }
+ } else if (mRequest->IsBinASTSource()) {
+ if (!mRequest->ScriptBinASTData().append(aData, aDataLength)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Below we will/shall consume entire data chunk.
+ *aConsumedLength = aDataLength;
+
+ // If SRI is required for this load, appending new bytes to the hash.
+ if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
+ mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
+ }
+ } else {
+ MOZ_ASSERT(mRequest->IsBytecode());
+ if (!mRequest->mScriptBytecode.append(aData, aDataLength)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ *aConsumedLength = aDataLength;
+ uint32_t sriLength = 0;
+ rv = MaybeDecodeSRI(&sriLength);
+ if (NS_FAILED(rv)) {
+ return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
+ }
+ if (sriLength) {
+ mRequest->mBytecodeOffset = JS::AlignTranscodingBytecodeOffset(sriLength);
+ }
+ }
+
+ return rv;
+}
+
+bool ScriptLoadHandler::TrySetDecoder(nsIIncrementalStreamLoader* aLoader,
+ const uint8_t* aData,
+ uint32_t aDataLength, bool aEndOfStream) {
+ MOZ_ASSERT(mDecoder == nullptr,
+ "can't have a decoder already if we're trying to set one");
+
+ // JavaScript modules are always UTF-8.
+ if (mRequest->IsModuleRequest()) {
+ mDecoder = UTF_8_ENCODING->NewDecoderWithBOMRemoval();
+ return true;
+ }
+
+ // Determine if BOM check should be done. This occurs either
+ // if end-of-stream has been reached, or at least 3 bytes have
+ // been read from input.
+ if (!aEndOfStream && (aDataLength < 3)) {
+ return false;
+ }
+
+ // Do BOM detection.
+ const Encoding* encoding;
+ size_t bomLength;
+ Tie(encoding, bomLength) = Encoding::ForBOM(Span(aData, aDataLength));
+ if (encoding) {
+ mDecoder = encoding->NewDecoderWithBOMRemoval();
+ return true;
+ }
+
+ // BOM detection failed, check content stream for charset.
+ nsCOMPtr<nsIRequest> req;
+ nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
+ NS_ASSERTION(req, "StreamLoader's request went away prematurely");
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
+
+ if (channel) {
+ nsAutoCString label;
+ if (NS_SUCCEEDED(channel->GetContentCharset(label)) &&
+ (encoding = Encoding::ForLabel(label))) {
+ mDecoder = encoding->NewDecoderWithoutBOMHandling();
+ return true;
+ }
+ }
+
+ // Check the hint charset from the script element or preload
+ // request.
+ nsAutoString hintCharset;
+ if (!mRequest->IsPreload()) {
+ mRequest->GetScriptElement()->GetScriptCharset(hintCharset);
+ } else {
+ nsTArray<ScriptLoader::PreloadInfo>::index_type i =
+ mScriptLoader->mPreloads.IndexOf(
+ mRequest, 0, ScriptLoader::PreloadRequestComparator());
+
+ NS_ASSERTION(i != mScriptLoader->mPreloads.NoIndex,
+ "Incorrect preload bookkeeping");
+ hintCharset = mScriptLoader->mPreloads[i].mCharset;
+ }
+
+ if ((encoding = Encoding::ForLabel(hintCharset))) {
+ mDecoder = encoding->NewDecoderWithoutBOMHandling();
+ return true;
+ }
+
+ // Get the charset from the charset of the document.
+ if (mScriptLoader->mDocument) {
+ encoding = mScriptLoader->mDocument->GetDocumentCharacterSet();
+ mDecoder = encoding->NewDecoderWithoutBOMHandling();
+ return true;
+ }
+
+ // Curiously, there are various callers that don't pass aDocument. The
+ // fallback in the old code was ISO-8859-1, which behaved like
+ // windows-1252.
+ mDecoder = WINDOWS_1252_ENCODING->NewDecoderWithoutBOMHandling();
+ return true;
+}
+
+nsresult ScriptLoadHandler::MaybeDecodeSRI(uint32_t* sriLength) {
+ *sriLength = 0;
+
+ if (!mSRIDataVerifier || mSRIDataVerifier->IsComplete() ||
+ NS_FAILED(mSRIStatus)) {
+ return NS_OK;
+ }
+
+ // Skip until the content is large enough to be decoded.
+ if (mRequest->mScriptBytecode.length() <=
+ mSRIDataVerifier->DataSummaryLength()) {
+ return NS_OK;
+ }
+
+ mSRIStatus = mSRIDataVerifier->ImportDataSummary(
+ mRequest->mScriptBytecode.length(), mRequest->mScriptBytecode.begin());
+
+ if (NS_FAILED(mSRIStatus)) {
+ // We are unable to decode the hash contained in the alternate data which
+ // contains the bytecode, or it does not use the same algorithm.
+ LOG(
+ ("ScriptLoadHandler::MaybeDecodeSRI, failed to decode SRI, restart "
+ "request"));
+ return mSRIStatus;
+ }
+
+ *sriLength = mSRIDataVerifier->DataSummaryLength();
+ MOZ_ASSERT(*sriLength > 0);
+ return NS_OK;
+}
+
+nsresult ScriptLoadHandler::EnsureKnownDataType(
+ nsIIncrementalStreamLoader* aLoader) {
+ MOZ_ASSERT(mRequest->IsUnknownDataType());
+ MOZ_ASSERT(mRequest->IsLoading());
+
+ nsCOMPtr<nsIRequest> req;
+ nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
+ MOZ_ASSERT(req, "StreamLoader's request went away prematurely");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mRequest->IsLoadingSource()) {
+ mRequest->SetTextSource();
+ TRACE_FOR_TEST(mRequest->GetScriptElement(), "scriptloader_load_source");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(req));
+ if (cic) {
+ nsAutoCString altDataType;
+ cic->GetAlternativeDataType(altDataType);
+ if (altDataType.Equals(nsContentUtils::JSBytecodeMimeType())) {
+ mRequest->SetBytecode();
+ TRACE_FOR_TEST(mRequest->GetScriptElement(),
+ "scriptloader_load_bytecode");
+ return NS_OK;
+ }
+ MOZ_ASSERT(altDataType.IsEmpty());
+ }
+
+ if (nsJSUtils::BinASTEncodingEnabled()) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(req);
+ if (httpChannel) {
+ nsAutoCString mimeType;
+ httpChannel->GetContentType(mimeType);
+ if (mimeType.LowerCaseEqualsASCII(APPLICATION_JAVASCRIPT_BINAST)) {
+ if (mRequest->ShouldAcceptBinASTEncoding()) {
+ mRequest->SetBinASTSource();
+ TRACE_FOR_TEST(mRequest->GetScriptElement(),
+ "scriptloader_load_source");
+ return NS_OK;
+ }
+
+ // If the request isn't allowed to accept BinAST, fallback to text
+ // source. The possibly binary source will be passed to normal
+ // JS parser and will throw error there.
+ mRequest->SetTextSource();
+ return NS_OK;
+ }
+ }
+ }
+
+ mRequest->SetTextSource();
+ TRACE_FOR_TEST(mRequest->GetScriptElement(), "scriptloader_load_source");
+
+ MOZ_ASSERT(!mRequest->IsUnknownDataType());
+ MOZ_ASSERT(mRequest->IsLoading());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ScriptLoadHandler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
+ nsISupports* aContext, nsresult aStatus,
+ uint32_t aDataLength,
+ const uint8_t* aData) {
+ nsresult rv = NS_OK;
+ if (LOG_ENABLED()) {
+ nsAutoCString url;
+ mRequest->mURI->GetAsciiSpec(url);
+ LOG(("ScriptLoadRequest (%p): Stream complete (url = %s)", mRequest.get(),
+ url.get()));
+ }
+
+ nsCOMPtr<nsIRequest> channelRequest;
+ aLoader->GetRequest(getter_AddRefs(channelRequest));
+
+ if (!mPreloadStartNotified) {
+ mPreloadStartNotified = true;
+ mRequest->NotifyStart(channelRequest);
+ }
+
+ auto notifyStop =
+ MakeScopeExit([&] { mRequest->NotifyStop(channelRequest, rv); });
+
+ if (!mRequest->IsCanceled()) {
+ if (mRequest->IsUnknownDataType()) {
+ rv = EnsureKnownDataType(aLoader);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mRequest->IsTextSource()) {
+ DebugOnly<bool> encoderSet =
+ EnsureDecoder(aLoader, aData, aDataLength, /* aEndOfStream = */ true);
+ MOZ_ASSERT(encoderSet);
+ rv = DecodeRawData(aData, aDataLength, /* aEndOfStream = */ true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("ScriptLoadRequest (%p): Source length in code units = %u",
+ mRequest.get(), unsigned(mRequest->ScriptTextLength())));
+
+ // If SRI is required for this load, appending new bytes to the hash.
+ if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
+ mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
+ }
+ } else if (mRequest->IsBinASTSource()) {
+ if (!mRequest->ScriptBinASTData().append(aData, aDataLength)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // If SRI is required for this load, appending new bytes to the hash.
+ if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
+ mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
+ }
+ } else {
+ MOZ_ASSERT(mRequest->IsBytecode());
+ if (!mRequest->mScriptBytecode.append(aData, aDataLength)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ LOG(("ScriptLoadRequest (%p): Bytecode length = %u", mRequest.get(),
+ unsigned(mRequest->mScriptBytecode.length())));
+
+ // If we abort while decoding the SRI, we fallback on explictly requesting
+ // the source. Thus, we should not continue in
+ // ScriptLoader::OnStreamComplete, which removes the request from the
+ // waiting lists.
+ //
+ // We calculate the SRI length below.
+ uint32_t unused;
+ rv = MaybeDecodeSRI(&unused);
+ if (NS_FAILED(rv)) {
+ return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
+ }
+
+ // The bytecode cache always starts with the SRI hash, thus even if there
+ // is no SRI data verifier instance, we still want to skip the hash.
+ uint32_t sriLength;
+ rv = SRICheckDataVerifier::DataSummaryLength(
+ mRequest->mScriptBytecode.length(), mRequest->mScriptBytecode.begin(),
+ &sriLength);
+ if (NS_FAILED(rv)) {
+ return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
+ }
+
+ mRequest->mBytecodeOffset = JS::AlignTranscodingBytecodeOffset(sriLength);
+ }
+ }
+
+ // Everything went well, keep the CacheInfoChannel alive such that we can
+ // later save the bytecode on the cache entry.
+ if (NS_SUCCEEDED(rv) && mRequest->IsSource() &&
+ StaticPrefs::dom_script_loader_bytecode_cache_enabled()) {
+ mRequest->mCacheInfo = do_QueryInterface(channelRequest);
+ LOG(("ScriptLoadRequest (%p): nsICacheInfoChannel = %p", mRequest.get(),
+ mRequest->mCacheInfo.get()));
+ }
+
+ // we have to mediate and use mRequest.
+ rv = mScriptLoader->OnStreamComplete(aLoader, mRequest, aStatus, mSRIStatus,
+ mSRIDataVerifier.get());
+
+ // In case of failure, clear the mCacheInfoChannel to avoid keeping it alive.
+ if (NS_FAILED(rv)) {
+ mRequest->mCacheInfo = nullptr;
+ }
+
+ return rv;
+}
+
+#undef LOG_ENABLED
+#undef LOG
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/script/ScriptLoadHandler.h b/dom/script/ScriptLoadHandler.h
new file mode 100644
index 0000000000..96352a11ac
--- /dev/null
+++ b/dom/script/ScriptLoadHandler.h
@@ -0,0 +1,117 @@
+/* -*- 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/. */
+
+/*
+ * A class that handles loading and evaluation of <script> elements.
+ */
+
+#ifndef mozilla_dom_ScriptLoadHandler_h
+#define mozilla_dom_ScriptLoadHandler_h
+
+#include "nsIIncrementalStreamLoader.h"
+#include "nsISupports.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+class Decoder;
+
+namespace dom {
+
+class ScriptLoadRequest;
+class ScriptLoader;
+class SRICheckDataVerifier;
+
+class ScriptLoadHandler final : public nsIIncrementalStreamLoaderObserver {
+ public:
+ explicit ScriptLoadHandler(
+ ScriptLoader* aScriptLoader, ScriptLoadRequest* aRequest,
+ UniquePtr<SRICheckDataVerifier>&& aSRIDataVerifier);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
+
+ private:
+ virtual ~ScriptLoadHandler();
+
+ /*
+ * Decode the given data into the already-allocated internal
+ * |ScriptTextBuffer<Unit>|.
+ *
+ * This function is intended to be called only by |DecodeRawData| after
+ * determining which sort of |ScriptTextBuffer<Unit>| has been allocated.
+ */
+ template <typename Unit>
+ nsresult DecodeRawDataHelper(const uint8_t* aData, uint32_t aDataLength,
+ bool aEndOfStream);
+
+ /*
+ * Once the charset is found by the EnsureDecoder function, we can
+ * incrementally convert the charset to the one expected by the JS Parser.
+ */
+ nsresult DecodeRawData(const uint8_t* aData, uint32_t aDataLength,
+ bool aEndOfStream);
+
+ /*
+ * Discover the charset by looking at the stream data, the script tag, and
+ * other indicators. Returns true if charset has been discovered.
+ */
+ bool EnsureDecoder(nsIIncrementalStreamLoader* aLoader, const uint8_t* aData,
+ uint32_t aDataLength, bool aEndOfStream) {
+ // Check if the decoder has already been created.
+ if (mDecoder) {
+ return true;
+ }
+
+ return TrySetDecoder(aLoader, aData, aDataLength, aEndOfStream);
+ }
+
+ /*
+ * Attempt to determine how script data will be decoded, when such
+ * determination hasn't already been made. (If you don't know whether it's
+ * been made yet, use |EnsureDecoder| above instead.) Return false if there
+ * isn't enough information yet to make the determination, or true if a
+ * determination was made.
+ */
+ bool TrySetDecoder(nsIIncrementalStreamLoader* aLoader, const uint8_t* aData,
+ uint32_t aDataLength, bool aEndOfStream);
+
+ /*
+ * When streaming bytecode, we have the opportunity to fallback early if SRI
+ * does not match the expectation of the document.
+ *
+ * If SRI hash is decoded, `sriLength` is set to the length of the hash.
+ */
+ nsresult MaybeDecodeSRI(uint32_t* sriLength);
+
+ // Query the channel to find the data type associated with the input stream.
+ nsresult EnsureKnownDataType(nsIIncrementalStreamLoader* aLoader);
+
+ // ScriptLoader which will handle the parsed script.
+ RefPtr<ScriptLoader> mScriptLoader;
+
+ // The ScriptLoadRequest for this load. Decoded data are accumulated on it.
+ RefPtr<ScriptLoadRequest> mRequest;
+
+ // SRI data verifier.
+ UniquePtr<SRICheckDataVerifier> mSRIDataVerifier;
+
+ // Status of SRI data operations.
+ nsresult mSRIStatus;
+
+ // Unicode decoder for charset.
+ mozilla::UniquePtr<mozilla::Decoder> mDecoder;
+
+ // Flipped to true after calling NotifyStart the first time
+ bool mPreloadStartNotified = false;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ScriptLoadHandler_h
diff --git a/dom/script/ScriptLoadRequest.cpp b/dom/script/ScriptLoadRequest.cpp
new file mode 100644
index 0000000000..08c72a118c
--- /dev/null
+++ b/dom/script/ScriptLoadRequest.cpp
@@ -0,0 +1,319 @@
+/* -*- 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 "mozilla/dom/Document.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+
+#include "ModuleLoadRequest.h"
+#include "nsContentUtils.h"
+#include "nsICacheInfoChannel.h"
+#include "nsIClassOfService.h"
+#include "nsISupportsPriority.h"
+#include "ScriptLoadRequest.h"
+#include "ScriptSettings.h"
+
+namespace mozilla {
+namespace dom {
+
+//////////////////////////////////////////////////////////////
+// ScriptFetchOptions
+//////////////////////////////////////////////////////////////
+
+NS_IMPL_CYCLE_COLLECTION(ScriptFetchOptions, mElement, mTriggeringPrincipal)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(ScriptFetchOptions, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(ScriptFetchOptions, Release)
+
+ScriptFetchOptions::ScriptFetchOptions(mozilla::CORSMode aCORSMode,
+ ReferrerPolicy aReferrerPolicy,
+ Element* aElement,
+ nsIPrincipal* aTriggeringPrincipal)
+ : mCORSMode(aCORSMode),
+ mReferrerPolicy(aReferrerPolicy),
+ mIsPreload(false),
+ mElement(aElement),
+ mTriggeringPrincipal(aTriggeringPrincipal) {
+ MOZ_ASSERT(mTriggeringPrincipal);
+}
+
+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)
+ tmp->mScript = nullptr;
+ if (Runnable* runnable = tmp->mRunnable.exchange(nullptr)) {
+ runnable->Release();
+ }
+ tmp->DropBytecodeCacheReferences();
+ tmp->MaybeUnblockOnload();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ScriptLoadRequest)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchOptions, mCacheInfo,
+ mLoadBlockedDocument)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ScriptLoadRequest)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScript)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+ScriptLoadRequest::ScriptLoadRequest(ScriptKind aKind, nsIURI* aURI,
+ ScriptFetchOptions* aFetchOptions,
+ const SRIMetadata& aIntegrity,
+ nsIURI* aReferrer)
+ : mKind(aKind),
+ mScriptMode(ScriptMode::eBlocking),
+ mProgress(Progress::eLoading),
+ mDataType(DataType::eUnknown),
+ mScriptFromHead(false),
+ mIsInline(true),
+ mInDeferList(false),
+ mInAsyncList(false),
+ mIsNonAsyncScriptInserted(false),
+ mIsXSLT(false),
+ mIsCanceled(false),
+ mWasCompiledOMT(false),
+ mIsTracking(false),
+ mFetchOptions(aFetchOptions),
+ mOffThreadToken(nullptr),
+ mRunnable(nullptr),
+ mScriptTextLength(0),
+ mScriptBytecode(),
+ mBytecodeOffset(0),
+ mURI(aURI),
+ mLineNo(1),
+ mIntegrity(aIntegrity),
+ mReferrer(aReferrer),
+ mUnreportedPreloadError(NS_OK) {
+ MOZ_ASSERT(mFetchOptions);
+}
+
+ScriptLoadRequest::~ScriptLoadRequest() {
+ // When speculative parsing is enabled, it is possible to off-main-thread
+ // compile scripts that are never executed. These should be cleaned up here
+ // if they exist.
+ MOZ_ASSERT_IF(
+ !StaticPrefs::
+ dom_script_loader_external_scripts_speculative_omt_parse_enabled(),
+ !mOffThreadToken);
+
+ MaybeCancelOffThreadScript();
+
+ if (mScript) {
+ DropBytecodeCacheReferences();
+ }
+
+ MaybeUnblockOnload();
+ DropJSObjects(this);
+}
+
+void ScriptLoadRequest::BlockOnload(Document* aDocument) {
+ MOZ_ASSERT(!mLoadBlockedDocument);
+ aDocument->BlockOnload();
+ mLoadBlockedDocument = aDocument;
+}
+
+void ScriptLoadRequest::MaybeUnblockOnload() {
+ if (mLoadBlockedDocument) {
+ mLoadBlockedDocument->UnblockOnload(false);
+ mLoadBlockedDocument = nullptr;
+ }
+}
+
+void ScriptLoadRequest::SetReady() {
+ MOZ_ASSERT(mProgress != Progress::eReady);
+ mProgress = Progress::eReady;
+}
+
+void ScriptLoadRequest::Cancel() {
+ MaybeCancelOffThreadScript();
+ mIsCanceled = true;
+}
+
+void ScriptLoadRequest::MaybeCancelOffThreadScript() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mOffThreadToken) {
+ return;
+ }
+
+ JSContext* cx = danger::GetJSContext();
+ // Follow the same conditions as ScriptLoader::AttemptAsyncScriptCompile
+ if (IsModuleRequest()) {
+ JS::CancelOffThreadModule(cx, mOffThreadToken);
+ } else if (IsSource()) {
+ JS::CancelOffThreadScript(cx, mOffThreadToken);
+ } else {
+ MOZ_ASSERT(IsBytecode());
+ JS::CancelOffThreadScriptDecoder(cx, mOffThreadToken);
+ }
+
+ // Cancellation request above should guarantee removal of the parse task, so
+ // releasing the runnable should be safe to do here.
+ if (Runnable* runnable = mRunnable.exchange(nullptr)) {
+ runnable->Release();
+ }
+
+ MaybeUnblockOnload();
+ mOffThreadToken = nullptr;
+}
+
+void ScriptLoadRequest::DropBytecodeCacheReferences() {
+ mCacheInfo = nullptr;
+ mScript = nullptr;
+ DropJSObjects(this);
+}
+
+inline ModuleLoadRequest* ScriptLoadRequest::AsModuleRequest() {
+ MOZ_ASSERT(IsModuleRequest());
+ return static_cast<ModuleLoadRequest*>(this);
+}
+
+void ScriptLoadRequest::SetScriptMode(bool aDeferAttr, bool aAsyncAttr,
+ bool aLinkPreload) {
+ if (aLinkPreload) {
+ mScriptMode = ScriptMode::eLinkPreload;
+ } else if (aAsyncAttr) {
+ mScriptMode = ScriptMode::eAsync;
+ } else if (aDeferAttr || IsModuleRequest()) {
+ mScriptMode = ScriptMode::eDeferred;
+ } else {
+ mScriptMode = ScriptMode::eBlocking;
+ }
+}
+
+void ScriptLoadRequest::SetUnknownDataType() {
+ mDataType = DataType::eUnknown;
+ mScriptData.reset();
+}
+
+void ScriptLoadRequest::SetTextSource() {
+ MOZ_ASSERT(IsUnknownDataType());
+ mDataType = DataType::eTextSource;
+ if (StaticPrefs::dom_script_loader_external_scripts_utf8_parsing_enabled()) {
+ mScriptData.emplace(VariantType<ScriptTextBuffer<Utf8Unit>>());
+ } else {
+ mScriptData.emplace(VariantType<ScriptTextBuffer<char16_t>>());
+ }
+}
+
+void ScriptLoadRequest::SetBinASTSource() { MOZ_CRASH("BinAST not supported"); }
+
+void ScriptLoadRequest::SetBytecode() {
+ MOZ_ASSERT(IsUnknownDataType());
+ mDataType = DataType::eBytecode;
+}
+
+bool ScriptLoadRequest::ShouldAcceptBinASTEncoding() const {
+ MOZ_CRASH("BinAST not supported");
+}
+
+void ScriptLoadRequest::ClearScriptSource() {
+ if (IsTextSource()) {
+ ClearScriptText();
+ } else if (IsBinASTSource()) {
+ ScriptBinASTData().clearAndFree();
+ }
+}
+
+void ScriptLoadRequest::SetScript(JSScript* aScript) {
+ MOZ_ASSERT(!mScript);
+ mScript = aScript;
+ HoldJSObjects(this);
+}
+
+// static
+void ScriptLoadRequest::PrioritizeAsPreload(nsIChannel* aChannel) {
+ if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(aChannel)) {
+ cos->AddClassFlags(nsIClassOfService::Unblocked);
+ }
+ if (nsCOMPtr<nsISupportsPriority> sp = do_QueryInterface(aChannel)) {
+ sp->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST);
+ }
+}
+
+void ScriptLoadRequest::PrioritizeAsPreload() {
+ if (!IsLinkPreloadScript()) {
+ // Do the prioritization only if this request has not already been created
+ // as a preload.
+ PrioritizeAsPreload(Channel());
+ }
+}
+
+nsIScriptElement* ScriptLoadRequest::GetScriptElement() const {
+ nsCOMPtr<nsIScriptElement> scriptElement =
+ do_QueryInterface(mFetchOptions->mElement);
+ return scriptElement;
+}
+
+void ScriptLoadRequest::SetIsLoadRequest(nsIScriptElement* aElement) {
+ MOZ_ASSERT(aElement);
+ MOZ_ASSERT(!GetScriptElement());
+ MOZ_ASSERT(IsPreload());
+ mFetchOptions->mElement = do_QueryInterface(aElement);
+ mFetchOptions->mIsPreload = false;
+}
+
+//////////////////////////////////////////////////////////////
+// ScriptLoadRequestList
+//////////////////////////////////////////////////////////////
+
+ScriptLoadRequestList::~ScriptLoadRequestList() { Clear(); }
+
+void ScriptLoadRequestList::Clear() {
+ 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
+
+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
diff --git a/dom/script/ScriptLoadRequest.h b/dom/script/ScriptLoadRequest.h
new file mode 100644
index 0000000000..cb1c50b7ea
--- /dev/null
+++ b/dom/script/ScriptLoadRequest.h
@@ -0,0 +1,409 @@
+/* -*- 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 "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);
+
+ const mozilla::CORSMode mCORSMode;
+ const enum ReferrerPolicy mReferrerPolicy;
+ bool mIsPreload;
+ nsCOMPtr<Element> mElement;
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+};
+
+/*
+ * A class that handles loading and evaluation of <script> elements.
+ */
+
+class ScriptLoadRequest
+ : public PreloaderBase,
+ 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, 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();
+
+#ifdef MOZ_GECKO_PROFILER
+ TimeStamp mOffThreadParseStartTime;
+ TimeStamp mOffThreadParseStopTime;
+#endif
+
+ 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,
+ eBinASTSource,
+ eBytecode
+ };
+
+ bool IsUnknownDataType() const { return mDataType == DataType::eUnknown; }
+ bool IsTextSource() const { return mDataType == DataType::eTextSource; }
+ bool IsBinASTSource() const { return false; }
+ bool IsSource() const { return IsTextSource() || IsBinASTSource(); }
+ bool IsBytecode() const { return mDataType == DataType::eBytecode; }
+
+ void SetUnknownDataType();
+ void SetTextSource();
+ void SetBinASTSource();
+ 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>;
+
+ // BinAST data isn't transferred to the JS engine, so it doesn't need to use
+ // the JS allocator.
+ using BinASTSourceBuffer = Vector<uint8_t>;
+
+ 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>>();
+ }
+
+ const BinASTSourceBuffer& ScriptBinASTData() const {
+ MOZ_ASSERT(IsBinASTSource());
+ return mScriptData->as<BinASTSourceBuffer>();
+ }
+ BinASTSourceBuffer& ScriptBinASTData() {
+ MOZ_ASSERT(IsBinASTSource());
+ return mScriptData->as<BinASTSourceBuffer>();
+ }
+
+ 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;
+ }
+
+ // 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();
+ }
+
+ bool ShouldAcceptBinASTEncoding() const;
+
+ 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 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>,
+ BinASTSourceBuffer>>
+ 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> {
+ 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);
+ }
+};
+
+void ImplCycleCollectionUnlink(ScriptLoadRequestList& aField);
+
+void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+ ScriptLoadRequestList& aField,
+ const char* aName, uint32_t aFlags);
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ScriptLoadRequest_h
diff --git a/dom/script/ScriptLoader.cpp b/dom/script/ScriptLoader.cpp
new file mode 100644
index 0000000000..501c953e92
--- /dev/null
+++ b/dom/script/ScriptLoader.cpp
@@ -0,0 +1,4343 @@
+/* -*- 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 "ScriptLoader.h"
+#include "ScriptLoadHandler.h"
+#include "ScriptLoadRequest.h"
+#include "ScriptTrace.h"
+#include "LoadedScript.h"
+#include "ModuleLoadRequest.h"
+
+#include "prsystem.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "js/Array.h" // JS::GetArrayLength
+#include "js/CompilationAndEvaluation.h"
+#include "js/ContextOptions.h" // JS::ContextOptionsRef
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/MemoryFunctions.h"
+#include "js/Modules.h" // JS::FinishDynamicModuleImport, JS::{G,S}etModuleResolveHook, JS::Get{ModulePrivate,ModuleScript,RequestedModule{s,Specifier,SourcePos}}, JS::SetModule{DynamicImport,Metadata}Hook
+#include "js/OffThreadScriptCompilation.h"
+#include "js/Realm.h"
+#include "js/SourceText.h"
+#include "js/Utility.h"
+#include "xpcpublic.h"
+#include "GeckoProfiler.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIContent.h"
+#include "nsJSUtils.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/JSExecutionContext.h"
+#include "mozilla/dom/ScriptDecoding.h" // mozilla::dom::ScriptDecoding
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/SRILogHelper.h"
+#include "mozilla/dom/WindowContext.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsAboutProtocolUtils.h"
+#include "nsGkAtoms.h"
+#include "nsNetUtil.h"
+#include "nsGlobalWindowInner.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptContext.h"
+#include "nsIPrincipal.h"
+#include "nsJSPrincipals.h"
+#include "nsContentPolicyUtils.h"
+#include "nsIClassifiedChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIClassOfService.h"
+#include "nsICacheInfoChannel.h"
+#include "nsITimedChannel.h"
+#include "nsIScriptElement.h"
+#include "nsISupportsPriority.h"
+#include "nsIDocShell.h"
+#include "nsContentUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsError.h"
+#include "nsThreadUtils.h"
+#include "nsDocShellCID.h"
+#include "nsIContentSecurityPolicy.h"
+#include "mozilla/Logging.h"
+#include "nsCRT.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsProxyRelease.h"
+#include "nsSandboxFlags.h"
+#include "nsContentTypeParser.h"
+#include "nsINetworkPredictor.h"
+#include "nsMimeTypes.h"
+#include "mozilla/ConsoleReportCollector.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/LoadInfo.h"
+#include "ReferrerInfo.h"
+
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+#include "nsIScriptError.h"
+#include "nsIAsyncOutputStream.h"
+
+using JS::SourceText;
+
+using mozilla::Telemetry::LABELS_DOM_SCRIPT_PRELOAD_RESULT;
+
+namespace mozilla {
+namespace dom {
+
+LazyLogModule ScriptLoader::gCspPRLog("CSP");
+LazyLogModule ScriptLoader::gScriptLoaderLog("ScriptLoader");
+
+#undef LOG
+#define LOG(args) \
+ MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
+
+#define LOG_ENABLED() \
+ MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug)
+
+// Alternate Data MIME type used by the ScriptLoader to register that we want to
+// store bytecode without reading it.
+static constexpr auto kNullMimeType = "javascript/null"_ns;
+
+/////////////////////////////////////////////////////////////
+// AsyncCompileShutdownObserver
+/////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(AsyncCompileShutdownObserver, nsIObserver)
+
+void AsyncCompileShutdownObserver::OnShutdown() {
+ if (mScriptLoader) {
+ mScriptLoader->Shutdown();
+ Unregister();
+ }
+}
+
+void AsyncCompileShutdownObserver::Unregister() {
+ if (mScriptLoader) {
+ mScriptLoader = nullptr;
+ nsContentUtils::UnregisterShutdownObserver(this);
+ }
+}
+
+NS_IMETHODIMP
+AsyncCompileShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ OnShutdown();
+ return NS_OK;
+}
+
+//////////////////////////////////////////////////////////////
+// ScriptLoader::PreloadInfo
+//////////////////////////////////////////////////////////////
+
+inline void ImplCycleCollectionUnlink(ScriptLoader::PreloadInfo& aField) {
+ ImplCycleCollectionUnlink(aField.mRequest);
+}
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ ScriptLoader::PreloadInfo& aField, const char* aName, uint32_t aFlags = 0) {
+ ImplCycleCollectionTraverse(aCallback, aField.mRequest, aName, aFlags);
+}
+
+//////////////////////////////////////////////////////////////
+// ScriptLoader
+//////////////////////////////////////////////////////////////
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptLoader)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(ScriptLoader, mNonAsyncExternalScriptInsertedRequests,
+ mLoadingAsyncRequests, mLoadedAsyncRequests,
+ mDeferRequests, mXSLTRequests, mDynamicImportRequests,
+ mParserBlockingRequest, mBytecodeEncodingQueue,
+ mPreloads, mPendingChildLoaders, mFetchedModules)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptLoader)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptLoader)
+
+ScriptLoader::ScriptLoader(Document* aDocument)
+ : mDocument(aDocument),
+ mParserBlockingBlockerCount(0),
+ mBlockerCount(0),
+ mNumberOfProcessors(0),
+ mEnabled(true),
+ mDeferEnabled(false),
+ mSpeculativeOMTParsingEnabled(false),
+ mDeferCheckpointReached(false),
+ mBlockingDOMContentLoaded(false),
+ mLoadEventFired(false),
+ mGiveUpEncoding(false),
+ mReporter(new ConsoleReportCollector()) {
+ LOG(("ScriptLoader::ScriptLoader %p", this));
+ EnsureModuleHooksInitialized();
+
+ mSpeculativeOMTParsingEnabled = StaticPrefs::
+ dom_script_loader_external_scripts_speculative_omt_parse_enabled();
+
+ mShutdownObserver = new AsyncCompileShutdownObserver(this);
+ nsContentUtils::RegisterShutdownObserver(mShutdownObserver);
+}
+
+ScriptLoader::~ScriptLoader() {
+ LOG(("ScriptLoader::~ScriptLoader %p", this));
+
+ mObservers.Clear();
+
+ if (mParserBlockingRequest) {
+ mParserBlockingRequest->FireScriptAvailable(NS_ERROR_ABORT);
+ }
+
+ for (ScriptLoadRequest* req = mXSLTRequests.getFirst(); req;
+ req = req->getNext()) {
+ req->FireScriptAvailable(NS_ERROR_ABORT);
+ }
+
+ for (ScriptLoadRequest* req = mDeferRequests.getFirst(); req;
+ req = req->getNext()) {
+ req->FireScriptAvailable(NS_ERROR_ABORT);
+ }
+
+ for (ScriptLoadRequest* req = mLoadingAsyncRequests.getFirst(); req;
+ req = req->getNext()) {
+ req->FireScriptAvailable(NS_ERROR_ABORT);
+ }
+
+ for (ScriptLoadRequest* req = mLoadedAsyncRequests.getFirst(); req;
+ req = req->getNext()) {
+ req->FireScriptAvailable(NS_ERROR_ABORT);
+ }
+
+ for (ScriptLoadRequest* req = mDynamicImportRequests.getFirst(); req;
+ req = req->getNext()) {
+ FinishDynamicImportAndReject(req->AsModuleRequest(), NS_ERROR_ABORT);
+ }
+
+ for (ScriptLoadRequest* req =
+ mNonAsyncExternalScriptInsertedRequests.getFirst();
+ req; req = req->getNext()) {
+ req->FireScriptAvailable(NS_ERROR_ABORT);
+ }
+
+ // Unblock the kids, in case any of them moved to a different document
+ // subtree in the meantime and therefore aren't actually going away.
+ for (uint32_t j = 0; j < mPendingChildLoaders.Length(); ++j) {
+ mPendingChildLoaders[j]->RemoveParserBlockingScriptExecutionBlocker();
+ }
+
+ for (size_t i = 0; i < mPreloads.Length(); i++) {
+ AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::NotUsed);
+ }
+
+ if (mShutdownObserver) {
+ mShutdownObserver->Unregister();
+ mShutdownObserver = nullptr;
+ }
+}
+
+// Collect telemtry data about the cache information, and the kind of source
+// which are being loaded, and where it is being loaded from.
+static void CollectScriptTelemetry(ScriptLoadRequest* aRequest) {
+ using namespace mozilla::Telemetry;
+
+ // Skip this function if we are not running telemetry.
+ if (!CanRecordExtended()) {
+ return;
+ }
+
+ // Report the script kind.
+ if (aRequest->IsModuleRequest()) {
+ AccumulateCategorical(LABELS_DOM_SCRIPT_KIND::ModuleScript);
+ } else {
+ AccumulateCategorical(LABELS_DOM_SCRIPT_KIND::ClassicScript);
+ }
+
+ // Report the type of source. This is used to monitor the status of the
+ // JavaScript Start-up Bytecode Cache, with the expectation of an almost zero
+ // source-fallback and alternate-data being roughtly equal to source loads.
+ if (aRequest->IsLoadingSource()) {
+ if (aRequest->mIsInline) {
+ AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::Inline);
+ } else if (aRequest->IsTextSource()) {
+ AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::SourceFallback);
+ }
+ // TODO: Add telemetry for BinAST encoded source.
+ } else {
+ MOZ_ASSERT(aRequest->IsLoading());
+ if (aRequest->IsTextSource()) {
+ AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::Source);
+ } else if (aRequest->IsBytecode()) {
+ AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::AltData);
+ }
+ // TODO: Add telemetry for BinAST encoded source.
+ }
+}
+
+// Helper method for checking if the script element is an event-handler
+// This means that it has both a for-attribute and a event-attribute.
+// Also, if the for-attribute has a value that matches "\s*window\s*",
+// and the event-attribute matches "\s*onload([ \(].*)?" then it isn't an
+// eventhandler. (both matches are case insensitive).
+// This is how IE seems to filter out a window's onload handler from a
+// <script for=... event=...> element.
+
+static bool IsScriptEventHandler(ScriptKind kind, nsIContent* aScriptElement) {
+ if (kind != ScriptKind::eClassic) {
+ return false;
+ }
+
+ if (!aScriptElement->IsHTMLElement()) {
+ return false;
+ }
+
+ nsAutoString forAttr, eventAttr;
+ if (!aScriptElement->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::_for,
+ forAttr) ||
+ !aScriptElement->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::event,
+ eventAttr)) {
+ return false;
+ }
+
+ const nsAString& for_str =
+ nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(forAttr);
+ if (!for_str.LowerCaseEqualsLiteral("window")) {
+ return true;
+ }
+
+ // We found for="window", now check for event="onload".
+ const nsAString& event_str =
+ nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(eventAttr, false);
+ if (!StringBeginsWith(event_str, u"onload"_ns,
+ nsCaseInsensitiveStringComparator)) {
+ // It ain't "onload.*".
+
+ return true;
+ }
+
+ nsAutoString::const_iterator start, end;
+ event_str.BeginReading(start);
+ event_str.EndReading(end);
+
+ start.advance(6); // advance past "onload"
+
+ if (start != end && *start != '(' && *start != ' ') {
+ // We got onload followed by something other than space or
+ // '('. Not good enough.
+
+ return true;
+ }
+
+ return false;
+}
+
+nsContentPolicyType ScriptLoadRequestToContentPolicyType(
+ ScriptLoadRequest* aRequest) {
+ if (aRequest->IsPreload()) {
+ return aRequest->IsModuleRequest()
+ ? nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD
+ : nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD;
+ }
+
+ return aRequest->IsModuleRequest() ? nsIContentPolicy::TYPE_INTERNAL_MODULE
+ : nsIContentPolicy::TYPE_INTERNAL_SCRIPT;
+}
+
+nsresult ScriptLoader::CheckContentPolicy(Document* aDocument,
+ nsISupports* aContext,
+ const nsAString& aType,
+ ScriptLoadRequest* aRequest) {
+ nsContentPolicyType contentPolicyType =
+ ScriptLoadRequestToContentPolicyType(aRequest);
+
+ nsCOMPtr<nsINode> requestingNode = do_QueryInterface(aContext);
+ nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
+ aDocument->NodePrincipal(), // loading principal
+ aDocument->NodePrincipal(), // triggering principal
+ requestingNode, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
+ contentPolicyType);
+
+ // snapshot the nonce at load start time for performing CSP checks
+ if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_SCRIPT ||
+ contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_MODULE) {
+ nsCOMPtr<nsINode> node = do_QueryInterface(aContext);
+ if (node) {
+ nsString* cspNonce =
+ static_cast<nsString*>(node->GetProperty(nsGkAtoms::nonce));
+ if (cspNonce) {
+ secCheckLoadInfo->SetCspNonce(*cspNonce);
+ }
+ }
+ }
+
+ int16_t shouldLoad = nsIContentPolicy::ACCEPT;
+ nsresult rv = NS_CheckContentLoadPolicy(
+ aRequest->mURI, secCheckLoadInfo, NS_LossyConvertUTF16toASCII(aType),
+ &shouldLoad, nsContentUtils::GetContentPolicy());
+ if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
+ if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) {
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+ return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
+ }
+
+ return NS_OK;
+}
+
+/* static */
+bool ScriptLoader::IsAboutPageLoadingChromeURI(ScriptLoadRequest* aRequest,
+ Document* aDocument) {
+ // if the uri to be loaded is not of scheme chrome:, there is nothing to do.
+ if (!aRequest->mURI->SchemeIs("chrome")) {
+ return false;
+ }
+
+ // we can either get here with a regular contentPrincipal or with a
+ // NullPrincipal in case we are showing an error page in a sandboxed iframe.
+ // In either case if the about: page is linkable from content, there is
+ // nothing to do.
+ uint32_t aboutModuleFlags = 0;
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal = aRequest->TriggeringPrincipal();
+ if (triggeringPrincipal->GetIsContentPrincipal()) {
+ if (!triggeringPrincipal->SchemeIs("about")) {
+ return false;
+ }
+ rv = triggeringPrincipal->GetAboutModuleFlags(&aboutModuleFlags);
+ NS_ENSURE_SUCCESS(rv, false);
+ } else if (triggeringPrincipal->GetIsNullPrincipal()) {
+ nsCOMPtr<nsIURI> docURI = aDocument->GetDocumentURI();
+ if (!docURI->SchemeIs("about")) {
+ return false;
+ }
+
+ nsCOMPtr<nsIAboutModule> aboutModule;
+ rv = NS_GetAboutModule(docURI, getter_AddRefs(aboutModule));
+ if (NS_FAILED(rv) || !aboutModule) {
+ return false;
+ }
+ rv = aboutModule->GetURIFlags(docURI, &aboutModuleFlags);
+ NS_ENSURE_SUCCESS(rv, false);
+ } else {
+ return false;
+ }
+
+ if (aboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) {
+ return false;
+ }
+
+ // seems like an about page wants to load a chrome URI.
+ return true;
+}
+
+bool ScriptLoader::ModuleMapContainsURL(nsIURI* aURL) const {
+ // Returns whether we have fetched, or are currently fetching, a module script
+ // for a URL.
+ return mFetchingModules.Contains(aURL) || mFetchedModules.Contains(aURL);
+}
+
+bool ScriptLoader::IsFetchingModule(ModuleLoadRequest* aRequest) const {
+ bool fetching = mFetchingModules.Contains(aRequest->mURI);
+ MOZ_ASSERT_IF(fetching, !mFetchedModules.Contains(aRequest->mURI));
+ return fetching;
+}
+
+void ScriptLoader::SetModuleFetchStarted(ModuleLoadRequest* aRequest) {
+ // Update the module map to indicate that a module is currently being fetched.
+
+ MOZ_ASSERT(aRequest->IsLoading());
+ MOZ_ASSERT(!ModuleMapContainsURL(aRequest->mURI));
+ mFetchingModules.Put(aRequest->mURI,
+ RefPtr<GenericNonExclusivePromise::Private>{});
+}
+
+void ScriptLoader::SetModuleFetchFinishedAndResumeWaitingRequests(
+ ModuleLoadRequest* aRequest, nsresult aResult) {
+ // Update module map with the result of fetching a single module script.
+ //
+ // If any requests for the same URL are waiting on this one to complete, they
+ // will have ModuleLoaded or LoadFailed on them when the promise is
+ // resolved/rejected. This is set up in StartLoad.
+
+ LOG(
+ ("ScriptLoadRequest (%p): Module fetch finished (script == %p, result == "
+ "%u)",
+ aRequest, aRequest->mModuleScript.get(), unsigned(aResult)));
+
+ RefPtr<GenericNonExclusivePromise::Private> promise;
+ MOZ_ALWAYS_TRUE(
+ mFetchingModules.Remove(aRequest->mURI, getter_AddRefs(promise)));
+
+ RefPtr<ModuleScript> moduleScript(aRequest->mModuleScript);
+ MOZ_ASSERT(NS_FAILED(aResult) == !moduleScript);
+
+ mFetchedModules.Put(aRequest->mURI, RefPtr{moduleScript});
+
+ if (promise) {
+ if (moduleScript) {
+ LOG(("ScriptLoadRequest (%p): resolving %p", aRequest, promise.get()));
+ promise->Resolve(true, __func__);
+ } else {
+ LOG(("ScriptLoadRequest (%p): rejecting %p", aRequest, promise.get()));
+ promise->Reject(aResult, __func__);
+ }
+ }
+}
+
+RefPtr<GenericNonExclusivePromise> ScriptLoader::WaitForModuleFetch(
+ nsIURI* aURL) {
+ MOZ_ASSERT(ModuleMapContainsURL(aURL));
+
+ if (auto entry = mFetchingModules.Lookup(aURL)) {
+ if (!entry.Data()) {
+ entry.Data() = new GenericNonExclusivePromise::Private(__func__);
+ }
+ return entry.Data();
+ }
+
+ RefPtr<ModuleScript> ms;
+ MOZ_ALWAYS_TRUE(mFetchedModules.Get(aURL, getter_AddRefs(ms)));
+ if (!ms) {
+ return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ }
+
+ return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
+}
+
+ModuleScript* ScriptLoader::GetFetchedModule(nsIURI* aURL) const {
+ if (LOG_ENABLED()) {
+ nsAutoCString url;
+ aURL->GetAsciiSpec(url);
+ LOG(("GetFetchedModule %s", url.get()));
+ }
+
+ bool found;
+ ModuleScript* ms = mFetchedModules.GetWeak(aURL, &found);
+ MOZ_ASSERT(found);
+ return ms;
+}
+
+nsresult ScriptLoader::ProcessFetchedModuleSource(ModuleLoadRequest* aRequest) {
+ MOZ_ASSERT(!aRequest->mModuleScript);
+
+ nsresult rv = CreateModuleScript(aRequest);
+ MOZ_ASSERT(NS_FAILED(rv) == !aRequest->mModuleScript);
+
+ aRequest->ClearScriptSource();
+
+ if (NS_FAILED(rv)) {
+ aRequest->LoadFailed();
+ return rv;
+ }
+
+ if (!aRequest->mIsInline) {
+ SetModuleFetchFinishedAndResumeWaitingRequests(aRequest, rv);
+ }
+
+ if (!aRequest->mModuleScript->HasParseError()) {
+ StartFetchingModuleDependencies(aRequest);
+ }
+
+ return NS_OK;
+}
+
+static nsresult ResolveRequestedModules(ModuleLoadRequest* aRequest,
+ nsCOMArray<nsIURI>* aUrlsOut);
+
+nsresult ScriptLoader::CreateModuleScript(ModuleLoadRequest* aRequest) {
+ MOZ_ASSERT(!aRequest->mModuleScript);
+ MOZ_ASSERT(aRequest->mBaseURL);
+
+ LOG(("ScriptLoadRequest (%p): Create module script", aRequest));
+
+ nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
+ if (!globalObject) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
+ if (!context) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoMicroTask mt;
+
+ AutoAllowLegacyScriptExecution exemption;
+
+ AutoEntryScript aes(globalObject, "CompileModule", true);
+
+ bool oldProcessingScriptTag = context->GetProcessingScriptTag();
+ context->SetProcessingScriptTag(true);
+
+ nsresult rv;
+ {
+ JSContext* cx = aes.cx();
+ JS::Rooted<JSObject*> module(cx);
+
+ if (aRequest->mWasCompiledOMT) {
+ module = JS::FinishOffThreadModule(cx, aRequest->mOffThreadToken);
+ aRequest->mOffThreadToken = nullptr;
+ rv = module ? NS_OK : NS_ERROR_FAILURE;
+ } else {
+ JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
+
+ JS::CompileOptions options(cx);
+ rv = FillCompileOptionsForRequest(aes, aRequest, global, &options);
+
+ if (NS_SUCCEEDED(rv)) {
+ MaybeSourceText maybeSource;
+ rv = GetScriptSource(cx, aRequest, &maybeSource);
+ if (NS_SUCCEEDED(rv)) {
+ rv = maybeSource.constructed<SourceText<char16_t>>()
+ ? nsJSUtils::CompileModule(
+ cx, maybeSource.ref<SourceText<char16_t>>(), global,
+ options, &module)
+ : nsJSUtils::CompileModule(
+ cx, maybeSource.ref<SourceText<Utf8Unit>>(), global,
+ options, &module);
+ }
+ }
+ }
+
+ MOZ_ASSERT(NS_SUCCEEDED(rv) == (module != nullptr));
+
+ RefPtr<ModuleScript> moduleScript =
+ new ModuleScript(aRequest->mFetchOptions, aRequest->mBaseURL);
+ aRequest->mModuleScript = moduleScript;
+
+ if (!module) {
+ LOG(("ScriptLoadRequest (%p): compilation failed (%d)", aRequest,
+ unsigned(rv)));
+
+ MOZ_ASSERT(aes.HasException());
+ JS::Rooted<JS::Value> error(cx);
+ if (!aes.StealException(&error)) {
+ aRequest->mModuleScript = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ moduleScript->SetParseError(error);
+ aRequest->ModuleErrored();
+ return NS_OK;
+ }
+
+ moduleScript->SetModuleRecord(module);
+
+ // Validate requested modules and treat failure to resolve module specifiers
+ // the same as a parse error.
+ rv = ResolveRequestedModules(aRequest, nullptr);
+ if (NS_FAILED(rv)) {
+ aRequest->ModuleErrored();
+ return NS_OK;
+ }
+ }
+
+ context->SetProcessingScriptTag(oldProcessingScriptTag);
+
+ LOG(("ScriptLoadRequest (%p): module script == %p", aRequest,
+ aRequest->mModuleScript.get()));
+
+ return rv;
+}
+
+static nsresult HandleResolveFailure(JSContext* aCx, LoadedScript* aScript,
+ const nsAString& aSpecifier,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber,
+ JS::MutableHandle<JS::Value> errorOut) {
+ JS::Rooted<JSString*> filename(aCx);
+ if (aScript) {
+ nsAutoCString url;
+ aScript->BaseURL()->GetAsciiSpec(url);
+ filename = JS_NewStringCopyZ(aCx, url.get());
+ } else {
+ filename = JS_NewStringCopyZ(aCx, "(unknown)");
+ }
+
+ if (!filename) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ AutoTArray<nsString, 1> errorParams;
+ errorParams.AppendElement(aSpecifier);
+
+ nsAutoString errorText;
+ nsresult rv = nsContentUtils::FormatLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES, "ModuleResolveFailure", errorParams,
+ errorText);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ JS::Rooted<JSString*> string(aCx, JS_NewUCStringCopyZ(aCx, errorText.get()));
+ if (!string) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (!JS::CreateError(aCx, JSEXN_TYPEERR, nullptr, filename, aLineNumber,
+ aColumnNumber, nullptr, string, errorOut)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+static already_AddRefed<nsIURI> ResolveModuleSpecifier(
+ ScriptLoader* aLoader, LoadedScript* aScript, const nsAString& aSpecifier) {
+ // The following module specifiers are allowed by the spec:
+ // - a valid absolute URL
+ // - a valid relative URL that starts with "/", "./" or "../"
+ //
+ // Bareword module specifiers are currently disallowed as these may be given
+ // special meanings in the future.
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aSpecifier);
+ if (NS_SUCCEEDED(rv)) {
+ return uri.forget();
+ }
+
+ if (rv != NS_ERROR_MALFORMED_URI) {
+ return nullptr;
+ }
+
+ if (!StringBeginsWith(aSpecifier, u"/"_ns) &&
+ !StringBeginsWith(aSpecifier, u"./"_ns) &&
+ !StringBeginsWith(aSpecifier, u"../"_ns)) {
+ return nullptr;
+ }
+
+ // Get the document's base URL if we don't have a referencing script here.
+ nsCOMPtr<nsIURI> baseURL;
+ if (aScript) {
+ baseURL = aScript->BaseURL();
+ } else {
+ baseURL = aLoader->GetDocument()->GetDocBaseURI();
+ }
+
+ rv = NS_NewURI(getter_AddRefs(uri), aSpecifier, nullptr, baseURL);
+ if (NS_SUCCEEDED(rv)) {
+ return uri.forget();
+ }
+
+ return nullptr;
+}
+
+static nsresult ResolveRequestedModules(ModuleLoadRequest* aRequest,
+ nsCOMArray<nsIURI>* aUrlsOut) {
+ ModuleScript* ms = aRequest->mModuleScript;
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(ms->ModuleRecord())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> moduleRecord(cx, ms->ModuleRecord());
+ JS::Rooted<JSObject*> requestedModules(cx);
+ requestedModules = JS::GetRequestedModules(cx, moduleRecord);
+ MOZ_ASSERT(requestedModules);
+
+ uint32_t length;
+ if (!JS::GetArrayLength(cx, requestedModules, &length)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::Rooted<JS::Value> element(cx);
+ for (uint32_t i = 0; i < length; i++) {
+ if (!JS_GetElement(cx, requestedModules, i, &element)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::Rooted<JSString*> str(cx, JS::GetRequestedModuleSpecifier(cx, element));
+ MOZ_ASSERT(str);
+
+ nsAutoJSString specifier;
+ if (!specifier.init(cx, str)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Let url be the result of resolving a module specifier given module script
+ // and requested.
+ nsCOMPtr<nsIURI> uri =
+ ResolveModuleSpecifier(aRequest->mLoader, ms, specifier);
+ if (!uri) {
+ uint32_t lineNumber = 0;
+ uint32_t columnNumber = 0;
+ JS::GetRequestedModuleSourcePos(cx, element, &lineNumber, &columnNumber);
+
+ JS::Rooted<JS::Value> error(cx);
+ nsresult rv = HandleResolveFailure(cx, ms, specifier, lineNumber,
+ columnNumber, &error);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ms->SetParseError(error);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aUrlsOut) {
+ aUrlsOut->AppendElement(uri.forget());
+ }
+ }
+
+ return NS_OK;
+}
+
+void ScriptLoader::StartFetchingModuleDependencies(
+ ModuleLoadRequest* aRequest) {
+ LOG(("ScriptLoadRequest (%p): Start fetching module dependencies", aRequest));
+
+ MOZ_ASSERT(aRequest->mModuleScript);
+ MOZ_ASSERT(!aRequest->mModuleScript->HasParseError());
+ MOZ_ASSERT(!aRequest->IsReadyToRun());
+
+ auto visitedSet = aRequest->mVisitedSet;
+ MOZ_ASSERT(visitedSet->Contains(aRequest->mURI));
+
+ aRequest->mProgress = ModuleLoadRequest::Progress::eFetchingImports;
+
+ nsCOMArray<nsIURI> urls;
+ nsresult rv = ResolveRequestedModules(aRequest, &urls);
+ if (NS_FAILED(rv)) {
+ aRequest->ModuleErrored();
+ return;
+ }
+
+ // Remove already visited URLs from the list. Put unvisited URLs into the
+ // visited set.
+ int32_t i = 0;
+ while (i < urls.Count()) {
+ nsIURI* url = urls[i];
+ if (visitedSet->Contains(url)) {
+ urls.RemoveObjectAt(i);
+ } else {
+ visitedSet->PutEntry(url);
+ i++;
+ }
+ }
+
+ if (urls.Count() == 0) {
+ // There are no descendants to load so this request is ready.
+ aRequest->DependenciesLoaded();
+ return;
+ }
+
+ // For each url in urls, fetch a module script tree given url, module script's
+ // CORS setting, and module script's settings object.
+ nsTArray<RefPtr<GenericPromise>> importsReady;
+ for (auto url : urls) {
+ RefPtr<GenericPromise> childReady =
+ StartFetchingModuleAndDependencies(aRequest, url);
+ importsReady.AppendElement(childReady);
+ }
+
+ // Wait for all imports to become ready.
+ RefPtr<GenericPromise::AllPromiseType> allReady =
+ GenericPromise::All(GetMainThreadSerialEventTarget(), importsReady);
+ allReady->Then(GetMainThreadSerialEventTarget(), __func__, aRequest,
+ &ModuleLoadRequest::DependenciesLoaded,
+ &ModuleLoadRequest::ModuleErrored);
+}
+
+RefPtr<GenericPromise> ScriptLoader::StartFetchingModuleAndDependencies(
+ ModuleLoadRequest* aParent, nsIURI* aURI) {
+ MOZ_ASSERT(aURI);
+
+ RefPtr<ModuleLoadRequest> childRequest =
+ ModuleLoadRequest::CreateStaticImport(aURI, aParent);
+
+ aParent->mImports.AppendElement(childRequest);
+
+ if (LOG_ENABLED()) {
+ nsAutoCString url1;
+ aParent->mURI->GetAsciiSpec(url1);
+
+ nsAutoCString url2;
+ aURI->GetAsciiSpec(url2);
+
+ LOG(("ScriptLoadRequest (%p): Start fetching dependency %p", aParent,
+ childRequest.get()));
+ LOG(("StartFetchingModuleAndDependencies \"%s\" -> \"%s\"", url1.get(),
+ url2.get()));
+ }
+
+ RefPtr<GenericPromise> ready = childRequest->mReady.Ensure(__func__);
+
+ nsresult rv = StartLoad(childRequest);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(!childRequest->mModuleScript);
+ LOG(("ScriptLoadRequest (%p): rejecting %p", aParent,
+ &childRequest->mReady));
+ childRequest->mReady.Reject(rv, __func__);
+ return ready;
+ }
+
+ return ready;
+}
+
+static ScriptLoader* GetCurrentScriptLoader(JSContext* aCx) {
+ auto reportError = mozilla::MakeScopeExit([aCx]() {
+ JS_ReportErrorASCII(aCx, "No ScriptLoader found for the current context");
+ });
+
+ JSObject* object = JS::CurrentGlobalOrNull(aCx);
+ if (!object) {
+ return nullptr;
+ }
+
+ nsIGlobalObject* global = xpc::NativeGlobal(object);
+ if (!global) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global);
+ nsGlobalWindowInner* innerWindow = nsGlobalWindowInner::Cast(win);
+ if (!innerWindow) {
+ return nullptr;
+ }
+
+ Document* document = innerWindow->GetDocument();
+ if (!document) {
+ return nullptr;
+ }
+
+ ScriptLoader* loader = document->ScriptLoader();
+ if (!loader) {
+ return nullptr;
+ }
+
+ reportError.release();
+ return loader;
+}
+
+static LoadedScript* GetLoadedScriptOrNull(
+ JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate) {
+ if (aReferencingPrivate.isUndefined()) {
+ return nullptr;
+ }
+
+ auto script = static_cast<LoadedScript*>(aReferencingPrivate.toPrivate());
+ if (script->IsEventScript()) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT_IF(
+ script->IsModuleScript(),
+ JS::GetModulePrivate(script->AsModuleScript()->ModuleRecord()) ==
+ aReferencingPrivate);
+
+ return script;
+}
+
+// 8.1.3.8.1 HostResolveImportedModule(referencingModule, specifier)
+JSObject* HostResolveImportedModule(JSContext* aCx,
+ JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSString*> aSpecifier) {
+ JS::Rooted<JSObject*> module(aCx);
+ ScriptLoader::ResolveImportedModule(aCx, aReferencingPrivate, aSpecifier,
+ &module);
+ return module;
+}
+
+/* static */
+void ScriptLoader::ResolveImportedModule(
+ JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSString*> aSpecifier, JS::MutableHandle<JSObject*> aModuleOut) {
+ MOZ_ASSERT(!aModuleOut);
+
+ RefPtr<LoadedScript> script(GetLoadedScriptOrNull(aCx, aReferencingPrivate));
+
+ // Let url be the result of resolving a module specifier given referencing
+ // module script and specifier.
+ nsAutoJSString string;
+ if (!string.init(aCx, aSpecifier)) {
+ return;
+ }
+
+ RefPtr<ScriptLoader> loader = GetCurrentScriptLoader(aCx);
+ if (!loader) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri = ResolveModuleSpecifier(loader, script, string);
+
+ // This cannot fail because resolving a module specifier must have been
+ // previously successful with these same two arguments.
+ MOZ_ASSERT(uri, "Failed to resolve previously-resolved module specifier");
+
+ // Let resolved module script be moduleMap[url]. (This entry must exist for us
+ // to have gotten to this point.)
+ ModuleScript* ms = loader->GetFetchedModule(uri);
+ MOZ_ASSERT(ms, "Resolved module not found in module map");
+ MOZ_ASSERT(!ms->HasParseError());
+ MOZ_ASSERT(ms->ModuleRecord());
+
+ aModuleOut.set(ms->ModuleRecord());
+}
+
+bool HostPopulateImportMeta(JSContext* aCx,
+ JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSObject*> aMetaObject) {
+ RefPtr<ModuleScript> script =
+ static_cast<ModuleScript*>(aReferencingPrivate.toPrivate());
+ MOZ_ASSERT(script->IsModuleScript());
+ MOZ_ASSERT(JS::GetModulePrivate(script->ModuleRecord()) ==
+ aReferencingPrivate);
+
+ nsAutoCString url;
+ MOZ_DIAGNOSTIC_ASSERT(script->BaseURL());
+ MOZ_ALWAYS_SUCCEEDS(script->BaseURL()->GetAsciiSpec(url));
+
+ JS::Rooted<JSString*> urlString(aCx, JS_NewStringCopyZ(aCx, url.get()));
+ if (!urlString) {
+ JS_ReportOutOfMemory(aCx);
+ return false;
+ }
+
+ return JS_DefineProperty(aCx, aMetaObject, "url", urlString,
+ JSPROP_ENUMERATE);
+}
+
+bool HostImportModuleDynamically(JSContext* aCx,
+ JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSString*> aSpecifier,
+ JS::Handle<JSObject*> aPromise) {
+ RefPtr<LoadedScript> script(GetLoadedScriptOrNull(aCx, aReferencingPrivate));
+
+ // Attempt to resolve the module specifier.
+ nsAutoJSString specifier;
+ if (!specifier.init(aCx, aSpecifier)) {
+ return false;
+ }
+
+ RefPtr<ScriptLoader> loader = GetCurrentScriptLoader(aCx);
+ if (!loader) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> uri = ResolveModuleSpecifier(loader, script, specifier);
+ if (!uri) {
+ JS::Rooted<JS::Value> error(aCx);
+ nsresult rv = HandleResolveFailure(aCx, script, specifier, 0, 0, &error);
+ if (NS_FAILED(rv)) {
+ JS_ReportOutOfMemory(aCx);
+ return false;
+ }
+
+ JS_SetPendingException(aCx, error);
+ return false;
+ }
+
+ // Create a new top-level load request.
+ ScriptFetchOptions* options;
+ nsIURI* baseURL;
+ if (script) {
+ options = script->GetFetchOptions();
+ baseURL = script->BaseURL();
+ } else {
+ // We don't have a referencing script so fall back on using
+ // options from the document. This can happen when the user
+ // triggers an inline event handler, as there is no active script
+ // there.
+ Document* document = loader->GetDocument();
+ options = new ScriptFetchOptions(mozilla::CORS_NONE,
+ document->GetReferrerPolicy(), nullptr,
+ document->NodePrincipal());
+ baseURL = document->GetDocBaseURI();
+ }
+
+ RefPtr<ModuleLoadRequest> request = ModuleLoadRequest::CreateDynamicImport(
+ uri, options, baseURL, loader, aReferencingPrivate, aSpecifier, aPromise);
+
+ loader->StartDynamicImport(request);
+ return true;
+}
+
+void ScriptLoader::StartDynamicImport(ModuleLoadRequest* aRequest) {
+ LOG(("ScriptLoadRequest (%p): Start dynamic import", aRequest));
+
+ mDynamicImportRequests.AppendElement(aRequest);
+
+ nsresult rv = StartLoad(aRequest);
+ if (NS_FAILED(rv)) {
+ FinishDynamicImportAndReject(aRequest, rv);
+ }
+}
+
+void ScriptLoader::FinishDynamicImportAndReject(ModuleLoadRequest* aRequest,
+ nsresult aResult) {
+ AutoJSAPI jsapi;
+ MOZ_ASSERT(NS_FAILED(aResult));
+ MOZ_ALWAYS_TRUE(jsapi.Init(aRequest->mDynamicPromise));
+ if (!JS::ContextOptionsRef(jsapi.cx()).topLevelAwait()) {
+ // This is used so that Top Level Await functionality can be turned off
+ // entirely. It will be removed in bug#1676612.
+ FinishDynamicImport_NoTLA(jsapi.cx(), aRequest, aResult);
+ } else {
+ // Path for when Top Level Await is enabled.
+ FinishDynamicImport(jsapi.cx(), aRequest, aResult, nullptr);
+ }
+}
+
+// This is used so that Top Level Await functionality can be turned off
+// entirely. It will be removed in bug#1676612.
+void ScriptLoader::FinishDynamicImport_NoTLA(JSContext* aCx,
+ ModuleLoadRequest* aRequest,
+ nsresult aResult) {
+ LOG(("ScriptLoadRequest (%p): Finish dynamic import %x %d", aRequest,
+ unsigned(aResult), JS_IsExceptionPending(aCx)));
+
+ // Complete the dynamic import, report failures indicated by aResult or as a
+ // pending exception on the context.
+
+ JS::DynamicImportStatus status =
+ (NS_FAILED(aResult) || JS_IsExceptionPending(aCx))
+ ? JS::DynamicImportStatus::Failed
+ : JS::DynamicImportStatus::Ok;
+
+ if (NS_FAILED(aResult) &&
+ aResult != NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE) {
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+ JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr,
+ JSMSG_DYNAMIC_IMPORT_FAILED);
+ }
+
+ JS::Rooted<JS::Value> referencingScript(aCx,
+ aRequest->mDynamicReferencingPrivate);
+ JS::Rooted<JSString*> specifier(aCx, aRequest->mDynamicSpecifier);
+ JS::Rooted<JSObject*> promise(aCx, aRequest->mDynamicPromise);
+
+ JS::FinishDynamicModuleImport_NoTLA(aCx, status, referencingScript, specifier,
+ promise);
+
+ // FinishDynamicModuleImport clears any pending exception.
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+
+ aRequest->ClearDynamicImport();
+}
+
+void ScriptLoader::FinishDynamicImport(
+ JSContext* aCx, ModuleLoadRequest* aRequest, nsresult aResult,
+ JS::Handle<JSObject*> aEvaluationPromise) {
+ // If aResult is a failed result, we don't have an EvaluationPromise. If it
+ // succeeded, evaluationPromise may still be null, but in this case it will
+ // be handled by rejecting the dynamic module import promise in the JSAPI.
+ MOZ_ASSERT_IF(NS_FAILED(aResult), !aEvaluationPromise);
+ LOG(("ScriptLoadRequest (%p): Finish dynamic import %x %d", aRequest,
+ unsigned(aResult), JS_IsExceptionPending(aCx)));
+
+ // Complete the dynamic import, report failures indicated by aResult or as a
+ // pending exception on the context.
+
+ if (NS_FAILED(aResult) &&
+ aResult != NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE) {
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+ JS_ReportErrorNumberUC(aCx, js::GetErrorMessage, nullptr,
+ JSMSG_DYNAMIC_IMPORT_FAILED);
+ }
+
+ JS::Rooted<JS::Value> referencingScript(aCx,
+ aRequest->mDynamicReferencingPrivate);
+ JS::Rooted<JSString*> specifier(aCx, aRequest->mDynamicSpecifier);
+ JS::Rooted<JSObject*> promise(aCx, aRequest->mDynamicPromise);
+
+ JS::FinishDynamicModuleImport(aCx, aEvaluationPromise, referencingScript,
+ specifier, promise);
+
+ // FinishDynamicModuleImport clears any pending exception.
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+
+ aRequest->ClearDynamicImport();
+}
+
+static void DynamicImportPrefChangedCallback(const char* aPrefName,
+ void* aClosure) {
+ bool enabled = Preferences::GetBool(aPrefName);
+ JS::ModuleDynamicImportHook hook =
+ enabled ? HostImportModuleDynamically : nullptr;
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSRuntime* rt = JS_GetRuntime(jsapi.cx());
+ JS::SetModuleDynamicImportHook(rt, hook);
+}
+
+void ScriptLoader::EnsureModuleHooksInitialized() {
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSRuntime* rt = JS_GetRuntime(jsapi.cx());
+ if (JS::GetModuleResolveHook(rt)) {
+ return;
+ }
+
+ JS::SetModuleResolveHook(rt, HostResolveImportedModule);
+ JS::SetModuleMetadataHook(rt, HostPopulateImportMeta);
+ JS::SetScriptPrivateReferenceHooks(rt, HostAddRefTopLevelScript,
+ HostReleaseTopLevelScript);
+
+ Preferences::RegisterCallbackAndCall(DynamicImportPrefChangedCallback,
+ "javascript.options.dynamicImport",
+ (void*)nullptr);
+}
+
+void ScriptLoader::CheckModuleDependenciesLoaded(ModuleLoadRequest* aRequest) {
+ LOG(("ScriptLoadRequest (%p): Check dependencies loaded", aRequest));
+
+ RefPtr<ModuleScript> moduleScript = aRequest->mModuleScript;
+ if (!moduleScript || moduleScript->HasParseError()) {
+ return;
+ }
+
+ for (auto childRequest : aRequest->mImports) {
+ ModuleScript* childScript = childRequest->mModuleScript;
+ if (!childScript) {
+ aRequest->mModuleScript = nullptr;
+ LOG(("ScriptLoadRequest (%p): %p failed (load error)", aRequest,
+ childRequest.get()));
+ return;
+ }
+ }
+
+ LOG(("ScriptLoadRequest (%p): all ok", aRequest));
+}
+
+class ScriptRequestProcessor : public Runnable {
+ private:
+ RefPtr<ScriptLoader> mLoader;
+ RefPtr<ScriptLoadRequest> mRequest;
+
+ public:
+ ScriptRequestProcessor(ScriptLoader* aLoader, ScriptLoadRequest* aRequest)
+ : Runnable("dom::ScriptRequestProcessor"),
+ mLoader(aLoader),
+ mRequest(aRequest) {}
+ NS_IMETHOD Run() override {
+ if (mRequest->IsModuleRequest() &&
+ mRequest->AsModuleRequest()->IsDynamicImport()) {
+ mLoader->ProcessDynamicImport(mRequest->AsModuleRequest());
+ return NS_OK;
+ }
+
+ return mLoader->ProcessRequest(mRequest);
+ }
+};
+
+void ScriptLoader::RunScriptWhenSafe(ScriptLoadRequest* aRequest) {
+ auto runnable = new ScriptRequestProcessor(this, aRequest);
+ nsContentUtils::AddScriptRunner(runnable);
+}
+
+void ScriptLoader::ProcessLoadedModuleTree(ModuleLoadRequest* aRequest) {
+ MOZ_ASSERT(aRequest->IsReadyToRun());
+
+ if (aRequest->IsTopLevel()) {
+ if (aRequest->IsDynamicImport()) {
+ MOZ_ASSERT(aRequest->isInList());
+ RefPtr<ScriptLoadRequest> req = mDynamicImportRequests.Steal(aRequest);
+ RunScriptWhenSafe(req);
+ } else if (aRequest->mIsInline &&
+ aRequest->GetParserCreated() == NOT_FROM_PARSER) {
+ MOZ_ASSERT(!aRequest->isInList());
+ RunScriptWhenSafe(aRequest);
+ } else {
+ MaybeMoveToLoadedList(aRequest);
+ ProcessPendingRequests();
+ }
+ }
+
+ aRequest->MaybeUnblockOnload();
+}
+
+JS::Value ScriptLoader::FindFirstParseError(ModuleLoadRequest* aRequest) {
+ MOZ_ASSERT(aRequest);
+
+ ModuleScript* moduleScript = aRequest->mModuleScript;
+ MOZ_ASSERT(moduleScript);
+
+ if (moduleScript->HasParseError()) {
+ return moduleScript->ParseError();
+ }
+
+ for (ModuleLoadRequest* childRequest : aRequest->mImports) {
+ JS::Value error = FindFirstParseError(childRequest);
+ if (!error.isUndefined()) {
+ return error;
+ }
+ }
+
+ return JS::UndefinedValue();
+}
+
+bool ScriptLoader::InstantiateModuleTree(ModuleLoadRequest* aRequest) {
+ // Instantiate a top-level module and record any error.
+
+ MOZ_ASSERT(aRequest);
+ MOZ_ASSERT(aRequest->IsTopLevel());
+
+ LOG(("ScriptLoadRequest (%p): Instantiate module tree", aRequest));
+
+ ModuleScript* moduleScript = aRequest->mModuleScript;
+ MOZ_ASSERT(moduleScript);
+
+ JS::Value parseError = FindFirstParseError(aRequest);
+ if (!parseError.isUndefined()) {
+ moduleScript->SetErrorToRethrow(parseError);
+ LOG(("ScriptLoadRequest (%p): found parse error", aRequest));
+ return true;
+ }
+
+ MOZ_ASSERT(moduleScript->ModuleRecord());
+
+ nsAutoMicroTask mt;
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(moduleScript->ModuleRecord()))) {
+ return false;
+ }
+
+ JS::Rooted<JSObject*> module(jsapi.cx(), moduleScript->ModuleRecord());
+ bool ok = NS_SUCCEEDED(nsJSUtils::ModuleInstantiate(jsapi.cx(), module));
+
+ if (!ok) {
+ LOG(("ScriptLoadRequest (%p): Instantiate failed", aRequest));
+ MOZ_ASSERT(jsapi.HasException());
+ JS::RootedValue exception(jsapi.cx());
+ if (!jsapi.StealException(&exception)) {
+ return false;
+ }
+ MOZ_ASSERT(!exception.isUndefined());
+ moduleScript->SetErrorToRethrow(exception);
+ }
+
+ return true;
+}
+
+nsresult ScriptLoader::InitDebuggerDataForModuleTree(
+ JSContext* aCx, ModuleLoadRequest* aRequest) {
+ // JS scripts can be associated with a DOM element for use by the debugger,
+ // but preloading can cause scripts to be compiled before DOM script element
+ // nodes have been created. This method ensures that this association takes
+ // place before the first time a module script is run.
+
+ MOZ_ASSERT(aRequest);
+
+ ModuleScript* moduleScript = aRequest->mModuleScript;
+ if (moduleScript->DebuggerDataInitialized()) {
+ return NS_OK;
+ }
+
+ for (ModuleLoadRequest* childRequest : aRequest->mImports) {
+ nsresult rv = InitDebuggerDataForModuleTree(aCx, childRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ JS::Rooted<JSObject*> module(aCx, moduleScript->ModuleRecord());
+ MOZ_ASSERT(module);
+
+ // The script is now ready to be exposed to the debugger.
+ JS::Rooted<JSScript*> script(aCx, JS::GetModuleScript(module));
+ JS::ExposeScriptToDebugger(aCx, script);
+
+ moduleScript->SetDebuggerDataInitialized();
+ return NS_OK;
+}
+
+nsresult ScriptLoader::RestartLoad(ScriptLoadRequest* aRequest) {
+ MOZ_ASSERT(aRequest->IsBytecode());
+ aRequest->mScriptBytecode.clearAndFree();
+ TRACE_FOR_TEST(aRequest->GetScriptElement(), "scriptloader_fallback");
+
+ // Notify preload restart so that we can register this preload request again.
+ aRequest->NotifyRestart(mDocument);
+
+ // Start a new channel from which we explicitly request to stream the source
+ // instead of the bytecode.
+ aRequest->mProgress = ScriptLoadRequest::Progress::eLoading_Source;
+ nsresult rv = StartLoad(aRequest);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Close the current channel and this ScriptLoadHandler as we created a new
+ // one for the same request.
+ return NS_BINDING_RETARGETED;
+}
+
+nsresult ScriptLoader::StartLoad(ScriptLoadRequest* aRequest) {
+ MOZ_ASSERT(aRequest->IsLoading());
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NULL_POINTER);
+ aRequest->SetUnknownDataType();
+
+ // If this document is sandboxed without 'allow-scripts', abort.
+ if (mDocument->HasScriptsBlockedBySandbox()) {
+ return NS_OK;
+ }
+
+ if (LOG_ENABLED()) {
+ nsAutoCString url;
+ aRequest->mURI->GetAsciiSpec(url);
+ LOG(("ScriptLoadRequest (%p): Start Load (url = %s)", aRequest, url.get()));
+ }
+
+ if (aRequest->IsModuleRequest()) {
+ // Check whether the module has been fetched or is currently being fetched,
+ // and if so wait for it rather than starting a new fetch.
+ ModuleLoadRequest* request = aRequest->AsModuleRequest();
+ if (ModuleMapContainsURL(request->mURI)) {
+ LOG(("ScriptLoadRequest (%p): Waiting for module fetch", aRequest));
+ WaitForModuleFetch(request->mURI)
+ ->Then(GetMainThreadSerialEventTarget(), __func__, request,
+ &ModuleLoadRequest::ModuleLoaded,
+ &ModuleLoadRequest::LoadFailed);
+ return NS_OK;
+ }
+ }
+
+ nsContentPolicyType contentPolicyType =
+ ScriptLoadRequestToContentPolicyType(aRequest);
+ nsCOMPtr<nsINode> context;
+ if (aRequest->GetScriptElement()) {
+ context = do_QueryInterface(aRequest->GetScriptElement());
+ } else {
+ context = mDocument;
+ }
+
+ nsCOMPtr<nsILoadGroup> loadGroup = mDocument->GetDocumentLoadGroup();
+ nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
+ NS_ENSURE_TRUE(window, NS_ERROR_NULL_POINTER);
+ nsIDocShell* docshell = window->GetDocShell();
+ nsCOMPtr<nsIInterfaceRequestor> prompter(do_QueryInterface(docshell));
+
+ nsSecurityFlags securityFlags;
+ if (aRequest->IsModuleRequest()) {
+ // According to the spec, module scripts have different behaviour to classic
+ // scripts and always use CORS. Only exception: Non linkable about: pages
+ // which load local module scripts.
+ if (IsAboutPageLoadingChromeURI(aRequest, mDocument)) {
+ securityFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
+ } else {
+ securityFlags = nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
+ if (aRequest->CORSMode() == CORS_NONE ||
+ aRequest->CORSMode() == CORS_ANONYMOUS) {
+ securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
+ } else {
+ MOZ_ASSERT(aRequest->CORSMode() == CORS_USE_CREDENTIALS);
+ securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
+ }
+ }
+ } else {
+ securityFlags =
+ aRequest->CORSMode() == CORS_NONE
+ ? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
+ : nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
+ if (aRequest->CORSMode() == CORS_ANONYMOUS) {
+ securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
+ } else if (aRequest->CORSMode() == CORS_USE_CREDENTIALS) {
+ securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
+ }
+ }
+ securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
+
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannelWithTriggeringPrincipal(
+ getter_AddRefs(channel), aRequest->mURI, context,
+ aRequest->TriggeringPrincipal(), securityFlags, contentPolicyType,
+ nullptr, // aPerformanceStorage
+ loadGroup, prompter);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // snapshot the nonce at load start time for performing CSP checks
+ if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_SCRIPT ||
+ contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_MODULE) {
+ if (context) {
+ nsString* cspNonce =
+ static_cast<nsString*>(context->GetProperty(nsGkAtoms::nonce));
+ if (cspNonce) {
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ loadInfo->SetCspNonce(*cspNonce);
+ }
+ }
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
+ if (!globalObject) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // To avoid decoding issues, the build-id is part of the JSBytecodeMimeType
+ // constant.
+ aRequest->mCacheInfo = nullptr;
+ nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(channel));
+ if (cic && StaticPrefs::dom_script_loader_bytecode_cache_enabled() &&
+ // Globals with instrumentation have modified script bytecode and can't
+ // use cached bytecode.
+ !js::GlobalHasInstrumentation(globalObject->GetGlobalJSObject()) &&
+ // Bug 1436400: no bytecode cache support for modules yet.
+ !aRequest->IsModuleRequest()) {
+ if (!aRequest->IsLoadingSource()) {
+ // Inform the HTTP cache that we prefer to have information coming from
+ // the bytecode cache instead of the sources, if such entry is already
+ // registered.
+ LOG(("ScriptLoadRequest (%p): Maybe request bytecode", aRequest));
+ cic->PreferAlternativeDataType(nsContentUtils::JSBytecodeMimeType(),
+ ""_ns, true);
+ } else {
+ // If we are explicitly loading from the sources, such as after a
+ // restarted request, we might still want to save the bytecode after.
+ //
+ // The following tell the cache to look for an alternative data type which
+ // does not exist, such that we can later save the bytecode with a
+ // different alternative data type.
+ LOG(("ScriptLoadRequest (%p): Request saving bytecode later", aRequest));
+ cic->PreferAlternativeDataType(kNullMimeType, ""_ns, true);
+ }
+ }
+
+ LOG(("ScriptLoadRequest (%p): mode=%u tracking=%d", aRequest,
+ unsigned(aRequest->mScriptMode), aRequest->IsTracking()));
+
+ if (aRequest->IsLinkPreloadScript()) {
+ // This is <link rel="preload" as="script"> initiated speculative load,
+ // put it to the group that is not blocked by leaders and doesn't block
+ // follower at the same time. Giving it a much higher priority will make
+ // this request be processed ahead of other Unblocked requests, but with
+ // the same weight as Leaders. This will make us behave similar way for
+ // both http2 and http1.
+ ScriptLoadRequest::PrioritizeAsPreload(channel);
+ ScriptLoadRequest::AddLoadBackgroundFlag(channel);
+ } else if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(channel)) {
+ if (aRequest->mScriptFromHead && aRequest->IsBlockingScript()) {
+ // synchronous head scripts block loading of most other non js/css
+ // content such as images, Leader implicitely disallows tailing
+ cos->AddClassFlags(nsIClassOfService::Leader);
+ } else if (aRequest->IsDeferredScript() &&
+ !StaticPrefs::network_http_tailing_enabled()) {
+ // Bug 1395525 and the !StaticPrefs::network_http_tailing_enabled() bit:
+ // We want to make sure that turing tailing off by the pref makes the
+ // browser behave exactly the same way as before landing the tailing
+ // patch.
+
+ // head/body deferred scripts are blocked by leaders but are not
+ // allowed tailing because they block DOMContentLoaded
+ cos->AddClassFlags(nsIClassOfService::TailForbidden);
+ } else {
+ // other scripts (=body sync or head/body async) are neither blocked
+ // nor prioritized
+ cos->AddClassFlags(nsIClassOfService::Unblocked);
+
+ if (aRequest->IsAsyncScript()) {
+ // async scripts are allowed tailing, since those and only those
+ // don't block DOMContentLoaded; this flag doesn't enforce tailing,
+ // just overweights the Unblocked flag when the channel is found
+ // to be a thrird-party tracker and thus set the Tail flag to engage
+ // tailing.
+ cos->AddClassFlags(nsIClassOfService::TailAllowed);
+ }
+ }
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ if (httpChannel) {
+ // HTTP content negotation has little value in this context.
+ nsAutoCString acceptTypes("*/*");
+ if (nsJSUtils::BinASTEncodingEnabled() &&
+ aRequest->ShouldAcceptBinASTEncoding()) {
+ acceptTypes = APPLICATION_JAVASCRIPT_BINAST ", */*";
+ }
+ rv = httpChannel->SetRequestHeader("Accept"_ns, acceptTypes, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ new ReferrerInfo(aRequest->mReferrer, aRequest->ReferrerPolicy());
+ rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel(
+ do_QueryInterface(httpChannel));
+ if (internalChannel) {
+ rv = internalChannel->SetIntegrityMetadata(
+ aRequest->mIntegrity.GetIntegrityString());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ mozilla::net::PredictorLearn(
+ aRequest->mURI, mDocument->GetDocumentURI(),
+ nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
+ mDocument->NodePrincipal()->OriginAttributesRef());
+
+ // Set the initiator type
+ nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
+ if (timedChannel) {
+ if (aRequest->IsLinkPreloadScript()) {
+ timedChannel->SetInitiatorType(u"link"_ns);
+ } else {
+ timedChannel->SetInitiatorType(u"script"_ns);
+ }
+ }
+
+ UniquePtr<mozilla::dom::SRICheckDataVerifier> sriDataVerifier;
+ if (!aRequest->mIntegrity.IsEmpty()) {
+ nsAutoCString sourceUri;
+ if (mDocument->GetDocumentURI()) {
+ mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
+ }
+ sriDataVerifier = MakeUnique<SRICheckDataVerifier>(aRequest->mIntegrity,
+ sourceUri, mReporter);
+ }
+
+ RefPtr<ScriptLoadHandler> handler =
+ new ScriptLoadHandler(this, aRequest, std::move(sriDataVerifier));
+
+ nsCOMPtr<nsIIncrementalStreamLoader> loader;
+ rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), handler);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ auto key = PreloadHashKey::CreateAsScript(
+ aRequest->mURI, aRequest->CORSMode(), aRequest->mKind);
+ aRequest->NotifyOpen(key, channel, mDocument,
+ aRequest->IsLinkPreloadScript());
+
+ rv = channel->AsyncOpen(loader);
+
+ if (NS_FAILED(rv)) {
+ // Make sure to inform any <link preload> tags about failure to load the
+ // resource.
+ aRequest->NotifyStart(channel);
+ aRequest->NotifyStop(rv);
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aRequest->IsModuleRequest()) {
+ // We successfully started fetching a module so put its URL in the module
+ // map and mark it as fetching.
+ SetModuleFetchStarted(aRequest->AsModuleRequest());
+ LOG(("ScriptLoadRequest (%p): Start fetching module", aRequest));
+ }
+
+ return NS_OK;
+}
+
+bool ScriptLoader::PreloadURIComparator::Equals(const PreloadInfo& aPi,
+ nsIURI* const& aURI) const {
+ bool same;
+ return NS_SUCCEEDED(aPi.mRequest->mURI->Equals(aURI, &same)) && same;
+}
+
+static bool CSPAllowsInlineScript(nsIScriptElement* aElement,
+ Document* aDocument) {
+ nsCOMPtr<nsIContentSecurityPolicy> csp = aDocument->GetCsp();
+ nsresult rv = NS_OK;
+
+ if (!csp) {
+ // no CSP --> allow
+ return true;
+ }
+
+ // query the nonce
+ nsCOMPtr<Element> scriptContent = do_QueryInterface(aElement);
+ nsAutoString nonce;
+ if (scriptContent) {
+ nsString* cspNonce =
+ static_cast<nsString*>(scriptContent->GetProperty(nsGkAtoms::nonce));
+ if (cspNonce) {
+ nonce = *cspNonce;
+ }
+ }
+
+ bool parserCreated =
+ aElement->GetParserCreated() != mozilla::dom::NOT_FROM_PARSER;
+
+ bool allowInlineScript = false;
+ rv = csp->GetAllowsInline(
+ nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE, nonce, parserCreated,
+ scriptContent, nullptr /* nsICSPEventListener */, u""_ns,
+ aElement->GetScriptLineNumber(), aElement->GetScriptColumnNumber(),
+ &allowInlineScript);
+ return NS_SUCCEEDED(rv) && allowInlineScript;
+}
+
+ScriptLoadRequest* ScriptLoader::CreateLoadRequest(
+ ScriptKind aKind, nsIURI* aURI, nsIScriptElement* aElement,
+ nsIPrincipal* aTriggeringPrincipal, CORSMode aCORSMode,
+ const SRIMetadata& aIntegrity, ReferrerPolicy aReferrerPolicy) {
+ nsIURI* referrer = mDocument->GetDocumentURIAsReferrer();
+ nsCOMPtr<Element> domElement = do_QueryInterface(aElement);
+ ScriptFetchOptions* fetchOptions = new ScriptFetchOptions(
+ aCORSMode, aReferrerPolicy, domElement, aTriggeringPrincipal);
+
+ if (aKind == ScriptKind::eClassic) {
+ return new ScriptLoadRequest(aKind, aURI, fetchOptions, aIntegrity,
+ referrer);
+ }
+
+ MOZ_ASSERT(aKind == ScriptKind::eModule);
+ return ModuleLoadRequest::CreateTopLevel(aURI, fetchOptions, aIntegrity,
+ referrer, this);
+}
+
+bool ScriptLoader::ProcessScriptElement(nsIScriptElement* aElement) {
+ // We need a document to evaluate scripts.
+ NS_ENSURE_TRUE(mDocument, false);
+
+ // Check to see if scripts has been turned off.
+ if (!mEnabled || !mDocument->IsScriptEnabled()) {
+ return false;
+ }
+
+ NS_ASSERTION(!aElement->IsMalformed(), "Executing malformed script");
+
+ nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement);
+
+ nsAutoString type;
+ bool hasType = aElement->GetScriptType(type);
+
+ ScriptKind scriptKind = aElement->GetScriptIsModule() ? ScriptKind::eModule
+ : ScriptKind::eClassic;
+
+ // Step 13. Check that the script is not an eventhandler
+ if (IsScriptEventHandler(scriptKind, scriptContent)) {
+ return false;
+ }
+
+ // For classic scripts, check the type attribute to determine language and
+ // version. If type exists, it trumps the deprecated 'language='
+ if (scriptKind == ScriptKind::eClassic) {
+ if (!type.IsEmpty()) {
+ NS_ENSURE_TRUE(nsContentUtils::IsJavascriptMIMEType(type), false);
+ } else if (!hasType) {
+ // no 'type=' element
+ // "language" is a deprecated attribute of HTML, so we check it only for
+ // HTML script elements.
+ if (scriptContent->IsHTMLElement()) {
+ nsAutoString language;
+ scriptContent->AsElement()->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::language, language);
+ if (!language.IsEmpty()) {
+ if (!nsContentUtils::IsJavaScriptLanguage(language)) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+
+ // "In modern user agents that support module scripts, the script element with
+ // the nomodule attribute will be ignored".
+ // "The nomodule attribute must not be specified on module scripts (and will
+ // be ignored if it is)."
+ if (mDocument->ModuleScriptsEnabled() && scriptKind == ScriptKind::eClassic &&
+ scriptContent->IsHTMLElement() &&
+ scriptContent->AsElement()->HasAttr(kNameSpaceID_None,
+ nsGkAtoms::nomodule)) {
+ return false;
+ }
+
+ // Step 15. and later in the HTML5 spec
+ if (aElement->GetScriptExternal()) {
+ return ProcessExternalScript(aElement, scriptKind, type, scriptContent);
+ }
+
+ return ProcessInlineScript(aElement, scriptKind);
+}
+
+bool ScriptLoader::ProcessExternalScript(nsIScriptElement* aElement,
+ ScriptKind aScriptKind,
+ nsAutoString aTypeAttr,
+ nsIContent* aScriptContent) {
+ LOG(("ScriptLoader (%p): Process external script for element %p", this,
+ aElement));
+
+ nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI();
+ if (!scriptURI) {
+ // Asynchronously report the failure to create a URI object
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement,
+ &nsIScriptElement::FireErrorEvent));
+ return false;
+ }
+
+ SRIMetadata sriMetadata;
+ {
+ nsAutoString integrity;
+ aScriptContent->AsElement()->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::integrity, integrity);
+ GetSRIMetadata(integrity, &sriMetadata);
+ }
+
+ RefPtr<ScriptLoadRequest> request =
+ LookupPreloadRequest(aElement, aScriptKind, sriMetadata);
+
+ if (request &&
+ NS_FAILED(CheckContentPolicy(mDocument, aElement, aTypeAttr, request))) {
+ LOG(("ScriptLoader (%p): content policy check failed for preload", this));
+
+ // Probably plans have changed; even though the preload was allowed seems
+ // like the actual load is not; let's cancel the preload request.
+ request->Cancel();
+ AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::RejectedByPolicy);
+ return false;
+ }
+
+ if (request) {
+ // Use the preload request.
+
+ LOG(("ScriptLoadRequest (%p): Using preload request", request.get()));
+
+ // It's possible these attributes changed since we started the preload so
+ // update them here.
+ request->SetScriptMode(aElement->GetScriptDeferred(),
+ aElement->GetScriptAsync(), false);
+
+ AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::Used);
+ } else {
+ // No usable preload found.
+
+ nsCOMPtr<nsIPrincipal> principal =
+ aElement->GetScriptURITriggeringPrincipal();
+ if (!principal) {
+ principal = aScriptContent->NodePrincipal();
+ }
+
+ CORSMode ourCORSMode = aElement->GetCORSMode();
+ ReferrerPolicy referrerPolicy = GetReferrerPolicy(aElement);
+
+ request = CreateLoadRequest(aScriptKind, scriptURI, aElement, principal,
+ ourCORSMode, sriMetadata, referrerPolicy);
+ request->mIsInline = false;
+ request->SetScriptMode(aElement->GetScriptDeferred(),
+ aElement->GetScriptAsync(), false);
+ // keep request->mScriptFromHead to false so we don't treat non preloaded
+ // scripts as blockers for full page load. See bug 792438.
+
+ LOG(("ScriptLoadRequest (%p): Created request for external script",
+ request.get()));
+
+ nsresult rv = StartLoad(request);
+ if (NS_FAILED(rv)) {
+ ReportErrorToConsole(request, rv);
+
+ // Asynchronously report the load failure
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement,
+ &nsIScriptElement::FireErrorEvent);
+ if (mDocument) {
+ mDocument->Dispatch(TaskCategory::Other, runnable.forget());
+ } else {
+ NS_DispatchToCurrentThread(runnable);
+ }
+ return false;
+ }
+ }
+
+ // We should still be in loading stage of script unless we're loading a
+ // module or speculatively off-main-thread parsing a script.
+ NS_ASSERTION(SpeculativeOMTParsingEnabled() || !request->InCompilingStage() ||
+ request->IsModuleRequest(),
+ "Request should not yet be in compiling stage.");
+
+ if (request->IsAsyncScript()) {
+ AddAsyncRequest(request);
+ if (request->IsReadyToRun()) {
+ // The script is available already. Run it ASAP when the event
+ // loop gets a chance to spin.
+
+ // KVKV TODO: Instead of processing immediately, try off-thread-parsing
+ // it and only schedule a pending ProcessRequest if that fails.
+ ProcessPendingRequestsAsync();
+ }
+ return false;
+ }
+ if (!aElement->GetParserCreated()) {
+ // Violate the HTML5 spec in order to make LABjs and the "order" plug-in
+ // for RequireJS work with their Gecko-sniffed code path. See
+ // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html
+ request->mIsNonAsyncScriptInserted = true;
+ mNonAsyncExternalScriptInsertedRequests.AppendElement(request);
+ if (request->IsReadyToRun()) {
+ // The script is available already. Run it ASAP when the event
+ // loop gets a chance to spin.
+ ProcessPendingRequestsAsync();
+ }
+ return false;
+ }
+ // we now have a parser-inserted request that may or may not be still
+ // loading
+ if (request->IsDeferredScript()) {
+ // We don't want to run this yet.
+ // If we come here, the script is a parser-created script and it has
+ // the defer attribute but not the async attribute. Since a
+ // a parser-inserted script is being run, we came here by the parser
+ // running the script, which means the parser is still alive and the
+ // parse is ongoing.
+ NS_ASSERTION(mDocument->GetCurrentContentSink() ||
+ aElement->GetParserCreated() == FROM_PARSER_XSLT,
+ "Non-XSLT Defer script on a document without an active "
+ "parser; bug 592366.");
+ AddDeferRequest(request);
+ return false;
+ }
+
+ if (aElement->GetParserCreated() == FROM_PARSER_XSLT) {
+ // Need to maintain order for XSLT-inserted scripts
+ NS_ASSERTION(!mParserBlockingRequest,
+ "Parser-blocking scripts and XSLT scripts in the same doc!");
+ request->mIsXSLT = true;
+ mXSLTRequests.AppendElement(request);
+ if (request->IsReadyToRun()) {
+ // The script is available already. Run it ASAP when the event
+ // loop gets a chance to spin.
+ ProcessPendingRequestsAsync();
+ }
+ return true;
+ }
+
+ if (request->IsReadyToRun() && ReadyToExecuteParserBlockingScripts()) {
+ // The request has already been loaded and there are no pending style
+ // sheets. If the script comes from the network stream, cheat for
+ // performance reasons and avoid a trip through the event loop.
+ if (aElement->GetParserCreated() == FROM_PARSER_NETWORK) {
+ return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
+ }
+ // Otherwise, we've got a document.written script, make a trip through
+ // the event loop to hide the preload effects from the scripts on the
+ // Web page.
+ NS_ASSERTION(!mParserBlockingRequest,
+ "There can be only one parser-blocking script at a time");
+ NS_ASSERTION(mXSLTRequests.isEmpty(),
+ "Parser-blocking scripts and XSLT scripts in the same doc!");
+ mParserBlockingRequest = request;
+ ProcessPendingRequestsAsync();
+ return true;
+ }
+
+ // The script hasn't loaded yet or there's a style sheet blocking it.
+ // The script will be run when it loads or the style sheet loads.
+ NS_ASSERTION(!mParserBlockingRequest,
+ "There can be only one parser-blocking script at a time");
+ NS_ASSERTION(mXSLTRequests.isEmpty(),
+ "Parser-blocking scripts and XSLT scripts in the same doc!");
+ mParserBlockingRequest = request;
+ return true;
+}
+
+bool ScriptLoader::ProcessInlineScript(nsIScriptElement* aElement,
+ ScriptKind aScriptKind) {
+ // Is this document sandboxed without 'allow-scripts'?
+ if (mDocument->HasScriptsBlockedBySandbox()) {
+ return false;
+ }
+
+ // Does CSP allow this inline script to run?
+ if (!CSPAllowsInlineScript(aElement, mDocument)) {
+ return false;
+ }
+
+ // Inline classic scripts ignore their CORS mode and are always CORS_NONE.
+ CORSMode corsMode = CORS_NONE;
+ if (aScriptKind == ScriptKind::eModule) {
+ corsMode = aElement->GetCORSMode();
+ }
+
+ ReferrerPolicy referrerPolicy = GetReferrerPolicy(aElement);
+ RefPtr<ScriptLoadRequest> request =
+ CreateLoadRequest(aScriptKind, mDocument->GetDocumentURI(), aElement,
+ mDocument->NodePrincipal(), corsMode,
+ SRIMetadata(), // SRI doesn't apply
+ referrerPolicy);
+ request->mIsInline = true;
+ request->mLineNo = aElement->GetScriptLineNumber();
+ request->mProgress = ScriptLoadRequest::Progress::eLoading_Source;
+ request->SetTextSource();
+ TRACE_FOR_TEST_BOOL(request->GetScriptElement(), "scriptloader_load_source");
+ CollectScriptTelemetry(request);
+
+ // Only the 'async' attribute is heeded on an inline module script and
+ // inline classic scripts ignore both these attributes.
+ MOZ_ASSERT(!aElement->GetScriptDeferred());
+ MOZ_ASSERT_IF(!request->IsModuleRequest(), !aElement->GetScriptAsync());
+ request->SetScriptMode(false, aElement->GetScriptAsync(), false);
+
+ LOG(("ScriptLoadRequest (%p): Created request for inline script",
+ request.get()));
+
+ request->mBaseURL = mDocument->GetDocBaseURI();
+
+ if (request->IsModuleRequest()) {
+ ModuleLoadRequest* modReq = request->AsModuleRequest();
+ if (aElement->GetParserCreated() != NOT_FROM_PARSER) {
+ if (aElement->GetScriptAsync()) {
+ AddAsyncRequest(modReq);
+ } else {
+ AddDeferRequest(modReq);
+ }
+ }
+
+ nsresult rv = ProcessFetchedModuleSource(modReq);
+ if (NS_FAILED(rv)) {
+ ReportErrorToConsole(modReq, rv);
+ HandleLoadError(modReq, rv);
+ }
+
+ return false;
+ }
+ request->mProgress = ScriptLoadRequest::Progress::eReady;
+ if (aElement->GetParserCreated() == FROM_PARSER_XSLT &&
+ (!ReadyToExecuteParserBlockingScripts() || !mXSLTRequests.isEmpty())) {
+ // Need to maintain order for XSLT-inserted scripts
+ NS_ASSERTION(!mParserBlockingRequest,
+ "Parser-blocking scripts and XSLT scripts in the same doc!");
+ mXSLTRequests.AppendElement(request);
+ return true;
+ }
+ if (aElement->GetParserCreated() == NOT_FROM_PARSER) {
+ NS_ASSERTION(
+ !nsContentUtils::IsSafeToRunScript(),
+ "A script-inserted script is inserted without an update batch?");
+ RunScriptWhenSafe(request);
+ return false;
+ }
+ if (aElement->GetParserCreated() == FROM_PARSER_NETWORK &&
+ !ReadyToExecuteParserBlockingScripts()) {
+ NS_ASSERTION(!mParserBlockingRequest,
+ "There can be only one parser-blocking script at a time");
+ mParserBlockingRequest = request;
+ NS_ASSERTION(mXSLTRequests.isEmpty(),
+ "Parser-blocking scripts and XSLT scripts in the same doc!");
+ return true;
+ }
+ // We now have a document.written inline script or we have an inline script
+ // from the network but there is no style sheet that is blocking scripts.
+ // Don't check for style sheets blocking scripts in the document.write
+ // case to avoid style sheet network activity affecting when
+ // document.write returns. It's not really necessary to do this if
+ // there's no document.write currently on the call stack. However,
+ // this way matches IE more closely than checking if document.write
+ // is on the call stack.
+ NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
+ "Not safe to run a parser-inserted script?");
+ return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
+}
+
+ScriptLoadRequest* ScriptLoader::LookupPreloadRequest(
+ nsIScriptElement* aElement, ScriptKind aScriptKind,
+ const SRIMetadata& aSRIMetadata) {
+ MOZ_ASSERT(aElement);
+
+ nsTArray<PreloadInfo>::index_type i =
+ mPreloads.IndexOf(aElement->GetScriptURI(), 0, PreloadURIComparator());
+ if (i == nsTArray<PreloadInfo>::NoIndex) {
+ return nullptr;
+ }
+
+ // Found preloaded request. Note that a script-inserted script can steal a
+ // preload!
+ RefPtr<ScriptLoadRequest> request = mPreloads[i].mRequest;
+ request->SetIsLoadRequest(aElement);
+
+ if (request->mWasCompiledOMT && !request->IsModuleRequest()) {
+ request->SetReady();
+ }
+
+ nsString preloadCharset(mPreloads[i].mCharset);
+ mPreloads.RemoveElementAt(i);
+
+ // Double-check that the charset the preload used is the same as the charset
+ // we have now.
+ nsAutoString elementCharset;
+ aElement->GetScriptCharset(elementCharset);
+
+ if (!elementCharset.Equals(preloadCharset) ||
+ aElement->GetCORSMode() != request->CORSMode() ||
+ aScriptKind != request->mKind) {
+ // Drop the preload.
+ request->Cancel();
+ AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::RequestMismatch);
+ return nullptr;
+ }
+
+ if (!aSRIMetadata.CanTrustBeDelegatedTo(request->mIntegrity)) {
+ // Don't cancel link preload requests, we want to deliver onload according
+ // the result of the load, cancellation would unexpectedly lead to error
+ // notification.
+ if (!request->IsLinkPreloadScript()) {
+ request->Cancel();
+ }
+ return nullptr;
+ }
+
+ // Report any errors that we skipped while preloading.
+ ReportPreloadErrorsToConsole(request);
+
+ // This makes sure the pending preload (if exists) for this resource is
+ // properly marked as used and thus not notified in the console as unused.
+ request->NotifyUsage();
+ // A used preload must no longer be found in the Document's hash table. Any
+ // <link preload> tag after the <script> tag will start a new request, that
+ // can be satisfied from a different cache, but not from the preload cache.
+ request->RemoveSelf(mDocument);
+
+ return request;
+}
+
+void ScriptLoader::GetSRIMetadata(const nsAString& aIntegrityAttr,
+ SRIMetadata* aMetadataOut) {
+ MOZ_ASSERT(aMetadataOut->IsEmpty());
+
+ if (aIntegrityAttr.IsEmpty()) {
+ return;
+ }
+
+ MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
+ ("ScriptLoader::GetSRIMetadata, integrity=%s",
+ NS_ConvertUTF16toUTF8(aIntegrityAttr).get()));
+
+ nsAutoCString sourceUri;
+ if (mDocument->GetDocumentURI()) {
+ mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
+ }
+ SRICheck::IntegrityMetadata(aIntegrityAttr, sourceUri, mReporter,
+ aMetadataOut);
+}
+
+ReferrerPolicy ScriptLoader::GetReferrerPolicy(nsIScriptElement* aElement) {
+ ReferrerPolicy scriptReferrerPolicy = aElement->GetReferrerPolicy();
+ if (scriptReferrerPolicy != ReferrerPolicy::_empty) {
+ return scriptReferrerPolicy;
+ }
+ return mDocument->GetReferrerPolicy();
+}
+
+namespace {
+
+class NotifyOffThreadScriptLoadCompletedRunnable : public Runnable {
+ RefPtr<ScriptLoadRequest> mRequest;
+ RefPtr<ScriptLoader> mLoader;
+ RefPtr<DocGroup> mDocGroup;
+ JS::OffThreadToken* mToken;
+
+ public:
+ ScriptLoadRequest* GetScriptLoadRequest() { return mRequest; }
+
+ NotifyOffThreadScriptLoadCompletedRunnable(ScriptLoadRequest* aRequest,
+ ScriptLoader* aLoader)
+ : Runnable("dom::NotifyOffThreadScriptLoadCompletedRunnable"),
+ mRequest(aRequest),
+ mLoader(aLoader),
+ mDocGroup(aLoader->GetDocGroup()),
+ mToken(nullptr) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ virtual ~NotifyOffThreadScriptLoadCompletedRunnable();
+
+ void SetToken(JS::OffThreadToken* aToken) {
+ MOZ_ASSERT(aToken && !mToken);
+ mToken = aToken;
+ }
+
+ static void Dispatch(
+ already_AddRefed<NotifyOffThreadScriptLoadCompletedRunnable>&& aSelf) {
+ RefPtr<NotifyOffThreadScriptLoadCompletedRunnable> self = aSelf;
+ RefPtr<DocGroup> docGroup = self->mDocGroup;
+ docGroup->Dispatch(TaskCategory::Other, self.forget());
+ }
+
+ NS_DECL_NSIRUNNABLE
+};
+
+} /* anonymous namespace */
+
+void ScriptLoader::Shutdown() {
+ CancelScriptLoadRequests();
+ GiveUpBytecodeEncoding();
+}
+
+void ScriptLoader::CancelScriptLoadRequests() {
+ // Cancel all requests that have not been executed.
+ if (mParserBlockingRequest) {
+ mParserBlockingRequest->Cancel();
+ }
+
+ for (ScriptLoadRequest* req = mXSLTRequests.getFirst(); req;
+ req = req->getNext()) {
+ req->Cancel();
+ }
+
+ for (ScriptLoadRequest* req = mDeferRequests.getFirst(); req;
+ req = req->getNext()) {
+ req->Cancel();
+ }
+
+ for (ScriptLoadRequest* req = mLoadingAsyncRequests.getFirst(); req;
+ req = req->getNext()) {
+ req->Cancel();
+ }
+
+ for (ScriptLoadRequest* req = mLoadedAsyncRequests.getFirst(); req;
+ req = req->getNext()) {
+ req->Cancel();
+ }
+
+ for (ScriptLoadRequest* req = mDynamicImportRequests.getFirst(); req;
+ req = req->getNext()) {
+ req->Cancel();
+ }
+
+ for (ScriptLoadRequest* req =
+ mNonAsyncExternalScriptInsertedRequests.getFirst();
+ req; req = req->getNext()) {
+ req->Cancel();
+ }
+
+ for (size_t i = 0; i < mPreloads.Length(); i++) {
+ mPreloads[i].mRequest->Cancel();
+ }
+}
+
+nsresult ScriptLoader::ProcessOffThreadRequest(ScriptLoadRequest* aRequest) {
+ MOZ_ASSERT(aRequest->mProgress == ScriptLoadRequest::Progress::eCompiling);
+ MOZ_ASSERT(!aRequest->mWasCompiledOMT);
+
+ aRequest->mWasCompiledOMT = true;
+
+ if (aRequest->IsModuleRequest()) {
+ MOZ_ASSERT(aRequest->mOffThreadToken);
+ ModuleLoadRequest* request = aRequest->AsModuleRequest();
+ return ProcessFetchedModuleSource(request);
+ }
+
+ // Element may not be ready yet if speculatively compiling, so process the
+ // request in ProcessPendingRequests when it is available.
+ MOZ_ASSERT_IF(!SpeculativeOMTParsingEnabled(), aRequest->GetScriptElement());
+ if (!aRequest->GetScriptElement()) {
+ // Unblock onload here in case this request never gets executed.
+ aRequest->MaybeUnblockOnload();
+ return NS_OK;
+ }
+
+ aRequest->SetReady();
+
+ if (aRequest == mParserBlockingRequest) {
+ if (!ReadyToExecuteParserBlockingScripts()) {
+ // If not ready to execute scripts, schedule an async call to
+ // ProcessPendingRequests to handle it.
+ ProcessPendingRequestsAsync();
+ return NS_OK;
+ }
+
+ // Same logic as in top of ProcessPendingRequests.
+ mParserBlockingRequest = nullptr;
+ UnblockParser(aRequest);
+ ProcessRequest(aRequest);
+ ContinueParserAsync(aRequest);
+ return NS_OK;
+ }
+
+ // Async scripts and blocking scripts can be executed right away.
+ if ((aRequest->IsAsyncScript() || aRequest->IsBlockingScript()) &&
+ !aRequest->isInList()) {
+ return ProcessRequest(aRequest);
+ }
+
+ // Process other scripts in the proper order.
+ ProcessPendingRequests();
+ return NS_OK;
+}
+
+NotifyOffThreadScriptLoadCompletedRunnable::
+ ~NotifyOffThreadScriptLoadCompletedRunnable() {
+ if (MOZ_UNLIKELY(mRequest || mLoader) && !NS_IsMainThread()) {
+ NS_ReleaseOnMainThread(
+ "NotifyOffThreadScriptLoadCompletedRunnable::mRequest",
+ mRequest.forget());
+ NS_ReleaseOnMainThread(
+ "NotifyOffThreadScriptLoadCompletedRunnable::mLoader",
+ mLoader.forget());
+ }
+}
+
+static void GetProfilerLabelForRequest(ScriptLoadRequest* aRequest,
+ nsACString& aOutString) {
+#ifdef MOZ_GECKO_PROFILER
+ if (!profiler_is_active()) {
+ aOutString.Append("<script> element");
+ return;
+ }
+ aOutString.Append("<script");
+ if (aRequest->IsAsyncScript()) {
+ aOutString.Append(" async");
+ } else if (aRequest->IsDeferredScript()) {
+ aOutString.Append(" defer");
+ }
+ if (aRequest->IsModuleRequest()) {
+ aOutString.Append(" type=\"module\"");
+ }
+
+ nsAutoCString url;
+ if (aRequest->mURI) {
+ aRequest->mURI->GetAsciiSpec(url);
+ } else {
+ url = "<unknown>";
+ }
+
+ if (aRequest->mIsInline) {
+ if (aRequest->GetParserCreated() != NOT_FROM_PARSER) {
+ aOutString.Append("> inline at line ");
+ aOutString.AppendInt(aRequest->mLineNo);
+ aOutString.Append(" of ");
+ } else {
+ aOutString.Append("> inline (dynamically created) in ");
+ }
+ aOutString.Append(url);
+ } else {
+ aOutString.Append(" src=\"");
+ aOutString.Append(url);
+ aOutString.Append("\">");
+ }
+#else
+ aOutString.Append("<script> element");
+#endif
+}
+
+NS_IMETHODIMP
+NotifyOffThreadScriptLoadCompletedRunnable::Run() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // We want these to be dropped on the main thread, once we return from this
+ // function.
+ RefPtr<ScriptLoadRequest> request = std::move(mRequest);
+
+ // Runnable pointer should have been cleared in the offthread callback.
+ MOZ_ASSERT(!request->mRunnable);
+
+#ifdef MOZ_GECKO_PROFILER
+ if (profiler_is_active()) {
+ ProfilerString8View scriptSourceString;
+ if (request->IsTextSource()) {
+ scriptSourceString = "ScriptCompileOffThread";
+ } else if (request->IsBinASTSource()) {
+ scriptSourceString = "BinASTDecodeOffThread";
+ } else {
+ MOZ_ASSERT(request->IsBytecode());
+ scriptSourceString = "BytecodeDecodeOffThread";
+ }
+
+ nsAutoCString profilerLabelString;
+ GetProfilerLabelForRequest(request, profilerLabelString);
+ PROFILER_MARKER_TEXT(
+ scriptSourceString, JS,
+ MarkerTiming::Interval(request->mOffThreadParseStartTime,
+ request->mOffThreadParseStopTime),
+ profilerLabelString);
+ }
+#endif
+
+ RefPtr<ScriptLoader> loader = std::move(mLoader);
+
+ // Request was already cancelled at some earlier point.
+ if (!request->mOffThreadToken) {
+ return NS_OK;
+ }
+
+ return loader->ProcessOffThreadRequest(request);
+}
+
+static void OffThreadScriptLoaderCallback(JS::OffThreadToken* aToken,
+ void* aCallbackData) {
+ RefPtr<NotifyOffThreadScriptLoadCompletedRunnable> aRunnable = dont_AddRef(
+ static_cast<NotifyOffThreadScriptLoadCompletedRunnable*>(aCallbackData));
+ MOZ_ASSERT(aRunnable.get() == aRunnable->GetScriptLoadRequest()->mRunnable);
+
+#ifdef MOZ_GECKO_PROFILER
+ aRunnable->GetScriptLoadRequest()->mOffThreadParseStopTime =
+ TimeStamp::NowUnfuzzed();
+#endif
+
+ LogRunnable::Run run(aRunnable);
+
+ aRunnable->SetToken(aToken);
+
+ // If mRunnable was cleared then request was canceled so do nothing.
+ if (!aRunnable->GetScriptLoadRequest()->mRunnable.exchange(nullptr)) {
+ return;
+ }
+
+ NotifyOffThreadScriptLoadCompletedRunnable::Dispatch(aRunnable.forget());
+}
+
+nsresult ScriptLoader::AttemptAsyncScriptCompile(ScriptLoadRequest* aRequest,
+ bool* aCouldCompileOut) {
+ // If speculative parsing is enabled, the request may not be ready to run if
+ // the element is not yet available.
+ MOZ_ASSERT_IF(!SpeculativeOMTParsingEnabled() && !aRequest->IsModuleRequest(),
+ aRequest->IsReadyToRun());
+ MOZ_ASSERT(!aRequest->mWasCompiledOMT);
+ MOZ_ASSERT(aCouldCompileOut && !*aCouldCompileOut);
+
+ // Don't off-thread compile inline scripts.
+ if (aRequest->mIsInline) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
+ if (!globalObject) {
+ return NS_ERROR_FAILURE;
+ }
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(globalObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
+ JS::CompileOptions options(cx);
+
+ nsresult rv = FillCompileOptionsForRequest(jsapi, aRequest, global, &options);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (aRequest->IsTextSource()) {
+ if (!JS::CanCompileOffThread(cx, options, aRequest->ScriptTextLength())) {
+ return NS_OK;
+ }
+ } else {
+ MOZ_ASSERT(aRequest->IsBytecode());
+
+ // NOTE: Regardless of using stencil XDR or not, we use off-thread parse
+ // global and instantiate off-thread, to avoid regressing performance.
+ options.useOffThreadParseGlobal = true;
+
+ size_t length =
+ aRequest->mScriptBytecode.length() - aRequest->mBytecodeOffset;
+ if (!JS::CanDecodeOffThread(cx, options, length)) {
+ return NS_OK;
+ }
+ }
+
+ RefPtr<NotifyOffThreadScriptLoadCompletedRunnable> runnable =
+ new NotifyOffThreadScriptLoadCompletedRunnable(aRequest, this);
+
+ // Emulate dispatch. CompileOffThreadModule will call
+ // OffThreadScriptLoaderCallback were we will emulate run.
+ LogRunnable::LogDispatch(runnable);
+
+#ifdef MOZ_GECKO_PROFILER
+ aRequest->mOffThreadParseStartTime = TimeStamp::NowUnfuzzed();
+#endif
+
+ // Save the runnable so it can be properly cleared during cancellation.
+ aRequest->mRunnable = runnable.get();
+ auto signalOOM =
+ mozilla::MakeScopeExit([&aRequest]() { aRequest->mRunnable = nullptr; });
+
+ if (aRequest->IsModuleRequest()) {
+ MOZ_ASSERT(aRequest->IsTextSource());
+ MaybeSourceText maybeSource;
+ nsresult rv = GetScriptSource(cx, aRequest, &maybeSource);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aRequest->mOffThreadToken =
+ maybeSource.constructed<SourceText<char16_t>>()
+ ? JS::CompileOffThreadModule(
+ cx, options, maybeSource.ref<SourceText<char16_t>>(),
+ OffThreadScriptLoaderCallback, static_cast<void*>(runnable))
+ : JS::CompileOffThreadModule(
+ cx, options, maybeSource.ref<SourceText<Utf8Unit>>(),
+ OffThreadScriptLoaderCallback, static_cast<void*>(runnable));
+ if (!aRequest->mOffThreadToken) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ } else if (aRequest->IsBytecode()) {
+ aRequest->mOffThreadToken = JS::DecodeOffThreadScript(
+ cx, options, aRequest->mScriptBytecode, aRequest->mBytecodeOffset,
+ OffThreadScriptLoaderCallback, static_cast<void*>(runnable));
+ if (!aRequest->mOffThreadToken) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ } else {
+ MOZ_ASSERT(aRequest->IsTextSource());
+ MaybeSourceText maybeSource;
+ nsresult rv = GetScriptSource(cx, aRequest, &maybeSource);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aRequest->mOffThreadToken =
+ maybeSource.constructed<SourceText<char16_t>>()
+ ? JS::CompileOffThread(
+ cx, options, maybeSource.ref<SourceText<char16_t>>(),
+ OffThreadScriptLoaderCallback, static_cast<void*>(runnable))
+ : JS::CompileOffThread(
+ cx, options, maybeSource.ref<SourceText<Utf8Unit>>(),
+ OffThreadScriptLoaderCallback, static_cast<void*>(runnable));
+ if (!aRequest->mOffThreadToken) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ signalOOM.release();
+
+ aRequest->BlockOnload(mDocument);
+
+ // Once the compilation is finished, an event would be added to the event loop
+ // to call ScriptLoader::ProcessOffThreadRequest with the same request.
+ aRequest->mProgress = ScriptLoadRequest::Progress::eCompiling;
+
+ *aCouldCompileOut = true;
+ Unused << runnable.forget();
+ return NS_OK;
+}
+
+nsresult ScriptLoader::CompileOffThreadOrProcessRequest(
+ ScriptLoadRequest* aRequest) {
+ NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
+ "Processing requests when running scripts is unsafe.");
+
+ if (!aRequest->mOffThreadToken && !aRequest->InCompilingStage()) {
+ bool couldCompile = false;
+ nsresult rv = AttemptAsyncScriptCompile(aRequest, &couldCompile);
+ if (NS_FAILED(rv)) {
+ HandleLoadError(aRequest, rv);
+ return rv;
+ }
+
+ if (couldCompile) {
+ return NS_OK;
+ }
+ }
+
+ return ProcessRequest(aRequest);
+}
+
+nsresult ScriptLoader::GetScriptSource(JSContext* aCx,
+ ScriptLoadRequest* aRequest,
+ MaybeSourceText* aMaybeSource) {
+ // If there's no script text, we try to get it from the element
+ if (aRequest->mIsInline) {
+ nsAutoString inlineData;
+ aRequest->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 = aRequest->ScriptTextLength();
+ if (aRequest->IsUTF16Text()) {
+ JS::UniqueTwoByteChars chars;
+ chars.reset(aRequest->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(aRequest->IsUTF8Text());
+ UniquePtr<Utf8Unit[], JS::FreePolicy> chars;
+ chars.reset(aRequest->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;
+}
+
+nsresult ScriptLoader::ProcessRequest(ScriptLoadRequest* aRequest) {
+ LOG(("ScriptLoadRequest (%p): Process request", aRequest));
+
+ NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
+ "Processing requests when running scripts is unsafe.");
+ NS_ASSERTION(aRequest->IsReadyToRun(),
+ "Processing a request that is not ready to run.");
+
+ NS_ENSURE_ARG(aRequest);
+
+ auto unblockOnload = MakeScopeExit([&] { aRequest->MaybeUnblockOnload(); });
+
+ if (aRequest->IsModuleRequest()) {
+ ModuleLoadRequest* request = aRequest->AsModuleRequest();
+ if (request->mModuleScript) {
+ if (!InstantiateModuleTree(request)) {
+ request->mModuleScript = nullptr;
+ }
+ }
+
+ if (!request->mModuleScript) {
+ // There was an error fetching a module script. Nothing to do here.
+ LOG(("ScriptLoadRequest (%p): Error loading request, firing error",
+ aRequest));
+ FireScriptAvailable(NS_ERROR_FAILURE, aRequest);
+ return NS_OK;
+ }
+ }
+
+ nsCOMPtr<nsINode> scriptElem =
+ do_QueryInterface(aRequest->GetScriptElement());
+
+ nsCOMPtr<Document> doc;
+ if (!aRequest->mIsInline) {
+ doc = scriptElem->OwnerDoc();
+ }
+
+ nsCOMPtr<nsIScriptElement> oldParserInsertedScript;
+ uint32_t parserCreated = aRequest->GetParserCreated();
+ if (parserCreated) {
+ oldParserInsertedScript = mCurrentParserInsertedScript;
+ mCurrentParserInsertedScript = aRequest->GetScriptElement();
+ }
+
+ aRequest->GetScriptElement()->BeginEvaluating();
+
+ FireScriptAvailable(NS_OK, aRequest);
+
+ // The window may have gone away by this point, in which case there's no point
+ // in trying to run the script.
+
+ {
+ // Try to perform a microtask checkpoint
+ nsAutoMicroTask mt;
+ }
+
+ nsPIDOMWindowInner* pwin = mDocument->GetInnerWindow();
+ bool runScript = !!pwin;
+ if (runScript) {
+ nsContentUtils::DispatchTrustedEvent(
+ scriptElem->OwnerDoc(), scriptElem, u"beforescriptexecute"_ns,
+ CanBubble::eYes, Cancelable::eYes, &runScript);
+ }
+
+ // Inner window could have gone away after firing beforescriptexecute
+ pwin = mDocument->GetInnerWindow();
+ if (!pwin) {
+ runScript = false;
+ }
+
+ nsresult rv = NS_OK;
+ if (runScript) {
+ if (doc) {
+ doc->IncrementIgnoreDestructiveWritesCounter();
+ }
+ rv = EvaluateScript(aRequest);
+ if (doc) {
+ doc->DecrementIgnoreDestructiveWritesCounter();
+ }
+
+ nsContentUtils::DispatchTrustedEvent(scriptElem->OwnerDoc(), scriptElem,
+ u"afterscriptexecute"_ns,
+ CanBubble::eYes, Cancelable::eNo);
+ }
+
+ FireScriptEvaluated(rv, aRequest);
+
+ aRequest->GetScriptElement()->EndEvaluating();
+
+ if (parserCreated) {
+ mCurrentParserInsertedScript = oldParserInsertedScript;
+ }
+
+ if (aRequest->mOffThreadToken) {
+ // The request was parsed off-main-thread, but the result of the off
+ // thread parse was not actually needed to process the request
+ // (disappearing window, some other error, ...). Finish the
+ // request to avoid leaks in the JS engine.
+ MOZ_ASSERT(!aRequest->IsModuleRequest());
+ aRequest->MaybeCancelOffThreadScript();
+ }
+
+ // Free any source data, but keep the bytecode content as we might have to
+ // save it later.
+ aRequest->ClearScriptSource();
+ if (aRequest->IsBytecode()) {
+ // We received bytecode as input, thus we were decoding, and we will not be
+ // encoding the bytecode once more. We can safely clear the content of this
+ // buffer.
+ aRequest->mScriptBytecode.clearAndFree();
+ }
+
+ return rv;
+}
+
+void ScriptLoader::ProcessDynamicImport(ModuleLoadRequest* aRequest) {
+ if (aRequest->mModuleScript) {
+ if (!InstantiateModuleTree(aRequest)) {
+ aRequest->mModuleScript = nullptr;
+ }
+ }
+
+ nsresult rv = NS_ERROR_FAILURE;
+ if (aRequest->mModuleScript) {
+ rv = EvaluateScript(aRequest);
+ }
+
+ if (NS_FAILED(rv)) {
+ FinishDynamicImportAndReject(aRequest, rv);
+ }
+}
+
+void ScriptLoader::FireScriptAvailable(nsresult aResult,
+ ScriptLoadRequest* aRequest) {
+ for (int32_t i = 0; i < mObservers.Count(); i++) {
+ nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i];
+ obs->ScriptAvailable(aResult, aRequest->GetScriptElement(),
+ aRequest->mIsInline, aRequest->mURI,
+ aRequest->mLineNo);
+ }
+
+ aRequest->FireScriptAvailable(aResult);
+}
+
+void ScriptLoader::FireScriptEvaluated(nsresult aResult,
+ ScriptLoadRequest* aRequest) {
+ for (int32_t i = 0; i < mObservers.Count(); i++) {
+ nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i];
+ obs->ScriptEvaluated(aResult, aRequest->GetScriptElement(),
+ aRequest->mIsInline);
+ }
+
+ aRequest->FireScriptEvaluated(aResult);
+}
+
+already_AddRefed<nsIScriptGlobalObject> ScriptLoader::GetScriptGlobalObject() {
+ if (!mDocument) {
+ return nullptr;
+ }
+
+ nsPIDOMWindowInner* pwin = mDocument->GetInnerWindow();
+ if (!pwin) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(pwin);
+ NS_ASSERTION(globalObject, "windows must be global objects");
+
+ // and make sure we are setup for this type of script.
+ nsresult rv = globalObject->EnsureScriptEnvironment();
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return globalObject.forget();
+}
+
+nsresult ScriptLoader::FillCompileOptionsForRequest(
+ const mozilla::dom::AutoJSAPI& jsapi, ScriptLoadRequest* aRequest,
+ JS::Handle<JSObject*> aScopeChain, JS::CompileOptions* aOptions) {
+ // It's very important to use aRequest->mURI, not the final URI of the channel
+ // aRequest ended up getting script data from, as the script filename.
+ nsresult rv = aRequest->mURI->GetSpec(aRequest->mURL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mDocument) {
+ mDocument->NoteScriptTrackingStatus(aRequest->mURL, aRequest->IsTracking());
+ }
+
+ const char* introductionType;
+ if (aRequest->IsModuleRequest() &&
+ !aRequest->AsModuleRequest()->IsTopLevel()) {
+ introductionType = "importedModule";
+ } else if (!aRequest->mIsInline) {
+ introductionType = "srcScript";
+ } else if (aRequest->GetParserCreated() == FROM_PARSER_NETWORK) {
+ introductionType = "inlineScript";
+ } else {
+ introductionType = "injectedScript";
+ }
+ aOptions->setIntroductionInfoToCaller(jsapi.cx(), introductionType);
+ aOptions->setFileAndLine(aRequest->mURL.get(), aRequest->mLineNo);
+ aOptions->setIsRunOnce(true);
+ aOptions->setNoScriptRval(true);
+ if (aRequest->mSourceMapURL) {
+ aOptions->setSourceMapURL(aRequest->mSourceMapURL->get());
+ }
+ if (aRequest->mOriginPrincipal) {
+ nsIPrincipal* scriptPrin = nsContentUtils::ObjectPrincipal(aScopeChain);
+ bool subsumes = scriptPrin->Subsumes(aRequest->mOriginPrincipal);
+ aOptions->setMutedErrors(!subsumes);
+ }
+
+ if (aRequest->IsModuleRequest()) {
+ aOptions->hideScriptFromDebugger = true;
+ }
+
+ return NS_OK;
+}
+
+/* static */
+bool ScriptLoader::ShouldCacheBytecode(ScriptLoadRequest* aRequest) {
+ using mozilla::TimeDuration;
+ using mozilla::TimeStamp;
+
+ // We need the nsICacheInfoChannel to exist to be able to open the alternate
+ // data output stream. This pointer would only be non-null if the bytecode was
+ // activated at the time the channel got created in StartLoad.
+ if (!aRequest->mCacheInfo) {
+ LOG(("ScriptLoadRequest (%p): Cannot cache anything (cacheInfo = %p)",
+ aRequest, aRequest->mCacheInfo.get()));
+ return false;
+ }
+
+ // Look at the preference to know which strategy (parameters) should be used
+ // when the bytecode cache is enabled.
+ int32_t strategy = StaticPrefs::dom_script_loader_bytecode_cache_strategy();
+
+ // List of parameters used by the strategies.
+ bool hasSourceLengthMin = false;
+ bool hasFetchCountMin = false;
+ size_t sourceLengthMin = 100;
+ size_t binASTLengthMin = 70;
+ int32_t fetchCountMin = 4;
+
+ LOG(("ScriptLoadRequest (%p): Bytecode-cache: strategy = %d.", aRequest,
+ strategy));
+ switch (strategy) {
+ case -2: {
+ // Reader mode, keep requesting alternate data but no longer save it.
+ LOG(("ScriptLoadRequest (%p): Bytecode-cache: Encoding disabled.",
+ aRequest));
+ return false;
+ }
+ case -1: {
+ // Eager mode, skip heuristics!
+ hasSourceLengthMin = false;
+ hasFetchCountMin = false;
+ break;
+ }
+ default:
+ case 0: {
+ hasSourceLengthMin = true;
+ hasFetchCountMin = true;
+ sourceLengthMin = 1024;
+ binASTLengthMin = 700;
+ // If we were to optimize only for speed, without considering the impact
+ // on memory, we should set this threshold to 2. (Bug 900784 comment 120)
+ fetchCountMin = 4;
+ break;
+ }
+ }
+
+ // If the script is too small/large, do not attempt at creating a bytecode
+ // cache for this script, as the overhead of parsing it might not be worth the
+ // effort.
+ if (hasSourceLengthMin) {
+ size_t sourceLength;
+ size_t minLength;
+ if (aRequest->IsTextSource()) {
+ sourceLength = aRequest->mScriptTextLength;
+ minLength = sourceLengthMin;
+ } else {
+ MOZ_ASSERT(aRequest->IsBinASTSource());
+ sourceLength = aRequest->ScriptBinASTData().length();
+ minLength = binASTLengthMin;
+ }
+ if (sourceLength < minLength) {
+ LOG(("ScriptLoadRequest (%p): Bytecode-cache: Script is too small.",
+ aRequest));
+ return false;
+ }
+ }
+
+ // Check that we loaded the cache entry a few times before attempting any
+ // bytecode-cache optimization, such that we do not waste time on entry which
+ // are going to be dropped soon.
+ if (hasFetchCountMin) {
+ int32_t fetchCount = 0;
+ if (NS_FAILED(aRequest->mCacheInfo->GetCacheTokenFetchCount(&fetchCount))) {
+ LOG(("ScriptLoadRequest (%p): Bytecode-cache: Cannot get fetchCount.",
+ aRequest));
+ return false;
+ }
+ LOG(("ScriptLoadRequest (%p): Bytecode-cache: fetchCount = %d.", aRequest,
+ fetchCount));
+ if (fetchCount < fetchCountMin) {
+ return false;
+ }
+ }
+
+ LOG(("ScriptLoadRequest (%p): Bytecode-cache: Trigger encoding.", aRequest));
+ return true;
+}
+
+class MOZ_RAII AutoSetProcessingScriptTag {
+ nsCOMPtr<nsIScriptContext> mContext;
+ bool mOldTag;
+
+ public:
+ explicit AutoSetProcessingScriptTag(nsIScriptContext* aContext)
+ : mContext(aContext), mOldTag(mContext->GetProcessingScriptTag()) {
+ mContext->SetProcessingScriptTag(true);
+ }
+
+ ~AutoSetProcessingScriptTag() { mContext->SetProcessingScriptTag(mOldTag); }
+};
+
+static nsresult ExecuteCompiledScript(JSContext* aCx,
+ ScriptLoadRequest* aRequest,
+ JSExecutionContext& aExec,
+ ClassicScript* aLoaderScript) {
+ JS::Rooted<JSScript*> script(aCx, aExec.GetScript());
+ if (!script) {
+ // Compilation succeeds without producing a script if scripting is
+ // disabled for the global.
+ return NS_OK;
+ }
+
+ if (JS::GetScriptPrivate(script).isUndefined()) {
+ aLoaderScript->AssociateWithScript(script);
+ }
+
+ return aExec.ExecScript();
+}
+
+nsresult ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest) {
+ using namespace mozilla::Telemetry;
+ MOZ_ASSERT(aRequest->IsReadyToRun());
+
+ // We need a document to evaluate scripts.
+ if (!mDocument) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool isDynamicImport = aRequest->IsModuleRequest() &&
+ aRequest->AsModuleRequest()->IsDynamicImport();
+ if (!isDynamicImport) {
+ nsCOMPtr<nsIContent> scriptContent(
+ do_QueryInterface(aRequest->GetScriptElement()));
+ MOZ_ASSERT(scriptContent);
+ Document* ownerDoc = scriptContent->OwnerDoc();
+ if (ownerDoc != mDocument) {
+ // Willful violation of HTML5 as of 2010-12-01
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
+ if (!globalObject) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Make sure context is a strong reference since we access it after
+ // we've executed a script, which may cause all other references to
+ // the context to go away.
+ nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
+ if (!context) {
+ return NS_ERROR_FAILURE;
+ }
+
+#ifdef MOZ_GECKO_PROFILER
+ nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
+ nsIDocShell* docShell = window ? window->GetDocShell() : nullptr;
+#endif
+ nsAutoCString profilerLabelString;
+ GetProfilerLabelForRequest(aRequest, profilerLabelString);
+
+ // New script entry point required, due to the "Create a script" sub-step of
+ // http://www.whatwg.org/specs/web-apps/current-work/#execute-the-script-block
+ nsAutoMicroTask mt;
+ AutoEntryScript aes(globalObject, profilerLabelString.get(), true);
+ JSContext* cx = aes.cx();
+ JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
+
+ AutoSetProcessingScriptTag setProcessingScriptTag(context);
+
+ nsresult rv;
+ {
+ if (aRequest->IsModuleRequest()) {
+ // When a module is already loaded, it is not feched a second time and the
+ // mDataType of the request might remain set to DataType::Unknown.
+ MOZ_ASSERT(aRequest->IsTextSource() || aRequest->IsUnknownDataType());
+ LOG(("ScriptLoadRequest (%p): Evaluate Module", aRequest));
+ AUTO_PROFILER_MARKER_TEXT("ModuleEvaluation", JS,
+ MarkerInnerWindowIdFromDocShell(docShell),
+ profilerLabelString);
+
+ // currentScript is set to null for modules.
+ AutoCurrentScriptUpdater scriptUpdater(this, nullptr);
+
+ ModuleLoadRequest* request = aRequest->AsModuleRequest();
+ MOZ_ASSERT(request->mModuleScript);
+ MOZ_ASSERT(!request->mOffThreadToken);
+
+ ModuleScript* moduleScript = request->mModuleScript;
+ if (moduleScript->HasErrorToRethrow()) {
+ LOG(("ScriptLoadRequest (%p): module has error to rethrow",
+ aRequest));
+ JS::Rooted<JS::Value> error(cx, moduleScript->ErrorToRethrow());
+ if (!JS::ContextOptionsRef(cx).topLevelAwait()) {
+ JS_SetPendingException(cx, error);
+ // For a dynamic import, the promise is rejected. Otherwise an error
+ // is either reported by AutoEntryScript.
+ if (request->IsDynamicImport()) {
+ FinishDynamicImport_NoTLA(cx, request, NS_OK);
+ }
+ } else {
+ ErrorResult err;
+ RefPtr<Promise> aPromise = Promise::Create(globalObject, err);
+ if (NS_WARN_IF(err.Failed())) {
+ return err.StealNSResult();
+ }
+ aPromise->MaybeReject(error);
+ JS::Rooted<JSObject*> aEvaluationPromise(cx, aPromise->PromiseObj());
+ if (request->IsDynamicImport()) {
+ FinishDynamicImport(cx, request, NS_OK, aEvaluationPromise);
+ } else {
+ if (!JS::ThrowOnModuleEvaluationFailure(cx, aEvaluationPromise)) {
+ LOG(("ScriptLoadRequest (%p): evaluation failed", aRequest));
+ // For a dynamic import, the promise is rejected. Otherwise an
+ // error is either reported by AutoEntryScript.
+ }
+ }
+ }
+ return NS_OK;
+ }
+
+ JS::Rooted<JSObject*> module(cx, moduleScript->ModuleRecord());
+ MOZ_ASSERT(module);
+
+ rv = InitDebuggerDataForModuleTree(cx, request);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ TRACE_FOR_TEST(aRequest->GetScriptElement(),
+ "scriptloader_evaluate_module");
+
+ JS::Rooted<JS::Value> rval(cx);
+
+ rv = nsJSUtils::ModuleEvaluate(cx, module, &rval);
+
+ if (NS_SUCCEEDED(rv)) {
+ // If we have an infinite loop in a module, which is stopped by the
+ // user, the module evaluation will fail, but we will not have an
+ // AutoEntryScript exception.
+ MOZ_ASSERT(!aes.HasException());
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(("ScriptLoadRequest (%p): evaluation failed", aRequest));
+ // For a dynamic import, the promise is rejected. Otherwise an error is
+ // either reported by AutoEntryScript.
+ rv = NS_OK;
+ }
+
+ if (!JS::ContextOptionsRef(cx).topLevelAwait()) {
+ if (request->IsDynamicImport()) {
+ FinishDynamicImport_NoTLA(cx, request, rv);
+ }
+ } else {
+ // Path for when Top Level Await is enabled
+ JS::Rooted<JSObject*> aEvaluationPromise(cx);
+ if (NS_SUCCEEDED(rv)) {
+ // If the user cancels the evaluation on an infinite loop, we need
+ // to skip this step. In that case, ModuleEvaluate will not return a
+ // promise, rval will be undefined. We should treat it as a failed
+ // evaluation, and reject appropriately.
+ aEvaluationPromise.set(&rval.toObject());
+ }
+ if (request->IsDynamicImport()) {
+ FinishDynamicImport(cx, request, rv, aEvaluationPromise);
+ } else {
+ // If this is not a dynamic import, and if the promise is rejected,
+ // the value is unwrapped from the promise value.
+ if (!JS::ThrowOnModuleEvaluationFailure(cx, aEvaluationPromise)) {
+ LOG(("ScriptLoadRequest (%p): evaluation failed on throw",
+ aRequest));
+ // For a dynamic import, the promise is rejected. Otherwise an
+ // error is either reported by AutoEntryScript.
+ rv = NS_OK;
+ }
+ }
+ }
+
+ TRACE_FOR_TEST_NONE(aRequest->GetScriptElement(),
+ "scriptloader_no_encode");
+ aRequest->mCacheInfo = nullptr;
+ } else {
+ // Update our current script.
+ AutoCurrentScriptUpdater scriptUpdater(this,
+ aRequest->GetScriptElement());
+
+ // Create a ClassicScript object and associate it with the JSScript.
+ RefPtr<ClassicScript> classicScript =
+ new ClassicScript(aRequest->mFetchOptions, aRequest->mBaseURL);
+
+ JS::CompileOptions options(cx);
+ rv = FillCompileOptionsForRequest(aes, aRequest, global, &options);
+ options.setPrivateValue(JS::PrivateValue(classicScript));
+
+ if (NS_SUCCEEDED(rv)) {
+ if (aRequest->IsBytecode()) {
+ TRACE_FOR_TEST(aRequest->GetScriptElement(), "scriptloader_execute");
+ JSExecutionContext exec(cx, global);
+ if (aRequest->mOffThreadToken) {
+ LOG(("ScriptLoadRequest (%p): Decode Bytecode & Join and Execute",
+ aRequest));
+ rv = exec.JoinDecode(&aRequest->mOffThreadToken);
+ } else {
+ LOG(("ScriptLoadRequest (%p): Decode Bytecode and Execute",
+ aRequest));
+ AUTO_PROFILER_MARKER_TEXT("BytecodeDecodeMainThread", JS,
+ MarkerInnerWindowIdFromDocShell(docShell),
+ profilerLabelString);
+
+ rv = exec.Decode(options, aRequest->mScriptBytecode,
+ aRequest->mBytecodeOffset);
+ }
+
+ if (rv == NS_OK) {
+ AUTO_PROFILER_MARKER_TEXT("ScriptExecution", JS,
+ MarkerInnerWindowIdFromDocShell(docShell),
+ profilerLabelString);
+ rv = ExecuteCompiledScript(cx, aRequest, exec, classicScript);
+ }
+
+ // We do not expect to be saving anything when we already have some
+ // bytecode.
+ MOZ_ASSERT(!aRequest->mCacheInfo);
+ } else {
+ MOZ_ASSERT(aRequest->IsSource());
+ JS::Rooted<JSScript*> script(cx);
+ bool encodeBytecode = ShouldCacheBytecode(aRequest);
+
+ {
+ JSExecutionContext exec(cx, global);
+ exec.SetEncodeBytecode(encodeBytecode);
+ TRACE_FOR_TEST(aRequest->GetScriptElement(),
+ "scriptloader_execute");
+ if (aRequest->mOffThreadToken) {
+ // Off-main-thread parsing.
+ LOG(
+ ("ScriptLoadRequest (%p): Join (off-thread parsing) and "
+ "Execute",
+ aRequest));
+ if (aRequest->IsBinASTSource()) {
+ rv = exec.JoinDecodeBinAST(&aRequest->mOffThreadToken);
+ } else {
+ MOZ_ASSERT(aRequest->IsTextSource());
+ rv = exec.JoinCompile(&aRequest->mOffThreadToken);
+ }
+ } else {
+ // Main thread parsing (inline and small scripts)
+ LOG(("ScriptLoadRequest (%p): Compile And Exec", aRequest));
+ if (aRequest->IsBinASTSource()) {
+ AUTO_PROFILER_MARKER_TEXT(
+ "BinASTDecodeMainThread", JS,
+ MarkerInnerWindowIdFromDocShell(docShell),
+ profilerLabelString);
+
+ rv = exec.DecodeBinAST(options,
+ aRequest->ScriptBinASTData().begin(),
+ aRequest->ScriptBinASTData().length());
+ } else {
+ MOZ_ASSERT(aRequest->IsTextSource());
+ MaybeSourceText maybeSource;
+ rv = GetScriptSource(cx, aRequest, &maybeSource);
+ if (NS_SUCCEEDED(rv)) {
+ AUTO_PROFILER_MARKER_TEXT(
+ "ScriptCompileMainThread", JS,
+ MarkerInnerWindowIdFromDocShell(docShell),
+ profilerLabelString);
+
+ rv = maybeSource.constructed<SourceText<char16_t>>()
+ ? exec.Compile(
+ options,
+ maybeSource.ref<SourceText<char16_t>>())
+ : exec.Compile(
+ options,
+ maybeSource.ref<SourceText<Utf8Unit>>());
+ }
+ }
+ }
+
+ if (rv == NS_OK) {
+ script = exec.GetScript();
+ AUTO_PROFILER_MARKER_TEXT(
+ "ScriptExecution", JS,
+ MarkerInnerWindowIdFromDocShell(docShell),
+ profilerLabelString);
+ rv = ExecuteCompiledScript(cx, aRequest, exec, classicScript);
+ }
+ }
+
+ // Queue the current script load request to later save the bytecode.
+ if (script && encodeBytecode) {
+ aRequest->SetScript(script);
+ TRACE_FOR_TEST(aRequest->GetScriptElement(), "scriptloader_encode");
+ MOZ_ASSERT(aRequest->mBytecodeOffset ==
+ aRequest->mScriptBytecode.length());
+ RegisterForBytecodeEncoding(aRequest);
+ } else {
+ LOG(
+ ("ScriptLoadRequest (%p): Bytecode-cache: disabled (rv = %X, "
+ "script = %p)",
+ aRequest, unsigned(rv), script.get()));
+ TRACE_FOR_TEST_NONE(aRequest->GetScriptElement(),
+ "scriptloader_no_encode");
+ aRequest->mCacheInfo = nullptr;
+ }
+ }
+ }
+ }
+
+ // Even if we are not saving the bytecode of the current script, we have
+ // to trigger the encoding of the bytecode, as the current script can
+ // call functions of a script for which we are recording the bytecode.
+ LOG(("ScriptLoadRequest (%p): ScriptLoader = %p", aRequest, this));
+ MaybeTriggerBytecodeEncoding();
+ }
+
+ return rv;
+}
+
+/* static */
+LoadedScript* ScriptLoader::GetActiveScript(JSContext* aCx) {
+ JS::Value value = JS::GetScriptedCallerPrivate(aCx);
+ if (value.isUndefined()) {
+ return nullptr;
+ }
+
+ return static_cast<LoadedScript*>(value.toPrivate());
+}
+
+void ScriptLoader::RegisterForBytecodeEncoding(ScriptLoadRequest* aRequest) {
+ MOZ_ASSERT(aRequest->mCacheInfo);
+ MOZ_ASSERT(aRequest->mScript);
+ MOZ_DIAGNOSTIC_ASSERT(!aRequest->isInList());
+ mBytecodeEncodingQueue.AppendElement(aRequest);
+}
+
+void ScriptLoader::LoadEventFired() {
+ mLoadEventFired = true;
+ MaybeTriggerBytecodeEncoding();
+}
+
+void ScriptLoader::Destroy() {
+ // Off thread compilations will be canceled in ProcessRequest after the inner
+ // window is removed in Document::Destroy()
+ if (mShutdownObserver) {
+ mShutdownObserver->Unregister();
+ mShutdownObserver = nullptr;
+ }
+ GiveUpBytecodeEncoding();
+}
+
+void ScriptLoader::MaybeTriggerBytecodeEncoding() {
+ // If we already gave up, ensure that we are not going to enqueue any script,
+ // and that we finalize them properly.
+ if (mGiveUpEncoding) {
+ LOG(("ScriptLoader (%p): Keep giving-up bytecode encoding.", this));
+ GiveUpBytecodeEncoding();
+ return;
+ }
+
+ // We wait for the load event to be fired before saving the bytecode of
+ // any script to the cache. It is quite common to have load event
+ // listeners trigger more JavaScript execution, that we want to save as
+ // part of this start-up bytecode cache.
+ if (!mLoadEventFired) {
+ LOG(("ScriptLoader (%p): Wait for the load-end event to fire.", this));
+ return;
+ }
+
+ // No need to fire any event if there is no bytecode to be saved.
+ if (mBytecodeEncodingQueue.isEmpty()) {
+ LOG(("ScriptLoader (%p): No script in queue to be encoded.", this));
+ return;
+ }
+
+ // Wait until all scripts are loaded before saving the bytecode, such that
+ // we capture most of the intialization of the page.
+ if (HasPendingRequests()) {
+ LOG(("ScriptLoader (%p): Wait for other pending request to finish.", this));
+ return;
+ }
+
+ // Create a new runnable dedicated to encoding the content of the bytecode of
+ // all enqueued scripts when the document is idle. In case of failure, we
+ // give-up on encoding the bytecode.
+ nsCOMPtr<nsIRunnable> encoder = NewRunnableMethod(
+ "ScriptLoader::EncodeBytecode", this, &ScriptLoader::EncodeBytecode);
+ if (NS_FAILED(NS_DispatchToCurrentThreadQueue(encoder.forget(),
+ EventQueuePriority::Idle))) {
+ GiveUpBytecodeEncoding();
+ return;
+ }
+
+ LOG(("ScriptLoader (%p): Schedule bytecode encoding.", this));
+}
+
+void ScriptLoader::EncodeBytecode() {
+ LOG(("ScriptLoader (%p): Start bytecode encoding.", this));
+
+ // If any script got added in the previous loop cycle, wait until all
+ // remaining script executions are completed, such that we capture most of
+ // the initialization.
+ if (HasPendingRequests()) {
+ return;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
+ if (!globalObject) {
+ GiveUpBytecodeEncoding();
+ return;
+ }
+
+ nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
+ if (!context) {
+ GiveUpBytecodeEncoding();
+ return;
+ }
+
+ TimeStamp startTime = TimeStamp::Now();
+
+ AutoEntryScript aes(globalObject, "encode bytecode", true);
+ RefPtr<ScriptLoadRequest> request;
+ while (!mBytecodeEncodingQueue.isEmpty()) {
+ request = mBytecodeEncodingQueue.StealFirst();
+ EncodeRequestBytecode(aes.cx(), request);
+ request->mScriptBytecode.clearAndFree();
+ request->DropBytecodeCacheReferences();
+ }
+
+ TimeDuration delta = TimeStamp::Now() - startTime;
+ Telemetry::Accumulate(Telemetry::JS_BYTECODE_CACHING_TIME,
+ static_cast<uint32_t>(delta.ToMilliseconds()));
+}
+
+void ScriptLoader::EncodeRequestBytecode(JSContext* aCx,
+ ScriptLoadRequest* aRequest) {
+ using namespace mozilla::Telemetry;
+ nsresult rv = NS_OK;
+ MOZ_ASSERT(aRequest->mCacheInfo);
+ auto bytecodeFailed = mozilla::MakeScopeExit([&]() {
+ TRACE_FOR_TEST_NONE(aRequest->GetScriptElement(),
+ "scriptloader_bytecode_failed");
+ });
+
+ JS::RootedScript script(aCx, aRequest->mScript);
+ if (!JS::FinishIncrementalEncoding(aCx, script, aRequest->mScriptBytecode)) {
+ // Encoding can be aborted for non-supported syntax (e.g. asm.js), or
+ // any other internal error.
+ // We don't care the error and just give up encoding.
+ JS_ClearPendingException(aCx);
+
+ LOG(("ScriptLoadRequest (%p): Cannot serialize bytecode", aRequest));
+ return;
+ }
+
+ if (aRequest->mScriptBytecode.length() >= UINT32_MAX) {
+ LOG(
+ ("ScriptLoadRequest (%p): Bytecode cache is too large to be decoded "
+ "correctly.",
+ aRequest));
+ return;
+ }
+
+ // Open the output stream to the cache entry alternate data storage. This
+ // might fail if the stream is already open by another request, in which
+ // case, we just ignore the current one.
+ nsCOMPtr<nsIAsyncOutputStream> output;
+ rv = aRequest->mCacheInfo->OpenAlternativeOutputStream(
+ nsContentUtils::JSBytecodeMimeType(), aRequest->mScriptBytecode.length(),
+ getter_AddRefs(output));
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("ScriptLoadRequest (%p): Cannot open bytecode cache (rv = %X, output "
+ "= %p)",
+ aRequest, unsigned(rv), output.get()));
+ return;
+ }
+ MOZ_ASSERT(output);
+
+ auto closeOutStream = mozilla::MakeScopeExit([&]() {
+ rv = output->CloseWithStatus(rv);
+ LOG(("ScriptLoadRequest (%p): Closing (rv = %X)", aRequest, unsigned(rv)));
+ });
+
+ uint32_t n;
+ rv = output->Write(reinterpret_cast<char*>(aRequest->mScriptBytecode.begin()),
+ aRequest->mScriptBytecode.length(), &n);
+ LOG((
+ "ScriptLoadRequest (%p): Write bytecode cache (rv = %X, length = %u, "
+ "written = %u)",
+ aRequest, unsigned(rv), unsigned(aRequest->mScriptBytecode.length()), n));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ MOZ_RELEASE_ASSERT(aRequest->mScriptBytecode.length() == n);
+
+ bytecodeFailed.release();
+ TRACE_FOR_TEST_NONE(aRequest->GetScriptElement(),
+ "scriptloader_bytecode_saved");
+}
+
+void ScriptLoader::GiveUpBytecodeEncoding() {
+ // If the document went away prematurely, we still want to set this, in order
+ // to avoid queuing more scripts.
+ mGiveUpEncoding = true;
+
+ // Ideally we prefer to properly end the incremental encoder, such that we
+ // would not keep a large buffer around. If we cannot, we fallback on the
+ // removal of all request from the current list and these large buffers would
+ // be removed at the same time as the source object.
+ nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
+ AutoAllowLegacyScriptExecution exemption;
+ Maybe<AutoEntryScript> aes;
+
+ if (globalObject) {
+ nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
+ if (context) {
+ aes.emplace(globalObject, "give-up bytecode encoding", true);
+ }
+ }
+
+ while (!mBytecodeEncodingQueue.isEmpty()) {
+ RefPtr<ScriptLoadRequest> request = mBytecodeEncodingQueue.StealFirst();
+ LOG(("ScriptLoadRequest (%p): Cannot serialize bytecode", request.get()));
+ TRACE_FOR_TEST_NONE(request->GetScriptElement(),
+ "scriptloader_bytecode_failed");
+
+ if (aes.isSome()) {
+ JS::RootedScript script(aes->cx(), request->mScript);
+ if (!JS::FinishIncrementalEncoding(aes->cx(), script,
+ request->mScriptBytecode)) {
+ JS_ClearPendingException(aes->cx());
+ }
+ }
+
+ request->mScriptBytecode.clearAndFree();
+ request->DropBytecodeCacheReferences();
+ }
+}
+
+bool ScriptLoader::HasPendingRequests() {
+ return mParserBlockingRequest || !mXSLTRequests.isEmpty() ||
+ !mLoadedAsyncRequests.isEmpty() ||
+ !mNonAsyncExternalScriptInsertedRequests.isEmpty() ||
+ !mDeferRequests.isEmpty() || !mDynamicImportRequests.isEmpty() ||
+ !mPendingChildLoaders.IsEmpty();
+}
+
+void ScriptLoader::ProcessPendingRequestsAsync() {
+ if (HasPendingRequests()) {
+ nsCOMPtr<nsIRunnable> task =
+ NewRunnableMethod("dom::ScriptLoader::ProcessPendingRequests", this,
+ &ScriptLoader::ProcessPendingRequests);
+ if (mDocument) {
+ mDocument->Dispatch(TaskCategory::Other, task.forget());
+ } else {
+ NS_DispatchToCurrentThread(task.forget());
+ }
+ }
+}
+
+void ScriptLoader::ProcessPendingRequests() {
+ RefPtr<ScriptLoadRequest> request;
+
+ if (mParserBlockingRequest && mParserBlockingRequest->IsReadyToRun() &&
+ ReadyToExecuteParserBlockingScripts()) {
+ request.swap(mParserBlockingRequest);
+ UnblockParser(request);
+ ProcessRequest(request);
+ ContinueParserAsync(request);
+ }
+
+ while (ReadyToExecuteParserBlockingScripts() && !mXSLTRequests.isEmpty() &&
+ mXSLTRequests.getFirst()->IsReadyToRun()) {
+ request = mXSLTRequests.StealFirst();
+ ProcessRequest(request);
+ }
+
+ while (ReadyToExecuteScripts() && !mLoadedAsyncRequests.isEmpty()) {
+ request = mLoadedAsyncRequests.StealFirst();
+ if (request->IsModuleRequest()) {
+ ProcessRequest(request);
+ } else {
+ CompileOffThreadOrProcessRequest(request);
+ }
+ }
+
+ while (ReadyToExecuteScripts() &&
+ !mNonAsyncExternalScriptInsertedRequests.isEmpty() &&
+ mNonAsyncExternalScriptInsertedRequests.getFirst()->IsReadyToRun()) {
+ // Violate the HTML5 spec and execute these in the insertion order in
+ // order to make LABjs and the "order" plug-in for RequireJS work with
+ // their Gecko-sniffed code path. See
+ // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html
+ request = mNonAsyncExternalScriptInsertedRequests.StealFirst();
+ ProcessRequest(request);
+ }
+
+ if (mDeferCheckpointReached && mXSLTRequests.isEmpty()) {
+ while (ReadyToExecuteScripts() && !mDeferRequests.isEmpty() &&
+ mDeferRequests.getFirst()->IsReadyToRun()) {
+ request = mDeferRequests.StealFirst();
+ ProcessRequest(request);
+ }
+ }
+
+ while (!mPendingChildLoaders.IsEmpty() &&
+ ReadyToExecuteParserBlockingScripts()) {
+ RefPtr<ScriptLoader> child = mPendingChildLoaders[0];
+ mPendingChildLoaders.RemoveElementAt(0);
+ child->RemoveParserBlockingScriptExecutionBlocker();
+ }
+
+ if (mDeferCheckpointReached && mDocument && !mParserBlockingRequest &&
+ mNonAsyncExternalScriptInsertedRequests.isEmpty() &&
+ mXSLTRequests.isEmpty() && mDeferRequests.isEmpty() &&
+ MaybeRemovedDeferRequests()) {
+ return ProcessPendingRequests();
+ }
+
+ if (mDeferCheckpointReached && mDocument && !mParserBlockingRequest &&
+ mLoadingAsyncRequests.isEmpty() && mLoadedAsyncRequests.isEmpty() &&
+ mNonAsyncExternalScriptInsertedRequests.isEmpty() &&
+ mXSLTRequests.isEmpty() && mDeferRequests.isEmpty()) {
+ // No more pending scripts; time to unblock onload.
+ // OK to unblock onload synchronously here, since callers must be
+ // prepared for the world changing anyway.
+ mDeferCheckpointReached = false;
+ mDocument->UnblockOnload(true);
+ }
+}
+
+bool ScriptLoader::ReadyToExecuteParserBlockingScripts() {
+ // Make sure the SelfReadyToExecuteParserBlockingScripts check is first, so
+ // that we don't block twice on an ancestor.
+ if (!SelfReadyToExecuteParserBlockingScripts()) {
+ return false;
+ }
+
+ if (mDocument && mDocument->GetWindowContext()) {
+ for (WindowContext* wc =
+ mDocument->GetWindowContext()->GetParentWindowContext();
+ wc; wc = wc->GetParentWindowContext()) {
+ if (Document* doc = wc->GetDocument()) {
+ ScriptLoader* ancestor = doc->ScriptLoader();
+ if (!ancestor->SelfReadyToExecuteParserBlockingScripts() &&
+ ancestor->AddPendingChildLoader(this)) {
+ AddParserBlockingScriptExecutionBlocker();
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+template <typename Unit>
+static nsresult ConvertToUnicode(nsIChannel* aChannel, const uint8_t* aData,
+ uint32_t aLength,
+ const nsAString& aHintCharset,
+ Document* aDocument, Unit*& aBufOut,
+ size_t& aLengthOut) {
+ if (!aLength) {
+ aBufOut = nullptr;
+ aLengthOut = 0;
+ return NS_OK;
+ }
+
+ auto data = Span(aData, aLength);
+
+ // The encoding info precedence is as follows from high to low:
+ // The BOM
+ // HTTP Content-Type (if name recognized)
+ // charset attribute (if name recognized)
+ // The encoding of the document
+
+ UniquePtr<Decoder> unicodeDecoder;
+
+ const Encoding* encoding;
+ size_t bomLength;
+ Tie(encoding, bomLength) = Encoding::ForBOM(data);
+ if (encoding) {
+ unicodeDecoder = encoding->NewDecoderWithBOMRemoval();
+ }
+
+ if (!unicodeDecoder && aChannel) {
+ nsAutoCString label;
+ if (NS_SUCCEEDED(aChannel->GetContentCharset(label)) &&
+ (encoding = Encoding::ForLabel(label))) {
+ unicodeDecoder = encoding->NewDecoderWithoutBOMHandling();
+ }
+ }
+
+ if (!unicodeDecoder && (encoding = Encoding::ForLabel(aHintCharset))) {
+ unicodeDecoder = encoding->NewDecoderWithoutBOMHandling();
+ }
+
+ if (!unicodeDecoder && aDocument) {
+ unicodeDecoder =
+ aDocument->GetDocumentCharacterSet()->NewDecoderWithoutBOMHandling();
+ }
+
+ if (!unicodeDecoder) {
+ // Curiously, there are various callers that don't pass aDocument. The
+ // fallback in the old code was ISO-8859-1, which behaved like
+ // windows-1252.
+ unicodeDecoder = WINDOWS_1252_ENCODING->NewDecoderWithoutBOMHandling();
+ }
+
+ auto signalOOM = mozilla::MakeScopeExit([&aBufOut, &aLengthOut]() {
+ aBufOut = nullptr;
+ aLengthOut = 0;
+ });
+
+ CheckedInt<size_t> bufferLength =
+ ScriptDecoding<Unit>::MaxBufferLength(unicodeDecoder, aLength);
+ if (!bufferLength.isValid()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ CheckedInt<size_t> bufferByteSize = bufferLength * sizeof(Unit);
+ if (!bufferByteSize.isValid()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ aBufOut = static_cast<Unit*>(js_malloc(bufferByteSize.value()));
+ if (!aBufOut) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ signalOOM.release();
+ aLengthOut = ScriptDecoding<Unit>::DecodeInto(
+ unicodeDecoder, data, Span(aBufOut, bufferLength.value()),
+ /* aEndOfSource = */ true);
+ return NS_OK;
+}
+
+/* static */
+nsresult ScriptLoader::ConvertToUTF16(nsIChannel* aChannel,
+ const uint8_t* aData, uint32_t aLength,
+ const nsAString& aHintCharset,
+ Document* aDocument, char16_t*& aBufOut,
+ size_t& aLengthOut) {
+ return ConvertToUnicode(aChannel, aData, aLength, aHintCharset, aDocument,
+ aBufOut, aLengthOut);
+}
+
+/* static */
+nsresult ScriptLoader::ConvertToUTF8(nsIChannel* aChannel, const uint8_t* aData,
+ uint32_t aLength,
+ const nsAString& aHintCharset,
+ Document* aDocument, Utf8Unit*& aBufOut,
+ size_t& aLengthOut) {
+ return ConvertToUnicode(aChannel, aData, aLength, aHintCharset, aDocument,
+ aBufOut, aLengthOut);
+}
+
+nsresult ScriptLoader::OnStreamComplete(
+ nsIIncrementalStreamLoader* aLoader, ScriptLoadRequest* aRequest,
+ nsresult aChannelStatus, nsresult aSRIStatus,
+ SRICheckDataVerifier* aSRIDataVerifier) {
+ NS_ASSERTION(aRequest, "null request in stream complete handler");
+ NS_ENSURE_TRUE(aRequest, NS_ERROR_FAILURE);
+
+ nsresult rv = VerifySRI(aRequest, aLoader, aSRIStatus, aSRIDataVerifier);
+
+ if (NS_SUCCEEDED(rv)) {
+ // If we are loading from source, save the computed SRI hash or a dummy SRI
+ // hash in case we are going to save the bytecode of this script in the
+ // cache.
+ if (aRequest->IsSource()) {
+ uint32_t sriLength = 0;
+ rv = SaveSRIHash(aRequest, aSRIDataVerifier, &sriLength);
+ MOZ_ASSERT_IF(NS_SUCCEEDED(rv),
+ aRequest->mScriptBytecode.length() == sriLength);
+
+ aRequest->mBytecodeOffset = JS::AlignTranscodingBytecodeOffset(sriLength);
+ if (aRequest->mBytecodeOffset != sriLength) {
+ // We need extra padding after SRI hash.
+ if (!aRequest->mScriptBytecode.resize(aRequest->mBytecodeOffset)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = PrepareLoadedRequest(aRequest, aLoader, aChannelStatus);
+ }
+
+ if (NS_FAILED(rv)) {
+ ReportErrorToConsole(aRequest, rv);
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ // When loading bytecode, we verify the SRI hash. If it does not match the
+ // one from the document we restart the load, forcing us to load the source
+ // instead. If this happens do not remove the current request from script
+ // loader's data structures or fire any events.
+ if (aChannelStatus != NS_BINDING_RETARGETED) {
+ HandleLoadError(aRequest, rv);
+ }
+ }
+
+ // Process our request and/or any pending ones
+ ProcessPendingRequests();
+
+ return rv;
+}
+
+nsresult ScriptLoader::VerifySRI(ScriptLoadRequest* aRequest,
+ nsIIncrementalStreamLoader* aLoader,
+ nsresult aSRIStatus,
+ SRICheckDataVerifier* aSRIDataVerifier) const {
+ nsCOMPtr<nsIRequest> channelRequest;
+ aLoader->GetRequest(getter_AddRefs(channelRequest));
+ nsCOMPtr<nsIChannel> channel;
+ channel = do_QueryInterface(channelRequest);
+
+ nsresult rv = NS_OK;
+ if (!aRequest->mIntegrity.IsEmpty() && NS_SUCCEEDED((rv = aSRIStatus))) {
+ MOZ_ASSERT(aSRIDataVerifier);
+ MOZ_ASSERT(mReporter);
+
+ nsAutoCString sourceUri;
+ if (mDocument && mDocument->GetDocumentURI()) {
+ mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
+ }
+ rv = aSRIDataVerifier->Verify(aRequest->mIntegrity, channel, sourceUri,
+ mReporter);
+ if (channelRequest) {
+ mReporter->FlushReportsToConsole(
+ nsContentUtils::GetInnerWindowID(channelRequest));
+ } else {
+ mReporter->FlushConsoleReports(mDocument);
+ }
+ if (NS_FAILED(rv)) {
+ rv = NS_ERROR_SRI_CORRUPT;
+ }
+ }
+
+ return rv;
+}
+
+nsresult ScriptLoader::SaveSRIHash(ScriptLoadRequest* aRequest,
+ SRICheckDataVerifier* aSRIDataVerifier,
+ uint32_t* sriLength) const {
+ MOZ_ASSERT(aRequest->IsSource());
+ MOZ_ASSERT(aRequest->mScriptBytecode.empty());
+
+ uint32_t len;
+
+ // If the integrity metadata does not correspond to a valid hash function,
+ // IsComplete would be false.
+ if (!aRequest->mIntegrity.IsEmpty() && aSRIDataVerifier->IsComplete()) {
+ MOZ_ASSERT(aRequest->mScriptBytecode.length() == 0);
+
+ // Encode the SRI computed hash.
+ len = aSRIDataVerifier->DataSummaryLength();
+
+ if (!aRequest->mScriptBytecode.resize(len)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ DebugOnly<nsresult> res = aSRIDataVerifier->ExportDataSummary(
+ len, aRequest->mScriptBytecode.begin());
+ MOZ_ASSERT(NS_SUCCEEDED(res));
+ } else {
+ MOZ_ASSERT(aRequest->mScriptBytecode.length() == 0);
+
+ // Encode a dummy SRI hash.
+ len = SRICheckDataVerifier::EmptyDataSummaryLength();
+
+ if (!aRequest->mScriptBytecode.resize(len)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ DebugOnly<nsresult> res = SRICheckDataVerifier::ExportEmptyDataSummary(
+ len, aRequest->mScriptBytecode.begin());
+ MOZ_ASSERT(NS_SUCCEEDED(res));
+ }
+
+ // Verify that the exported and predicted length correspond.
+ mozilla::DebugOnly<uint32_t> srilen;
+ MOZ_ASSERT(NS_SUCCEEDED(SRICheckDataVerifier::DataSummaryLength(
+ len, aRequest->mScriptBytecode.begin(), &srilen)));
+ MOZ_ASSERT(srilen == len);
+
+ *sriLength = len;
+
+ return NS_OK;
+}
+
+void ScriptLoader::ReportErrorToConsole(ScriptLoadRequest* aRequest,
+ nsresult aResult) const {
+ MOZ_ASSERT(aRequest);
+
+ if (aRequest->IsPreload()) {
+ // Skip reporting errors in preload requests. If the request is actually
+ // used then we will report the error in ReportPreloadErrorsToConsole below.
+ aRequest->mUnreportedPreloadError = aResult;
+ return;
+ }
+
+ bool isScript = !aRequest->IsModuleRequest();
+ const char* message;
+ if (aResult == NS_ERROR_MALFORMED_URI) {
+ message = isScript ? "ScriptSourceMalformed" : "ModuleSourceMalformed";
+ } else if (aResult == NS_ERROR_DOM_BAD_URI) {
+ message = isScript ? "ScriptSourceNotAllowed" : "ModuleSourceNotAllowed";
+ } else if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
+ aResult)) {
+ // Blocking classifier error codes already show their own console messages.
+ return;
+ } else {
+ message = isScript ? "ScriptSourceLoadFailed" : "ModuleSourceLoadFailed";
+ }
+
+ AutoTArray<nsString, 1> params;
+ CopyUTF8toUTF16(aRequest->mURI->GetSpecOrDefault(), *params.AppendElement());
+
+ nsIScriptElement* element = aRequest->GetScriptElement();
+ uint32_t lineNo = element ? element->GetScriptLineNumber() : 0;
+ uint32_t columnNo = element ? element->GetScriptColumnNumber() : 0;
+
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ "Script Loader"_ns, mDocument,
+ nsContentUtils::eDOM_PROPERTIES, message,
+ params, nullptr, u""_ns, lineNo, columnNo);
+}
+
+void ScriptLoader::ReportPreloadErrorsToConsole(ScriptLoadRequest* aRequest) {
+ if (NS_FAILED(aRequest->mUnreportedPreloadError)) {
+ ReportErrorToConsole(aRequest, aRequest->mUnreportedPreloadError);
+ aRequest->mUnreportedPreloadError = NS_OK;
+ }
+
+ if (aRequest->IsModuleRequest()) {
+ for (auto childRequest : aRequest->AsModuleRequest()->mImports) {
+ ReportPreloadErrorsToConsole(childRequest);
+ }
+ }
+}
+
+void ScriptLoader::HandleLoadError(ScriptLoadRequest* aRequest,
+ nsresult aResult) {
+ /*
+ * Handle script not loading error because source was an tracking URL (or
+ * fingerprinting, cryptomining, etc).
+ * We make a note of this script node by including it in a dedicated
+ * array of blocked tracking nodes under its parent document.
+ */
+ if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
+ aResult)) {
+ nsCOMPtr<nsIContent> cont = do_QueryInterface(aRequest->GetScriptElement());
+ mDocument->AddBlockedNodeByClassifier(cont);
+ }
+
+ if (aRequest->IsModuleRequest() && !aRequest->mIsInline) {
+ auto request = aRequest->AsModuleRequest();
+ SetModuleFetchFinishedAndResumeWaitingRequests(request, aResult);
+ }
+
+ if (aRequest->mInDeferList) {
+ MOZ_ASSERT_IF(aRequest->IsModuleRequest(),
+ aRequest->AsModuleRequest()->IsTopLevel());
+ if (aRequest->isInList()) {
+ RefPtr<ScriptLoadRequest> req = mDeferRequests.Steal(aRequest);
+ FireScriptAvailable(aResult, req);
+ }
+ } else if (aRequest->mInAsyncList) {
+ MOZ_ASSERT_IF(aRequest->IsModuleRequest(),
+ aRequest->AsModuleRequest()->IsTopLevel());
+ if (aRequest->isInList()) {
+ RefPtr<ScriptLoadRequest> req = mLoadingAsyncRequests.Steal(aRequest);
+ FireScriptAvailable(aResult, req);
+ }
+ } else if (aRequest->mIsNonAsyncScriptInserted) {
+ if (aRequest->isInList()) {
+ RefPtr<ScriptLoadRequest> req =
+ mNonAsyncExternalScriptInsertedRequests.Steal(aRequest);
+ FireScriptAvailable(aResult, req);
+ }
+ } else if (aRequest->mIsXSLT) {
+ if (aRequest->isInList()) {
+ RefPtr<ScriptLoadRequest> req = mXSLTRequests.Steal(aRequest);
+ FireScriptAvailable(aResult, req);
+ }
+ } else if (aRequest->IsPreload()) {
+ if (aRequest->IsModuleRequest()) {
+ aRequest->Cancel();
+ }
+ if (aRequest->IsTopLevel()) {
+ MOZ_ALWAYS_TRUE(
+ mPreloads.RemoveElement(aRequest, PreloadRequestComparator()));
+ }
+ MOZ_ASSERT(!aRequest->isInList());
+ AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::LoadError);
+ } else if (aRequest->IsModuleRequest()) {
+ ModuleLoadRequest* modReq = aRequest->AsModuleRequest();
+ if (modReq->IsDynamicImport()) {
+ MOZ_ASSERT(modReq->IsTopLevel());
+ if (aRequest->isInList()) {
+ RefPtr<ScriptLoadRequest> req = mDynamicImportRequests.Steal(aRequest);
+ modReq->Cancel();
+ // FinishDynamicImport must happen exactly once for each dynamic import
+ // request. If the load is aborted we do it when we remove the request
+ // from mDynamicImportRequests.
+ FinishDynamicImportAndReject(modReq, aResult);
+ }
+ } else {
+ MOZ_ASSERT(!modReq->IsTopLevel());
+ MOZ_ASSERT(!modReq->isInList());
+ modReq->Cancel();
+ // The error is handled for the top level module.
+ }
+ } else if (mParserBlockingRequest == aRequest) {
+ MOZ_ASSERT(!aRequest->isInList());
+ mParserBlockingRequest = nullptr;
+ UnblockParser(aRequest);
+
+ // Ensure that we treat aRequest->GetScriptElement() as our current
+ // parser-inserted script while firing onerror on it.
+ MOZ_ASSERT(aRequest->GetScriptElement()->GetParserCreated());
+ nsCOMPtr<nsIScriptElement> oldParserInsertedScript =
+ mCurrentParserInsertedScript;
+ mCurrentParserInsertedScript = aRequest->GetScriptElement();
+ FireScriptAvailable(aResult, aRequest);
+ ContinueParserAsync(aRequest);
+ mCurrentParserInsertedScript = oldParserInsertedScript;
+ } else {
+ // This happens for blocking requests cancelled by ParsingComplete().
+ // Ignore cancellation status for link-preload requests, as cancellation can
+ // be omitted for them when SRI is stronger on consumer tags.
+ MOZ_ASSERT(aRequest->IsCanceled() || aRequest->IsLinkPreloadScript());
+ MOZ_ASSERT(!aRequest->isInList());
+ }
+}
+
+void ScriptLoader::UnblockParser(ScriptLoadRequest* aParserBlockingRequest) {
+ aParserBlockingRequest->GetScriptElement()->UnblockParser();
+}
+
+void ScriptLoader::ContinueParserAsync(
+ ScriptLoadRequest* aParserBlockingRequest) {
+ aParserBlockingRequest->GetScriptElement()->ContinueParserAsync();
+}
+
+uint32_t ScriptLoader::NumberOfProcessors() {
+ if (mNumberOfProcessors > 0) return mNumberOfProcessors;
+
+ int32_t numProcs = PR_GetNumberOfProcessors();
+ if (numProcs > 0) mNumberOfProcessors = numProcs;
+ return mNumberOfProcessors;
+}
+
+static bool IsInternalURIScheme(nsIURI* uri) {
+ return uri->SchemeIs("moz-extension") || uri->SchemeIs("resource") ||
+ uri->SchemeIs("chrome");
+}
+
+bool ScriptLoader::ShouldCompileOffThread(ScriptLoadRequest* aRequest) {
+ if (NumberOfProcessors() <= 1) {
+ return false;
+ }
+ if (aRequest == mParserBlockingRequest) {
+ return true;
+ }
+ if (SpeculativeOMTParsingEnabled()) {
+ // Processing non async inserted scripts too early can potentially delay the
+ // load event from firing so focus on other scripts instead.
+ if (aRequest->mIsNonAsyncScriptInserted &&
+ !StaticPrefs::
+ dom_script_loader_external_scripts_speculate_non_parser_inserted_enabled()) {
+ return false;
+ }
+
+ // Async and link preload scripts do not need to be parsed right away.
+ if (aRequest->IsAsyncScript() &&
+ !StaticPrefs::
+ dom_script_loader_external_scripts_speculate_async_enabled()) {
+ return false;
+ }
+
+ if (aRequest->IsLinkPreloadScript() &&
+ !StaticPrefs::
+ dom_script_loader_external_scripts_speculate_link_preload_enabled()) {
+ return false;
+ }
+
+ return true;
+ }
+ return false;
+}
+
+nsresult ScriptLoader::PrepareLoadedRequest(ScriptLoadRequest* aRequest,
+ nsIIncrementalStreamLoader* aLoader,
+ nsresult aStatus) {
+ if (NS_FAILED(aStatus)) {
+ return aStatus;
+ }
+
+ if (aRequest->IsCanceled()) {
+ return NS_BINDING_ABORTED;
+ }
+ MOZ_ASSERT(aRequest->IsLoading());
+ CollectScriptTelemetry(aRequest);
+
+ // If we don't have a document, then we need to abort further
+ // evaluation.
+ if (!mDocument) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // If the load returned an error page, then we need to abort
+ nsCOMPtr<nsIRequest> req;
+ nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
+ NS_ASSERTION(req, "StreamLoader's request went away prematurely");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(req);
+ if (httpChannel) {
+ bool requestSucceeded;
+ rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
+ if (NS_SUCCEEDED(rv) && !requestSucceeded) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsAutoCString sourceMapURL;
+ if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) {
+ aRequest->mSourceMapURL = Some(NS_ConvertUTF8toUTF16(sourceMapURL));
+ }
+
+ nsCOMPtr<nsIClassifiedChannel> classifiedChannel = do_QueryInterface(req);
+ MOZ_ASSERT(classifiedChannel);
+ if (classifiedChannel &&
+ classifiedChannel->IsThirdPartyTrackingResource()) {
+ aRequest->SetIsTracking();
+ }
+ }
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
+ // If this load was subject to a CORS check, don't flag it with a separate
+ // origin principal, so that it will treat our document's principal as the
+ // origin principal. Module loads always use CORS.
+ if (!aRequest->IsModuleRequest() && aRequest->CORSMode() == CORS_NONE) {
+ rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ channel, getter_AddRefs(aRequest->mOriginPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // This assertion could fire errorously if we ran out of memory when
+ // inserting the request in the array. However it's an unlikely case
+ // so if you see this assertion it is likely something else that is
+ // wrong, especially if you see it more than once.
+ NS_ASSERTION(mDeferRequests.Contains(aRequest) ||
+ mLoadingAsyncRequests.Contains(aRequest) ||
+ mNonAsyncExternalScriptInsertedRequests.Contains(aRequest) ||
+ mXSLTRequests.Contains(aRequest) ||
+ mDynamicImportRequests.Contains(aRequest) ||
+ (aRequest->IsModuleRequest() &&
+ !aRequest->AsModuleRequest()->IsTopLevel() &&
+ !aRequest->isInList()) ||
+ mPreloads.Contains(aRequest, PreloadRequestComparator()) ||
+ mParserBlockingRequest == aRequest,
+ "aRequest should be pending!");
+
+ nsCOMPtr<nsIURI> uri;
+ rv = channel->GetOriginalURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Fixup moz-extension: and resource: URIs, because the channel URI will
+ // point to file:, which won't be allowed to load.
+ if (uri && IsInternalURIScheme(uri)) {
+ aRequest->mBaseURL = uri;
+ } else {
+ channel->GetURI(getter_AddRefs(aRequest->mBaseURL));
+ }
+
+ if (aRequest->IsModuleRequest()) {
+ MOZ_ASSERT(aRequest->IsSource());
+ ModuleLoadRequest* request = aRequest->AsModuleRequest();
+
+ // When loading a module, only responses with a JavaScript MIME type are
+ // acceptable.
+ nsAutoCString mimeType;
+ channel->GetContentType(mimeType);
+ NS_ConvertUTF8toUTF16 typeString(mimeType);
+ if (!nsContentUtils::IsJavascriptMIMEType(typeString)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Attempt to compile off main thread.
+ bool couldCompile = false;
+ rv = AttemptAsyncScriptCompile(request, &couldCompile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (couldCompile) {
+ return NS_OK;
+ }
+
+ // Otherwise compile it right away and start fetching descendents.
+ return ProcessFetchedModuleSource(request);
+ }
+
+ // The script is now loaded and ready to run.
+ aRequest->SetReady();
+
+ // If speculative parsing is enabled attempt to compile all
+ // external scripts off-main-thread. Otherwise, only omt compile scripts
+ // blocking the parser.
+ if (ShouldCompileOffThread(aRequest)) {
+ MOZ_ASSERT(!aRequest->IsModuleRequest());
+ bool couldCompile = false;
+ nsresult rv = AttemptAsyncScriptCompile(aRequest, &couldCompile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (couldCompile) {
+ MOZ_ASSERT(aRequest->mProgress == ScriptLoadRequest::Progress::eCompiling,
+ "Request should be off-thread compiling now.");
+ return NS_OK;
+ }
+
+ // If off-thread compile was rejected, continue with regular processing.
+ }
+
+ MaybeMoveToLoadedList(aRequest);
+
+ return NS_OK;
+}
+
+void ScriptLoader::DeferCheckpointReached() {
+ if (mDeferEnabled) {
+ // Have to check because we apparently get ParsingComplete
+ // without BeginDeferringScripts in some cases
+ mDeferCheckpointReached = true;
+ }
+
+ mDeferEnabled = false;
+ ProcessPendingRequests();
+}
+
+void ScriptLoader::ParsingComplete(bool aTerminated) {
+ if (!aTerminated) {
+ return;
+ }
+ mDeferRequests.Clear();
+ mLoadingAsyncRequests.Clear();
+ mLoadedAsyncRequests.Clear();
+ mNonAsyncExternalScriptInsertedRequests.Clear();
+ mXSLTRequests.Clear();
+
+ for (ScriptLoadRequest* req = mDynamicImportRequests.getFirst(); req;
+ req = req->getNext()) {
+ req->Cancel();
+ // FinishDynamicImport must happen exactly once for each dynamic import
+ // request. If the load is aborted we do it when we remove the request
+ // from mDynamicImportRequests.
+ FinishDynamicImportAndReject(req->AsModuleRequest(), NS_ERROR_ABORT);
+ }
+ mDynamicImportRequests.Clear();
+
+ if (mParserBlockingRequest) {
+ mParserBlockingRequest->Cancel();
+ mParserBlockingRequest = nullptr;
+ }
+
+ // Cancel any unused scripts that were compiled speculatively
+ for (size_t i = 0; i < mPreloads.Length(); i++) {
+ mPreloads[i].mRequest->MaybeCancelOffThreadScript();
+ }
+
+ // Have to call this even if aTerminated so we'll correctly unblock
+ // onload and all.
+ DeferCheckpointReached();
+}
+
+void ScriptLoader::PreloadURI(nsIURI* aURI, const nsAString& aCharset,
+ const nsAString& aType,
+ const nsAString& aCrossOrigin,
+ const nsAString& aIntegrity, bool aScriptFromHead,
+ bool aAsync, bool aDefer, bool aNoModule,
+ bool aLinkPreload,
+ const ReferrerPolicy aReferrerPolicy) {
+ NS_ENSURE_TRUE_VOID(mDocument);
+ // Check to see if scripts has been turned off.
+ if (!mEnabled || !mDocument->IsScriptEnabled()) {
+ return;
+ }
+
+ ScriptKind scriptKind = ScriptKind::eClassic;
+
+ if (mDocument->ModuleScriptsEnabled()) {
+ // Don't load nomodule scripts.
+ if (aNoModule) {
+ return;
+ }
+
+ static const char kASCIIWhitespace[] = "\t\n\f\r ";
+
+ nsAutoString type(aType);
+ type.Trim(kASCIIWhitespace);
+ if (type.LowerCaseEqualsASCII("module")) {
+ scriptKind = ScriptKind::eModule;
+ }
+ }
+
+ if (scriptKind == ScriptKind::eClassic && !aType.IsEmpty() &&
+ !nsContentUtils::IsJavascriptMIMEType(aType)) {
+ // Unknown type. Don't load it.
+ return;
+ }
+
+ SRIMetadata sriMetadata;
+ GetSRIMetadata(aIntegrity, &sriMetadata);
+
+ RefPtr<ScriptLoadRequest> request = CreateLoadRequest(
+ scriptKind, aURI, nullptr, mDocument->NodePrincipal(),
+ Element::StringToCORSMode(aCrossOrigin), sriMetadata, aReferrerPolicy);
+ request->mIsInline = false;
+ request->mScriptFromHead = aScriptFromHead;
+ request->SetScriptMode(aDefer, aAsync, aLinkPreload);
+ request->SetIsPreloadRequest();
+
+ if (LOG_ENABLED()) {
+ nsAutoCString url;
+ aURI->GetAsciiSpec(url);
+ LOG(("ScriptLoadRequest (%p): Created preload request for %s",
+ request.get(), url.get()));
+ }
+
+ nsresult rv = StartLoad(request);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ PreloadInfo* pi = mPreloads.AppendElement();
+ pi->mRequest = request;
+ pi->mCharset = aCharset;
+}
+
+void ScriptLoader::AddDeferRequest(ScriptLoadRequest* aRequest) {
+ MOZ_ASSERT(aRequest->IsDeferredScript());
+ MOZ_ASSERT(!aRequest->mInDeferList && !aRequest->mInAsyncList);
+
+ aRequest->mInDeferList = true;
+ mDeferRequests.AppendElement(aRequest);
+ if (mDeferEnabled && aRequest == mDeferRequests.getFirst() && mDocument &&
+ !mBlockingDOMContentLoaded) {
+ MOZ_ASSERT(mDocument->GetReadyStateEnum() == Document::READYSTATE_LOADING);
+ mBlockingDOMContentLoaded = true;
+ mDocument->BlockDOMContentLoaded();
+ }
+}
+
+void ScriptLoader::AddAsyncRequest(ScriptLoadRequest* aRequest) {
+ MOZ_ASSERT(aRequest->IsAsyncScript());
+ MOZ_ASSERT(!aRequest->mInDeferList && !aRequest->mInAsyncList);
+
+ aRequest->mInAsyncList = true;
+ if (aRequest->IsReadyToRun()) {
+ mLoadedAsyncRequests.AppendElement(aRequest);
+ } else {
+ mLoadingAsyncRequests.AppendElement(aRequest);
+ }
+}
+
+void ScriptLoader::MaybeMoveToLoadedList(ScriptLoadRequest* aRequest) {
+ MOZ_ASSERT(aRequest->IsReadyToRun());
+
+ // If it's async, move it to the loaded list. aRequest->mInAsyncList really
+ // _should_ be in a list, but the consequences if it's not are bad enough we
+ // want to avoid trying to move it if it's not.
+ if (aRequest->mInAsyncList) {
+ MOZ_ASSERT(aRequest->isInList());
+ if (aRequest->isInList()) {
+ RefPtr<ScriptLoadRequest> req = mLoadingAsyncRequests.Steal(aRequest);
+ mLoadedAsyncRequests.AppendElement(req);
+ }
+ }
+}
+
+bool ScriptLoader::MaybeRemovedDeferRequests() {
+ if (mDeferRequests.isEmpty() && mDocument && mBlockingDOMContentLoaded) {
+ mBlockingDOMContentLoaded = false;
+ mDocument->UnblockDOMContentLoaded();
+ return true;
+ }
+ return false;
+}
+
+DocGroup* ScriptLoader::GetDocGroup() const { return mDocument->GetDocGroup(); }
+
+void ScriptLoader::BeginDeferringScripts() {
+ mDeferEnabled = true;
+ if (mDeferCheckpointReached) {
+ // We already completed a parse and were just waiting for some async
+ // scripts to load (and were already blocking the load event waiting for
+ // that to happen), when document.open() happened and now we're doing a
+ // new parse. We shouldn't block the load event again, but _should_ reset
+ // mDeferCheckpointReached to false. It'll get set to true again when the
+ // DeferCheckpointReached call that corresponds to this
+ // BeginDeferringScripts call happens (on document.close()), since we just
+ // set mDeferEnabled to true.
+ mDeferCheckpointReached = false;
+ } else {
+ if (mDocument) {
+ mDocument->BlockOnload();
+ }
+ }
+}
+
+nsAutoScriptLoaderDisabler::nsAutoScriptLoaderDisabler(Document* aDoc) {
+ mLoader = aDoc->ScriptLoader();
+ mWasEnabled = mLoader->GetEnabled();
+ if (mWasEnabled) {
+ mLoader->SetEnabled(false);
+ }
+}
+
+nsAutoScriptLoaderDisabler::~nsAutoScriptLoaderDisabler() {
+ if (mWasEnabled) {
+ mLoader->SetEnabled(true);
+ }
+}
+
+#undef TRACE_FOR_TEST
+#undef TRACE_FOR_TEST_BOOL
+#undef TRACE_FOR_TEST_NONE
+
+#undef LOG
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/script/ScriptLoader.h b/dom/script/ScriptLoader.h
new file mode 100644
index 0000000000..4228f7c758
--- /dev/null
+++ b/dom/script/ScriptLoader.h
@@ -0,0 +1,780 @@
+/* -*- 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 "js/TypeDecls.h"
+#include "nsCOMPtr.h"
+#include "nsRefPtrHashtable.h"
+#include "nsIScriptElement.h"
+#include "nsCOMArray.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsTArray.h"
+#include "nsINode.h"
+#include "nsIObserver.h"
+#include "nsIScriptLoaderObserver.h"
+#include "nsURIHashKey.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/dom/LoadedScript.h"
+#include "mozilla/dom/ScriptLoadRequest.h"
+#include "mozilla/MaybeOneOf.h"
+#include "mozilla/MozPromise.h"
+#include "ScriptKind.h"
+
+class nsCycleCollectionTraversalCallback;
+class nsIChannel;
+class nsIConsoleReportCollector;
+class nsIContent;
+class nsIIncrementalStreamLoader;
+class nsIPrincipal;
+class nsIScriptGlobalObject;
+class nsIURI;
+
+namespace JS {
+
+class CompileOptions;
+
+template <typename UnitT>
+class SourceText;
+
+} // namespace JS
+
+namespace mozilla {
+
+class LazyLogModule;
+union Utf8Unit;
+
+namespace dom {
+
+class AutoJSAPI;
+class DocGroup;
+class Document;
+class LoadedScript;
+class ModuleLoadRequest;
+class ModuleScript;
+class SRICheckDataVerifier;
+class SRIMetadata;
+class ScriptLoadHandler;
+class ScriptLoader;
+class ScriptRequestProcessor;
+
+enum class ReferrerPolicy : uint8_t;
+
+class AsyncCompileShutdownObserver final : public nsIObserver {
+ ~AsyncCompileShutdownObserver() { Unregister(); }
+
+ public:
+ explicit AsyncCompileShutdownObserver(ScriptLoader* aLoader)
+ : mScriptLoader(aLoader) {}
+
+ void OnShutdown();
+ void Unregister();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ private:
+ // Defined during registration in ScriptLoader constructor, and
+ // cleared during destructor, ScriptLoader::Destroy() or Shutdown.
+ ScriptLoader* mScriptLoader;
+};
+
+//////////////////////////////////////////////////////////////
+// Script loader implementation
+//////////////////////////////////////////////////////////////
+
+class ScriptLoader final : public nsISupports {
+ class MOZ_STACK_CLASS AutoCurrentScriptUpdater {
+ public:
+ AutoCurrentScriptUpdater(ScriptLoader* aScriptLoader,
+ nsIScriptElement* aCurrentScript)
+ : mOldScript(aScriptLoader->mCurrentScript),
+ mScriptLoader(aScriptLoader) {
+ nsCOMPtr<nsINode> node = do_QueryInterface(aCurrentScript);
+ mScriptLoader->mCurrentScript =
+ node && !node->IsInShadowTree() ? aCurrentScript : nullptr;
+ }
+
+ ~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(Document* 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;
+ }
+
+ /**
+ * Check whether to speculatively OMT parse scripts as soon as
+ * they are fetched, even if not a parser blocking request.
+ * Controlled by
+ * dom.script_loader.external_scripts.speculative_omt_parse_enabled
+ */
+ bool SpeculativeOMTParsingEnabled() const {
+ return mSpeculativeOMTParsingEnabled;
+ }
+
+ /**
+ * 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. If the buffer begins with a
+ * BOM, it is interpreted as that encoding; otherwise the first of |aChannel|,
+ * |aHintCharset|, or |aDocument| that provides a recognized encoding is used,
+ * or Windows-1252 if none of them do.
+ *
+ * Encoding errors in the buffer are converted to replacement characters, so
+ * allocation failure is the only way this function can fail.
+ *
+ * @param aChannel Channel corresponding to the data. May be null.
+ * @param aData The data to convert
+ * @param aLength Length of the data
+ * @param aHintCharset Character set hint (e.g., from a charset attribute).
+ * @param aDocument Document which the data is loaded for. May be null.
+ * @param aBufOut [out] fresh char16_t array containing data converted to
+ * Unicode. Caller must js_free() this data when finished
+ * with it.
+ * @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,
+ Document* aDocument, char16_t*& aBufOut,
+ size_t& aLengthOut);
+
+ static inline nsresult ConvertToUTF16(nsIChannel* aChannel,
+ const uint8_t* aData, uint32_t aLength,
+ const nsAString& aHintCharset,
+ Document* aDocument,
+ JS::UniqueTwoByteChars& aBufOut,
+ size_t& aLengthOut) {
+ char16_t* bufOut;
+ nsresult rv = ConvertToUTF16(aChannel, aData, aLength, aHintCharset,
+ aDocument, bufOut, aLengthOut);
+ if (NS_SUCCEEDED(rv)) {
+ aBufOut.reset(bufOut);
+ }
+ return rv;
+ };
+
+ /**
+ * Convert the given buffer to a UTF-8 string. If the buffer begins with a
+ * BOM, it is interpreted as that encoding; otherwise the first of |aChannel|,
+ * |aHintCharset|, or |aDocument| that provides a recognized encoding is used,
+ * or Windows-1252 if none of them do.
+ *
+ * Encoding errors in the buffer are converted to replacement characters, so
+ * allocation failure is the only way this function can fail.
+ *
+ * @param aChannel Channel corresponding to the data. May be null.
+ * @param aData The data to convert
+ * @param aLength Length of the data
+ * @param aHintCharset Character set hint (e.g., from a charset attribute).
+ * @param aDocument Document which the data is loaded for. May be null.
+ * @param aBufOut [out] fresh Utf8Unit array containing data converted to
+ * Unicode. Caller must js_free() this data when finished
+ * with it.
+ * @param aLengthOut [out] Length of array returned in aBufOut in UTF-8 code
+ * units (i.e. in bytes).
+ */
+ static nsresult ConvertToUTF8(nsIChannel* aChannel, const uint8_t* aData,
+ uint32_t aLength, const nsAString& aHintCharset,
+ Document* aDocument, Utf8Unit*& aBufOut,
+ size_t& aLengthOut);
+
+ /**
+ * Handle the completion of a stream. This is called by the
+ * ScriptLoadHandler object which observes the IncrementalStreamLoader
+ * loading the script. The streamed content is expected to be stored on the
+ * aRequest argument.
+ */
+ nsresult OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
+ ScriptLoadRequest* aRequest,
+ nsresult aChannelStatus, nsresult aSRIStatus,
+ SRICheckDataVerifier* aSRIDataVerifier);
+
+ /**
+ * Returns wether any request is queued, and not executed yet.
+ */
+ bool HasPendingRequests();
+
+ /**
+ * 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();
+
+ /**
+ * 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
+ * do nothing.
+ */
+ void ParsingComplete(bool aTerminated);
+
+ /**
+ * Notifies the script loader that the checkpoint to begin execution of defer
+ * scripts has been reached. This is either the end of of the document parse
+ * or the end of loading of parser-inserted stylesheets, whatever happens
+ * last.
+ *
+ * Otherwise, it will stop 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 DeferCheckpointReached();
+
+ /**
+ * 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,
+ bool aAsync, bool aDefer, bool aNoModule,
+ bool aLinkPreload,
+ const 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) {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier. Else, change the return type to void.
+ mPendingChildLoaders.AppendElement(aChild);
+ return true;
+ }
+
+ mozilla::dom::DocGroup* GetDocGroup() const;
+
+ /**
+ * Register the fact that we saw the load event, and that we need to save the
+ * bytecode at the next loop cycle unless new scripts are waiting in the
+ * pipeline.
+ */
+ void LoadEventFired();
+
+ /**
+ * Destroy and prevent the ScriptLoader or the ScriptLoadRequests from owning
+ * any references to the JSScript or to the Request which might be used for
+ * caching the encoded bytecode.
+ */
+ void Destroy();
+
+ /**
+ * Implement the HostResolveImportedModule abstract operation.
+ *
+ * Resolve a module specifier string and look this up in the module
+ * map, returning the result. This is only called for previously
+ * loaded modules and always succeeds.
+ *
+ * @param aReferencingPrivate A JS::Value which is either undefined
+ * or contains a LoadedScript private pointer.
+ * @param aSpecifier The module specifier.
+ * @param aModuleOut This is set to the module found.
+ */
+ static void ResolveImportedModule(JSContext* aCx,
+ JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSString*> aSpecifier,
+ JS::MutableHandle<JSObject*> aModuleOut);
+
+ void StartDynamicImport(ModuleLoadRequest* aRequest);
+
+ /**
+ * Shorthand Wrapper for JSAPI FinishDynamicImport function for the reject
+ * case where we do not have `aEvaluationPromise`. As there is no evaluation
+ * Promise, JS::FinishDynamicImport will always reject.
+ *
+ * @param aRequest
+ * The module load request for the dynamic module.
+ * @param aResult
+ * The result of running ModuleEvaluate -- If this is successful, then
+ * we can await the associated EvaluationPromise.
+ */
+ void FinishDynamicImportAndReject(ModuleLoadRequest* aRequest,
+ nsresult aResult);
+
+ /**
+ * Wrapper for JSAPI FinishDynamicImport function. Takes an optional argument
+ * `aEvaluationPromise` which, if null, exits early.
+ *
+ * This is the non-tla version, which works with modules which return
+ * completion records.
+ *
+ * @param aCX
+ * The JSContext for the module.
+ * @param aRequest
+ * The module load request for the dynamic module.
+ * @param aResult
+ * The result of running ModuleEvaluate
+ */
+ void FinishDynamicImport_NoTLA(JSContext* aCx, ModuleLoadRequest* aRequest,
+ nsresult aResult);
+
+ /**
+ * Wrapper for JSAPI FinishDynamicImport function. Takes an optional argument
+ * `aEvaluationPromise` which, if null, exits early.
+ *
+ * This is the Top Level Await version, which works with modules which return
+ * promises.
+ *
+ * @param aCX
+ * The JSContext for the module.
+ * @param aRequest
+ * The module load request for the dynamic module.
+ * @param aResult
+ * The result of running ModuleEvaluate -- If this is successful, then
+ * we can await the associated EvaluationPromise.
+ * @param aEvaluationPromise
+ * The evaluation promise returned from evaluating the module. If this
+ * is null, JS::FinishDynamicImport will reject the dynamic import
+ * module promise.
+ */
+ void FinishDynamicImport(JSContext* aCx, ModuleLoadRequest* aRequest,
+ nsresult aResult,
+ JS::Handle<JSObject*> aEvaluationPromise);
+
+ /*
+ * Get the currently active script. This is used as the initiating script when
+ * executing timeout handler scripts.
+ */
+ static LoadedScript* GetActiveScript(JSContext* aCx);
+
+ Document* GetDocument() const { return mDocument; }
+
+ /**
+ * Called by shutdown observer.
+ */
+ void Shutdown();
+
+ private:
+ virtual ~ScriptLoader();
+
+ void EnsureModuleHooksInitialized();
+
+ ScriptLoadRequest* CreateLoadRequest(ScriptKind aKind, nsIURI* aURI,
+ nsIScriptElement* aElement,
+ nsIPrincipal* aTriggeringPrincipal,
+ mozilla::CORSMode aCORSMode,
+ const SRIMetadata& aIntegrity,
+ ReferrerPolicy aReferrerPolicy);
+
+ /**
+ * 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);
+
+ bool ProcessExternalScript(nsIScriptElement* aElement, ScriptKind aScriptKind,
+ nsAutoString aTypeAttr,
+ nsIContent* aScriptContent);
+
+ bool ProcessInlineScript(nsIScriptElement* aElement, ScriptKind aScriptKind);
+
+ ScriptLoadRequest* LookupPreloadRequest(nsIScriptElement* aElement,
+ ScriptKind aScriptKind,
+ const SRIMetadata& aSRIMetadata);
+
+ void GetSRIMetadata(const nsAString& aIntegrityAttr,
+ SRIMetadata* aMetadataOut);
+
+ /**
+ * Given a script element, get the referrer policy should be applied to load
+ * requests.
+ */
+ ReferrerPolicy GetReferrerPolicy(nsIScriptElement* aElement);
+
+ /**
+ * Helper function to check the content policy for a given request.
+ */
+ static nsresult CheckContentPolicy(Document* aDocument, nsISupports* aContext,
+ const nsAString& aType,
+ ScriptLoadRequest* aRequest);
+
+ /**
+ * Helper function to determine whether an about: page loads a chrome: URI.
+ * Please note that this function only returns true if:
+ * * the about: page uses a ContentPrincipal with scheme about:
+ * * the about: page is not linkable from content
+ * (e.g. the function will return false for about:blank or about:srcdoc)
+ */
+ static bool IsAboutPageLoadingChromeURI(ScriptLoadRequest* aRequest,
+ Document* aDocument);
+
+ /**
+ * Start a load for aRequest's URI.
+ */
+ nsresult StartLoad(ScriptLoadRequest* aRequest);
+
+ /**
+ * Abort the current stream, and re-start with a new load request from scratch
+ * without requesting any alternate data. Returns NS_BINDING_RETARGETED on
+ * success, as this error code is used to abort the input stream.
+ */
+ nsresult RestartLoad(ScriptLoadRequest* aRequest);
+
+ void HandleLoadError(ScriptLoadRequest* aRequest, nsresult aResult);
+
+ /**
+ * 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 VerifySRI(ScriptLoadRequest* aRequest,
+ nsIIncrementalStreamLoader* aLoader, nsresult aSRIStatus,
+ SRICheckDataVerifier* aSRIDataVerifier) const;
+
+ nsresult SaveSRIHash(ScriptLoadRequest* aRequest,
+ SRICheckDataVerifier* aSRIDataVerifier,
+ uint32_t* sriLength) const;
+
+ void ReportErrorToConsole(ScriptLoadRequest* aRequest,
+ nsresult aResult) const;
+ void ReportPreloadErrorsToConsole(ScriptLoadRequest* aRequest);
+
+ nsresult AttemptAsyncScriptCompile(ScriptLoadRequest* aRequest,
+ bool* aCouldCompileOut);
+ nsresult ProcessRequest(ScriptLoadRequest* aRequest);
+ void ProcessDynamicImport(ModuleLoadRequest* aRequest);
+ nsresult CompileOffThreadOrProcessRequest(ScriptLoadRequest* aRequest);
+ void FireScriptAvailable(nsresult aResult, ScriptLoadRequest* aRequest);
+ void FireScriptEvaluated(nsresult aResult, ScriptLoadRequest* aRequest);
+ nsresult EvaluateScript(ScriptLoadRequest* aRequest);
+
+ /**
+ * Queue the current script load request to be saved, when the page
+ * initialization ends. The page initialization end is defined as being the
+ * time when the load event got received, and when no more scripts are waiting
+ * to be executed.
+ */
+ void RegisterForBytecodeEncoding(ScriptLoadRequest* aRequest);
+
+ /**
+ * Check if all conditions are met, i-e that the onLoad event fired and that
+ * no more script have to be processed. If all conditions are met, queue an
+ * event to encode all the bytecode and save them on the cache.
+ */
+ void MaybeTriggerBytecodeEncoding();
+
+ /**
+ * Iterate over all script load request and save the bytecode of executed
+ * functions on the cache provided by the channel.
+ */
+ void EncodeBytecode();
+ void EncodeRequestBytecode(JSContext* aCx, ScriptLoadRequest* aRequest);
+
+ void GiveUpBytecodeEncoding();
+
+ 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);
+
+ void AddDeferRequest(ScriptLoadRequest* aRequest);
+ void AddAsyncRequest(ScriptLoadRequest* aRequest);
+ bool MaybeRemovedDeferRequests();
+
+ bool ShouldCompileOffThread(ScriptLoadRequest* aRequest);
+
+ void MaybeMoveToLoadedList(ScriptLoadRequest* aRequest);
+
+ using MaybeSourceText =
+ mozilla::MaybeOneOf<JS::SourceText<char16_t>, JS::SourceText<Utf8Unit>>;
+
+ // Get source text. On success |aMaybeSource| will contain either UTF-8 or
+ // UTF-16 source; on failure it will remain in its initial state.
+ MOZ_MUST_USE nsresult GetScriptSource(JSContext* aCx,
+ ScriptLoadRequest* aRequest,
+ MaybeSourceText* aMaybeSource);
+
+ void SetModuleFetchStarted(ModuleLoadRequest* aRequest);
+ void SetModuleFetchFinishedAndResumeWaitingRequests(
+ ModuleLoadRequest* aRequest, nsresult aResult);
+
+ bool IsFetchingModule(ModuleLoadRequest* aRequest) const;
+
+ bool ModuleMapContainsURL(nsIURI* aURL) const;
+ RefPtr<mozilla::GenericNonExclusivePromise> WaitForModuleFetch(nsIURI* aURL);
+ ModuleScript* GetFetchedModule(nsIURI* aURL) const;
+
+ friend JSObject* HostResolveImportedModule(
+ JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSString*> aSpecifier);
+
+ // Returns wether we should save the bytecode of this script after the
+ // execution of the script.
+ static bool ShouldCacheBytecode(ScriptLoadRequest* aRequest);
+
+ nsresult CreateModuleScript(ModuleLoadRequest* aRequest);
+ nsresult ProcessFetchedModuleSource(ModuleLoadRequest* aRequest);
+ void CheckModuleDependenciesLoaded(ModuleLoadRequest* aRequest);
+ void ProcessLoadedModuleTree(ModuleLoadRequest* aRequest);
+ bool InstantiateModuleTree(ModuleLoadRequest* aRequest);
+ JS::Value FindFirstParseError(ModuleLoadRequest* aRequest);
+ void StartFetchingModuleDependencies(ModuleLoadRequest* aRequest);
+
+ RefPtr<mozilla::GenericPromise> StartFetchingModuleAndDependencies(
+ ModuleLoadRequest* aParent, nsIURI* aURI);
+
+ nsresult InitDebuggerDataForModuleTree(JSContext* aCx,
+ ModuleLoadRequest* aRequest);
+
+ void RunScriptWhenSafe(ScriptLoadRequest* aRequest);
+
+ /**
+ * Wait for any unused off thread compilations to finish and then
+ * cancel them.
+ */
+ void CancelScriptLoadRequests();
+
+ Document* 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;
+ ScriptLoadRequestList mDynamicImportRequests;
+ RefPtr<ScriptLoadRequest> mParserBlockingRequest;
+
+ // List of script load request that are holding a buffer which has to be saved
+ // on the cache.
+ ScriptLoadRequestList mBytecodeEncodingQueue;
+
+ // 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 mSpeculativeOMTParsingEnabled;
+ bool mDeferCheckpointReached;
+ bool mBlockingDOMContentLoaded;
+ bool mLoadEventFired;
+ bool mGiveUpEncoding;
+
+ // Module map
+ nsRefPtrHashtable<nsURIHashKey, mozilla::GenericNonExclusivePromise::Private>
+ mFetchingModules;
+ nsRefPtrHashtable<nsURIHashKey, ModuleScript> mFetchedModules;
+
+ nsCOMPtr<nsIConsoleReportCollector> mReporter;
+
+ // ShutdownObserver for off thread compilations
+ RefPtr<AsyncCompileShutdownObserver> mShutdownObserver;
+
+ // Logging
+ public:
+ static LazyLogModule gCspPRLog;
+ static LazyLogModule gScriptLoaderLog;
+};
+
+class nsAutoScriptLoaderDisabler {
+ public:
+ explicit nsAutoScriptLoaderDisabler(Document* aDoc);
+
+ ~nsAutoScriptLoaderDisabler();
+
+ bool mWasEnabled;
+ RefPtr<ScriptLoader> mLoader;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ScriptLoader_h
diff --git a/dom/script/ScriptSettings.cpp b/dom/script/ScriptSettings.cpp
new file mode 100644
index 0000000000..589b46999c
--- /dev/null
+++ b/dom/script/ScriptSettings.cpp
@@ -0,0 +1,841 @@
+/* -*- 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 "mozilla/dom/ScriptSettings.h"
+#include "LoadedScript.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/ThreadLocal.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/JSExecutionManager.h"
+#include "mozilla/dom/WorkerPrivate.h"
+
+#include "jsapi.h"
+#include "js/CompilationAndEvaluation.h"
+#include "js/friend/ErrorMessages.h" // JSMSG_OUT_OF_MEMORY
+#include "js/Warnings.h" // JS::{Get,}WarningReporter
+#include "xpcpublic.h"
+#include "nsIGlobalObject.h"
+#include "nsIDocShell.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptContext.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsTArray.h"
+#include "nsJSUtils.h"
+#include "nsDOMJSUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+JSObject* GetElementCallback(JSContext* aCx, JS::HandleValue aValue) {
+ JS::RootedValue privateValue(aCx, aValue);
+ MOZ_ASSERT(!privateValue.isObjectOrNull() && !privateValue.isUndefined());
+ LoadedScript* script = static_cast<LoadedScript*>(privateValue.toPrivate());
+
+ if (!script->GetFetchOptions()) {
+ return nullptr;
+ }
+
+ JS::Rooted<JS::Value> elementValue(aCx);
+ {
+ nsCOMPtr<Element> domElement = script->GetFetchOptions()->mElement;
+ if (!domElement) {
+ return nullptr;
+ }
+
+ JSObject* globalObject =
+ domElement->OwnerDoc()->GetScopeObject()->GetGlobalJSObject();
+ JSAutoRealm ar(aCx, globalObject);
+
+ nsresult rv = nsContentUtils::WrapNative(aCx, domElement, &elementValue,
+ /* aAllowWrapping = */ true);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+ }
+ return elementValue.toObjectOrNull();
+}
+
+static MOZ_THREAD_LOCAL(ScriptSettingsStackEntry*) sScriptSettingsTLS;
+
+// Assert if it's not safe to run script. The helper class
+// AutoAllowLegacyScriptExecution allows to allow-list
+// legacy cases where it's actually not safe to run script.
+#ifdef DEBUG
+static void AssertIfNotSafeToRunScript() {
+ // if it's safe to run script, then there is nothing to do here.
+ if (nsContentUtils::IsSafeToRunScript()) {
+ return;
+ }
+
+ // auto allowing legacy script execution is fine for now.
+ if (AutoAllowLegacyScriptExecution::IsAllowed()) {
+ return;
+ }
+
+ MOZ_ASSERT(false, "is it safe to run script?");
+}
+#endif
+
+class ScriptSettingsStack {
+ public:
+ static ScriptSettingsStackEntry* Top() { return sScriptSettingsTLS.get(); }
+
+ static void Push(ScriptSettingsStackEntry* aEntry) {
+ MOZ_ASSERT(!aEntry->mOlder);
+ // Whenever JSAPI use is disabled, the next stack entry pushed must
+ // not be an AutoIncumbentScript.
+ MOZ_ASSERT_IF(!Top() || Top()->NoJSAPI(), !aEntry->IsIncumbentScript());
+ // Whenever the top entry is not an incumbent canidate, the next stack entry
+ // pushed must not be an AutoIncumbentScript.
+ MOZ_ASSERT_IF(Top() && !Top()->IsIncumbentCandidate(),
+ !aEntry->IsIncumbentScript());
+
+ aEntry->mOlder = Top();
+ sScriptSettingsTLS.set(aEntry);
+ }
+
+ static void Pop(ScriptSettingsStackEntry* aEntry) {
+ MOZ_ASSERT(aEntry == Top());
+ sScriptSettingsTLS.set(aEntry->mOlder);
+ }
+
+ static nsIGlobalObject* IncumbentGlobal() {
+ ScriptSettingsStackEntry* entry = Top();
+ while (entry) {
+ if (entry->IsIncumbentCandidate()) {
+ return entry->mGlobalObject;
+ }
+ entry = entry->mOlder;
+ }
+ return nullptr;
+ }
+
+ static ScriptSettingsStackEntry* EntryPoint() {
+ ScriptSettingsStackEntry* entry = Top();
+ while (entry) {
+ if (entry->IsEntryCandidate()) {
+ return entry;
+ }
+ entry = entry->mOlder;
+ }
+ return nullptr;
+ }
+
+ static nsIGlobalObject* EntryGlobal() {
+ ScriptSettingsStackEntry* entry = EntryPoint();
+ if (!entry) {
+ return nullptr;
+ }
+ return entry->mGlobalObject;
+ }
+
+#ifdef DEBUG
+ static ScriptSettingsStackEntry* TopNonIncumbentScript() {
+ ScriptSettingsStackEntry* entry = Top();
+ while (entry) {
+ if (!entry->IsIncumbentScript()) {
+ return entry;
+ }
+ entry = entry->mOlder;
+ }
+ return nullptr;
+ }
+#endif // DEBUG
+};
+
+static unsigned long gRunToCompletionListeners = 0;
+
+void UseEntryScriptProfiling() {
+ MOZ_ASSERT(NS_IsMainThread());
+ ++gRunToCompletionListeners;
+}
+
+void UnuseEntryScriptProfiling() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(gRunToCompletionListeners > 0);
+ --gRunToCompletionListeners;
+}
+
+void InitScriptSettings() {
+ bool success = sScriptSettingsTLS.init();
+ if (!success) {
+ MOZ_CRASH();
+ }
+
+ sScriptSettingsTLS.set(nullptr);
+}
+
+void DestroyScriptSettings() {
+ MOZ_ASSERT(sScriptSettingsTLS.get() == nullptr);
+}
+
+ScriptSettingsStackEntry::ScriptSettingsStackEntry(nsIGlobalObject* aGlobal,
+ Type aType)
+ : mGlobalObject(aGlobal), mType(aType), mOlder(nullptr) {
+ MOZ_ASSERT_IF(IsIncumbentCandidate() && !NoJSAPI(), mGlobalObject);
+ MOZ_ASSERT(!mGlobalObject || mGlobalObject->HasJSGlobal(),
+ "Must have an actual JS global for the duration on the stack");
+ MOZ_ASSERT(
+ !mGlobalObject ||
+ JS_IsGlobalObject(mGlobalObject->GetGlobalJSObjectPreserveColor()),
+ "No outer windows allowed");
+}
+
+ScriptSettingsStackEntry::~ScriptSettingsStackEntry() {
+ // We must have an actual JS global for the entire time this is on the stack.
+ MOZ_ASSERT_IF(mGlobalObject, mGlobalObject->HasJSGlobal());
+}
+
+// If the entry or incumbent global ends up being something that the subject
+// principal doesn't subsume, we don't want to use it. This never happens on
+// the web, but can happen with asymmetric privilege relationships (i.e.
+// ExpandedPrincipal and System Principal).
+//
+// The most correct thing to use instead would be the topmost global on the
+// callstack whose principal is subsumed by the subject principal. But that's
+// hard to compute, so we just substitute the global of the current
+// compartment. In practice, this is fine.
+//
+// Note that in particular things like:
+//
+// |SpecialPowers.wrap(crossOriginWindow).eval(open())|
+//
+// trigger this case. Although both the entry global and the current global
+// have normal principals, the use of Gecko-specific System-Principaled JS
+// puts the code from two different origins on the callstack at once, which
+// doesn't happen normally on the web.
+static nsIGlobalObject* ClampToSubject(nsIGlobalObject* aGlobalOrNull) {
+ if (!aGlobalOrNull || !NS_IsMainThread()) {
+ return aGlobalOrNull;
+ }
+
+ nsIPrincipal* globalPrin = aGlobalOrNull->PrincipalOrNull();
+ NS_ENSURE_TRUE(globalPrin, GetCurrentGlobal());
+ if (!nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller()
+ ->SubsumesConsideringDomain(globalPrin)) {
+ return GetCurrentGlobal();
+ }
+
+ return aGlobalOrNull;
+}
+
+nsIGlobalObject* GetEntryGlobal() {
+ return ClampToSubject(ScriptSettingsStack::EntryGlobal());
+}
+
+Document* GetEntryDocument() {
+ nsIGlobalObject* global = GetEntryGlobal();
+ nsCOMPtr<nsPIDOMWindowInner> entryWin = do_QueryInterface(global);
+
+ return entryWin ? entryWin->GetExtantDoc() : nullptr;
+}
+
+nsIGlobalObject* GetIncumbentGlobal() {
+ // We need the current JSContext in order to check the JS for
+ // scripted frames that may have appeared since anyone last
+ // manipulated the stack. If it's null, that means that there
+ // must be no entry global on the stack, and therefore no incumbent
+ // global either.
+ JSContext* cx = nsContentUtils::GetCurrentJSContext();
+ if (!cx) {
+ MOZ_ASSERT(ScriptSettingsStack::EntryGlobal() == nullptr);
+ return nullptr;
+ }
+
+ // See what the JS engine has to say. If we've got a scripted caller
+ // override in place, the JS engine will lie to us and pretend that
+ // there's nothing on the JS stack, which will cause us to check the
+ // incumbent script stack below.
+ if (JSObject* global = JS::GetScriptedCallerGlobal(cx)) {
+ return ClampToSubject(xpc::NativeGlobal(global));
+ }
+
+ // Ok, nothing from the JS engine. Let's use whatever's on the
+ // explicit stack.
+ return ClampToSubject(ScriptSettingsStack::IncumbentGlobal());
+}
+
+nsIGlobalObject* GetCurrentGlobal() {
+ JSContext* cx = nsContentUtils::GetCurrentJSContext();
+ if (!cx) {
+ return nullptr;
+ }
+
+ JSObject* global = JS::CurrentGlobalOrNull(cx);
+ if (!global) {
+ return nullptr;
+ }
+
+ return xpc::NativeGlobal(global);
+}
+
+nsIPrincipal* GetWebIDLCallerPrincipal() {
+ MOZ_ASSERT(NS_IsMainThread());
+ ScriptSettingsStackEntry* entry = ScriptSettingsStack::EntryPoint();
+
+ // If we have an entry point that is not NoJSAPI, we know it must be an
+ // AutoEntryScript.
+ if (!entry || entry->NoJSAPI()) {
+ return nullptr;
+ }
+ AutoEntryScript* aes = static_cast<AutoEntryScript*>(entry);
+
+ return aes->mWebIDLCallerPrincipal;
+}
+
+bool IsJSAPIActive() {
+ ScriptSettingsStackEntry* topEntry = ScriptSettingsStack::Top();
+ return topEntry && !topEntry->NoJSAPI();
+}
+
+namespace danger {
+JSContext* GetJSContext() { return CycleCollectedJSContext::Get()->Context(); }
+} // namespace danger
+
+JS::RootingContext* RootingCx() {
+ return CycleCollectedJSContext::Get()->RootingCx();
+}
+
+AutoJSAPI::AutoJSAPI()
+ : ScriptSettingsStackEntry(nullptr, eJSAPI),
+ mCx(nullptr),
+ mIsMainThread(false) // For lack of anything better
+{}
+
+AutoJSAPI::~AutoJSAPI() {
+ if (!mCx) {
+ // No need to do anything here: we never managed to Init, so can't have an
+ // exception on our (nonexistent) JSContext. We also don't need to restore
+ // any state on it. Finally, we never made it to pushing outselves onto the
+ // ScriptSettingsStack, so shouldn't pop.
+ MOZ_ASSERT(ScriptSettingsStack::Top() != this);
+ return;
+ }
+
+ ReportException();
+
+ if (mOldWarningReporter.isSome()) {
+ JS::SetWarningReporter(cx(), mOldWarningReporter.value());
+ }
+
+ ScriptSettingsStack::Pop(this);
+}
+
+void WarningOnlyErrorReporter(JSContext* aCx, JSErrorReport* aRep);
+
+void AutoJSAPI::InitInternal(nsIGlobalObject* aGlobalObject, JSObject* aGlobal,
+ JSContext* aCx, bool aIsMainThread) {
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aCx == danger::GetJSContext());
+ MOZ_ASSERT(aIsMainThread == NS_IsMainThread());
+ MOZ_ASSERT(bool(aGlobalObject) == bool(aGlobal));
+ MOZ_ASSERT_IF(aGlobalObject,
+ aGlobalObject->GetGlobalJSObjectPreserveColor() == aGlobal);
+#ifdef DEBUG
+ bool haveException = JS_IsExceptionPending(aCx);
+#endif // DEBUG
+
+ mCx = aCx;
+ mIsMainThread = aIsMainThread;
+ if (aGlobal) {
+ JS::AssertObjectIsNotGray(aGlobal);
+ }
+ mAutoNullableRealm.emplace(mCx, aGlobal);
+ mGlobalObject = aGlobalObject;
+
+ ScriptSettingsStack::Push(this);
+
+ mOldWarningReporter.emplace(JS::GetWarningReporter(aCx));
+
+ JS::SetWarningReporter(aCx, WarningOnlyErrorReporter);
+ JS::SetGetElementCallback(aCx, &GetElementCallback);
+
+#ifdef DEBUG
+ if (haveException) {
+ JS::Rooted<JS::Value> exn(aCx);
+ JS_GetPendingException(aCx, &exn);
+
+ JS_ClearPendingException(aCx);
+ if (exn.isObject()) {
+ JS::Rooted<JSObject*> exnObj(aCx, &exn.toObject());
+
+ // Make sure we can actually read things from it. This UncheckedUwrap is
+ // safe because we're only getting data for a debug printf. In
+ // particular, we do not expose this data to anyone, which is very
+ // important; otherwise it could be a cross-origin information leak.
+ exnObj = js::UncheckedUnwrap(exnObj);
+ JSAutoRealm ar(aCx, exnObj);
+
+ nsAutoJSString stack, filename, name, message;
+ int32_t line;
+
+ JS::Rooted<JS::Value> tmp(aCx);
+ if (!JS_GetProperty(aCx, exnObj, "filename", &tmp)) {
+ JS_ClearPendingException(aCx);
+ }
+ if (tmp.isUndefined()) {
+ if (!JS_GetProperty(aCx, exnObj, "fileName", &tmp)) {
+ JS_ClearPendingException(aCx);
+ }
+ }
+
+ if (!filename.init(aCx, tmp)) {
+ JS_ClearPendingException(aCx);
+ }
+
+ if (!JS_GetProperty(aCx, exnObj, "stack", &tmp) ||
+ !stack.init(aCx, tmp)) {
+ JS_ClearPendingException(aCx);
+ }
+
+ if (!JS_GetProperty(aCx, exnObj, "name", &tmp) || !name.init(aCx, tmp)) {
+ JS_ClearPendingException(aCx);
+ }
+
+ if (!JS_GetProperty(aCx, exnObj, "message", &tmp) ||
+ !message.init(aCx, tmp)) {
+ JS_ClearPendingException(aCx);
+ }
+
+ if (!JS_GetProperty(aCx, exnObj, "lineNumber", &tmp) ||
+ !JS::ToInt32(aCx, tmp, &line)) {
+ JS_ClearPendingException(aCx);
+ line = 0;
+ }
+
+ printf_stderr("PREEXISTING EXCEPTION OBJECT: '%s: %s'\n%s:%d\n%s\n",
+ NS_ConvertUTF16toUTF8(name).get(),
+ NS_ConvertUTF16toUTF8(message).get(),
+ NS_ConvertUTF16toUTF8(filename).get(), line,
+ NS_ConvertUTF16toUTF8(stack).get());
+ } else {
+ // It's a primitive... not much we can do other than stringify it.
+ nsAutoJSString exnStr;
+ if (!exnStr.init(aCx, exn)) {
+ JS_ClearPendingException(aCx);
+ }
+
+ printf_stderr("PREEXISTING EXCEPTION PRIMITIVE: %s\n",
+ NS_ConvertUTF16toUTF8(exnStr).get());
+ }
+ MOZ_ASSERT(false, "We had an exception; we should not have");
+ }
+#endif // DEBUG
+}
+
+AutoJSAPI::AutoJSAPI(nsIGlobalObject* aGlobalObject, bool aIsMainThread,
+ Type aType)
+ : ScriptSettingsStackEntry(aGlobalObject, aType),
+ mIsMainThread(aIsMainThread) {
+ MOZ_ASSERT(aGlobalObject);
+ MOZ_ASSERT(aGlobalObject->HasJSGlobal(), "Must have a JS global");
+ MOZ_ASSERT(aIsMainThread == NS_IsMainThread());
+
+ InitInternal(aGlobalObject, aGlobalObject->GetGlobalJSObject(),
+ danger::GetJSContext(), aIsMainThread);
+}
+
+void AutoJSAPI::Init() {
+ MOZ_ASSERT(!mCx, "An AutoJSAPI should only be initialised once");
+
+ InitInternal(/* aGlobalObject */ nullptr, /* aGlobal */ nullptr,
+ danger::GetJSContext(), NS_IsMainThread());
+}
+
+bool AutoJSAPI::Init(nsIGlobalObject* aGlobalObject, JSContext* aCx) {
+ MOZ_ASSERT(!mCx, "An AutoJSAPI should only be initialised once");
+ MOZ_ASSERT(aCx);
+
+ if (NS_WARN_IF(!aGlobalObject)) {
+ return false;
+ }
+
+ JSObject* global = aGlobalObject->GetGlobalJSObject();
+ if (NS_WARN_IF(!global)) {
+ return false;
+ }
+
+ InitInternal(aGlobalObject, global, aCx, NS_IsMainThread());
+ return true;
+}
+
+bool AutoJSAPI::Init(nsIGlobalObject* aGlobalObject) {
+ return Init(aGlobalObject, danger::GetJSContext());
+}
+
+bool AutoJSAPI::Init(JSObject* aObject) {
+ MOZ_ASSERT(!js::IsCrossCompartmentWrapper(aObject));
+ return Init(xpc::NativeGlobal(aObject));
+}
+
+bool AutoJSAPI::Init(nsPIDOMWindowInner* aWindow, JSContext* aCx) {
+ return Init(nsGlobalWindowInner::Cast(aWindow), aCx);
+}
+
+bool AutoJSAPI::Init(nsPIDOMWindowInner* aWindow) {
+ return Init(nsGlobalWindowInner::Cast(aWindow));
+}
+
+bool AutoJSAPI::Init(nsGlobalWindowInner* aWindow, JSContext* aCx) {
+ return Init(static_cast<nsIGlobalObject*>(aWindow), aCx);
+}
+
+bool AutoJSAPI::Init(nsGlobalWindowInner* aWindow) {
+ return Init(static_cast<nsIGlobalObject*>(aWindow));
+}
+
+// Even with autoJSAPIOwnsErrorReporting, the JS engine still sends warning
+// reports to the JSErrorReporter as soon as they are generated. These go
+// directly to the console, so we can handle them easily here.
+//
+// Eventually, SpiderMonkey will have a special-purpose callback for warnings
+// only.
+void WarningOnlyErrorReporter(JSContext* aCx, JSErrorReport* aRep) {
+ MOZ_ASSERT(aRep->isWarning());
+ if (!NS_IsMainThread()) {
+ // Reporting a warning on workers is a bit complicated because we have to
+ // climb our parent chain until we get to the main thread. So go ahead and
+ // just go through the worker or worklet ReportError codepath here.
+ //
+ // That said, it feels like we should be able to short-circuit things a bit
+ // here by posting an appropriate runnable to the main thread directly...
+ // Worth looking into sometime.
+ CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::GetFor(aCx);
+ MOZ_ASSERT(ccjscx);
+
+ ccjscx->ReportError(aRep, JS::ConstUTF8CharsZ());
+ return;
+ }
+
+ RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
+ nsGlobalWindowInner* win = xpc::CurrentWindowOrNull(aCx);
+ xpcReport->Init(aRep, nullptr, nsContentUtils::IsSystemCaller(aCx),
+ win ? win->WindowID() : 0);
+ xpcReport->LogToConsole();
+}
+
+void AutoJSAPI::ReportException() {
+ if (!HasException()) {
+ return;
+ }
+
+ // AutoJSAPI uses a JSAutoNullableRealm, and may be in a null realm
+ // when the destructor is called. However, the JS engine requires us
+ // to be in a realm when we fetch the pending exception. In this case,
+ // we enter the privileged junk scope and don't dispatch any error events.
+ JS::Rooted<JSObject*> errorGlobal(cx(), JS::CurrentGlobalOrNull(cx()));
+ if (!errorGlobal) {
+ if (mIsMainThread) {
+ errorGlobal = xpc::PrivilegedJunkScope();
+ } else {
+ errorGlobal = GetCurrentThreadWorkerGlobal();
+ if (!errorGlobal) {
+ // We might be reporting an error in debugger code that ran before the
+ // worker's global was created. Use the debugger global instead.
+ errorGlobal = GetCurrentThreadWorkerDebuggerGlobal();
+ if (NS_WARN_IF(!errorGlobal)) {
+ // An exception may have been thrown on attempt to create a global
+ // and now there is no realm from which to fetch the exception.
+ // Give up.
+ ClearException();
+ return;
+ }
+ }
+ }
+ }
+ MOZ_ASSERT(JS_IsGlobalObject(errorGlobal));
+ JSAutoRealm ar(cx(), errorGlobal);
+ JS::ExceptionStack exnStack(cx());
+ JS::ErrorReportBuilder jsReport(cx());
+ if (StealExceptionAndStack(&exnStack) &&
+ jsReport.init(cx(), exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
+ if (mIsMainThread) {
+ RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
+
+ RefPtr<nsGlobalWindowInner> inner = xpc::WindowOrNull(errorGlobal);
+ bool isChrome =
+ nsContentUtils::ObjectPrincipal(errorGlobal)->IsSystemPrincipal();
+ xpcReport->Init(jsReport.report(), jsReport.toStringResult().c_str(),
+ isChrome, inner ? inner->WindowID() : 0);
+ if (inner && jsReport.report()->errorNumber != JSMSG_OUT_OF_MEMORY) {
+ JS::RootingContext* rcx = JS::RootingContext::get(cx());
+ DispatchScriptErrorEvent(inner, rcx, xpcReport, exnStack.exception(),
+ exnStack.stack());
+ } else {
+ JS::Rooted<JSObject*> stack(cx());
+ JS::Rooted<JSObject*> stackGlobal(cx());
+ xpc::FindExceptionStackForConsoleReport(inner, exnStack.exception(),
+ exnStack.stack(), &stack,
+ &stackGlobal);
+ // This error is not associated with a specific window,
+ // so omit the exception value to mitigate potential leaks.
+ xpcReport->LogToConsoleWithStack(inner, JS::NothingHandleValue, stack,
+ stackGlobal);
+ }
+ } else {
+ // On a worker or worklet, we just use the error reporting mechanism and
+ // don't bother with xpc::ErrorReport. This will ensure that all the
+ // right worker events (which are a lot more complicated than in the
+ // window case) get fired.
+ CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::GetFor(cx());
+ MOZ_ASSERT(ccjscx);
+ // Before invoking ReportError, put the exception back on the context,
+ // because it may want to put it in its error events and has no other way
+ // to get hold of it. After we invoke ReportError, clear the exception on
+ // cx(), just in case ReportError didn't.
+ JS::SetPendingExceptionStack(cx(), exnStack);
+ ccjscx->ReportError(jsReport.report(), jsReport.toStringResult());
+ ClearException();
+ }
+ } else {
+ NS_WARNING("OOMed while acquiring uncaught exception from JSAPI");
+ ClearException();
+ }
+}
+
+bool AutoJSAPI::PeekException(JS::MutableHandle<JS::Value> aVal) {
+ MOZ_ASSERT_IF(mIsMainThread, IsStackTop());
+ MOZ_ASSERT(HasException());
+ MOZ_ASSERT(js::GetContextRealm(cx()));
+ return JS_GetPendingException(cx(), aVal);
+}
+
+bool AutoJSAPI::StealException(JS::MutableHandle<JS::Value> aVal) {
+ JS::ExceptionStack exnStack(cx());
+ if (!StealExceptionAndStack(&exnStack)) {
+ return false;
+ }
+ aVal.set(exnStack.exception());
+ return true;
+}
+
+bool AutoJSAPI::StealExceptionAndStack(JS::ExceptionStack* aExnStack) {
+ MOZ_ASSERT_IF(mIsMainThread, IsStackTop());
+ MOZ_ASSERT(HasException());
+ MOZ_ASSERT(js::GetContextRealm(cx()));
+
+ return JS::StealPendingExceptionStack(cx(), aExnStack);
+}
+
+#ifdef DEBUG
+bool AutoJSAPI::IsStackTop() const {
+ return ScriptSettingsStack::TopNonIncumbentScript() == this;
+}
+#endif // DEBUG
+
+AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject,
+ const char* aReason, bool aIsMainThread)
+ : AutoJSAPI(aGlobalObject, aIsMainThread, eEntryScript),
+ mWebIDLCallerPrincipal(nullptr)
+ // This relies on us having a cx() because the AutoJSAPI constructor
+ // already ran.
+ ,
+ mCallerOverride(cx())
+#ifdef MOZ_GECKO_PROFILER
+ ,
+ mAutoProfilerLabel(
+ "", aReason, JS::ProfilingCategoryPair::JS,
+ uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS))
+#endif
+ ,
+ mJSThreadExecution(aGlobalObject, aIsMainThread) {
+ MOZ_ASSERT(aGlobalObject);
+
+ if (aIsMainThread) {
+#ifdef DEBUG
+ AssertIfNotSafeToRunScript();
+#endif
+ if (gRunToCompletionListeners > 0) {
+ mDocShellEntryMonitor.emplace(cx(), aReason);
+ }
+ mScriptActivity.emplace(true);
+ }
+}
+
+AutoEntryScript::AutoEntryScript(JSObject* aObject, const char* aReason,
+ bool aIsMainThread)
+ : AutoEntryScript(xpc::NativeGlobal(aObject), aReason, aIsMainThread) {
+ // xpc::NativeGlobal uses JS::GetNonCCWObjectGlobal, which asserts that
+ // aObject is not a CCW.
+}
+
+AutoEntryScript::~AutoEntryScript() = default;
+
+AutoEntryScript::DocshellEntryMonitor::DocshellEntryMonitor(JSContext* aCx,
+ const char* aReason)
+ : JS::dbg::AutoEntryMonitor(aCx), mReason(aReason) {}
+
+void AutoEntryScript::DocshellEntryMonitor::Entry(
+ JSContext* aCx, JSFunction* aFunction, JSScript* aScript,
+ JS::Handle<JS::Value> aAsyncStack, const char* aAsyncCause) {
+ JS::Rooted<JSFunction*> rootedFunction(aCx);
+ if (aFunction) {
+ rootedFunction = aFunction;
+ }
+ JS::Rooted<JSScript*> rootedScript(aCx);
+ if (aScript) {
+ rootedScript = aScript;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = xpc::CurrentWindowOrNull(aCx);
+ if (!window || !window->GetDocShell() ||
+ !window->GetDocShell()->GetRecordProfileTimelineMarkers()) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocShell> docShellForJSRunToCompletion = window->GetDocShell();
+
+ nsAutoJSString functionName;
+ if (rootedFunction) {
+ JS::Rooted<JSString*> displayId(aCx,
+ JS_GetFunctionDisplayId(rootedFunction));
+ if (displayId) {
+ if (!functionName.init(aCx, displayId)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+ }
+ }
+
+ nsString filename;
+ uint32_t lineNumber = 0;
+ if (!rootedScript) {
+ rootedScript = JS_GetFunctionScript(aCx, rootedFunction);
+ }
+ if (rootedScript) {
+ CopyUTF8toUTF16(MakeStringSpan(JS_GetScriptFilename(rootedScript)),
+ filename);
+ lineNumber = JS_GetScriptBaseLineNumber(aCx, rootedScript);
+ }
+
+ if (!filename.IsEmpty() || !functionName.IsEmpty()) {
+ docShellForJSRunToCompletion->NotifyJSRunToCompletionStart(
+ mReason, functionName, filename, lineNumber, aAsyncStack, aAsyncCause);
+ }
+}
+
+void AutoEntryScript::DocshellEntryMonitor::Exit(JSContext* aCx) {
+ nsCOMPtr<nsPIDOMWindowInner> window = xpc::CurrentWindowOrNull(aCx);
+ // Not really worth checking GetRecordProfileTimelineMarkers here.
+ if (window && window->GetDocShell()) {
+ nsCOMPtr<nsIDocShell> docShellForJSRunToCompletion = window->GetDocShell();
+ docShellForJSRunToCompletion->NotifyJSRunToCompletionStop();
+ }
+}
+
+AutoIncumbentScript::AutoIncumbentScript(nsIGlobalObject* aGlobalObject)
+ : ScriptSettingsStackEntry(aGlobalObject, eIncumbentScript),
+ mCallerOverride(nsContentUtils::GetCurrentJSContext()) {
+ ScriptSettingsStack::Push(this);
+}
+
+AutoIncumbentScript::~AutoIncumbentScript() { ScriptSettingsStack::Pop(this); }
+
+AutoNoJSAPI::AutoNoJSAPI(JSContext* aCx)
+ : ScriptSettingsStackEntry(nullptr, eNoJSAPI),
+ JSAutoNullableRealm(aCx, nullptr),
+ mCx(aCx) {
+ // Make sure we don't seem to have an incumbent global due to
+ // whatever script is running right now.
+ JS::HideScriptedCaller(aCx);
+
+ // Make sure the fallback GetIncumbentGlobal() behavior and
+ // GetEntryGlobal() both return null.
+ ScriptSettingsStack::Push(this);
+}
+
+AutoNoJSAPI::~AutoNoJSAPI() {
+ ScriptSettingsStack::Pop(this);
+ JS::UnhideScriptedCaller(mCx);
+}
+
+} // namespace dom
+
+AutoJSContext::AutoJSContext() : mCx(nullptr) {
+ JS::AutoSuppressGCAnalysis nogc;
+ MOZ_ASSERT(!mCx, "mCx should not be initialized!");
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (dom::IsJSAPIActive()) {
+ mCx = dom::danger::GetJSContext();
+ } else {
+ mJSAPI.Init();
+ mCx = mJSAPI.cx();
+ }
+}
+
+AutoJSContext::operator JSContext*() const { return mCx; }
+
+AutoSafeJSContext::AutoSafeJSContext() : AutoJSAPI() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ DebugOnly<bool> ok = Init(xpc::UnprivilegedJunkScope());
+ MOZ_ASSERT(ok,
+ "This is quite odd. We should have crashed in the "
+ "xpc::NativeGlobal() call if xpc::UnprivilegedJunkScope() "
+ "returned null, and inited correctly otherwise!");
+}
+
+AutoSlowOperation::AutoSlowOperation() : mIsMainThread(NS_IsMainThread()) {
+ if (mIsMainThread) {
+ mScriptActivity.emplace(true);
+ }
+}
+
+void AutoSlowOperation::CheckForInterrupt() {
+ // For now we support only main thread!
+ if (mIsMainThread) {
+ // JS_CheckForInterrupt expects us to be in a realm, so we use a junk scope.
+ // In principle, it doesn't matter which one we use, since we aren't really
+ // running scripts here, and none of our interrupt callbacks can stop
+ // scripts in a junk scope anyway. In practice, though, the privileged junk
+ // scope is the same as the JSM global, and therefore always exists, while
+ // the unprivileged junk scope is created lazily, and may not exist until we
+ // try to use it. So we use the former for the sake of efficiency.
+ dom::AutoJSAPI jsapi;
+ MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
+ JS_CheckForInterrupt(jsapi.cx());
+ }
+}
+
+AutoAllowLegacyScriptExecution::AutoAllowLegacyScriptExecution() {
+#ifdef DEBUG
+ // no need to do that dance if we are off the main thread,
+ // because we only assert if we are on the main thread!
+ if (!NS_IsMainThread()) {
+ return;
+ }
+ sAutoAllowLegacyScriptExecution++;
+#endif
+}
+
+AutoAllowLegacyScriptExecution::~AutoAllowLegacyScriptExecution() {
+#ifdef DEBUG
+ // no need to do that dance if we are off the main thread,
+ // because we only assert if we are on the main thread!
+ if (!NS_IsMainThread()) {
+ return;
+ }
+ sAutoAllowLegacyScriptExecution--;
+ MOZ_ASSERT(sAutoAllowLegacyScriptExecution >= 0,
+ "how can the stack guard produce a value less than 0?");
+#endif
+}
+
+int AutoAllowLegacyScriptExecution::sAutoAllowLegacyScriptExecution = 0;
+
+/*static*/
+bool AutoAllowLegacyScriptExecution::IsAllowed() {
+ return sAutoAllowLegacyScriptExecution > 0;
+}
+
+} // namespace mozilla
diff --git a/dom/script/ScriptSettings.h b/dom/script/ScriptSettings.h
new file mode 100644
index 0000000000..289ebe41ff
--- /dev/null
+++ b/dom/script/ScriptSettings.h
@@ -0,0 +1,527 @@
+/* -*- 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/. */
+
+/* Utilities for managing the script settings object stack defined in webapps */
+
+#ifndef mozilla_dom_ScriptSettings_h
+#define mozilla_dom_ScriptSettings_h
+
+#include "MainThreadUtils.h"
+#include "xpcpublic.h"
+
+#include "mozilla/dom/JSExecutionManager.h"
+#include "mozilla/Maybe.h"
+
+#include "jsapi.h"
+#include "js/Debug.h"
+#include "js/Warnings.h" // JS::WarningReporter
+
+#ifdef MOZ_GECKO_PROFILER
+# include "GeckoProfiler.h"
+#endif
+
+class JSFunction;
+class JSObject;
+class JSScript;
+class nsIGlobalObject;
+class nsIPrincipal;
+class nsPIDOMWindowInner;
+class nsGlobalWindowInner;
+class nsIScriptContext;
+struct JSContext;
+
+namespace JS {
+class ExceptionStack;
+class Value;
+} // namespace JS
+
+namespace mozilla {
+namespace dom {
+
+class Document;
+
+/*
+ * Per thread setup/teardown routines. Init and Destroy should be invoked
+ * once each, at startup and shutdown of the script runtime (respectively).
+ */
+void InitScriptSettings();
+void DestroyScriptSettings();
+
+/*
+ * Static helpers in ScriptSettings which track the number of listeners
+ * of Javascript RunToCompletion events. These should be used by the code in
+ * nsDocShell::SetRecordProfileTimelineMarkers to indicate to script
+ * settings that script run-to-completion needs to be monitored.
+ * SHOULD BE CALLED ONLY BY MAIN THREAD.
+ */
+void UseEntryScriptProfiling();
+void UnuseEntryScriptProfiling();
+
+// To implement a web-compatible browser, it is often necessary to obtain the
+// global object that is "associated" with the currently-running code. This
+// process is made more complicated by the fact that, historically, different
+// algorithms have operated with different definitions of the "associated"
+// global.
+//
+// HTML5 formalizes this into two concepts: the "incumbent global" and the
+// "entry global". The incumbent global corresponds to the global of the
+// current script being executed, whereas the entry global corresponds to the
+// global of the script where the current JS execution began.
+//
+// There is also a potentially-distinct third global that is determined by the
+// current compartment. This roughly corresponds with the notion of Realms in
+// ECMAScript.
+//
+// Suppose some event triggers an event listener in window |A|, which invokes a
+// scripted function in window |B|, which invokes the |window.location.href|
+// setter in window |C|. The entry global would be |A|, the incumbent global
+// would be |B|, and the current compartment would be that of |C|.
+//
+// In general, it's best to use to use the most-closely-associated global
+// unless the spec says to do otherwise. In 95% of the cases, the global of
+// the current compartment (GetCurrentGlobal()) is the right thing. For
+// example, WebIDL constructors (new C.XMLHttpRequest()) are initialized with
+// the global of the current compartment (i.e. |C|).
+//
+// The incumbent global is very similar, but differs in a few edge cases. For
+// example, if window |B| does |C.location.href = "..."|, the incumbent global
+// used for the navigation algorithm is B, because no script from |C| was ever
+// run.
+//
+// The entry global is used for various things like computing base URIs, mostly
+// for historical reasons.
+//
+// Note that all of these functions return bonafide global objects. This means
+// that, for Windows, they always return the inner.
+
+// Returns the global associated with the top-most Candidate Entry Point on
+// the Script Settings Stack. See the HTML spec. This may be null.
+nsIGlobalObject* GetEntryGlobal();
+
+// If the entry global is a window, returns its extant document. Otherwise,
+// returns null.
+Document* GetEntryDocument();
+
+// Returns the global associated with the top-most entry of the the Script
+// Settings Stack. See the HTML spec. This may be null.
+nsIGlobalObject* GetIncumbentGlobal();
+
+// Returns the global associated with the current compartment. This may be null.
+nsIGlobalObject* GetCurrentGlobal();
+
+// JS-implemented WebIDL presents an interesting situation with respect to the
+// subject principal. A regular C++-implemented API can simply examine the
+// compartment of the most-recently-executed script, and use that to infer the
+// responsible party. However, JS-implemented APIs are run with system
+// principal, and thus clobber the subject principal of the script that
+// invoked the API. So we have to do some extra work to keep track of this
+// information.
+//
+// We therefore implement the following behavior:
+// * Each Script Settings Object has an optional WebIDL Caller Principal field.
+// This defaults to null.
+// * When we push an Entry Point in preparation to run a JS-implemented WebIDL
+// callback, we grab the subject principal at the time of invocation, and
+// store that as the WebIDL Caller Principal.
+// * When non-null, callers can query this principal from script via an API on
+// Components.utils.
+nsIPrincipal* GetWebIDLCallerPrincipal();
+
+// Returns whether JSAPI is active right now. If it is not, working with a
+// JSContext you grab from somewhere random is not OK and you should be doing
+// AutoJSAPI or AutoEntryScript to get yourself a properly set up JSContext.
+bool IsJSAPIActive();
+
+namespace danger {
+
+// Get the JSContext for this thread. This is in the "danger" namespace because
+// we generally want people using AutoJSAPI instead, unless they really know
+// what they're doing.
+JSContext* GetJSContext();
+
+} // namespace danger
+
+JS::RootingContext* RootingCx();
+
+class ScriptSettingsStack;
+class ScriptSettingsStackEntry {
+ friend class ScriptSettingsStack;
+
+ public:
+ ~ScriptSettingsStackEntry();
+
+ bool NoJSAPI() const { return mType == eNoJSAPI; }
+ bool IsEntryCandidate() const {
+ return mType == eEntryScript || mType == eNoJSAPI;
+ }
+ bool IsIncumbentCandidate() { return mType != eJSAPI; }
+ bool IsIncumbentScript() { return mType == eIncumbentScript; }
+
+ protected:
+ enum Type { eEntryScript, eIncumbentScript, eJSAPI, eNoJSAPI };
+
+ ScriptSettingsStackEntry(nsIGlobalObject* aGlobal, Type aEntryType);
+
+ nsCOMPtr<nsIGlobalObject> mGlobalObject;
+ Type mType;
+
+ private:
+ ScriptSettingsStackEntry* mOlder;
+};
+
+/*
+ * For any interaction with JSAPI, an AutoJSAPI (or one of its subclasses)
+ * must be on the stack.
+ *
+ * This base class should be instantiated as-is when the caller wants to use
+ * JSAPI but doesn't expect to run script. The caller must then call one of its
+ * Init functions before being able to access the JSContext through cx().
+ * Its current duties are as-follows (see individual Init comments for details):
+ *
+ * * Grabbing an appropriate JSContext, and, on the main thread, pushing it onto
+ * the JSContext stack.
+ * * Entering an initial (possibly null) compartment, to ensure that the
+ * previously entered compartment for that JSContext is not used by mistake.
+ * * Reporting any exceptions left on the JSRuntime, unless the caller steals
+ * or silences them.
+ *
+ * Additionally, the following duties are planned, but not yet implemented:
+ *
+ * * De-poisoning the JSRuntime to allow manipulation of JSAPI. This requires
+ * implementing the poisoning first. For now, this de-poisoning
+ * effectively corresponds to having a non-null cx on the stack.
+ *
+ * In situations where the consumer expects to run script, AutoEntryScript
+ * should be used, which does additional manipulation of the script settings
+ * stack. In bug 991758, we'll add hard invariants to SpiderMonkey, such that
+ * any attempt to run script without an AutoEntryScript on the stack will
+ * fail. This prevents system code from accidentally triggering script
+ * execution at inopportune moments via surreptitious getters and proxies.
+ */
+class MOZ_STACK_CLASS AutoJSAPI : protected ScriptSettingsStackEntry {
+ public:
+ // Trivial constructor. One of the Init functions must be called before
+ // accessing the JSContext through cx().
+ AutoJSAPI();
+
+ ~AutoJSAPI();
+
+ // This uses the SafeJSContext (or worker equivalent), and enters a null
+ // compartment, so that the consumer is forced to select a compartment to
+ // enter before manipulating objects.
+ //
+ // This variant will ensure that any errors reported by this AutoJSAPI as it
+ // comes off the stack will not fire error events or be associated with any
+ // particular web-visible global.
+ void Init();
+
+ // This uses the SafeJSContext (or worker equivalent), and enters the
+ // compartment of aGlobalObject.
+ // If aGlobalObject or its associated JS global are null then it returns
+ // false and use of cx() will cause an assertion.
+ //
+ // If aGlobalObject represents a web-visible global, errors reported by this
+ // AutoJSAPI as it comes off the stack will fire the relevant error events and
+ // show up in the corresponding web console.
+ //
+ // Successfully initializing the AutoJSAPI will ensure that it enters the
+ // Realm of aGlobalObject's JSObject and exposes that JSObject to active JS.
+ MOZ_MUST_USE bool Init(nsIGlobalObject* aGlobalObject);
+
+ // This is a helper that grabs the native global associated with aObject and
+ // invokes the above Init() with that. aObject must not be a cross-compartment
+ // wrapper: CCWs are not associated with a single global.
+ MOZ_MUST_USE bool Init(JSObject* aObject);
+
+ // Unsurprisingly, this uses aCx and enters the compartment of aGlobalObject.
+ // If aGlobalObject or its associated JS global are null then it returns
+ // false and use of cx() will cause an assertion.
+ // If aCx is null it will cause an assertion.
+ //
+ // If aGlobalObject represents a web-visible global, errors reported by this
+ // AutoJSAPI as it comes off the stack will fire the relevant error events and
+ // show up in the corresponding web console.
+ MOZ_MUST_USE bool Init(nsIGlobalObject* aGlobalObject, JSContext* aCx);
+
+ // Convenience functions to take an nsPIDOMWindowInner or nsGlobalWindowInner,
+ // when it is more easily available than an nsIGlobalObject.
+ MOZ_MUST_USE bool Init(nsPIDOMWindowInner* aWindow);
+ MOZ_MUST_USE bool Init(nsPIDOMWindowInner* aWindow, JSContext* aCx);
+
+ MOZ_MUST_USE bool Init(nsGlobalWindowInner* aWindow);
+ MOZ_MUST_USE bool Init(nsGlobalWindowInner* aWindow, JSContext* aCx);
+
+ JSContext* cx() const {
+ MOZ_ASSERT(mCx, "Must call Init before using an AutoJSAPI");
+ MOZ_ASSERT(IsStackTop());
+ return mCx;
+ }
+
+#ifdef DEBUG
+ bool IsStackTop() const;
+#endif
+
+ // If HasException, report it. Otherwise, a no-op.
+ void ReportException();
+
+ bool HasException() const {
+ MOZ_ASSERT(IsStackTop());
+ return JS_IsExceptionPending(cx());
+ };
+
+ // Transfers ownership of the current exception from the JS engine to the
+ // caller. Callers must ensure that HasException() is true, and that cx()
+ // is in a non-null compartment.
+ //
+ // Note that this fails if and only if we OOM while wrapping the exception
+ // into the current compartment.
+ MOZ_MUST_USE bool StealException(JS::MutableHandle<JS::Value> aVal);
+
+ // As for StealException(), but uses the JS::ExceptionStack class to also
+ // include the exception's stack, represented by SavedFrames.
+ MOZ_MUST_USE bool StealExceptionAndStack(JS::ExceptionStack* aExnStack);
+
+ // Peek the current exception from the JS engine, without stealing it.
+ // Callers must ensure that HasException() is true, and that cx() is in a
+ // non-null compartment.
+ //
+ // Note that this fails if and only if we OOM while wrapping the exception
+ // into the current compartment.
+ MOZ_MUST_USE bool PeekException(JS::MutableHandle<JS::Value> aVal);
+
+ void ClearException() {
+ MOZ_ASSERT(IsStackTop());
+ JS_ClearPendingException(cx());
+ }
+
+ protected:
+ // Protected constructor for subclasses. This constructor initialises the
+ // AutoJSAPI, so Init must NOT be called on subclasses that use this.
+ AutoJSAPI(nsIGlobalObject* aGlobalObject, bool aIsMainThread, Type aType);
+
+ mozilla::Maybe<JSAutoNullableRealm> mAutoNullableRealm;
+ JSContext* mCx;
+
+ // Whether we're mainthread or not; set when we're initialized.
+ bool mIsMainThread;
+ Maybe<JS::WarningReporter> mOldWarningReporter;
+
+ private:
+ void InitInternal(nsIGlobalObject* aGlobalObject, JSObject* aGlobal,
+ JSContext* aCx, bool aIsMainThread);
+
+ AutoJSAPI(const AutoJSAPI&) = delete;
+ AutoJSAPI& operator=(const AutoJSAPI&) = delete;
+};
+
+/*
+ * A class that represents a new script entry point.
+ *
+ * |aReason| should be a statically-allocated C string naming the reason we're
+ * invoking JavaScript code: "setTimeout", "event", and so on. The devtools use
+ * these strings to label JS execution in timeline and profiling displays.
+ *
+ */
+class MOZ_STACK_CLASS AutoEntryScript : public AutoJSAPI {
+ public:
+ // Constructing the AutoEntryScript will ensure that it enters the
+ // Realm of aGlobalObject's JSObject and exposes that JSObject to active JS.
+ AutoEntryScript(nsIGlobalObject* aGlobalObject, const char* aReason,
+ bool aIsMainThread = NS_IsMainThread());
+
+ // aObject can be any object from the relevant global. It must not be a
+ // cross-compartment wrapper because CCWs are not associated with a single
+ // global.
+ //
+ // Constructing the AutoEntryScript will ensure that it enters the
+ // Realm of aObject JSObject and exposes aObject's global to active JS.
+ AutoEntryScript(JSObject* aObject, const char* aReason,
+ bool aIsMainThread = NS_IsMainThread());
+
+ ~AutoEntryScript();
+
+ void SetWebIDLCallerPrincipal(nsIPrincipal* aPrincipal) {
+ mWebIDLCallerPrincipal = aPrincipal;
+ }
+
+ private:
+ // A subclass of AutoEntryMonitor that notifies the docshell.
+ class DocshellEntryMonitor final : public JS::dbg::AutoEntryMonitor {
+ public:
+ DocshellEntryMonitor(JSContext* aCx, const char* aReason);
+
+ // Please note that |aAsyncCause| here is owned by the caller, and its
+ // lifetime must outlive the lifetime of the DocshellEntryMonitor object.
+ // In practice, |aAsyncCause| is identical to |aReason| passed into
+ // the AutoEntryScript constructor, so the lifetime requirements are
+ // trivially satisfied by |aReason| being a statically allocated string.
+ void Entry(JSContext* aCx, JSFunction* aFunction,
+ JS::Handle<JS::Value> aAsyncStack,
+ const char* aAsyncCause) override {
+ Entry(aCx, aFunction, nullptr, aAsyncStack, aAsyncCause);
+ }
+
+ void Entry(JSContext* aCx, JSScript* aScript,
+ JS::Handle<JS::Value> aAsyncStack,
+ const char* aAsyncCause) override {
+ Entry(aCx, nullptr, aScript, aAsyncStack, aAsyncCause);
+ }
+
+ void Exit(JSContext* aCx) override;
+
+ private:
+ void Entry(JSContext* aCx, JSFunction* aFunction, JSScript* aScript,
+ JS::Handle<JS::Value> aAsyncStack, const char* aAsyncCause);
+
+ const char* mReason;
+ };
+
+ // It's safe to make this a weak pointer, since it's the subject principal
+ // when we go on the stack, so can't go away until after we're gone. In
+ // particular, this is only used from the CallSetup constructor, and only in
+ // the aIsJSImplementedWebIDL case. And in that case, the subject principal
+ // is the principal of the callee function that is part of the CallArgs just a
+ // bit up the stack, and which will outlive us. So we know the principal
+ // can't go away until then either.
+ nsIPrincipal* MOZ_NON_OWNING_REF mWebIDLCallerPrincipal;
+ friend nsIPrincipal* GetWebIDLCallerPrincipal();
+
+ Maybe<DocshellEntryMonitor> mDocShellEntryMonitor;
+ Maybe<xpc::AutoScriptActivity> mScriptActivity;
+ JS::AutoHideScriptedCaller mCallerOverride;
+#ifdef MOZ_GECKO_PROFILER
+ AutoProfilerLabel mAutoProfilerLabel;
+#endif
+ AutoRequestJSThreadExecution mJSThreadExecution;
+};
+
+/*
+ * A class that can be used to force a particular incumbent script on the stack.
+ */
+class AutoIncumbentScript : protected ScriptSettingsStackEntry {
+ public:
+ explicit AutoIncumbentScript(nsIGlobalObject* aGlobalObject);
+ ~AutoIncumbentScript();
+
+ private:
+ JS::AutoHideScriptedCaller mCallerOverride;
+};
+
+/*
+ * A class to put the JS engine in an unusable state. The subject principal
+ * will become System, the information on the script settings stack is
+ * rendered inaccessible, and JSAPI may not be manipulated until the class is
+ * either popped or an AutoJSAPI instance is subsequently pushed.
+ *
+ * This class may not be instantiated if an exception is pending.
+ */
+class AutoNoJSAPI : protected ScriptSettingsStackEntry,
+ protected JSAutoNullableRealm {
+ public:
+ AutoNoJSAPI() : AutoNoJSAPI(danger::GetJSContext()) {}
+ ~AutoNoJSAPI();
+
+ private:
+ // Helper constructor to avoid doing GetJSContext() multiple times
+ // during construction.
+ explicit AutoNoJSAPI(JSContext* aCx);
+
+ // Stashed JSContext* so we don't need to GetJSContext in our destructor.
+ // It's probably safe to hold on to this, in the sense that the world should
+ // not get torn down while we're on the stack, and if it's not, we'd need to
+ // fix JSAutoNullableRealm to not hold on to a JSContext either, or
+ // something.
+ JSContext* mCx;
+
+ AutoYieldJSThreadExecution mExecutionYield;
+};
+
+} // namespace dom
+
+/**
+ * Use AutoJSContext when you need a JS context on the stack but don't have one
+ * passed as a parameter. AutoJSContext will take care of finding the most
+ * appropriate JS context and release it when leaving the stack.
+ */
+class MOZ_RAII AutoJSContext {
+ public:
+ explicit AutoJSContext();
+ operator JSContext*() const;
+
+ protected:
+ JSContext* mCx;
+ dom::AutoJSAPI mJSAPI;
+};
+
+/**
+ * AutoSafeJSContext is similar to AutoJSContext but will only return the safe
+ * JS context. That means it will never call
+ * nsContentUtils::GetCurrentJSContext().
+ *
+ * Note - This is deprecated. Please use AutoJSAPI instead.
+ */
+class MOZ_RAII AutoSafeJSContext : public dom::AutoJSAPI {
+ public:
+ explicit AutoSafeJSContext();
+ operator JSContext*() const { return cx(); }
+
+ private:
+};
+
+/**
+ * Use AutoSlowOperation when native side calls many JS callbacks in a row
+ * and slow script dialog should be activated if too much time is spent going
+ * through those callbacks.
+ * AutoSlowOperation puts an AutoScriptActivity on the stack so that we don't
+ * continue to reset the watchdog. CheckForInterrupt can then be used to check
+ * whether JS execution should be interrupted.
+ * This class (including CheckForInterrupt) is a no-op when used off the main
+ * thread.
+ */
+class MOZ_RAII AutoSlowOperation {
+ public:
+ explicit AutoSlowOperation();
+ void CheckForInterrupt();
+
+ private:
+ bool mIsMainThread;
+ Maybe<xpc::AutoScriptActivity> mScriptActivity;
+};
+
+/**
+ * A class to disable interrupt callback temporary.
+ */
+class MOZ_RAII AutoDisableJSInterruptCallback {
+ public:
+ explicit AutoDisableJSInterruptCallback(JSContext* aCx)
+ : mCx(aCx), mOld(JS_DisableInterruptCallback(aCx)) {}
+
+ ~AutoDisableJSInterruptCallback() { JS_ResetInterruptCallback(mCx, mOld); }
+
+ private:
+ JSContext* mCx;
+ bool mOld;
+};
+
+/**
+ * A helper class which allows to allow-list legacy callers executing script
+ * in the AutoEntryScript constructor. The goal is to remove these exceptions
+ * one by one. Do not add a new one without review from a DOM peer.
+ */
+class MOZ_RAII AutoAllowLegacyScriptExecution {
+ public:
+ AutoAllowLegacyScriptExecution();
+ ~AutoAllowLegacyScriptExecution();
+
+ static bool IsAllowed();
+
+ private:
+ static int sAutoAllowLegacyScriptExecution;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_dom_ScriptSettings_h
diff --git a/dom/script/ScriptTrace.cpp b/dom/script/ScriptTrace.cpp
new file mode 100644
index 0000000000..8e29381e4e
--- /dev/null
+++ b/dom/script/ScriptTrace.cpp
@@ -0,0 +1,33 @@
+/* -*- 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 "ScriptTrace.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/StaticPrefs_dom.h"
+
+namespace mozilla {
+namespace dom {
+namespace script {
+
+static nsresult TestingDispatchEvent(nsIScriptElement* aScriptElement,
+ const nsAString& aEventType) {
+ if (!StaticPrefs::dom_expose_test_interfaces()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINode> target(do_QueryInterface(aScriptElement));
+ if (!target) {
+ return NS_OK;
+ }
+
+ RefPtr<AsyncEventDispatcher> dispatcher = new AsyncEventDispatcher(
+ target, aEventType, CanBubble::eYes, ChromeOnlyDispatch::eNo);
+ return dispatcher->PostDOMEvent();
+}
+
+} // namespace script
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/script/ScriptTrace.h b/dom/script/ScriptTrace.h
new file mode 100644
index 0000000000..110ec15d2e
--- /dev/null
+++ b/dom/script/ScriptTrace.h
@@ -0,0 +1,47 @@
+/* -*- 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_ScriptTrace_h
+#define mozilla_dom_ScriptTrace_h
+
+#include "ScriptLoader.h"
+
+namespace mozilla {
+namespace dom {
+namespace script {
+
+// This macro is used to wrap a tracing mechanism which is scheduling events
+// which are then used by the JavaScript code of test cases to track the code
+// path to verify the optimizations are working as expected.
+#define TRACE_FOR_TEST(elem, str) \
+ PR_BEGIN_MACRO \
+ nsresult rv = NS_OK; \
+ rv = \
+ script::TestingDispatchEvent(elem, NS_LITERAL_STRING_FROM_CSTRING(str)); \
+ NS_ENSURE_SUCCESS(rv, rv); \
+ PR_END_MACRO
+
+#define TRACE_FOR_TEST_BOOL(elem, str) \
+ PR_BEGIN_MACRO \
+ nsresult rv = NS_OK; \
+ rv = \
+ script::TestingDispatchEvent(elem, NS_LITERAL_STRING_FROM_CSTRING(str)); \
+ NS_ENSURE_SUCCESS(rv, false); \
+ PR_END_MACRO
+
+#define TRACE_FOR_TEST_NONE(elem, str) \
+ PR_BEGIN_MACRO \
+ script::TestingDispatchEvent(elem, NS_LITERAL_STRING_FROM_CSTRING(str)); \
+ PR_END_MACRO
+
+static nsresult TestingDispatchEvent(nsIScriptElement* aScriptElement,
+ const nsAString& aEventType);
+
+} // namespace script
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ScriptTrace_h
diff --git a/dom/script/moz.build b/dom/script/moz.build
new file mode 100644
index 0000000000..64ac2300de
--- /dev/null
+++ b/dom/script/moz.build
@@ -0,0 +1,48 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+XPIDL_SOURCES += [
+ "nsIScriptLoaderObserver.idl",
+]
+
+XPIDL_MODULE = "dom"
+
+EXPORTS += [
+ "nsIScriptElement.h",
+]
+
+EXPORTS.mozilla.dom += [
+ "LoadedScript.h",
+ "ScriptDecoding.h",
+ "ScriptElement.h",
+ "ScriptKind.h",
+ "ScriptLoader.h",
+ "ScriptLoadRequest.h",
+ "ScriptSettings.h",
+]
+
+UNIFIED_SOURCES += [
+ "LoadedScript.cpp",
+ "ModuleLoadRequest.cpp",
+ "nsIScriptElement.cpp",
+ "ScriptElement.cpp",
+ "ScriptLoader.cpp",
+ "ScriptLoadHandler.cpp",
+ "ScriptLoadRequest.cpp",
+ "ScriptSettings.cpp",
+ "ScriptTrace.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/script/nsIScriptElement.cpp b/dom/script/nsIScriptElement.cpp
new file mode 100644
index 0000000000..d0b6282874
--- /dev/null
+++ b/dom/script/nsIScriptElement.cpp
@@ -0,0 +1,53 @@
+
+/* -*- 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 "nsIScriptElement.h"
+
+#include "mozilla/dom/ReferrerPolicyBinding.h"
+#include "nsIParser.h"
+#include "nsIWeakReference.h"
+
+void nsIScriptElement::SetCreatorParser(nsIParser* aParser) {
+ mCreatorParser = do_GetWeakReference(aParser);
+}
+
+void nsIScriptElement::UnblockParser() {
+ nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
+ if (parser) {
+ parser->UnblockParser();
+ }
+}
+
+void nsIScriptElement::ContinueParserAsync() {
+ nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
+ if (parser) {
+ parser->ContinueInterruptedParsingAsync();
+ }
+}
+
+void nsIScriptElement::BeginEvaluating() {
+ nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
+ if (parser) {
+ parser->IncrementScriptNestingLevel();
+ }
+}
+
+void nsIScriptElement::EndEvaluating() {
+ nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
+ if (parser) {
+ parser->DecrementScriptNestingLevel();
+ }
+}
+
+already_AddRefed<nsIParser> nsIScriptElement::GetCreatorParser() {
+ nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
+ return parser.forget();
+}
+
+mozilla::dom::ReferrerPolicy nsIScriptElement::GetReferrerPolicy() {
+ return mozilla::dom::ReferrerPolicy::_empty;
+}
diff --git a/dom/script/nsIScriptElement.h b/dom/script/nsIScriptElement.h
new file mode 100644
index 0000000000..c2fd702110
--- /dev/null
+++ b/dom/script/nsIScriptElement.h
@@ -0,0 +1,343 @@
+/* -*- 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 nsIScriptElement_h___
+#define nsIScriptElement_h___
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/dom/FromParser.h"
+#include "nsCOMPtr.h"
+#include "nsID.h"
+#include "nsIScriptLoaderObserver.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsStringFwd.h"
+#include "nscore.h"
+
+// XXX Avoid including this here by moving function bodies to the cpp file
+#include "nsIPrincipal.h"
+
+class nsIParser;
+class nsIPrincipal;
+class nsIURI;
+
+namespace mozilla::dom {
+class Document;
+enum class ReferrerPolicy : uint8_t;
+} // namespace mozilla::dom
+
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
+#define NS_ISCRIPTELEMENT_IID \
+ { \
+ 0xe60fca9b, 0x1b96, 0x4e4e, { \
+ 0xa9, 0xb4, 0xdc, 0x98, 0x4f, 0x88, 0x3f, 0x9c \
+ } \
+ }
+
+/**
+ * Internal interface implemented by script elements
+ */
+class nsIScriptElement : public nsIScriptLoaderObserver {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCRIPTELEMENT_IID)
+
+ explicit nsIScriptElement(mozilla::dom::FromParser aFromParser)
+ : mLineNumber(1),
+ mColumnNumber(1),
+ mAlreadyStarted(false),
+ mMalformed(false),
+ mDoneAddingChildren(aFromParser == mozilla::dom::NOT_FROM_PARSER ||
+ aFromParser == mozilla::dom::FROM_PARSER_FRAGMENT),
+ mForceAsync(aFromParser == mozilla::dom::NOT_FROM_PARSER ||
+ aFromParser == mozilla::dom::FROM_PARSER_FRAGMENT),
+ mFrozen(false),
+ mIsModule(false),
+ mDefer(false),
+ mAsync(false),
+ mExternal(false),
+ mParserCreated(aFromParser == mozilla::dom::FROM_PARSER_FRAGMENT
+ ? mozilla::dom::NOT_FROM_PARSER
+ : aFromParser),
+ // Fragment parser-created scripts (if executable)
+ // behave like script-created scripts.
+ mCreatorParser(nullptr) {}
+
+ /**
+ * Content type identifying the scripting language. Can be empty, in
+ * which case javascript will be assumed.
+ * Return false if type attribute is not found.
+ */
+ virtual bool GetScriptType(nsAString& type) = 0;
+
+ /**
+ * Location of script source text. Can return null, in which case
+ * this is assumed to be an inline script element.
+ */
+ nsIURI* GetScriptURI() {
+ MOZ_ASSERT(mFrozen, "Not ready for this call yet!");
+ return mUri;
+ }
+
+ nsIPrincipal* GetScriptURITriggeringPrincipal() {
+ MOZ_ASSERT(mFrozen, "Not ready for this call yet!");
+ return mSrcTriggeringPrincipal;
+ }
+
+ /**
+ * Script source text for inline script elements.
+ */
+ virtual void GetScriptText(nsAString& text) = 0;
+
+ virtual void GetScriptCharset(nsAString& charset) = 0;
+
+ /**
+ * Freezes the return values of the following methods so that subsequent
+ * modifications to the attributes don't change execution behavior:
+ * - GetScriptIsModule()
+ * - GetScriptDeferred()
+ * - GetScriptAsync()
+ * - GetScriptURI()
+ * - GetScriptExternal()
+ */
+ virtual void FreezeExecutionAttrs(mozilla::dom::Document*) = 0;
+
+ /**
+ * Is the script a module script. Currently only supported by HTML scripts.
+ */
+ bool GetScriptIsModule() {
+ MOZ_ASSERT(mFrozen, "Not ready for this call yet!");
+ return mIsModule;
+ }
+
+ /**
+ * Is the script deferred. Currently only supported by HTML scripts.
+ */
+ bool GetScriptDeferred() {
+ MOZ_ASSERT(mFrozen, "Not ready for this call yet!");
+ return mDefer;
+ }
+
+ /**
+ * Is the script async. Currently only supported by HTML scripts.
+ */
+ bool GetScriptAsync() {
+ MOZ_ASSERT(mFrozen, "Not ready for this call yet!");
+ return mAsync;
+ }
+
+ /**
+ * Is the script an external script?
+ */
+ bool GetScriptExternal() {
+ MOZ_ASSERT(mFrozen, "Not ready for this call yet!");
+ return mExternal;
+ }
+
+ /**
+ * Returns how the element was created.
+ */
+ mozilla::dom::FromParser GetParserCreated() { return mParserCreated; }
+
+ void SetScriptLineNumber(uint32_t aLineNumber) { mLineNumber = aLineNumber; }
+
+ uint32_t GetScriptLineNumber() { return mLineNumber; }
+
+ void SetScriptColumnNumber(uint32_t aColumnNumber) {
+ mColumnNumber = aColumnNumber;
+ }
+
+ uint32_t GetScriptColumnNumber() { return mColumnNumber; }
+
+ void SetIsMalformed() { mMalformed = true; }
+
+ bool IsMalformed() { return mMalformed; }
+
+ void PreventExecution() { mAlreadyStarted = true; }
+
+ void LoseParserInsertedness() {
+ mUri = nullptr;
+ mCreatorParser = nullptr;
+ mParserCreated = mozilla::dom::NOT_FROM_PARSER;
+ mForceAsync = !GetAsyncState();
+
+ // Reset state set by FreezeExecutionAttrs().
+ mFrozen = false;
+ mIsModule = false;
+ mExternal = false;
+ mAsync = false;
+ mDefer = false;
+ }
+
+ void SetCreatorParser(nsIParser* aParser);
+
+ /**
+ * Unblocks the creator parser
+ */
+ void UnblockParser();
+
+ /**
+ * Attempts to resume parsing asynchronously
+ */
+ void ContinueParserAsync();
+
+ /**
+ * Informs the creator parser that the evaluation of this script is starting
+ */
+ void BeginEvaluating();
+
+ /**
+ * Informs the creator parser that the evaluation of this script is ending
+ */
+ void EndEvaluating();
+
+ /**
+ * Retrieves a pointer to the creator parser if this has one or null if not
+ */
+ already_AddRefed<nsIParser> GetCreatorParser();
+
+ /**
+ * This method is called when the parser finishes creating the script
+ * element's children, if any are present.
+ *
+ * @return whether the parser will be blocked while this script is being
+ * loaded
+ */
+ bool AttemptToExecute() {
+ mDoneAddingChildren = true;
+ bool block = MaybeProcessScript();
+ if (!mAlreadyStarted) {
+ // Need to lose parser-insertedness here to allow another script to cause
+ // execution later.
+ LoseParserInsertedness();
+ }
+ return block;
+ }
+
+ /**
+ * Get the CORS mode of the script element
+ */
+ virtual mozilla::CORSMode GetCORSMode() const {
+ /* Default to no CORS */
+ return mozilla::CORS_NONE;
+ }
+
+ /**
+ * Get referrer policy of the script element
+ */
+ virtual mozilla::dom::ReferrerPolicy GetReferrerPolicy();
+
+ /**
+ * Fire an error event
+ */
+ virtual nsresult FireErrorEvent() = 0;
+
+ protected:
+ /**
+ * Processes the script if it's in the document-tree and links to or
+ * contains a script. Once it has been evaluated there is no way to make it
+ * reevaluate the script, you'll have to create a new element. This also means
+ * that when adding a src attribute to an element that already contains an
+ * inline script, the script referenced by the src attribute will not be
+ * loaded.
+ *
+ * In order to be able to use multiple childNodes, or to use the
+ * fallback mechanism of using both inline script and linked script you have
+ * to add all attributes and childNodes before adding the element to the
+ * document-tree.
+ *
+ * @return whether the parser will be blocked while this script is being
+ * loaded
+ */
+ virtual bool MaybeProcessScript() = 0;
+
+ /**
+ * Since we've removed the XPCOM interface to HTML elements, we need a way to
+ * retreive async state from script elements without bringing the type in.
+ */
+ virtual bool GetAsyncState() = 0;
+
+ /**
+ * The start line number of the script.
+ */
+ uint32_t mLineNumber;
+
+ /**
+ * The start column number of the script.
+ */
+ uint32_t mColumnNumber;
+
+ /**
+ * The "already started" flag per HTML5.
+ */
+ bool mAlreadyStarted;
+
+ /**
+ * The script didn't have an end tag.
+ */
+ bool mMalformed;
+
+ /**
+ * False if parser-inserted but the parser hasn't triggered running yet.
+ */
+ bool mDoneAddingChildren;
+
+ /**
+ * If true, the .async property returns true instead of reflecting the
+ * content attribute.
+ */
+ bool mForceAsync;
+
+ /**
+ * Whether src, defer and async are frozen.
+ */
+ bool mFrozen;
+
+ /**
+ * The effective moduleness.
+ */
+ bool mIsModule;
+
+ /**
+ * The effective deferredness.
+ */
+ bool mDefer;
+
+ /**
+ * The effective asyncness.
+ */
+ bool mAsync;
+
+ /**
+ * The effective externalness. A script can be external with mUri being null
+ * if the src attribute contained an invalid URL string.
+ */
+ bool mExternal;
+
+ /**
+ * Whether this element was parser-created.
+ */
+ mozilla::dom::FromParser mParserCreated;
+
+ /**
+ * The effective src (or null if no src).
+ */
+ nsCOMPtr<nsIURI> mUri;
+
+ /**
+ * The triggering principal for the src URL.
+ */
+ nsCOMPtr<nsIPrincipal> mSrcTriggeringPrincipal;
+
+ /**
+ * The creator parser of a non-defer, non-async parser-inserted script.
+ */
+ nsWeakPtr mCreatorParser;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptElement, NS_ISCRIPTELEMENT_IID)
+
+#endif // nsIScriptElement_h___
diff --git a/dom/script/nsIScriptLoaderObserver.idl b/dom/script/nsIScriptLoaderObserver.idl
new file mode 100644
index 0000000000..8fafa201cc
--- /dev/null
+++ b/dom/script/nsIScriptLoaderObserver.idl
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsISupports.idl"
+
+interface nsIScriptElement;
+interface nsIURI;
+
+[scriptable, uuid(7b787204-76fb-4764-96f1-fb7a666db4f4)]
+interface nsIScriptLoaderObserver : nsISupports {
+
+ /**
+ * The script is available for evaluation. For inline scripts, this
+ * method will be called synchronously. For externally loaded scripts,
+ * this method will be called when the load completes.
+ *
+ * @param aResult A result code representing the result of loading
+ * a script. If this is a failure code, script evaluation
+ * will not occur.
+ * @param aElement The element being processed.
+ * @param aIsInline Is this an inline classic script (as opposed to an
+ * externally loaded classic script or module script)?
+ * @param aURI What is the URI of the script (the document URI if
+ * it is inline).
+ * @param aLineNo At what line does the script appear (generally 1
+ * if it is a loaded script).
+ */
+ void scriptAvailable(in nsresult aResult,
+ in nsIScriptElement aElement,
+ in boolean aIsInlineClassicScript,
+ in nsIURI aURI,
+ in int32_t aLineNo);
+
+ /**
+ * The script has been evaluated.
+ *
+ * @param aResult A result code representing the success or failure of
+ * the script evaluation.
+ * @param aElement The element being processed.
+ * @param aIsInline Is this an inline script or externally loaded?
+ */
+ void scriptEvaluated(in nsresult aResult,
+ in nsIScriptElement aElement,
+ in boolean aIsInline);
+};