/*
 * 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.
 *
 * 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/html/html_script_element.h"

#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/script/script_type.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_union_htmlscriptelement_svgscriptelement.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_union_string_trustedscript.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_union_stringlegacynulltoemptystring_trustedscript.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_union_trustedscripturl_usvstring.h"
#include "third_party/blink/renderer/core/dom/attribute.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/dom_node_ids.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/web_feature.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/loader/render_blocking_resource_manager.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/script/script_loader.h"
#include "third_party/blink/renderer/core/script/script_runner.h"
#include "third_party/blink/renderer/core/script_type_names.h"
#include "third_party/blink/renderer/core/trustedtypes/trusted_script.h"
#include "third_party/blink/renderer/core/trustedtypes/trusted_types_util.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/weborigin/security_policy.h"

namespace blink {

HTMLScriptElement::HTMLScriptElement(Document& document,
                                     const CreateElementFlags flags)
    : HTMLElement(html_names::kScriptTag, document),
      children_changed_by_api_(false),
      blocking_attribute_(MakeGarbageCollected<BlockingAttribute>(this)),
      loader_(InitializeScriptLoader(flags)) {
  if (!flags.IsCreatedByParser()) {
    async_task_context_.Schedule(document.GetExecutionContext(), localName());
  }
}

const AttrNameToTrustedType& HTMLScriptElement::GetCheckedAttributeTypes()
    const {
  DEFINE_STATIC_LOCAL(AttrNameToTrustedType, attribute_map,
                      ({{trusted_types_names::kSrc,
                         std::pair{SpecificTrustedType::kScriptURL,
                                   trusted_types_names::kHTMLScriptElement}}}));
  return attribute_map;
}

bool HTMLScriptElement::IsURLAttribute(const Attribute& attribute) const {
  return attribute.GetName() == html_names::kSrcAttr ||
         HTMLElement::IsURLAttribute(attribute);
}

bool HTMLScriptElement::HasLegalLinkAttribute(const QualifiedName& name) const {
  return name == html_names::kSrcAttr ||
         HTMLElement::HasLegalLinkAttribute(name);
}

void HTMLScriptElement::ChildrenChanged(const ChildrenChange& change) {
  HTMLElement::ChildrenChanged(change);

  if (!GetDocument().StatePreservingAtomicMoveInProgress()) {
    loader_->ChildrenChanged(change);
  }

  // We'll record whether the script element children were ever changed by
  // the API (as opposed to the parser).
  children_changed_by_api_ |= !change.ByParser();
}

void HTMLScriptElement::ParseAttribute(
    const AttributeModificationParams& params) {
  if (params.name == html_names::kSrcAttr) {
    loader_->HandleSourceAttribute(params.new_value);
    LogUpdateAttributeIfIsolatedWorldAndInDocument("script", params);
  } else if (params.name == html_names::kAsyncAttr) {
    // https://html.spec.whatwg.org/C/#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."
    loader_->HandleAsyncAttribute();
  } else if (params.name == html_names::kFetchpriorityAttr) {
    // The only thing we need to do for the the fetchPriority attribute/Priority
    // Hints is count usage upon parsing. Processing the value happens when the
    // element loads.
    UseCounter::Count(GetDocument(), WebFeature::kPriorityHints);
  } else if (params.name == html_names::kBlockingAttr) {
    blocking_attribute_->OnAttributeValueChanged(params.old_value,
                                                 params.new_value);
    if (GetDocument().GetRenderBlockingResourceManager() &&
        !IsPotentiallyRenderBlocking()) {
      GetDocument().GetRenderBlockingResourceManager()->RemovePendingScript(
          *this);
    }
  } else {
    HTMLElement::ParseAttribute(params);
  }
}

Node::InsertionNotificationRequest HTMLScriptElement::InsertedInto(
    ContainerNode& insertion_point) {
  if (insertion_point.isConnected() && HasSourceAttribute() &&
      ScriptLoader::GetScriptTypeAtPrepare(TypeAttributeValue(),
                                           LanguageAttributeValue()) ==
          ScriptLoader::ScriptTypeAtPrepare::kInvalid) {
    UseCounter::Count(GetDocument(),
                      WebFeature::kScriptElementWithInvalidTypeHasSrc);
  }
  HTMLElement::InsertedInto(insertion_point);
  LogAddElementIfIsolatedWorldAndInDocument("script", html_names::kSrcAttr);

  return kInsertionShouldCallDidNotifySubtreeInsertions;
}

void HTMLScriptElement::RemovedFrom(ContainerNode& insertion_point) {
  HTMLElement::RemovedFrom(insertion_point);
  loader_->Removed();
  if (GetDocument().GetRenderBlockingResourceManager() &&
      !GetDocument().StatePreservingAtomicMoveInProgress()) {
    GetDocument().GetRenderBlockingResourceManager()->RemovePendingScript(
        *this);
  }
}

void HTMLScriptElement::DidNotifySubtreeInsertionsToDocument() {
  loader_->DidNotifySubtreeInsertionsToDocument();
}

void HTMLScriptElement::setInnerTextForBinding(
    const V8UnionStringLegacyNullToEmptyStringOrTrustedScript*
        string_or_trusted_script,
    ExceptionState& exception_state) {
  // Old behaviour: Run the Trusted Type script when the super-classes
  //   innerText property is set.
  // New behaviour (TrustedTypesHTML): Run only the superclass' behaviour.
  //   Only when HTMLScriptElement's own innerText property is set, run the
  //   Trusted Types check (in setScriptInnerTextForBinding, below).
  //
  // This can be simplified once TrustedTypesHTMLEnabled is removed.
  if (RuntimeEnabledFeatures::TrustedTypesHTMLEnabled()) {
    const String string =
        string_or_trusted_script->IsStringLegacyNullToEmptyString()
            ? string_or_trusted_script->GetAsStringLegacyNullToEmptyString()
            : string_or_trusted_script->GetAsTrustedScript()->toString();
    HTMLElement::setInnerText(string);
  } else {
    setScriptInnerTextForBinding(string_or_trusted_script, exception_state);
  }
}

void HTMLScriptElement::setScriptInnerTextForBinding(
    const V8UnionStringLegacyNullToEmptyStringOrTrustedScript*
        string_or_trusted_script,
    ExceptionState& exception_state) {
  const String& value = TrustedTypesCheckForScript(
      string_or_trusted_script, GetExecutionContext(),
      trusted_types_names::kHTMLScriptElement, trusted_types_names::kInnerText,
      exception_state);
  if (exception_state.HadException())
    return;
  // https://w3c.github.io/trusted-types/dist/spec/#setting-slot-values
  // "On setting the innerText [...]: Set [[ScriptText]] internal slot value to
  // the stringified attribute value. Perform the usual attribute setter steps."
  script_text_internal_slot_ = ParkableString(value.Impl());
  HTMLElement::setInnerText(value);
}

void HTMLScriptElement::setTextContentForBinding(
    const V8UnionStringOrTrustedScript* value,
    ExceptionState& exception_state) {
  // Old behaviour: Run the Trusted Type script when the super-class'
  //   textContent property is set.
  // New behaviour (TrustedTypesHTML): Run only the superclass' behaviour.
  //   Only when HTMLScriptElement's own textContent property is set, run the
  //   Trusted Types check (in setScriptTextContentForBinding, below).
  //
  // This can be simplified once TrustedTypesHTMLEnabled is removed.
  if (RuntimeEnabledFeatures::TrustedTypesHTMLEnabled()) {
    const String string = value->IsString()
                              ? value->GetAsString()
                              : value->GetAsTrustedScript()->toString();
    HTMLElement::setTextContent(string);
  } else {
    setScriptTextContentForBinding(value, exception_state);
  }
}

void HTMLScriptElement::setScriptTextContentForBinding(
    const V8UnionStringOrTrustedScript* value,
    ExceptionState& exception_state) {
  const String& string = TrustedTypesCheckForScript(
      value, GetExecutionContext(), trusted_types_names::kHTMLScriptElement,
      trusted_types_names::kTextContent, exception_state);
  if (exception_state.HadException())
    return;
  setTextContent(string);
}

void HTMLScriptElement::setTextContent(const String& string) {
  // https://w3c.github.io/trusted-types/dist/spec/#setting-slot-values
  // "On setting [.. textContent ..]: Set [[ScriptText]] internal slot value to
  // the stringified attribute value. Perform the usual attribute setter steps."
  script_text_internal_slot_ = ParkableString(string.Impl());
  Node::setTextContent(string);
}

String HTMLScriptElement::scriptTextContentForBinding() {
  return textContentForBinding();
}

String HTMLScriptElement::scriptInnerTextForBinding() {
  return innerTextForBinding();
}

V8UnionStringOrTrustedScript::Ret HTMLScriptElement::text(
    ScriptState* script_state) {
  return V8UnionStringOrTrustedScript::Ret(script_state, TextFromChildren());
}

void HTMLScriptElement::setText(V8UnionStringOrTrustedScript* value,
                                ExceptionState& exception_state) {
  String compliant_value = TrustedTypesCheckForScript(
      value, GetExecutionContext(), trusted_types_names::kHTMLScriptElement,
      trusted_types_names::kText, exception_state);
  if (exception_state.HadException()) {
    return;
  }
  setTextContent(compliant_value);
}

void HTMLScriptElement::setTextWithoutTrustedTypes(const String& value) {
  setTextContent(value);
}

String HTMLScriptElement::src() {
  return GetURLAttribute(html_names::kSrcAttr);
}

void HTMLScriptElement::setSrc(const V8UnionTrustedScriptURLOrUSVString* value,
                               ExceptionState& exception_state) {
  String compliant_value = TrustedTypesCheckForScriptURL(
      value, GetExecutionContext(), trusted_types_names::kHTMLScriptElement,
      trusted_types_names::kSrc, exception_state);
  if (exception_state.HadException()) {
    return;
  }
  SetAttributeWithoutValidation(html_names::kSrcAttr,
                                AtomicString(compliant_value));
}

void HTMLScriptElement::setAsync(bool async) {
  // https://html.spec.whatwg.org/multipage/scripting.html#dom-script-async
  SetBooleanAttribute(html_names::kAsyncAttr, async);
  loader_->HandleAsyncAttribute();
}

void HTMLScriptElement::FinishParsingChildren() {
  Element::FinishParsingChildren();

  // We normally expect the parser to finish parsing before any script gets
  // a chance to manipulate the script. However, if script parsing gets
  // deferrred (or similar; see crbug.com/1033101) then a script might get
  // access to the HTMLScriptElement before. In this case, we cannot blindly
  // accept the current TextFromChildren as a parser result.
  DCHECK(children_changed_by_api_ || !script_text_internal_slot_.length());
  if (!children_changed_by_api_)
    script_text_internal_slot_ = ParkableString(TextFromChildren().Impl());
}

bool HTMLScriptElement::async() const {
  return FastHasAttribute(html_names::kAsyncAttr) || loader_->IsForceAsync();
}

String HTMLScriptElement::SourceAttributeValue() const {
  return FastGetAttribute(html_names::kSrcAttr).GetString();
}

String HTMLScriptElement::CharsetAttributeValue() const {
  return FastGetAttribute(html_names::kCharsetAttr).GetString();
}

String HTMLScriptElement::TypeAttributeValue() const {
  return FastGetAttribute(html_names::kTypeAttr).GetString();
}

String HTMLScriptElement::LanguageAttributeValue() const {
  return FastGetAttribute(html_names::kLanguageAttr).GetString();
}

bool HTMLScriptElement::NomoduleAttributeValue() const {
  return FastHasAttribute(html_names::kNomoduleAttr);
}

String HTMLScriptElement::ForAttributeValue() const {
  return FastGetAttribute(html_names::kForAttr).GetString();
}

String HTMLScriptElement::EventAttributeValue() const {
  return FastGetAttribute(html_names::kEventAttr).GetString();
}

String HTMLScriptElement::CrossOriginAttributeValue() const {
  return FastGetAttribute(html_names::kCrossoriginAttr);
}

String HTMLScriptElement::IntegrityAttributeValue() const {
  return FastGetAttribute(html_names::kIntegrityAttr);
}

String HTMLScriptElement::SignatureAttributeValue() const {
  return FastGetAttribute(html_names::kSignatureAttr);
}

String HTMLScriptElement::ReferrerPolicyAttributeValue() const {
  return FastGetAttribute(html_names::kReferrerpolicyAttr);
}

String HTMLScriptElement::FetchPriorityAttributeValue() const {
  return FastGetAttribute(html_names::kFetchpriorityAttr);
}

String HTMLScriptElement::CacheHintAttributeValue() const {
  return FastGetAttribute(html_names::kCachehintAttr);
}

String HTMLScriptElement::ChildTextContent() {
  return TextFromChildren();
}

String HTMLScriptElement::ScriptTextInternalSlot() const {
  return script_text_internal_slot_.ToString();
}

bool HTMLScriptElement::AsyncAttributeValue() const {
  return FastHasAttribute(html_names::kAsyncAttr);
}

bool HTMLScriptElement::DeferAttributeValue() const {
  return FastHasAttribute(html_names::kDeferAttr);
}

bool HTMLScriptElement::HasSourceAttribute() const {
  return FastHasAttribute(html_names::kSrcAttr);
}

bool HTMLScriptElement::IsConnected() const {
  return Node::isConnected();
}

bool HTMLScriptElement::HasChildren() const {
  return Node::hasChildren();
}

const AtomicString& HTMLScriptElement::GetNonceForElement() const {
  return ContentSecurityPolicy::IsNonceableElement(this) ? nonce()
                                                         : g_null_atom;
}

bool HTMLScriptElement::AllowInlineScriptForCSP(
    const AtomicString& nonce,
    const OrdinalNumber& context_line,
    const String& script_content) {
  // Support 'inline-speculation-rules' source.
  // https://wicg.github.io/nav-speculation/speculation-rules.html#content-security-policy
  DCHECK(loader_);
  ContentSecurityPolicy::InlineType inline_type =
      loader_->GetScriptType() ==
              ScriptLoader::ScriptTypeAtPrepare::kSpeculationRules
          ? ContentSecurityPolicy::InlineType::kScriptSpeculationRules
          : ContentSecurityPolicy::InlineType::kScript;
  return GetExecutionContext()
      ->GetContentSecurityPolicyForCurrentWorld()
      ->AllowInline(inline_type, this, script_content, nonce,
                    GetDocument().Url(), context_line);
}

Document& HTMLScriptElement::GetDocument() const {
  return Node::GetDocument();
}

ExecutionContext* HTMLScriptElement::GetExecutionContext() const {
  return Node::GetExecutionContext();
}

V8HTMLOrSVGScriptElement* HTMLScriptElement::AsV8HTMLOrSVGScriptElement() {
  if (IsInShadowTree())
    return nullptr;
  return MakeGarbageCollected<V8HTMLOrSVGScriptElement>(this);
}

DOMNodeId HTMLScriptElement::GetDOMNodeId() {
  return this->GetDomNodeId();
}

void HTMLScriptElement::DispatchLoadEvent() {
  probe::AsyncTask async_task(GetExecutionContext(), &async_task_context_);
  DispatchEvent(*Event::Create(event_type_names::kLoad));
}

void HTMLScriptElement::DispatchErrorEvent() {
  probe::AsyncTask async_task(GetExecutionContext(), &async_task_context_);
  DispatchEvent(*Event::Create(event_type_names::kError));
}

ScriptElementBase::Type HTMLScriptElement::GetScriptElementType() {
  return ScriptElementBase::Type::kHTMLScriptElement;
}

Element& HTMLScriptElement::CloneWithoutAttributesAndChildren(
    Document& factory,
    CustomElementRegistry* registry) const {
  CreateElementFlags flags =
      CreateElementFlags::ByCloneNode().SetAlreadyStarted(
          loader_->AlreadyStarted());
  return *factory.CreateElement(TagQName(), flags, IsValue(), registry);
}

bool HTMLScriptElement::IsPotentiallyRenderBlocking() const {
  if (blocking_attribute_->HasRenderToken())
    return true;

  if (loader_->IsParserInserted() &&
      loader_->GetScriptType() == ScriptLoader::ScriptTypeAtPrepare::kClassic) {
    return !AsyncAttributeValue() && !DeferAttributeValue();
  }

  return false;
}

// static
bool HTMLScriptElement::supports(const AtomicString& type) {
  if (type == script_type_names::kClassic)
    return true;
  if (type == script_type_names::kModule)
    return true;
  if (type == script_type_names::kImportmap)
    return true;
  if (type == script_type_names::kSpeculationrules) {
    return true;
  }
  if (type == script_type_names::kWebbundle)
    return true;

  return false;
}

void HTMLScriptElement::Trace(Visitor* visitor) const {
  visitor->Trace(blocking_attribute_);
  visitor->Trace(loader_);
  HTMLElement::Trace(visitor);
  ScriptElementBase::Trace(visitor);
}

}  // namespace blink
