#include "third_party/blink/renderer/core/script/script_loader.h"

#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/dom/scriptable_document_parser.h"
#include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/use_counter.h"
#include "third_party/blink/renderer/core/html/imports/html_import.h"
#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/loader/modulescript/module_script_fetch_request.h"
#include "third_party/blink/renderer/core/loader/subresource_integrity_helper.h"
#include "third_party/blink/renderer/core/script/classic_pending_script.h"
#include "third_party/blink/renderer/core/script/classic_script.h"
#include "third_party/blink/renderer/core/script/modulator.h"
#include "third_party/blink/renderer/core/script/module_pending_script.h"
#include "third_party/blink/renderer/core/script/script.h"
#include "third_party/blink/renderer/core/script/script_element_base.h"
#include "third_party/blink/renderer/core/script/script_runner.h"
#include "third_party/blink/renderer/core/svg_names.h"
#include "third_party/blink/renderer/platform/loader/fetch/access_control_status.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/loader/subresource_integrity.h"
#include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "third_party/blink/renderer/platform/wtf/text/string_hash.h"

namespace blink {

ScriptLoader::ScriptLoader(ScriptElementBase* element,
                           bool parser_inserted,
                           bool already_started)
    : element_(element),
      will_be_parser_executed_(false),
      will_execute_when_document_finished_parsing_(false) {

  if (already_started)
    already_started_ = true;

  if (parser_inserted) {

    parser_inserted_ = true;

    non_blocking_ = false;
  }
}

ScriptLoader::~ScriptLoader() {}

void ScriptLoader::Trace(blink::Visitor* visitor) {
  visitor->Trace(element_);
  visitor->Trace(pending_script_);
  visitor->Trace(prepared_pending_script_);
  visitor->Trace(resource_keep_alive_);
  PendingScriptClient::Trace(visitor);
}

void ScriptLoader::TraceWrappers(ScriptWrappableVisitor* visitor) const {
  visitor->TraceWrappers(pending_script_);
  visitor->TraceWrappers(prepared_pending_script_);
}

void ScriptLoader::DidNotifySubtreeInsertionsToDocument() {
  if (!parser_inserted_)
    PrepareScript();  
}

void ScriptLoader::ChildrenChanged() {
  if (!parser_inserted_ && element_->IsConnected())
    PrepareScript();  
}

void ScriptLoader::HandleSourceAttribute(const String& source_url) {
  if (IgnoresLoadRequest() || source_url.IsEmpty())
    return;

  PrepareScript();  
}

void ScriptLoader::HandleAsyncAttribute() {

  non_blocking_ = false;
}

void ScriptLoader::DetachPendingScript() {
  if (!pending_script_)
    return;
  pending_script_->Dispose();
  pending_script_ = nullptr;
}

namespace {

bool IsValidClassicScriptTypeAndLanguage(
    const String& type,
    const String& language,
    ScriptLoader::LegacyTypeSupport support_legacy_types) {

  if (type.IsEmpty()) {
    return language.IsEmpty() ||  
           MIMETypeRegistry::IsSupportedJavaScriptMIMEType("text/" +
                                                           language) ||
           MIMETypeRegistry::IsLegacySupportedJavaScriptLanguage(language);
  } else if (MIMETypeRegistry::IsSupportedJavaScriptMIMEType(
                 type.StripWhiteSpace()) ||
             (support_legacy_types ==
                  ScriptLoader::kAllowLegacyTypeInTypeAttribute &&
              MIMETypeRegistry::IsLegacySupportedJavaScriptLanguage(type))) {
    return true;
  }

  return false;
}

}  

bool ScriptLoader::IsValidScriptTypeAndLanguage(
    const String& type,
    const String& language,
    LegacyTypeSupport support_legacy_types,
    ScriptType& out_script_type) {
  if (IsValidClassicScriptTypeAndLanguage(type, language,
                                          support_legacy_types)) {

    out_script_type = ScriptType::kClassic;
    return true;
  }

  if (type == "module") {

    out_script_type = ScriptType::kModule;
    return true;
  }

  return false;
}

bool ScriptLoader::BlockForNoModule(ScriptType script_type, bool nomodule) {
  return nomodule && script_type == ScriptType::kClassic;
}

network::mojom::FetchCredentialsMode ScriptLoader::ModuleScriptCredentialsMode(
    CrossOriginAttributeValue cross_origin) {
  switch (cross_origin) {
    case kCrossOriginAttributeNotSet:
      return network::mojom::FetchCredentialsMode::kOmit;
    case kCrossOriginAttributeAnonymous:
      return network::mojom::FetchCredentialsMode::kSameOrigin;
    case kCrossOriginAttributeUseCredentials:
      return network::mojom::FetchCredentialsMode::kInclude;
  }
  NOTREACHED();
  return network::mojom::FetchCredentialsMode::kOmit;
}

bool ScriptLoader::PrepareScript(const TextPosition& script_start_position,
                                 LegacyTypeSupport support_legacy_types) {

  if (already_started_)
    return false;

  bool was_parser_inserted;
  if (parser_inserted_) {
    was_parser_inserted = true;
    parser_inserted_ = false;
  } else {
    was_parser_inserted = false;
  }

  if (was_parser_inserted && !element_->AsyncAttributeValue())
    non_blocking_ = true;

  if (!element_->HasSourceAttribute() && !element_->HasChildren())
    return false;

  if (!element_->IsConnected())
    return false;

  if (!IsValidScriptTypeAndLanguage(element_->TypeAttributeValue(),
                                    element_->LanguageAttributeValue(),
                                    support_legacy_types, script_type_)) {
    return false;
  }

  if (was_parser_inserted) {
    parser_inserted_ = true;
    non_blocking_ = false;
  }

  already_started_ = true;

  Document& element_document = element_->GetDocument();
  Document* context_document = element_document.ContextDocument();
  if (!element_document.ExecutingFrame())
    return false;
  if (!context_document || !context_document->ExecutingFrame())
    return false;

  if (!context_document->CanExecuteScripts(kAboutToExecuteScript))
    return false;

  if (BlockForNoModule(script_type_, element_->NomoduleAttributeValue()))
    return false;

  if (!IsScriptForEventSupported())
    return false;

  CrossOriginAttributeValue cross_origin =
      GetCrossOriginAttributeValue(element_->CrossOriginAttributeValue());

  network::mojom::FetchCredentialsMode credentials_mode =
      ModuleScriptCredentialsMode(cross_origin);

  String nonce = element_->GetNonceForElement();

  String integrity_attr = element_->IntegrityAttributeValue();
  IntegrityMetadataSet integrity_metadata;
  if (!integrity_attr.IsEmpty()) {
    SubresourceIntegrity::IntegrityFeatures integrity_features =
        SubresourceIntegrityHelper::GetFeatures(&element_document);
    SubresourceIntegrity::ReportInfo report_info;
    SubresourceIntegrity::ParseIntegrityAttribute(
        integrity_attr, integrity_features, integrity_metadata, &report_info);
    SubresourceIntegrityHelper::DoReport(element_document, report_info);
  }

  ParserDisposition parser_state =
      IsParserInserted() ? kParserInserted : kNotParserInserted;

  if (GetScriptType() == ScriptType::kModule)
    UseCounter::Count(*context_document, WebFeature::kPrepareModuleScript);

  DCHECK(!prepared_pending_script_);

  const bool is_in_document_write = element_document.IsInDocumentWrite();

  TextPosition position =
      is_in_document_write ? TextPosition() : script_start_position;

  ScriptFetchOptions options(nonce, integrity_metadata, integrity_attr,
                             parser_state, credentials_mode);

  if (element_->HasSourceAttribute()) {

    String src =
        StripLeadingAndTrailingHTMLSpaces(element_->SourceAttributeValue());

    if (src.IsEmpty()) {

      element_->DispatchErrorEvent();
      return false;
    }

    is_external_script_ = true;

    KURL url = element_document.CompleteURL(src);

    if (!url.IsValid()) {

      element_->DispatchErrorEvent();
      return false;
    }

    if (GetScriptType() == ScriptType::kClassic) {

      WTF::TextEncoding encoding;
      if (!element_->CharsetAttributeValue().IsEmpty())
        encoding = WTF::TextEncoding(element_->CharsetAttributeValue());
      else
        encoding = element_document.Encoding();

      FetchClassicScript(url, element_document, options, encoding);
    } else {

      Modulator* modulator = Modulator::From(
          ToScriptStateForMainWorld(context_document->GetFrame()));
      FetchModuleScriptTree(url, modulator, options);
    }

  }

  if (!element_->HasSourceAttribute()) {

    KURL base_url = element_document.BaseURL();

    switch (GetScriptType()) {

      case ScriptType::kClassic: {

        ScriptSourceLocationType script_location_type =
            ScriptSourceLocationType::kInline;
        if (!parser_inserted_) {
          script_location_type =
              ScriptSourceLocationType::kInlineInsideGeneratedElement;
        } else if (is_in_document_write) {
          script_location_type =
              ScriptSourceLocationType::kInlineInsideDocumentWrite;
        }

        prepared_pending_script_ = ClassicPendingScript::CreateInline(
            element_, position, script_location_type, options);

        break;
      }

      case ScriptType::kModule: {

        const KURL& source_url = element_document.Url();
        Modulator* modulator = Modulator::From(
            ToScriptStateForMainWorld(context_document->GetFrame()));
        ModuleScript* module_script = ModuleScript::Create(
            element_->TextFromChildren(), modulator, source_url, base_url,
            options, kSharableCrossOrigin, position);

        if (!module_script)
          return false;

        auto* module_tree_client = ModulePendingScriptTreeClient::Create();
        modulator->FetchDescendantsForInlineScript(
            module_script, WebURLRequest::kRequestContextScript,
            module_tree_client);
        prepared_pending_script_ = ModulePendingScript::Create(
            element_, module_tree_client, is_external_script_);
        break;
      }
    }
  }

  DCHECK(prepared_pending_script_);

  if ((GetScriptType() == ScriptType::kClassic &&
       element_->HasSourceAttribute() && element_->DeferAttributeValue() &&
       parser_inserted_ && !element_->AsyncAttributeValue()) ||
      (GetScriptType() == ScriptType::kModule && parser_inserted_ &&
       !element_->AsyncAttributeValue())) {

    will_execute_when_document_finished_parsing_ = true;
    will_be_parser_executed_ = true;

    return true;
  }

  if (GetScriptType() == ScriptType::kClassic &&
      element_->HasSourceAttribute() && parser_inserted_ &&
      !element_->AsyncAttributeValue()) {

    will_be_parser_executed_ = true;

    return true;
  }

  if ((GetScriptType() == ScriptType::kClassic &&
       element_->HasSourceAttribute() && !element_->AsyncAttributeValue() &&
       !non_blocking_) ||
      (GetScriptType() == ScriptType::kModule &&
       !element_->AsyncAttributeValue() && !non_blocking_)) {

    pending_script_ = TakePendingScript(ScriptSchedulingType::kInOrder);

    context_document->GetScriptRunner()->QueueScriptForExecution(
        pending_script_);

    pending_script_->WatchForLoad(this);

    return true;
  }

  if ((GetScriptType() == ScriptType::kClassic &&
       element_->HasSourceAttribute()) ||
      GetScriptType() == ScriptType::kModule) {

    pending_script_ = TakePendingScript(ScriptSchedulingType::kAsync);

    context_document->GetScriptRunner()->QueueScriptForExecution(
        pending_script_);

    pending_script_->WatchForLoad(this);

    return true;
  }

  DCHECK_EQ(GetScriptType(), ScriptType::kClassic);
  DCHECK(!is_external_script_);

  if (!element_->HasSourceAttribute() && parser_inserted_ &&
      !element_document.IsScriptExecutionReady()) {

    will_be_parser_executed_ = true;

    ready_to_be_parser_executed_ = true;

    return true;
  }

  KURL script_url = (!is_in_document_write && parser_inserted_)
                        ? element_document.Url()
                        : KURL();
  TakePendingScript(ScriptSchedulingType::kImmediate)
      ->ExecuteScriptBlock(script_url);
  return true;
}

void ScriptLoader::FetchClassicScript(const KURL& url,
                                      Document& element_document,
                                      const ScriptFetchOptions& options,
                                      const WTF::TextEncoding& encoding) {
  FetchParameters::DeferOption defer = FetchParameters::kNoDefer;
  if (!parser_inserted_ || element_->AsyncAttributeValue() ||
      element_->DeferAttributeValue())
    defer = FetchParameters::kLazyLoad;

  ClassicPendingScript* pending_script = ClassicPendingScript::Fetch(
      url, element_document, options, encoding, element_, defer);
  prepared_pending_script_ = pending_script;
  resource_keep_alive_ = pending_script->GetResource();
}

void ScriptLoader::FetchModuleScriptTree(const KURL& url,
                                         Modulator* modulator,
                                         const ScriptFetchOptions& options) {

  auto* module_tree_client = ModulePendingScriptTreeClient::Create();
  modulator->FetchTree(url, WebURLRequest::kRequestContextScript, options,
                       module_tree_client);
  prepared_pending_script_ = ModulePendingScript::Create(
      element_, module_tree_client, is_external_script_);
}

PendingScript* ScriptLoader::TakePendingScript(
    ScriptSchedulingType scheduling_type) {
  CHECK(prepared_pending_script_);

  switch (scheduling_type) {
    case ScriptSchedulingType::kAsync:
    case ScriptSchedulingType::kInOrder:

      resource_keep_alive_ = nullptr;
      break;

    default:

      break;
  }

  PendingScript* pending_script = prepared_pending_script_;
  prepared_pending_script_ = nullptr;
  pending_script->SetSchedulingType(scheduling_type);
  return pending_script;
}

void ScriptLoader::PendingScriptFinished(PendingScript* pending_script) {
  DCHECK(!will_be_parser_executed_);
  DCHECK_EQ(pending_script_, pending_script);
  DCHECK_EQ(pending_script_->GetScriptType(), GetScriptType());
  DCHECK(pending_script->IsControlledByScriptRunner());

  Document* context_document = element_->GetDocument().ContextDocument();
  if (!context_document) {
    DetachPendingScript();
    return;
  }

  context_document->GetScriptRunner()->NotifyScriptReady(pending_script);
  pending_script_->StopWatchingForLoad();
  pending_script_ = nullptr;
}

bool ScriptLoader::IgnoresLoadRequest() const {
  return already_started_ || is_external_script_ || parser_inserted_ ||
         !element_->IsConnected();
}

bool ScriptLoader::IsScriptForEventSupported() const {

  String event_attribute = element_->EventAttributeValue();

  String for_attribute = element_->ForAttributeValue();

  if (GetScriptType() != ScriptType::kClassic || event_attribute.IsNull() ||
      for_attribute.IsNull())
    return true;

  for_attribute = for_attribute.StripWhiteSpace();

  if (!DeprecatedEqualIgnoringCase(for_attribute, "window"))
    return false;
  event_attribute = event_attribute.StripWhiteSpace();

  return DeprecatedEqualIgnoringCase(event_attribute, "onload") ||
         DeprecatedEqualIgnoringCase(event_attribute, "onload()");
}

PendingScript*
ScriptLoader::GetPendingScriptIfControlledByScriptRunnerForCrossDocMove() {
  DCHECK(!pending_script_ || pending_script_->IsControlledByScriptRunner());
  return pending_script_;
}

}
/*
 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
 *           (C) 1999 Antti Koivisto (koivisto@kde.org)
 *           (C) 2001 Dirk Mueller (mueller@kde.org)
 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights
 * reserved.
 * Copyright (C) 2008 Nikolas Zimmermann <zimmermann@kde.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "third_party/blink/renderer/core/script/script_loader.h"

#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/dom/scriptable_document_parser.h"
#include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/use_counter.h"
#include "third_party/blink/renderer/core/html/imports/html_import.h"
#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/loader/modulescript/module_script_fetch_request.h"
#include "third_party/blink/renderer/core/loader/subresource_integrity_helper.h"
#include "third_party/blink/renderer/core/script/classic_pending_script.h"
#include "third_party/blink/renderer/core/script/classic_script.h"
#include "third_party/blink/renderer/core/script/modulator.h"
#include "third_party/blink/renderer/core/script/module_pending_script.h"
#include "third_party/blink/renderer/core/script/script.h"
#include "third_party/blink/renderer/core/script/script_element_base.h"
#include "third_party/blink/renderer/core/script/script_runner.h"
#include "third_party/blink/renderer/core/svg_names.h"
#include "third_party/blink/renderer/platform/loader/fetch/access_control_status.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/loader/subresource_integrity.h"
#include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "third_party/blink/renderer/platform/wtf/text/string_hash.h"

namespace blink {

ScriptLoader::ScriptLoader(ScriptElementBase* element,
                           bool parser_inserted,
                           bool already_started)
    : element_(element),
      will_be_parser_executed_(false),
      will_execute_when_document_finished_parsing_(false) {
  // <spec
  // href="https://html.spec.whatwg.org/multipage/scripting.html#already-started">
  // ... The cloning steps for script elements must set the "already started"
  // flag on the copy if it is set on the element being cloned.</spec>
  //
  // TODO(hiroshige): Cloning is implemented together with
  // {HTML,SVG}ScriptElement::cloneElementWithoutAttributesAndChildren().
  // Clean up these later.
  if (already_started)
    already_started_ = true;

  if (parser_inserted) {
    // <spec
    // href="https://html.spec.whatwg.org/multipage/scripting.html#parser-inserted">
    // ... It is set by the HTML parser and the XML parser on script elements
    // they insert ...</spec>
    parser_inserted_ = true;

    // <spec
    // href="https://html.spec.whatwg.org/multipage/scripting.html#non-blocking">
    // ... It is unset by the HTML parser and the XML parser on script elements
    // they insert. ...</spec>
    non_blocking_ = false;
  }
}

ScriptLoader::~ScriptLoader() {}

void ScriptLoader::Trace(blink::Visitor* visitor) {
  visitor->Trace(element_);
  visitor->Trace(pending_script_);
  visitor->Trace(prepared_pending_script_);
  visitor->Trace(resource_keep_alive_);
  PendingScriptClient::Trace(visitor);
}

void ScriptLoader::TraceWrappers(ScriptWrappableVisitor* visitor) const {
  visitor->TraceWrappers(pending_script_);
  visitor->TraceWrappers(prepared_pending_script_);
}

void ScriptLoader::DidNotifySubtreeInsertionsToDocument() {
  if (!parser_inserted_)
    PrepareScript();  // FIXME: Provide a real starting line number here.
}

void ScriptLoader::ChildrenChanged() {
  if (!parser_inserted_ && element_->IsConnected())
    PrepareScript();  // FIXME: Provide a real starting line number here.
}

void ScriptLoader::HandleSourceAttribute(const String& source_url) {
  if (IgnoresLoadRequest() || source_url.IsEmpty())
    return;

  PrepareScript();  // FIXME: Provide a real starting line number here.
}

void ScriptLoader::HandleAsyncAttribute() {
  // <spec
  // href="https://html.spec.whatwg.org/multipage/scripting.html#non-blocking">
  // ... In addition, whenever a script element whose "non-blocking" flag is set
  // has an async content attribute added, the element's "non-blocking" flag
  // must be unset.</spec>
  non_blocking_ = false;
}

void ScriptLoader::DetachPendingScript() {
  if (!pending_script_)
    return;
  pending_script_->Dispose();
  pending_script_ = nullptr;
}

namespace {

bool IsValidClassicScriptTypeAndLanguage(
    const String& type,
    const String& language,
    ScriptLoader::LegacyTypeSupport support_legacy_types) {
  // FIXME: isLegacySupportedJavaScriptLanguage() is not valid HTML5. It is used
  // here to maintain backwards compatibility with existing layout tests. The
  // specific violations are:
  // - Allowing type=javascript. type= should only support MIME types, such as
  //   text/javascript.
  // - Allowing a different set of languages for language= and type=. language=
  //   supports Javascript 1.1 and 1.4-1.6, but type= does not.
  if (type.IsEmpty()) {
    return language.IsEmpty() ||  // assume text/javascript.
           MIMETypeRegistry::IsSupportedJavaScriptMIMEType("text/" +
                                                           language) ||
           MIMETypeRegistry::IsLegacySupportedJavaScriptLanguage(language);
  } else if (MIMETypeRegistry::IsSupportedJavaScriptMIMEType(
                 type.StripWhiteSpace()) ||
             (support_legacy_types ==
                  ScriptLoader::kAllowLegacyTypeInTypeAttribute &&
              MIMETypeRegistry::IsLegacySupportedJavaScriptLanguage(type))) {
    return true;
  }

  return false;
}

}  // namespace

// https://html.spec.whatwg.org/multipage/scripting.html#prepare-a-script
bool ScriptLoader::IsValidScriptTypeAndLanguage(
    const String& type,
    const String& language,
    LegacyTypeSupport support_legacy_types,
    ScriptType& out_script_type) {
  if (IsValidClassicScriptTypeAndLanguage(type, language,
                                          support_legacy_types)) {
    // <spec step="7">... If the script block's type string is a JavaScript MIME
    // type essence match, the script's type is "classic". ...</spec>
    //
    // TODO(hiroshige): Annotate and/or cleanup this step.
    out_script_type = ScriptType::kClassic;
    return true;
  }

  if (type == "module") {
    // <spec step="7">... If the script block's type string is an ASCII
    // case-insensitive match for the string "module", the script's type is
    // "module". ...</spec>
    out_script_type = ScriptType::kModule;
    return true;
  }

  // <spec step="7">... If neither of the above conditions are true, then
  // return. No script is executed.</spec>
  return false;
}

bool ScriptLoader::BlockForNoModule(ScriptType script_type, bool nomodule) {
  return nomodule && script_type == ScriptType::kClassic;
}

// Step 16 of
// https://html.spec.whatwg.org/multipage/scripting.html#prepare-a-script
network::mojom::FetchCredentialsMode ScriptLoader::ModuleScriptCredentialsMode(
    CrossOriginAttributeValue cross_origin) {
  switch (cross_origin) {
    case kCrossOriginAttributeNotSet:
      return network::mojom::FetchCredentialsMode::kOmit;
    case kCrossOriginAttributeAnonymous:
      return network::mojom::FetchCredentialsMode::kSameOrigin;
    case kCrossOriginAttributeUseCredentials:
      return network::mojom::FetchCredentialsMode::kInclude;
  }
  NOTREACHED();
  return network::mojom::FetchCredentialsMode::kOmit;
}

// https://html.spec.whatwg.org/multipage/scripting.html#prepare-a-script
bool ScriptLoader::PrepareScript(const TextPosition& script_start_position,
                                 LegacyTypeSupport support_legacy_types) {
  // <spec step="1">If the script element is marked as having "already started",
  // then return. The script is not executed.</spec>
  if (already_started_)
    return false;

  // <spec step="2">If the element has its "parser-inserted" flag set, then set
  // was-parser-inserted to true and unset the element's "parser-inserted" flag.
  // Otherwise, set was-parser-inserted to false.</spec>
  bool was_parser_inserted;
  if (parser_inserted_) {
    was_parser_inserted = true;
    parser_inserted_ = false;
  } else {
    was_parser_inserted = false;
  }

  // <spec step="3">If was-parser-inserted is true and the element does not have
  // an async attribute, then set the element's "non-blocking" flag to
  // true.</spec>
  if (was_parser_inserted && !element_->AsyncAttributeValue())
    non_blocking_ = true;

  // <spec step="4">Let source text be the element's child text content.</spec>
  //
  // <spec step="5">If the element has no src attribute, and source text is the
  // empty string, then return. The script is not executed.</spec>
  //
  // TODO(hiroshige): Update the behavior according to the spec.
  if (!element_->HasSourceAttribute() && !element_->HasChildren())
    return false;

  // <spec step="6">If the element is not connected, then return. The script is
  // not executed.</spec>
  if (!element_->IsConnected())
    return false;

  // <spec step="7">... Determine the script's type as follows: ...</spec>
  //
  // |script_type_| is set here.

  if (!IsValidScriptTypeAndLanguage(element_->TypeAttributeValue(),
                                    element_->LanguageAttributeValue(),
                                    support_legacy_types, script_type_)) {
    return false;
  }

  // <spec step="8">If was-parser-inserted is true, then flag the element as
  // "parser-inserted" again, and set the element's "non-blocking" flag to
  // false.</spec>
  if (was_parser_inserted) {
    parser_inserted_ = true;
    non_blocking_ = false;
  }

  // <spec step="9">Set the element's "already started" flag.</spec>
  already_started_ = true;

  // <spec step="10">If the element is flagged as "parser-inserted", but the
  // element's node document is not the Document of the parser that created the
  // element, then return.</spec>
  //
  // FIXME: If script is parser inserted, verify it's still in the original
  // document.
  Document& element_document = element_->GetDocument();
  Document* context_document = element_document.ContextDocument();
  if (!element_document.ExecutingFrame())
    return false;
  if (!context_document || !context_document->ExecutingFrame())
    return false;

  // <spec step="11">If scripting is disabled for the script element, then
  // return. The script is not executed.</spec>
  if (!context_document->CanExecuteScripts(kAboutToExecuteScript))
    return false;

  // <spec step="12">If the script element has a nomodule content attribute and
  // the script's type is "classic", then return. The script is not
  // executed.</spec>
  if (BlockForNoModule(script_type_, element_->NomoduleAttributeValue()))
    return false;

  // 13.
  if (!IsScriptForEventSupported())
    return false;

  // 14. is handled below.

  // <spec step="16">Let classic script CORS setting be the current state of the
  // element's crossorigin content attribute.</spec>
  CrossOriginAttributeValue cross_origin =
      GetCrossOriginAttributeValue(element_->CrossOriginAttributeValue());

  // <spec step="17">Let module script credentials mode be the module script
  // credentials mode for the element's crossorigin content attribute.</spec>
  network::mojom::FetchCredentialsMode credentials_mode =
      ModuleScriptCredentialsMode(cross_origin);

  // <spec step="18">Let cryptographic nonce be the element's
  // [[CryptographicNonce]] internal slot's value.</spec>
  String nonce = element_->GetNonceForElement();

  // <spec step="19">If the script element has an integrity attribute, then let
  // integrity metadata be that attribute's value. Otherwise, let integrity
  // metadata be the empty string.</spec>
  String integrity_attr = element_->IntegrityAttributeValue();
  IntegrityMetadataSet integrity_metadata;
  if (!integrity_attr.IsEmpty()) {
    SubresourceIntegrity::IntegrityFeatures integrity_features =
        SubresourceIntegrityHelper::GetFeatures(&element_document);
    SubresourceIntegrity::ReportInfo report_info;
    SubresourceIntegrity::ParseIntegrityAttribute(
        integrity_attr, integrity_features, integrity_metadata, &report_info);
    SubresourceIntegrityHelper::DoReport(element_document, report_info);
  }

  // <spec step="20">Let parser metadata be "parser-inserted" if the script
  // element has been flagged as "parser-inserted", and "not-parser-inserted"
  // otherwise.</spec>
  ParserDisposition parser_state =
      IsParserInserted() ? kParserInserted : kNotParserInserted;

  if (GetScriptType() == ScriptType::kModule)
    UseCounter::Count(*context_document, WebFeature::kPrepareModuleScript);

  DCHECK(!prepared_pending_script_);

  // TODO(csharrison): This logic only works if the tokenizer/parser was not
  // blocked waiting for scripts when the element was inserted. This usually
  // fails for instance, on second document.write if a script writes twice
  // in a row. To fix this, the parser might have to keep track of raw
  // string position.
  //
  // Also PendingScript's contructor has the same code.
  const bool is_in_document_write = element_document.IsInDocumentWrite();

  // Reset line numbering for nested writes.
  TextPosition position =
      is_in_document_write ? TextPosition() : script_start_position;

  // <spec step="21">Let options be a script fetch options whose cryptographic
  // nonce is cryptographic nonce, integrity metadata is integrity metadata,
  // parser metadata is parser metadata, credentials mode is module script
  // credentials mode, and referrer policy is the empty string.</spec>
  ScriptFetchOptions options(nonce, integrity_metadata, integrity_attr,
                             parser_state, credentials_mode);

  // <spec step="22">Let settings object be the element's node document's Window
  // object's environment settings object.</spec>
  //
  // Note: We use |element_document| as "settings object" in the steps below.

  // <spec step="23">If the element has a src content attribute, then:</spec>
  if (element_->HasSourceAttribute()) {
    // <spec step="23.1">Let src be the value of the element's src
    // attribute.</spec>
    String src =
        StripLeadingAndTrailingHTMLSpaces(element_->SourceAttributeValue());

    // <spec step="23.2">If src is the empty string, queue a task to fire an
    // event named error at the element, and return.</spec>
    if (src.IsEmpty()) {
      // TODO(hiroshige): Make this asynchronous. Currently we fire the error
      // event synchronously to keep the existing behavior.
      element_->DispatchErrorEvent();
      return false;
    }

    // <spec step="23.3">Set the element's from an external file flag.</spec>
    is_external_script_ = true;

    // <spec step="23.4">Parse src relative to the element's node
    // document.</spec>
    KURL url = element_document.CompleteURL(src);

    // <spec step="23.5">If the previous step failed, queue a task to fire an
    // event named error at the element, and return. Otherwise, let url be the
    // resulting URL record.</spec>
    if (!url.IsValid()) {
      // TODO(hiroshige): Make this asynchronous. Currently we fire the error
      // event synchronously to keep the existing behavior.
      element_->DispatchErrorEvent();
      return false;
    }

    // <spec step="23.6">Switch on the script's type:</spec>
    if (GetScriptType() == ScriptType::kClassic) {
      // - "classic":

      // <spec step="15">If the script element has a charset attribute, then let
      // encoding be the result of getting an encoding from the value of the
      // charset attribute. If the script element does not have a charset
      // attribute, or if getting an encoding failed, let encoding be the same
      // as the encoding of the script element's node document.</spec>
      //
      // TODO(hiroshige): Should we handle failure in getting an encoding?
      WTF::TextEncoding encoding;
      if (!element_->CharsetAttributeValue().IsEmpty())
        encoding = WTF::TextEncoding(element_->CharsetAttributeValue());
      else
        encoding = element_document.Encoding();

      // <spec step="23.6.A">"classic"
      //
      // Fetch a classic script given url, settings object, options, classic
      // script CORS setting, and encoding.</spec>
      FetchClassicScript(url, element_document, options, encoding);
    } else {
      // - "module":

      // Step 15 is skipped because they are not used in module
      // scripts.

      // <spec step="23.6.B">"module"
      //
      // Fetch a module script graph given url, settings object, "script", and
      // options.</spec>
      Modulator* modulator = Modulator::From(
          ToScriptStateForMainWorld(context_document->GetFrame()));
      FetchModuleScriptTree(url, modulator, options);
    }
    // <spec step="23.6">When the chosen algorithm asynchronously completes, set
    // the script's script to the result. At that time, the script is ready.
    // ...</spec>
    //
    // When the script is ready, PendingScriptClient::pendingScriptFinished()
    // is used as the notification, and the action to take when
    // the script is ready is specified later, in
    // - ScriptLoader::PrepareScript(), or
    // - HTMLParserScriptRunner,
    // depending on the conditions in Step 25 of "prepare a script".
  }

  // <spec step="24">If the element does not have a src content attribute, run
  // these substeps:</spec>
  if (!element_->HasSourceAttribute()) {
    // <spec step="23.1">Let src be the value of the element's src
    // attribute.</spec>
    //
    // This step is done later as ScriptElementBase::TextFromChildren():
    // - in ScriptLoader::PrepareScript() (Step 25, 6th Clause),
    // - in HTMLParserScriptRunner::ProcessScriptElementInternal()
    //   (Duplicated code of Step 25, 6th Clause),
    // - in XMLDocumentParser::EndElementNs() (Step 25, 5th Clause), or
    // - in PendingScript::GetSource() (Indirectly used via
    //   HTMLParserScriptRunner::ProcessScriptElementInternal(),
    //   Step 25, 5th Clause).

    // <spec step="24.1">Let base URL be the script element's node document's
    // document base URL.</spec>
    KURL base_url = element_document.BaseURL();

    // <spec step="24.2">Switch on the script's type:</spec>
    switch (GetScriptType()) {
      // Step 24.2.A. "classic" [spec text]
      case ScriptType::kClassic: {
        // <spec step="24.2.A.1">Let script be the result of creating a classic
        // script using source text, settings object, base URL, and
        // options.</spec>

        ScriptSourceLocationType script_location_type =
            ScriptSourceLocationType::kInline;
        if (!parser_inserted_) {
          script_location_type =
              ScriptSourceLocationType::kInlineInsideGeneratedElement;
        } else if (is_in_document_write) {
          script_location_type =
              ScriptSourceLocationType::kInlineInsideDocumentWrite;
        }

        prepared_pending_script_ = ClassicPendingScript::CreateInline(
            element_, position, script_location_type, options);

        // <spec step="24.2.A.2">Set the script's script to script.</spec>
        //
        // <spec step="24.2.A.3">The script is ready.</spec>
        //
        // Implemented by ClassicPendingScript.
        break;
      }

      // Step 24.2.B. "module" [spec text]
      case ScriptType::kModule: {
        // <spec step="24.2.B.1">Let script be the result of creating a module
        // script using source text, settings object, base URL, and
        // options.</spec>
        const KURL& source_url = element_document.Url();
        Modulator* modulator = Modulator::From(
            ToScriptStateForMainWorld(context_document->GetFrame()));
        ModuleScript* module_script = ModuleScript::Create(
            element_->TextFromChildren(), modulator, source_url, base_url,
            options, kSharableCrossOrigin, position);

        // <spec step="24.2.B.2">If this returns null, set the script's script
        // to null and return; the script is ready.</spec>
        if (!module_script)
          return false;

        // <spec step="24.2.B.3">Fetch the descendants of and instantiate
        // script, given the destination "script". When this asynchronously
        // completes, set the script's script to the result. At that time, the
        // script is ready.</spec>
        auto* module_tree_client = ModulePendingScriptTreeClient::Create();
        modulator->FetchDescendantsForInlineScript(
            module_script, WebURLRequest::kRequestContextScript,
            module_tree_client);
        prepared_pending_script_ = ModulePendingScript::Create(
            element_, module_tree_client, is_external_script_);
        break;
      }
    }
  }

  DCHECK(prepared_pending_script_);

  // <spec step="25">Then, follow the first of the following options that
  // describes the situation:</spec>

  // Three flags are used to instruct the caller of prepareScript() to execute
  // a part of Step 24, when |m_willBeParserExecuted| is true:
  // - |m_willBeParserExecuted|
  // - |m_willExecuteWhenDocumentFinishedParsing|
  // - |m_readyToBeParserExecuted|
  // TODO(hiroshige): Clean up the dependency.

  // <spec step="25.A">If the script's type is "classic", and the element has a
  // src attribute, and the element has a defer attribute, and the element has
  // been flagged as "parser-inserted", and the element does not have an async
  // attribute
  //
  // If the script's type is "module", and the element has been flagged as
  // "parser-inserted", and the element does not have an async attribute
  // ...</spec>
  if ((GetScriptType() == ScriptType::kClassic &&
       element_->HasSourceAttribute() && element_->DeferAttributeValue() &&
       parser_inserted_ && !element_->AsyncAttributeValue()) ||
      (GetScriptType() == ScriptType::kModule && parser_inserted_ &&
       !element_->AsyncAttributeValue())) {
    // This clause is implemented by the caller-side of prepareScript():
    // - HTMLParserScriptRunner::requestDeferredScript(), and
    // - TODO(hiroshige): Investigate XMLDocumentParser::endElementNs()
    will_execute_when_document_finished_parsing_ = true;
    will_be_parser_executed_ = true;

    return true;
  }

  // <spec step="25.B">If the script's type is "classic", and the element has a
  // src attribute, and the element has been flagged as "parser-inserted", and
  // the element does not have an async attribute ...</spec>
  if (GetScriptType() == ScriptType::kClassic &&
      element_->HasSourceAttribute() && parser_inserted_ &&
      !element_->AsyncAttributeValue()) {
    // This clause is implemented by the caller-side of prepareScript():
    // - HTMLParserScriptRunner::requestParsingBlockingScript()
    // - TODO(hiroshige): Investigate XMLDocumentParser::endElementNs()
    will_be_parser_executed_ = true;

    return true;
  }

  // <spec step="25.C">If the script's type is "classic", and the element has a
  // src attribute, and the element does not have an async attribute, and the
  // element does not have the "non-blocking" flag set
  //
  // If the script's type is "module", and the element does not have an async
  // attribute, and the element does not have the "non-blocking" flag set
  // ...</spec>
  if ((GetScriptType() == ScriptType::kClassic &&
       element_->HasSourceAttribute() && !element_->AsyncAttributeValue() &&
       !non_blocking_) ||
      (GetScriptType() == ScriptType::kModule &&
       !element_->AsyncAttributeValue() && !non_blocking_)) {
    // <spec step="25.C">... Add the element to the end of the list of scripts
    // that will execute in order as soon as possible associated with the node
    // document of the script element at the time the prepare a script algorithm
    // started. ...</spec>
    pending_script_ = TakePendingScript(ScriptSchedulingType::kInOrder);
    // TODO(hiroshige): Here |contextDocument| is used as "node document"
    // while Step 14 uses |elementDocument| as "node document". Fix this.
    context_document->GetScriptRunner()->QueueScriptForExecution(
        pending_script_);
    // Note that watchForLoad can immediately call pendingScriptFinished.
    pending_script_->WatchForLoad(this);
    // The part "When the script is ready..." is implemented in
    // ScriptRunner::notifyScriptReady().
    // TODO(hiroshige): Annotate it.

    return true;
  }

  // <spec step="25.D">If the script's type is "classic", and the element has a
  // src attribute
  //
  // If the script's type is "module" ...</spec>
  if ((GetScriptType() == ScriptType::kClassic &&
       element_->HasSourceAttribute()) ||
      GetScriptType() == ScriptType::kModule) {
    // <spec step="25.D">... The element must be added to the set of scripts
    // that will execute as soon as possible of the node document of the script
    // element at the time the prepare a script algorithm started. When the
    // script is ready, execute the script block and then remove the element
    // from the set of scripts that will execute as soon as possible.</spec>
    pending_script_ = TakePendingScript(ScriptSchedulingType::kAsync);
    // TODO(hiroshige): Here |contextDocument| is used as "node document"
    // while Step 14 uses |elementDocument| as "node document". Fix this.
    context_document->GetScriptRunner()->QueueScriptForExecution(
        pending_script_);
    // Note that watchForLoad can immediately call pendingScriptFinished.
    pending_script_->WatchForLoad(this);
    // The part "When the script is ready..." is implemented in
    // ScriptRunner::notifyScriptReady().
    // TODO(hiroshige): Annotate it.

    return true;
  }

  // The following clauses are executed only if the script's type is "classic"
  // and the element doesn't have a src attribute.
  DCHECK_EQ(GetScriptType(), ScriptType::kClassic);
  DCHECK(!is_external_script_);

  // <spec step="25.E">If the element does not have a src attribute, and the
  // element has been flagged as "parser-inserted", and either the parser that
  // created the script is an XML parser or it's an HTML parser whose script
  // nesting level is not greater than one, and the Document of the HTML parser
  // or XML parser that created the script element has a style sheet that is
  // blocking scripts ...</spec>
  //
  // The last part "... has a style sheet that is blocking scripts"
  // is implemented in Document::isScriptExecutionReady().
  // Part of the condition check is done in
  // HTMLParserScriptRunner::processScriptElementInternal().
  // TODO(hiroshige): Clean up the split condition check.
  if (!element_->HasSourceAttribute() && parser_inserted_ &&
      !element_document.IsScriptExecutionReady()) {
    // The former part of this clause is
    // implemented by the caller-side of prepareScript():
    // - HTMLParserScriptRunner::requestParsingBlockingScript()
    // - TODO(hiroshige): Investigate XMLDocumentParser::endElementNs()
    will_be_parser_executed_ = true;
    // <spec step="25.E">... Set the element's "ready to be parser-executed"
    // flag. ...</spec>
    ready_to_be_parser_executed_ = true;

    return true;
  }

  // <spec step="25.F">Otherwise
  //
  // Immediately execute the script block, even if other scripts are already
  // executing.</spec>
  //
  // Note: this block is also duplicated in
  // HTMLParserScriptRunner::processScriptElementInternal().
  // TODO(hiroshige): Merge the duplicated code.
  KURL script_url = (!is_in_document_write && parser_inserted_)
                        ? element_document.Url()
                        : KURL();
  TakePendingScript(ScriptSchedulingType::kImmediate)
      ->ExecuteScriptBlock(script_url);
  return true;
}

// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-script
void ScriptLoader::FetchClassicScript(const KURL& url,
                                      Document& element_document,
                                      const ScriptFetchOptions& options,
                                      const WTF::TextEncoding& encoding) {
  FetchParameters::DeferOption defer = FetchParameters::kNoDefer;
  if (!parser_inserted_ || element_->AsyncAttributeValue() ||
      element_->DeferAttributeValue())
    defer = FetchParameters::kLazyLoad;

  ClassicPendingScript* pending_script = ClassicPendingScript::Fetch(
      url, element_document, options, encoding, element_, defer);
  prepared_pending_script_ = pending_script;
  resource_keep_alive_ = pending_script->GetResource();
}

void ScriptLoader::FetchModuleScriptTree(const KURL& url,
                                         Modulator* modulator,
                                         const ScriptFetchOptions& options) {
  // <spec
  // href="https://html.spec.whatwg.org/multipage/scripting.html#prepare-a-script"
  // step="23.6.B">"module"
  //
  // Fetch a module script graph given url, settings object, "script", and
  // options.</spec>
  auto* module_tree_client = ModulePendingScriptTreeClient::Create();
  modulator->FetchTree(url, WebURLRequest::kRequestContextScript, options,
                       module_tree_client);
  prepared_pending_script_ = ModulePendingScript::Create(
      element_, module_tree_client, is_external_script_);
}

PendingScript* ScriptLoader::TakePendingScript(
    ScriptSchedulingType scheduling_type) {
  CHECK(prepared_pending_script_);

  switch (scheduling_type) {
    case ScriptSchedulingType::kAsync:
    case ScriptSchedulingType::kInOrder:
      // As ClassicPendingScript keeps a reference to ScriptResource,
      // the ScriptResource is anyway kept alive until evaluation,
      // and can be garbage-collected after that (together with
      // ClassicPendingScript).
      resource_keep_alive_ = nullptr;
      break;

    default:
      // ScriptResource is kept alive by resource_keep_alive_
      // until ScriptLoader is garbage collected.
      break;
  }

  PendingScript* pending_script = prepared_pending_script_;
  prepared_pending_script_ = nullptr;
  pending_script->SetSchedulingType(scheduling_type);
  return pending_script;
}

void ScriptLoader::PendingScriptFinished(PendingScript* pending_script) {
  DCHECK(!will_be_parser_executed_);
  DCHECK_EQ(pending_script_, pending_script);
  DCHECK_EQ(pending_script_->GetScriptType(), GetScriptType());
  DCHECK(pending_script->IsControlledByScriptRunner());

  Document* context_document = element_->GetDocument().ContextDocument();
  if (!context_document) {
    DetachPendingScript();
    return;
  }

  context_document->GetScriptRunner()->NotifyScriptReady(pending_script);
  pending_script_->StopWatchingForLoad();
  pending_script_ = nullptr;
}

bool ScriptLoader::IgnoresLoadRequest() const {
  return already_started_ || is_external_script_ || parser_inserted_ ||
         !element_->IsConnected();
}

// https://html.spec.whatwg.org/multipage/scripting.html#prepare-a-script
bool ScriptLoader::IsScriptForEventSupported() const {
  // <spec step="14.1">Let for be the value of the for attribute.</spec>
  String event_attribute = element_->EventAttributeValue();
  // <spec step="14.2">Let event be the value of the event attribute.</spec>
  String for_attribute = element_->ForAttributeValue();

  // <spec step="14">If the script element has an event attribute and a for
  // attribute, and the script's type is "classic", then:</spec>
  if (GetScriptType() != ScriptType::kClassic || event_attribute.IsNull() ||
      for_attribute.IsNull())
    return true;

  // <spec step="14.3">Strip leading and trailing ASCII whitespace from event
  // and for.</spec>
  for_attribute = for_attribute.StripWhiteSpace();
  // <spec step="14.4">If for is not an ASCII case-insensitive match for the
  // string "window", then return. The script is not executed.</spec>
  if (!DeprecatedEqualIgnoringCase(for_attribute, "window"))
    return false;
  event_attribute = event_attribute.StripWhiteSpace();
  // <spec step="14.5">If event is not an ASCII case-insensitive match for
  // either the string "onload" or the string "onload()", then return. The
  // script is not executed.</spec>
  return DeprecatedEqualIgnoringCase(event_attribute, "onload") ||
         DeprecatedEqualIgnoringCase(event_attribute, "onload()");
}

PendingScript*
ScriptLoader::GetPendingScriptIfControlledByScriptRunnerForCrossDocMove() {
  DCHECK(!pending_script_ || pending_script_->IsControlledByScriptRunner());
  return pending_script_;
}

}  // namespace blink
