/*
 * Copyright (C) 2004, 2005, 2008 Nikolas Zimmermann <zimmermann@kde.org>
 * Copyright (C) 2004, 2005, 2007 Rob Buis <buis@kde.org>
 * Copyright (C) 2007 Eric Seidel <eric@webkit.org>
 * Copyright (C) 2010 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/svg/svg_a_element.h"

#include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h"
#include "third_party/blink/renderer/core/dom/attr.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/element.h"
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/events/keyboard_event.h"
#include "third_party/blink/renderer/core/events/mouse_event.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/html/anchor_element_utils.h"
#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_inline.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_transformable_container.h"
#include "third_party/blink/renderer/core/loader/frame_load_request.h"
#include "third_party/blink/renderer/core/loader/frame_loader.h"
#include "third_party/blink/renderer/core/loader/frame_loader_types.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/svg/animation/svg_smil_element.h"
#include "third_party/blink/renderer/core/svg/svg_animated_string.h"
#include "third_party/blink/renderer/core/svg_names.h"
#include "third_party/blink/renderer/core/xlink_names.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
#include "third_party/blink/renderer/platform/weborigin/security_policy.h"

namespace blink {

SVGAElement::SVGAElement(Document& document)
    : SVGGraphicsElement(svg_names::kATag, document),
      SVGURIReference(this),
      svg_target_(
          MakeGarbageCollected<SVGAnimatedString>(this,
                                                  svg_names::kTargetAttr)),
      rel_list_(MakeGarbageCollected<RelList>(this, svg_names::kRelAttr)) {}

void SVGAElement::Trace(Visitor* visitor) const {
  visitor->Trace(svg_target_);
  visitor->Trace(rel_list_);
  SVGGraphicsElement::Trace(visitor);
  SVGURIReference::Trace(visitor);
}

#if DCHECK_IS_ON()
bool SVGAElement::IsAnimatableAttribute(const QualifiedName& name) const {
  if (name == svg_names::kTypeAttr) {
    return false;
  }
  return SVGElement::IsAnimatableAttribute(name);
}
#endif

String SVGAElement::title() const {
  // If the xlink:title is set (non-empty string), use it.
  const AtomicString& title = FastGetAttribute(xlink_names::kTitleAttr);
  if (!title.empty())
    return title;

  // Otherwise, use the title of this element.
  return SVGElement::title();
}

void SVGAElement::SvgAttributeChanged(const SvgAttributeChangedParams& params) {
  // Unlike other SVG*Element classes, SVGAElement only listens to
  // SVGURIReference changes as none of the other properties changes the linking
  // behaviour for our <a> element.
  if (SVGURIReference::IsKnownAttribute(params.name)) {
    AnchorElementUtils::UpdateHref(*this, AtomicString(HrefString()));
    return;
  }

  SVGGraphicsElement::SvgAttributeChanged(params);
}

LayoutObject* SVGAElement::CreateLayoutObject(const ComputedStyle&) {
  auto* svg_element = DynamicTo<SVGElement>(parentNode());
  if (svg_element && svg_element->IsTextContent())
    return MakeGarbageCollected<LayoutSVGInline>(this);

  return MakeGarbageCollected<LayoutSVGTransformableContainer>(this);
}

void SVGAElement::DefaultEventHandler(Event& event) {
  if (IsLink()) {
    if (IsFocused() && KeyboardEvent::IsEnterKeyKeydownEvent(event)) {
      event.SetDefaultHandled();
      DispatchSimulatedClick(&event);
      return;
    }

    if (AnchorElementUtils::IsLinkClick(event)) {
      // It's unclear whether synthesized middle-button "click" events should be
      // allowed to be dispatched and create a navigation. Measure how common
      // this is to see if we can disallow it. Per Pointer Events: "The click
      // event should only be fired for the primary pointer button (i.e., when
      // button value is 0, buttons value is 1). Secondary buttons (like the
      // middle or right button on a standard mouse) MUST NOT fire click
      // events." (https://w3c.github.io/pointerevents/#dfn-click)
      if (event.type() == event_type_names::kClick && !event.isTrusted() &&
          To<MouseEvent>(event).button() ==
              static_cast<int16_t>(WebPointerProperties::Button::kMiddle)) {
        UseCounter::Count(GetDocument(),
                          WebFeature::kSynthesizedMiddleClickSVGAnchor);
      }

      StringView url = StripLeadingAndTrailingHtmlSpaces(HrefString());

      if (url.starts_with('#')) {
        Element* target_element =
            GetTreeScope().getElementById(AtomicString(url.substr(1)));
        if (auto* svg_smil_element =
                DynamicTo<SVGSMILElement>(target_element)) {
          svg_smil_element->BeginByLinkActivation();
          event.SetDefaultHandled();
          return;
        }
      }

      LocalFrame* frame = GetDocument().GetFrame();
      if (!frame) {
        return;
      }

      NavigationPolicy navigation_policy = NavigationPolicyFromEvent(&event);

      const KURL& resolved_url = GetDocument().CompleteURL(url);
      ResourceRequest request(resolved_url);

      // Schedule the ping before the frame load. Prerender in Chrome may kill
      // the renderer as soon as the navigation is sent out.
      AnchorElementUtils::SendPings(resolved_url, GetDocument(),
                                    FastGetAttribute(svg_names::kPingAttr));

      AnchorElementUtils::HandleReferrerPolicyAttribute(
          request, FastGetAttribute(svg_names::kReferrerpolicyAttr),
          link_relations_, GetDocument());

      // Respect the download attribute only if we can read the content, and the
      // event is not an alt-click or similar.
      if (FastHasAttribute(svg_names::kDownloadAttr) &&
          navigation_policy != kNavigationPolicyDownload &&
          GetDocument().domWindow()->GetSecurityOrigin()->CanReadContent(
              resolved_url)) {
        const String download_attr =
            FastGetAttribute(svg_names::kDownloadAttr).GetString();

        AnchorElementUtils::HandleDownloadAttribute(
            this, download_attr, resolved_url, GetDocument().domWindow(),
            event.isTrusted(), std::move(request));
        return;
      }

      FrameLoadRequest frame_request(GetDocument().domWindow(), request);
      AtomicString target = frame_request.CleanNavigationTarget(
          AtomicString(svg_target_->CurrentValue()->Value()));
      if (target.empty() && FastGetAttribute(xlink_names::kShowAttr) == "new") {
        target = AtomicString("_blank");
      }
      event.SetDefaultHandled();

      frame_request.SetNavigationPolicy(navigation_policy);
      frame_request.SetClientNavigationReason(
          ClientNavigationReason::kAnchorClick);
      frame_request.SetSourceElement(this);

      AnchorElementUtils::HandleRelAttribute(
          frame_request, frame->GetSettings(), GetExecutionContext(), target,
          link_relations_);

      AnchorElementUtils::EnforceBlobUrlNoopenerIfNeeded(
          frame_request, resolved_url, *GetDocument().domWindow());

      frame_request.SetTriggeringEventInfo(
          event.isTrusted()
              ? mojom::blink::TriggeringEventInfo::kFromTrustedEvent
              : mojom::blink::TriggeringEventInfo::kFromUntrustedEvent);

      Frame* target_frame =
          frame->Tree()
              .FindOrCreateFrameForNavigation(frame_request, target)
              .frame;
      if (!target_frame) {
        return;
      }
      target_frame->Navigate(frame_request, WebFrameLoadType::kStandard);
      return;
    }
  }

  SVGGraphicsElement::DefaultEventHandler(event);
}

bool SVGAElement::IsValidInterestInvoker(Element& target) const {
  // Anchor elements that don't have the `href` attribute are not interactive,
  // so they can't support `interestfor`.
  return IsLink();
}

KURL SVGAElement::Url() const {
  return GetDocument().CompleteURL(
      StripLeadingAndTrailingHtmlSpaces(HrefString()));
}

bool SVGAElement::HasActivationBehavior() const {
  return true;
}

int SVGAElement::DefaultTabIndex() const {
  return 0;
}

FocusableState SVGAElement::SupportsFocus(
    UpdateBehavior update_behavior) const {
  if (IsEditable(*this)) {
    return SVGGraphicsElement::SupportsFocus(update_behavior);
  }
  if (IsLink()) {
    if (IsNonRendered(GetLayoutObject())) {
      return FocusableState::kNotFocusable;
    }

    return FocusableState::kFocusable;
  }
  // If not a link we should still be able to focus the element if it has
  // tabIndex.
  return SVGGraphicsElement::SupportsFocus(update_behavior);
}

bool SVGAElement::ShouldHaveFocusAppearance() const {
  return (GetDocument().LastFocusType() != mojom::blink::FocusType::kMouse) ||
         SVGGraphicsElement::SupportsFocus(
             UpdateBehavior::kNoneForFocusManagement) !=
             FocusableState::kNotFocusable;
}

bool SVGAElement::IsURLAttribute(const Attribute& attribute) const {
  return attribute.GetName().LocalName() == html_names::kHrefAttr ||
         SVGGraphicsElement::IsURLAttribute(attribute);
}

bool SVGAElement::IsKeyboardFocusableSlow(
    UpdateBehavior update_behavior) const {
  if (IsLink() && !GetDocument().GetPage()->GetChromeClient().TabsToLinks()) {
    return false;
  }
  return SVGElement::IsKeyboardFocusableSlow(update_behavior);
}

bool SVGAElement::CanStartSelection() const {
  if (!IsLink())
    return SVGElement::CanStartSelection();
  return IsEditable(*this);
}

bool SVGAElement::WillRespondToMouseClickEvents() {
  return IsLink() || SVGGraphicsElement::WillRespondToMouseClickEvents();
}

SVGAnimatedPropertyBase* SVGAElement::PropertyFromAttribute(
    const QualifiedName& attribute_name) const {
  if (attribute_name == svg_names::kTargetAttr) {
    return svg_target_.Get();
  } else {
    SVGAnimatedPropertyBase* ret =
        SVGURIReference::PropertyFromAttribute(attribute_name);
    if (ret) {
      return ret;
    } else {
      return SVGGraphicsElement::PropertyFromAttribute(attribute_name);
    }
  }
}

void SVGAElement::SynchronizeAllSVGAttributes() const {
  SVGAnimatedPropertyBase* attrs[]{svg_target_.Get()};
  SynchronizeListOfSVGAttributes(attrs);
  SVGURIReference::SynchronizeAllSVGAttributes();
  SVGGraphicsElement::SynchronizeAllSVGAttributes();
}

void SVGAElement::ParseAttribute(const AttributeModificationParams& params) {
  if (params.name == svg_names::kRelAttr) {
    link_relations_ =
        AnchorElementUtils::ParseRelAttribute(params.new_value, GetDocument());
    rel_list_->DidUpdateAttributeValue(params.old_value, params.new_value);
  } else {
    SVGGraphicsElement::ParseAttribute(params);
  }
}

}  // namespace blink
