/* 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 "ModuleLoader.h"
#include "SharedScriptCache.h"
#include "js/TypeDecls.h"
#include "js/Utility.h"                     // JS::FreePolicy
#include "js/experimental/CompileScript.h"  // JS::FrontendContext
#include "js/loader/LoadedScript.h"
#include "js/loader/ModuleLoaderBase.h"
#include "js/loader/ScriptKind.h"
#include "js/loader/ScriptLoadRequest.h"
#include "js/loader/ScriptLoadRequestList.h"
#include "mozilla/CORSMode.h"
#include "mozilla/MaybeOneOf.h"
#include "mozilla/MozPromise.h"
#include "mozilla/dom/ScriptLoadContext.h"
#include "mozilla/dom/ScriptLoadRequestType.h"
#include "nsCOMArray.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsILoadInfo.h"  // nsSecurityFlags
#include "nsINode.h"
#include "nsIObserver.h"
#include "nsIScriptElement.h"
#include "nsIScriptLoaderObserver.h"
#include "nsRefPtrHashtable.h"
#include "nsTArray.h"
#include "nsURIHashKey.h"

class nsCycleCollectionTraversalCallback;
class nsIChannel;
class nsIConsoleReportCollector;
class nsIContent;
class nsIIncrementalStreamLoader;
class nsIPrincipal;
class nsIScriptGlobalObject;
class nsITimer;
class nsIURI;

namespace JS {

class CompileOptions;

template <typename UnitT>
class SourceText;

namespace loader {

class LoadedScript;
class ModuleLoadRequest;
class ModuleScript;
class ScriptLoadRequest;

}  // namespace loader
}  // namespace JS

namespace mozilla {

class LazyLogModule;
union Utf8Unit;

namespace dom {

class AutoJSAPI;
class DocGroup;
class Document;
class ModuleLoader;
class SRICheckDataVerifier;
class SRIMetadata;
class ScriptLoadHandler;
class ScriptLoadContext;
class ScriptLoader;
class ScriptRequestProcessor;

enum class ReferrerPolicy : uint8_t;
enum class RequestPriority : uint8_t;

class ShutdownAndMemoryPressureObserver final : public nsIObserver {
  ~ShutdownAndMemoryPressureObserver() { Unregister(); }

 public:
  explicit ShutdownAndMemoryPressureObserver(ScriptLoader* aLoader)
      : mScriptLoader(aLoader) {}

  void OnShutdown();
  void OnMemoryPressure();
  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 JS::loader::ScriptLoaderInterface {
  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 JS::loader::ModuleLoadRequest;
  friend class ScriptRequestProcessor;
  friend class ModuleLoader;
  friend class ScriptLoadHandler;
  friend class AutoCurrentScriptUpdater;

 public:
  using MaybeSourceText =
      mozilla::MaybeOneOf<JS::SourceText<char16_t>, JS::SourceText<Utf8Unit>>;
  using ScriptLoadRequest = JS::loader::ScriptLoadRequest;

  explicit ScriptLoader(Document* aDocument);

  NS_DECL_CYCLE_COLLECTING_ISUPPORTS_FINAL
  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ScriptLoader)

  /**
   * Called when the document that owns this script loader changes global. The
   * argument is null when the document is detached from a window.
   */
  void SetGlobalObject(nsIGlobalObject* aGlobalObject);

  /**
   * The loader maintains a weak reference to the document with
   * which it is initialized. This call forces the reference to
   * be dropped.
   */
  void DropDocumentReference();

  /**
   * Register this loader to the shared script cache.
   *
   * When the principal for the document for the loader is modified,
   * DeregisterFromCache and RegisterToCache should be called to reflect the
   * modification (See Document::SetPrincipals).
   */
  void RegisterToCache();

  /**
   * Deregister this loader from the shared script cache and
   * clear the cache data associated with this loader.
   */
  void DeregisterFromCache();

  // Methods for SharedScriptCache.
  nsIPrincipal* LoaderPrincipal() const;
  nsIPrincipal* PartitionedPrincipal() const;

  bool ShouldBypassCache() const;

#ifdef NIGHTLY_BUILD
  // The return value depends on the Integrity-Policy-WAICT-v1 header and
  // doesn't change after the document started loading.
  bool WAICTHandlesScripts() const;
#endif

  template <typename T>
  bool HasLoaded(const T& aKey) {
    // NOTE: ScriptLoader doesn't cache pending/loading requests, and
    //       this method is unsed.
    return false;
  }

  /**
   * 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.
   * @param aSourceText For inline non-trusted script, the source text after
   * application of the default Trusted Types policy, a void string otherwise.
   * See https://html.spec.whatwg.org/#prepare-the-script-element
   */
  bool ProcessScriptElement(nsIScriptElement* aElement,
                            const nsAString& aSourceText);

  /**
   * 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; }

  void ContinueParsingDocumentAfterCurrentScript() {
    MOZ_ASSERT(mCurrentScript);
    mContinueParsingDocumentAfterCurrentScript = true;
  }

  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;
  }

  ModuleLoader* GetModuleLoader() { return mModuleLoader; }

  void RegisterContentScriptModuleLoader(ModuleLoader* aLoader);

  /**
   *  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.
   * @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,
                                 UniquePtr<char16_t[], JS::FreePolicy>& aBufOut,
                                 size_t& aLengthOut);

  /**
   * 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.
   * @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,
                                UniquePtr<Utf8Unit[], JS::FreePolicy>& 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(nsIChannel* aChannel, ScriptLoadRequest* aRequest,
                            nsresult aChannelStatus, nsresult aSRIStatus,
                            SRICheckDataVerifier* aSRIDataVerifier);

  /**
   * Returns wether any request is queued, and not executed yet.
   */
  bool HasPendingRequests() const;

  /**
   * Returns wether there are any dynamic module import requests pending.
   */
  bool HasPendingDynamicImports() const;

  /**
   * Processes any pending requests that are ready for processing.
   */
  void ProcessPendingRequests(bool aAllowBypassingParserBlocking = false);

  /**
   * 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 aFetchPriority
   * <https://html.spec.whatwg.org/#the-script-element:attr-script-fetchpriority>.
   * @param aIntegrity The expect hash url, if avail, of the request

   * @param aScriptFromHead Whether or not the script was a child of head
   */
  void PreloadURI(nsIURI* aURI, const nsAString& aCharset,
                  const nsAString& aType, const nsAString& aCrossOrigin,
                  const nsAString& aNonce, const nsAString& aFetchPriority,
                  const nsAString& aIntegrity, bool aScriptFromHead,
                  bool aAsync, bool aDefer, bool aLinkPreload,
                  const ReferrerPolicy aReferrerPolicy,
                  uint64_t aEarlyHintPreloaderId);

  /**
   * 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 perform
   * the caching 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.
   */
  void Destroy();

  /**
   * Called when memory pressure is detected.
   * This clears cache-related fields.
   */
  void OnMemoryPressure();

  /*
   * Get the currently active script's ScriptFetchInfo.
   *
   * This is used as the ScriptFetchInfo of the initiating script when executing
   * timeout handler scripts.
   */
  static JS::loader::ScriptFetchInfo* GetActiveScriptFetchInfo(JSContext* aCx);

  Document* GetDocument() const { return mDocument; }

  nsIURI* GetBaseURI() const override;

 private:
  ~ScriptLoader();

  already_AddRefed<ScriptLoadRequest> CreateLoadRequest(
      JS::loader::ScriptKind aKind, nsIURI* aURI, nsIScriptElement* aElement,
      const nsAString& aScriptContent, nsIPrincipal* aTriggeringPrincipal,
      mozilla::CORSMode aCORSMode, const nsAString& aNonce,
      RequestPriority aRequestPriority, const SRIMetadata& aIntegrity,
      ReferrerPolicy aReferrerPolicy,
      JS::loader::ParserMetadata aParserMetadata,
      ScriptLoadRequestType aRequestType);

  /**
   * Helper function to lookup the cache entry and associate it to the
   * request if any.
   */
  void TryUseCache(
      ReferrerPolicy aReferrerPolicy, ScriptFetchOptions* aFetchOptions,
      nsIURI* aURI, ScriptLoadRequest* aRequest,
      nsIScriptElement* aElement = nullptr, const nsAString& aNonce = u""_ns,
      ScriptLoadRequestType aRequestType = ScriptLoadRequestType::External);

  /**
   * Helper function to notify network observers for cached request.
   */
  void EmulateNetworkEvents(ScriptLoadRequest* aRequest,
                            const Maybe<nsAutoString>& aCharsetForPreload);

  void NotifyObserversForCachedScript(
      ScriptLoadRequest* aRequest,
      const Maybe<nsAutoString>& aCharsetForPreload);

  /**
   * 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,
                             JS::loader::ScriptKind aScriptKind,
                             nsIContent* aScriptContent);

  bool ProcessInlineScript(nsIScriptElement* aElement,
                           JS::loader::ScriptKind aScriptKind,
                           const nsAString& aSourceText);

  enum class CacheBehavior : uint8_t {
    DoNothingDisabled,
    DoNothingExisting,
    Insert,
    Evict,
  };

  CacheBehavior GetCacheBehavior(ScriptLoadRequest* aRequest);

  void TryCacheRequest(ScriptLoadRequest* aRequest);

  JS::loader::ScriptLoadRequest* LookupPreloadRequest(
      nsIScriptElement* aElement, JS::loader::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.
   */
  nsresult CheckContentPolicy(nsIScriptElement* aElement,
                              const nsAString& aNonce,
                              ScriptLoadRequest* aRequest,
                              ScriptFetchOptions* aFetchOptions, nsIURI* aURI);

  /**
   * 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,
                     const Maybe<nsAutoString>& aCharsetForPreload);
  /**
   * Start a load for a classic script URI.
   * Sets up the necessary security flags before calling StartLoadInternal.
   */
  nsresult StartClassicLoad(ScriptLoadRequest* aRequest,
                            const Maybe<nsAutoString>& aCharsetForPreload);

  void OnDelayedReady(ScriptLoadRequest* aRequest,
                      const Maybe<nsAutoString>& aCharsetForPreload);

  static void PrepareCacheInfoChannel(nsIChannel* aChannel,
                                      ScriptLoadRequest* aRequest);

  static void PrepareRequestPriorityAndRequestDependencies(
      nsIChannel* aChannel, ScriptLoadRequest* aRequest);

  [[nodiscard]] static nsresult PrepareHttpRequestAndInitiatorType(
      nsIChannel* aChannel, ScriptLoadRequest* aRequest,
      const Maybe<nsAutoString>& aCharsetForPreload);

  [[nodiscard]] nsresult PrepareIncrementalStreamLoader(
      nsIIncrementalStreamLoader** aOutLoader, nsIChannel* aChannel,
      ScriptLoadRequest* aRequest);

  /**
   * Start a load for a script (module or classic) URI.
   *
   * aCharsetForPreload is only needed when this load is a preload (via
   * ScriptLoader::PreloadURI), because ScriptLoadRequest doesn't
   * have this information.
   */
  nsresult StartLoadInternal(ScriptLoadRequest* aRequest,
                             nsSecurityFlags securityFlags,
                             const Maybe<nsAutoString>& aCharsetForPreload);

  /**
   * 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);

  void HandleLoadErrorAndProcessPendingRequests(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()
   */
  void ProcessPendingRequestsAsync();

  void ProcessPendingRequestsAsyncBypassParserBlocking();

  /**
   * 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, nsIChannel* aChannel,
                     nsresult aSRIStatus,
                     SRICheckDataVerifier* aSRIDataVerifier) const;

  nsresult SaveSRIHash(ScriptLoadRequest* aRequest,
                       SRICheckDataVerifier* aSRIDataVerifier) const;

  void ReportErrorToConsole(ScriptLoadRequest* aRequest,
                            nsresult aResult) const override;

  void ReportWarningToConsole(
      ScriptLoadRequest* aRequest, const char* aMessageName,
      const nsTArray<nsString>& aParams = nsTArray<nsString>()) const override;

  bool IsImportMapSupported() const override { return true; }

  void ReportPreloadErrorsToConsole(ScriptLoadRequest* aRequest);

  nsIConsoleReportCollector* GetConsoleReportCollector() const override {
    return mReporter;
  }

  nsresult AttemptOffThreadScriptCompile(ScriptLoadRequest* aRequest,
                                         bool* aCouldCompileOut);

  nsresult CreateOffThreadTask(JSContext* aCx, ScriptLoadRequest* aRequest,
                               JS::CompileOptions& aOptions,
                               CompileOrDecodeTask** aCompileOrDecodeTask);

  nsresult ProcessRequest(ScriptLoadRequest* aRequest);
  nsresult CompileOffThreadOrProcessRequest(ScriptLoadRequest* aRequest);
  void FireScriptAvailable(nsresult aResult, ScriptLoadRequest* aRequest);
  // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
  MOZ_CAN_RUN_SCRIPT_BOUNDARY void FireScriptEvaluated(
      nsresult aResult, ScriptLoadRequest* aRequest);

  // Implements https://html.spec.whatwg.org/#execute-the-script-block
  nsresult EvaluateScriptElement(ScriptLoadRequest* aRequest);

  bool StartCollectingDelazifications(JSContext* aCx,
                                      JS::Handle<JSScript*> aScript,
                                      JS::Stencil* aStencil);
  bool StartCollectingDelazifications(JSContext* aCx,
                                      JS::Handle<JSObject*> aModule,
                                      JS::Stencil* aStencil);

 private:
  void AppendDelazificationCollection(JS::Handle<JSScript*> aScript);
  void AppendDelazificationCollection(JS::Handle<JSObject*> aModule);

  enum class CollectDelazifications : bool { No, Yes };

  void InstantiateStencil(JSContext* aCx, JS::CompileOptions& aCompileOptions,
                          JS::Stencil* aStencil,
                          JS::MutableHandle<JSScript*> aScript,
                          JS::Handle<JSScript*> aDebuggerIntroductionScript,
                          ErrorResult& aRv,
                          const nsAutoCString& aProfilerLabelString,
                          JS::InstantiationStorage* aStorage = nullptr,
                          CollectDelazifications aCollectDelazifications =
                              CollectDelazifications::No);

 public:
  // Instantiate classic script from one of the following data:
  //   * text source
  //   * serialized stencil
  //   * cached stencil
  void InstantiateClassicScriptFromAny(
      JSContext* aCx, JS::CompileOptions& aCompileOptions,
      ScriptLoadRequest* aRequest, JS::MutableHandle<JSScript*> aScript,
      JS::Handle<JSScript*> aDebuggerIntroductionScript, ErrorResult& aRv);

  // Instantiate classic script from one of the following data:
  //   * text source
  //   * serialized stencil
  //
  // aStencilOut is set to the compiled stencil.
  void InstantiateClassicScriptFromMaybeEncodedSource(
      JSContext* aCx, JS::CompileOptions& aCompileOptions,
      ScriptLoadRequest* aRequest, JS::MutableHandle<JSScript*> aScript,
      JS::Handle<JSScript*> aDebuggerIntroductionScript, ErrorResult& aRv);

  // Instantiate classic script from the following data:
  //   * cached stencil
  void InstantiateClassicScriptFromCachedStencil(
      JSContext* aCx, JS::CompileOptions& aCompileOptions,
      ScriptLoadRequest* aRequest, JS::Stencil* aStencil,
      JS::MutableHandle<JSScript*> aScript,
      JS::Handle<JSScript*> aDebuggerIntroductionScript, ErrorResult& aRv);

  static nsCString& BytecodeMimeTypeFor(const ScriptLoadRequest* aRequest);
  static nsCString& BytecodeMimeTypeFor(
      const JS::loader::LoadedScript* aLoadedScript);

  // Queue the script load request for caching if we decided to cache it, or
  // cleanup the script load request fields otherwise.
  //
  // This method must be called after executing the script.
  nsresult MaybePrepareForDiskCacheAfterExecute(ScriptLoadRequest* aRequest,
                                                nsresult aRv);

  // Queue the top-level module load request for caching if we decided to cache
  // it, or cleanup the module load request fields otherwise.
  //
  // This method must be called after executing the script.
  nsresult MaybePrepareModuleForDiskCacheAfterExecute(
      ModuleLoadRequest* aRequest, nsresult aRv) override;

  // Implements https://html.spec.whatwg.org/#run-a-classic-script
  nsresult EvaluateScript(nsIGlobalObject* aGlobalObject,
                          ScriptLoadRequest* aRequest);

  /**
   * Register the script load request to be cached on the disk.
   *
   * The caller can call this at the same time instantiating the stencil,
   * and also start collecting delazifications.
   *
   * The cache handling will be performed 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 RegisterForDiskCache(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 perform the cache handling, which saves them to the necko cache.
   */
  void MaybeUpdateDiskCache() override;

  /**
   * Iterate over all scripts and save them to the necko cache.
   */
  void UpdateDiskCache();

  /**
   * Stop collecting any ongoing delazifications.
   */
  void StopCollectingDelazifications();

 public:
  /**
   * Encode the stencils and compress it.
   * aLoadedScript is used only for logging purpose, in order to allow
   * performing this off main thread.
   */
  static bool EncodeAndCompress(JS::FrontendContext* aFc,
                                const JS::loader::LoadedScript* aLoadedScript,
                                JS::Stencil* aStencil,
                                const JS::TranscodeBuffer& aSRI,
                                Vector<uint8_t>& aCompressed);

  /**
   * Save the serialized and maybe-compressed stencil to the necko cache.
   */
  static bool SaveToDiskCache(const JS::loader::LoadedScript* aLoadedScript,
                              const Vector<uint8_t>& aCompressed);

 private:
  void Decode(JSContext* aCx, JS::CompileOptions& aCompileOptions,
              const JS::TranscodeRange& aRange, RefPtr<JS::Stencil>& aStencil,
              ErrorResult& aRv);

  /**
   * Discard all disk-cache-related info for scripts queued for the disk cache.
   *
   * This should be used when the ScriptLoader is getting destroyed, or
   * when it hits any critical error.
   */
  void GiveUpDiskCaching();

  already_AddRefed<nsIGlobalObject> GetGlobalForRequest(
      ScriptLoadRequest* aRequest);

  already_AddRefed<nsIScriptGlobalObject> GetScriptGlobalObject();

  // Fill in CompileOptions, as well as produce the introducer script for
  // subsequent calls to UpdateDebuggerMetadata
  nsresult FillCompileOptionsForRequest(
      JSContext* aCx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions,
      JS::MutableHandle<JSScript*> aIntroductionScript) override;

  uint32_t NumberOfProcessors();
  int32_t PhysicalSizeOfMemoryInGB();

  nsresult PrepareLoadedRequest(ScriptLoadRequest* aRequest,
                                nsIChannel* aChannel, nsresult aStatus);

  void AddDeferRequest(ScriptLoadRequest* aRequest);
  void AddAsyncRequest(ScriptLoadRequest* aRequest);
  bool MaybeRemovedDeferRequests();

  bool ShouldApplyDelazifyStrategy(ScriptLoadRequest* aRequest);
  void ApplyDelazifyStrategy(JS::CompileOptions* aOptions);

  bool ShouldCompileOffThread(ScriptLoadRequest* aRequest);

  void MaybeMoveToLoadedList(ScriptLoadRequest* aRequest);

  bool IsBeforeFCP();

  bool UsesMemoryCache() const { return !!mCache; }

 public:
  struct DiskCacheStrategy {
    bool mIsDisabled = false;
    bool mHasSourceLengthMin = false;
    bool mHasFetchCountMin = false;
    uint8_t mFetchCountMin = 0;
    size_t mSourceLengthMin = 0;
  };

  static DiskCacheStrategy GetDiskCacheStrategy();

  uint16_t GetLoadedFromNeckoAsText() const { return mLoadedFromNeckoAsText; }
  uint16_t GetLoadedFromNeckoAsSerializedStencil() const {
    return mLoadedFromNeckoAsSerializedStencil;
  }
  uint16_t GetMemoryCacheUsed() const { return mMemoryCacheUsed; }
  uint16_t GetMemoryCacheRevived() const { return mMemoryCacheRevived; }
  uint16_t GetMemoryCacheEvictedDirty() const {
    return mMemoryCacheEvictedDirty;
  }

 private:
  // Check whether the request should be saved to the following or not:
  //   * in-memory cache as Stencil
  //   * necko alternative stream as Stencil XDR
  //
  // If the request is a non-top-level module request and it passed the
  // condition, it's stored into mDiskCacheableDependencyModules in order
  // to iterate over them later.
  void CalculateCacheFlag(ScriptLoadRequest* aRequest);

  void RunScriptWhenSafe(ScriptLoadRequest* aRequest);

  /**
   * Cancel and remove all outstanding load requests, including waiting for any
   * off thread compilations to finish.
   */
  void CancelAndClearScriptLoadRequests();

  Document* mDocument;  // [WEAK]
  nsCOMArray<nsIScriptLoaderObserver> mObservers;

  // The list of ongoing delazification collections.
  // They'll get aborted on memory pressure.
  nsTArray<JS::Heap<JSScript*>> mDelazificationCollectingScripts;
  nsTArray<JS::Heap<JSObject*>> mDelazificationCollectingModules;

  // The following lists maintains the list of requests for each phase and
  // situation.
  // Each request can be a part of at most one list.

  // Holds non-async, non-parser-created requests until it's evaluated or it
  // hits load error.
  JS::loader::ScriptLoadRequestList mNonAsyncExternalScriptInsertedRequests;

  // Holds async requests until it's loaded or it hits load error.
  // When they have been loaded they are moved to mLoadedAsyncRequests.
  JS::loader::ScriptLoadRequestList mLoadingAsyncRequests;

  // Holds async script requests and dynamic module import
  // requests, which are processed in the same way, until it's evaluated,
  // or it's passed to off-thread.
  JS::loader::ScriptLoadRequestList mLoadedAsyncRequests;

  // Holds non-async, parser-created, defer requests, until it's evaluated
  // or it hits load error.
  JS::loader::ScriptLoadRequestList mDeferRequests;

  // Holds parser-created XSLT requests, until it's evaluated or it hits
  // load error.
  JS::loader::ScriptLoadRequestList mXSLTRequests;

  RefPtr<ScriptLoadRequest> mParserBlockingRequest;

  // Holds requests which is passed to off-thread compilation.
  // When the off-thread compilation finishes, the request is added back to
  // the original list if any.
  JS::loader::ScriptLoadRequestList mOffThreadCompilingRequests;

  // Holds non-top-level module requests which passed disk caching conditions,
  // until it's queued to mDiskCacheQueue.
  //
  // TODO: Remove this and per-ScriptLoader caching queue (bug 1902951).
  JS::loader::ScriptLoadRequestList mDiskCacheableDependencyModules;

  // Holds already-evaluted requests' script that are holding a stencil which
  // has to be saved on the disk cache, until it's cached or the caching is
  // aborted.
  nsTArray<RefPtr<JS::loader::LoadedScript>> mDiskCacheQueue;

  // 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 = 0;
  uint32_t mBlockerCount = 0;
  uint32_t mNumberOfProcessors = 0;
  uint32_t mTotalFullParseSize = 0;
  int32_t mPhysicalSizeOfMemory = -1;

  // Telemetry data for the memory cache usage per document.
  //
  //   Load
  //     |
  //     v         YES              NO
  //   has cache? -----> is dirty? ----> mMemoryCacheUsed++
  //     |                 |
  //     | NO              | YES
  //     |                 v           YES
  //     |               still valid? -----> mMemoryCacheRevived++
  //     |                 |
  //     |                 | NO
  //     |                 v
  //     |               mMemoryCacheEvictedDirty++
  //     v                 |
  //     +<----------------+
  //     |
  //     v       NO
  //   is text? ----> mLoadedFromNeckoAsSerializedStencil++
  //     |
  //     | YES
  //     v
  //   mLoadedFromNeckoAsText++
  //
  // TotalLoads = mLoadedFromNeckoAsText +
  //              mLoadedFromNeckoAsSerializedStencil +
  //              mMemoryCacheUsed +
  //              mMemoryCacheRevived
  //
  // SkippedIPC = mMemoryCacheUsed / TotalLoads
  //
  // UsedRawStencil = (mMemoryCacheUsed + mMemoryCacheRevived) / TotalLoads
  //
  // SkippedCompilation = (mMemoryCacheUsed + mMemoryCacheRevived +
  //                       mLoadedFromNeckoAsSerializedStencil) / TotalLoads
  //
  uint16_t mLoadedFromNeckoAsText = 0;
  uint16_t mLoadedFromNeckoAsSerializedStencil = 0;
  uint16_t mMemoryCacheUsed = 0;
  uint16_t mMemoryCacheRevived = 0;
  uint16_t mMemoryCacheEvictedDirty = 0;

  bool mEnabled = true;
  bool mDeferEnabled = false;
  bool mSpeculativeOMTParsingEnabled = false;
  bool mDeferCheckpointReached = false;
  bool mBlockingDOMContentLoaded = false;
  bool mLoadEventFired = false;
  bool mGiveUpDiskCaching = false;
  bool mContinueParsingDocumentAfterCurrentScript = false;
  bool mHadFCPDoNotUseDirectly = false;

  TimeDuration mMainThreadParseTime;

  nsCOMPtr<nsIConsoleReportCollector> mReporter;

  // ShutdownObserver for off thread compilations, and the
  // observer for the memory pressure.
  RefPtr<ShutdownAndMemoryPressureObserver> mObserver;

  RefPtr<ModuleLoader> mModuleLoader;
  nsTArray<RefPtr<ModuleLoader>> mWebExtModuleLoaders;

  RefPtr<SharedScriptCache> mCache;

  nsCOMPtr<nsITimer> mProcessPendingRequestsAsyncBypassParserBlocking;

  // 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
