blob: 5abcd6d64c10e8ce6d4843d281e6c40771d3b851 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2000 Simon Hausmann <hausmann@kde.org>
* Copyright (C) 2003, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights
* reserved.
* (C) 2006 Graham Dennis (graham.dennis@gmail.com)
*
* 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_anchor_element.h"
#include <utility>
#include "base/metrics/histogram_macros.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/navigation/impression.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
#include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h"
#include "third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom-blink.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/web/web_link_preview_triggerer.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/events/pointer_event.h"
#include "third_party/blink/renderer/core/events/web_input_event_conversion.h"
#include "third_party/blink/renderer/core/frame/ad_tracker.h"
#include "third_party/blink/renderer/core/frame/attribution_src_loader.h"
#include "third_party/blink/renderer/core/frame/deprecation/deprecation.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/local_frame_client.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/html/anchor_element_metrics_sender.h"
#include "third_party/blink/renderer/core/html/anchor_element_observer_for_service_worker.h"
#include "third_party/blink/renderer/core/html/html_image_element.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/input/event_handler.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/loader/anchor_element_interaction_tracker.h"
#include "third_party/blink/renderer/core/loader/frame_load_request.h"
#include "third_party/blink/renderer/core/loader/navigation_policy.h"
#include "third_party/blink/renderer/core/loader/ping_loader.h"
#include "third_party/blink/renderer/core/loader/render_blocking_resource_manager.h"
#include "third_party/blink/renderer/core/navigation_api/navigation_api.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/speculation_rules/document_speculation_rules.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_fetcher.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/timer.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/weborigin/security_policy.h"
#include "ui/events/event_constants.h"
#include "ui/gfx/geometry/point_conversions.h"
namespace blink {
namespace {
// The download attribute specifies a filename, and an excessively long one can
// crash the browser process. Filepaths probably can't be longer than 4096
// characters, but this is enough to prevent the browser process from becoming
// unresponsive or crashing.
const int kMaxDownloadAttrLength = 1000000;
// Note: Here it covers download originated from clicking on <a download> link
// that results in direct download. Features in this method can also be logged
// from browser for download due to navigations to non-web-renderable content.
bool ShouldInterveneDownloadByFramePolicy(LocalFrame* frame) {
bool should_intervene_download = false;
Document& document = *(frame->GetDocument());
UseCounter::Count(document, WebFeature::kDownloadPrePolicyCheck);
bool has_gesture = LocalFrame::HasTransientUserActivation(frame);
if (!has_gesture) {
UseCounter::Count(document, WebFeature::kDownloadWithoutUserGesture);
}
if (frame->IsAdFrame()) {
UseCounter::Count(document, WebFeature::kDownloadInAdFrame);
if (!has_gesture) {
UseCounter::Count(document,
WebFeature::kDownloadInAdFrameWithoutUserGesture);
should_intervene_download = true;
}
}
if (frame->DomWindow()->IsSandboxed(
network::mojom::blink::WebSandboxFlags::kDownloads)) {
UseCounter::Count(document, WebFeature::kDownloadInSandbox);
should_intervene_download = true;
}
if (!should_intervene_download)
UseCounter::Count(document, WebFeature::kDownloadPostPolicyCheck);
return should_intervene_download;
}
void EmitDidAnchorElementReceiveMouseEvent(HTMLAnchorElement& anchor_element,
Event& event) {
if (!event.IsMouseEvent()) {
return;
}
auto* mev = To<MouseEvent>(&event);
LocalFrame* local_frame = anchor_element.GetDocument().GetFrame();
if (!local_frame) {
return;
}
WebLinkPreviewTriggerer* triggerer =
local_frame->GetOrCreateLinkPreviewTriggerer();
if (!triggerer) {
return;
}
auto button = WebMouseEvent::Button(mev->button());
if (event.type() == event_type_names::kMousedown) {
triggerer->DidAnchorElementReceiveMouseDownEvent(
WebElement(&anchor_element), button, mev->ClickCount());
} else if (event.type() == event_type_names::kMouseup) {
triggerer->DidAnchorElementReceiveMouseUpEvent(WebElement(&anchor_element),
button, mev->ClickCount());
}
}
} // namespace
HTMLAnchorElement::HTMLAnchorElement(Document& document)
: HTMLAnchorElement(html_names::kATag, document) {}
HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tag_name,
Document& document)
: HTMLElement(tag_name, document),
link_relations_(0),
cached_visited_link_hash_(0),
rel_list_(MakeGarbageCollected<RelList>(this)) {}
HTMLAnchorElement::~HTMLAnchorElement() = default;
bool HTMLAnchorElement::SupportsFocus(UpdateBehavior update_behavior) const {
if (IsLink() && !IsEditable(*this)) {
return true;
}
return HTMLElement::SupportsFocus(update_behavior);
}
bool HTMLAnchorElement::ShouldHaveFocusAppearance() const {
// TODO(crbug.com/1444450): Can't this be done with focus-visible now?
return (GetDocument().LastFocusType() != mojom::blink::FocusType::kMouse) ||
HTMLElement::SupportsFocus(UpdateBehavior::kNoneForIsFocused);
}
bool HTMLAnchorElement::IsFocusable(UpdateBehavior update_behavior) const {
if (!IsFocusableStyle(update_behavior)) {
return false;
}
if (IsLink()) {
return SupportsFocus(update_behavior);
}
return HTMLElement::IsFocusable(update_behavior);
}
bool HTMLAnchorElement::IsKeyboardFocusable(
UpdateBehavior update_behavior) const {
if (!IsFocusableStyle(update_behavior)) {
return false;
}
// Anchor is focusable if the base element supports focus and is focusable.
if (Element::SupportsFocus(update_behavior) && IsFocusable(update_behavior)) {
return HTMLElement::IsKeyboardFocusable(update_behavior);
}
if (IsLink() && !GetDocument().GetPage()->GetChromeClient().TabsToLinks())
return false;
return HTMLElement::IsKeyboardFocusable(update_behavior);
}
static void AppendServerMapMousePosition(StringBuilder& url, Event* event) {
auto* mouse_event = DynamicTo<MouseEvent>(event);
if (!mouse_event)
return;
DCHECK(event->target());
Node* target = event->target()->ToNode();
DCHECK(target);
auto* image_element = DynamicTo<HTMLImageElement>(target);
if (!image_element || !image_element->IsServerMap())
return;
LayoutObject* layout_object = image_element->GetLayoutObject();
if (!layout_object || !layout_object->IsBox())
return;
// The coordinates sent in the query string are relative to the height and
// width of the image element, ignoring CSS transform/zoom.
gfx::PointF map_point =
layout_object->AbsoluteToLocalPoint(mouse_event->AbsoluteLocation());
// The origin (0,0) is at the upper left of the content area, inside the
// padding and border.
map_point -=
gfx::Vector2dF(To<LayoutBox>(layout_object)->PhysicalContentBoxOffset());
// CSS zoom is not reflected in the map coordinates.
float scale_factor = 1 / layout_object->Style()->EffectiveZoom();
map_point.Scale(scale_factor, scale_factor);
// Negative coordinates are clamped to 0 such that clicks in the left and
// top padding/border areas receive an X or Y coordinate of 0.
gfx::Point clamped_point = gfx::ToRoundedPoint(map_point);
clamped_point.SetToMax(gfx::Point());
url.Append('?');
url.AppendNumber(clamped_point.x());
url.Append(',');
url.AppendNumber(clamped_point.y());
}
void HTMLAnchorElement::DefaultEventHandler(Event& event) {
if (IsLink()) {
EmitDidAnchorElementReceiveMouseEvent(*this, event);
if (isConnected() && base::FeatureList::IsEnabled(
features::kSpeculativeServiceWorkerWarmUp)) {
Document& top_document = GetDocument().TopDocument();
if (auto* observer =
AnchorElementObserverForServiceWorker::From(top_document)) {
if (features::kSpeculativeServiceWorkerWarmUpOnPointerover.Get() &&
(event.type() == event_type_names::kMouseover ||
event.type() == event_type_names::kPointerover)) {
observer->MaybeSendNavigationTargetLinks({this});
} else if (features::kSpeculativeServiceWorkerWarmUpOnPointerdown
.Get() &&
(event.type() == event_type_names::kMousedown ||
event.type() == event_type_names::kPointerdown ||
event.type() == event_type_names::kTouchstart)) {
observer->MaybeSendNavigationTargetLinks({this});
}
}
}
if (IsFocused() && IsEnterKeyKeydownEvent(event) && IsLiveLink()) {
event.SetDefaultHandled();
DispatchSimulatedClick(&event);
return;
}
if (IsLinkClick(event) && IsLiveLink()) {
// IsLinkClick validates that |event| is a MouseEvent.
HandleClick(To<MouseEvent>(event));
return;
}
}
HTMLElement::DefaultEventHandler(event);
}
bool HTMLAnchorElement::HasActivationBehavior() const {
return IsLink();
}
void HTMLAnchorElement::SetActive(bool active) {
if (active && IsEditable(*this))
return;
HTMLElement::SetActive(active);
}
void HTMLAnchorElement::AttributeChanged(
const AttributeModificationParams& params) {
HTMLElement::AttributeChanged(params);
if (params.reason != AttributeModificationReason::kDirectly)
return;
if (params.name != html_names::kHrefAttr)
return;
if (!IsLink() && AdjustedFocusedElementInTreeScope() == this)
blur();
}
void HTMLAnchorElement::ParseAttribute(
const AttributeModificationParams& params) {
if (params.name == html_names::kHrefAttr) {
if (params.old_value == params.new_value) {
return;
}
bool was_link = IsLink();
SetIsLink(!params.new_value.IsNull());
if (was_link || IsLink()) {
PseudoStateChanged(CSSSelector::kPseudoLink);
PseudoStateChanged(CSSSelector::kPseudoVisited);
if (was_link != IsLink()) {
PseudoStateChanged(CSSSelector::kPseudoWebkitAnyLink);
PseudoStateChanged(CSSSelector::kPseudoAnyLink);
}
}
if (isConnected() && params.old_value != params.new_value) {
if (auto* document_rules =
DocumentSpeculationRules::FromIfExists(GetDocument())) {
document_rules->HrefAttributeChanged(this, params.old_value,
params.new_value);
}
}
InvalidateCachedVisitedLinkHash();
LogUpdateAttributeIfIsolatedWorldAndInDocument("a", params);
} else if (params.name == html_names::kNameAttr) {
if (GetDocument().HasRenderBlockingExpectLinkElements() && isConnected() &&
IsFinishedParsingChildren() && !params.new_value.empty()) {
DCHECK(GetDocument().GetRenderBlockingResourceManager());
GetDocument()
.GetRenderBlockingResourceManager()
->RemovePendingParsingElement(params.new_value, this);
}
} else if (params.name == html_names::kTitleAttr) {
// Do nothing.
} else if (params.name == html_names::kRelAttr) {
SetRel(params.new_value);
rel_list_->DidUpdateAttributeValue(params.old_value, params.new_value);
if (isConnected() && IsLink() && params.old_value != params.new_value) {
if (auto* document_rules =
DocumentSpeculationRules::FromIfExists(GetDocument())) {
document_rules->RelAttributeChanged(this);
}
}
} else if (params.name == html_names::kReferrerpolicyAttr) {
if (isConnected() && IsLink() && params.old_value != params.new_value) {
if (auto* document_rules =
DocumentSpeculationRules::FromIfExists(GetDocument())) {
document_rules->ReferrerPolicyAttributeChanged(this);
}
}
} else if (params.name == html_names::kTargetAttr) {
if (isConnected() && IsLink() && params.old_value != params.new_value) {
if (auto* document_rules =
DocumentSpeculationRules::FromIfExists(GetDocument())) {
document_rules->TargetAttributeChanged(this);
}
}
} else {
HTMLElement::ParseAttribute(params);
}
}
bool HTMLAnchorElement::IsURLAttribute(const Attribute& attribute) const {
return attribute.GetName().LocalName() == html_names::kHrefAttr ||
HTMLElement::IsURLAttribute(attribute);
}
bool HTMLAnchorElement::HasLegalLinkAttribute(const QualifiedName& name) const {
return name == html_names::kHrefAttr ||
HTMLElement::HasLegalLinkAttribute(name);
}
void HTMLAnchorElement::FinishParsingChildren() {
Element::FinishParsingChildren();
if (GetDocument().HasRenderBlockingExpectLinkElements()) {
DCHECK(GetDocument().GetRenderBlockingResourceManager());
GetDocument()
.GetRenderBlockingResourceManager()
->RemovePendingParsingElement(GetNameAttribute(), this);
}
}
bool HTMLAnchorElement::CanStartSelection() const {
if (!IsLink())
return HTMLElement::CanStartSelection();
return IsEditable(*this);
}
bool HTMLAnchorElement::draggable() const {
// Should be draggable if we have an href attribute.
const AtomicString& value = FastGetAttribute(html_names::kDraggableAttr);
if (EqualIgnoringASCIICase(value, "true"))
return true;
if (EqualIgnoringASCIICase(value, "false"))
return false;
return FastHasAttribute(html_names::kHrefAttr);
}
KURL HTMLAnchorElement::Href() const {
return GetDocument().CompleteURL(StripLeadingAndTrailingHTMLSpaces(
FastGetAttribute(html_names::kHrefAttr)));
}
void HTMLAnchorElement::SetHref(const AtomicString& value) {
setAttribute(html_names::kHrefAttr, value);
}
KURL HTMLAnchorElement::Url() const {
KURL href = Href();
if (!href.IsValid()) {
return KURL();
}
return href;
}
void HTMLAnchorElement::SetURL(const KURL& url) {
SetHref(AtomicString(url.GetString()));
}
String HTMLAnchorElement::Input() const {
return FastGetAttribute(html_names::kHrefAttr);
}
void HTMLAnchorElement::setHref(const String& value) {
SetHref(AtomicString(value));
}
bool HTMLAnchorElement::HasRel(uint32_t relation) const {
return link_relations_ & relation;
}
void HTMLAnchorElement::SetRel(const AtomicString& value) {
link_relations_ = 0;
SpaceSplitString new_link_relations(value.LowerASCII());
// FIXME: Add link relations as they are implemented
if (new_link_relations.Contains(AtomicString("noreferrer"))) {
link_relations_ |= kRelationNoReferrer;
}
if (new_link_relations.Contains(AtomicString("noopener"))) {
link_relations_ |= kRelationNoOpener;
}
if (new_link_relations.Contains(AtomicString("opener"))) {
link_relations_ |= kRelationOpener;
UseCounter::Count(GetDocument(), WebFeature::kLinkRelOpener);
}
// These don't currently have web-facing behavior, but embedders may wish to
// expose their presence to users:
if (new_link_relations.Contains(AtomicString("privacy-policy"))) {
link_relations_ |= kRelationPrivacyPolicy;
UseCounter::Count(GetDocument(), WebFeature::kLinkRelPrivacyPolicy);
}
if (new_link_relations.Contains(AtomicString("terms-of-service"))) {
link_relations_ |= kRelationTermsOfService;
UseCounter::Count(GetDocument(), WebFeature::kLinkRelTermsOfService);
}
// Adding or removing a value here whose processing model is web-visible
// (e.g. if the value is listed as a "supported token" for `<a>`'s `rel`
// attribute in HTML) also requires you to update the list of tokens in
// RelList::SupportedTokensAnchorAndAreaAndForm().
}
const AtomicString& HTMLAnchorElement::GetName() const {
return GetNameAttribute();
}
const AtomicString& HTMLAnchorElement::GetEffectiveTarget() const {
const AtomicString& target = FastGetAttribute(html_names::kTargetAttr);
if (!target.empty())
return target;
return GetDocument().BaseTarget();
}
int HTMLAnchorElement::DefaultTabIndex() const {
return 0;
}
bool HTMLAnchorElement::IsLiveLink() const {
return IsLink() && !IsEditable(*this);
}
void HTMLAnchorElement::SendPings(const KURL& destination_url) const {
const AtomicString& ping_value = FastGetAttribute(html_names::kPingAttr);
if (ping_value.IsNull() || !GetDocument().GetSettings() ||
!GetDocument().GetSettings()->GetHyperlinkAuditingEnabled()) {
return;
}
// Pings should not be sent if MHTML page is loaded.
if (GetDocument().Fetcher()->Archive())
return;
if ((ping_value.Contains('\n') || ping_value.Contains('\r') ||
ping_value.Contains('\t')) &&
ping_value.Contains('<')) {
Deprecation::CountDeprecation(
GetExecutionContext(), WebFeature::kCanRequestURLHTTPContainingNewline);
return;
}
UseCounter::Count(GetDocument(), WebFeature::kHTMLAnchorElementPingAttribute);
SpaceSplitString ping_urls(ping_value);
for (unsigned i = 0; i < ping_urls.size(); i++) {
PingLoader::SendLinkAuditPing(GetDocument().GetFrame(),
GetDocument().CompleteURL(ping_urls[i]),
destination_url);
}
}
void HTMLAnchorElement::NavigateToHyperlink(ResourceRequest request,
NavigationPolicy navigation_policy,
bool is_trusted,
base::TimeTicks platform_time_stamp,
KURL completed_url) {
LocalDOMWindow* window = GetDocument().domWindow();
if (!window) {
return;
}
LocalFrame* frame = window->GetFrame();
if (!frame) {
return;
}
if (navigation_policy == kNavigationPolicyLinkPreview) {
// Ensured by third_party/blink/renderer/core/loader/navigation_policy.cc.
CHECK(base::FeatureList::IsEnabled(features::kLinkPreview));
DocumentSpeculationRules::From(GetDocument()).InitiatePreview(Url());
return;
}
request.SetRequestContext(mojom::blink::RequestContextType::HYPERLINK);
FrameLoadRequest frame_request(window, request);
frame_request.SetNavigationPolicy(navigation_policy);
frame_request.SetClientRedirectReason(ClientNavigationReason::kAnchorClick);
frame_request.SetSourceElement(this);
const AtomicString& target =
frame_request.CleanNavigationTarget(GetEffectiveTarget());
if (HasRel(kRelationNoReferrer)) {
frame_request.SetNoReferrer();
frame_request.SetNoOpener();
}
if (HasRel(kRelationNoOpener) ||
(EqualIgnoringASCIICase(target, "_blank") && !HasRel(kRelationOpener) &&
frame->GetSettings()
->GetTargetBlankImpliesNoOpenerEnabledWillBeRemoved())) {
frame_request.SetNoOpener();
}
frame_request.SetTriggeringEventInfo(
is_trusted ? mojom::blink::TriggeringEventInfo::kFromTrustedEvent
: mojom::blink::TriggeringEventInfo::kFromUntrustedEvent);
frame_request.SetInputStartTime(platform_time_stamp);
if (const AtomicString& attribution_src =
FastGetAttribute(html_names::kAttributionsrcAttr);
!attribution_src.IsNull()) {
// An impression must be attached prior to the
// `FindOrCreateFrameForNavigation()` call, as that call may result in
// performing a navigation if the call results in creating a new window with
// noopener set.
// At this time we don't know if the navigation will navigate a main frame
// or subframe. For example, a middle click on the anchor element will
// set `target_frame` to `frame`, but end up targeting a new window.
// Attach the impression regardless, the embedder will be able to drop
// impressions for subframe navigations.
frame_request.SetImpression(
frame->GetAttributionSrcLoader()->RegisterNavigation(
/*navigation_url=*/completed_url, attribution_src,
/*element=*/this, request.HasUserGesture()));
}
Frame* target_frame =
frame->Tree().FindOrCreateFrameForNavigation(frame_request, target).frame;
// If hrefTranslate is enabled and set restrict processing it
// to same frame or navigations with noopener set.
if (RuntimeEnabledFeatures::HrefTranslateEnabled(GetExecutionContext()) &&
FastHasAttribute(html_names::kHreftranslateAttr) &&
(target_frame == frame || frame_request.GetWindowFeatures().noopener)) {
frame_request.SetHrefTranslate(
FastGetAttribute(html_names::kHreftranslateAttr));
UseCounter::Count(GetDocument(),
WebFeature::kHTMLAnchorElementHrefTranslateAttribute);
}
if (target_frame == frame && HasRel(kRelationOpener)) {
// TODO(https://crbug.com/1431495): rel=opener is currently only meaningful
// with target=_blank. Applying it to same-frame navigations is a potential
// opt-out for issue 1431495, but how many sites would trigger this opt-out
// inadvertently?
UseCounter::Count(GetDocument(),
WebFeature::kLinkRelOpenerTargetingSameFrame);
}
if (target_frame) {
target_frame->Navigate(frame_request, WebFrameLoadType::kStandard);
}
}
void HTMLAnchorElement::SetHovered(bool hovered) {
HTMLElement::SetHovered(hovered);
}
Element* HTMLAnchorElement::interestTargetElement() {
CHECK(RuntimeEnabledFeatures::HTMLInterestTargetAttributeEnabled());
if (!IsInTreeScope()) {
return nullptr;
}
return GetElementAttribute(html_names::kInteresttargetAttr);
}
AtomicString HTMLAnchorElement::interestAction() const {
CHECK(RuntimeEnabledFeatures::HTMLInterestTargetAttributeEnabled());
const AtomicString& attribute_value =
FastGetAttribute(html_names::kInterestactionAttr);
if (attribute_value && !attribute_value.IsNull() &&
!attribute_value.empty()) {
return attribute_value;
}
return g_empty_atom;
}
void HTMLAnchorElement::HandleClick(MouseEvent& event) {
event.SetDefaultHandled();
LocalDOMWindow* window = GetDocument().domWindow();
if (!window)
return;
if (!isConnected()) {
UseCounter::Count(GetDocument(),
WebFeature::kAnchorClickDispatchForNonConnectedNode);
}
if (auto* tracker = GetDocument().GetAnchorElementInteractionTracker()) {
tracker->OnClickEvent(*this, event);
}
StringBuilder url;
url.Append(StripLeadingAndTrailingHTMLSpaces(
FastGetAttribute(html_names::kHrefAttr)));
AppendServerMapMousePosition(url, &event);
KURL completed_url = GetDocument().CompleteURL(url.ToString());
// Schedule the ping before the frame load. Prerender in Chrome may kill the
// renderer as soon as the navigation is sent out.
SendPings(completed_url);
ResourceRequest request(completed_url);
network::mojom::ReferrerPolicy policy;
if (FastHasAttribute(html_names::kReferrerpolicyAttr) &&
SecurityPolicy::ReferrerPolicyFromString(
FastGetAttribute(html_names::kReferrerpolicyAttr),
kSupportReferrerPolicyLegacyKeywords, &policy) &&
!HasRel(kRelationNoReferrer)) {
UseCounter::Count(GetDocument(),
WebFeature::kHTMLAnchorElementReferrerPolicyAttribute);
request.SetReferrerPolicy(policy);
}
LocalFrame* frame = window->GetFrame();
request.SetHasUserGesture(LocalFrame::HasTransientUserActivation(frame));
NavigationPolicy navigation_policy = NavigationPolicyFromEvent(&event);
// Respect the download attribute only if we can read the content, and the
// event is not an alt-click or similar.
if (FastHasAttribute(html_names::kDownloadAttr) &&
navigation_policy != kNavigationPolicyDownload &&
window->GetSecurityOrigin()->CanReadContent(completed_url)) {
if (ShouldInterveneDownloadByFramePolicy(frame))
return;
String download_attr =
static_cast<String>(FastGetAttribute(html_names::kDownloadAttr));
if (download_attr.length() > kMaxDownloadAttrLength) {
AddConsoleMessage(
mojom::blink::ConsoleMessageSource::kRendering,
mojom::blink::ConsoleMessageLevel::kError,
String::Format("Download attribute for anchor element is too long. "
"Max: %d, given: %d",
kMaxDownloadAttrLength, download_attr.length()));
return;
}
auto* params = MakeGarbageCollected<NavigateEventDispatchParams>(
completed_url, NavigateEventType::kCrossDocument,
WebFrameLoadType::kStandard);
if (event.isTrusted())
params->involvement = UserNavigationInvolvement::kActivation;
params->download_filename = download_attr;
params->source_element = this;
if (window->navigation()->DispatchNavigateEvent(params) !=
NavigationApi::DispatchResult::kContinue) {
return;
}
// A download will never notify blink about its completion. Tell the
// NavigationApi that the navigation was dropped, so that it doesn't
// leave the frame thinking it is loading indefinitely.
window->navigation()->InformAboutCanceledNavigation(
CancelNavigationReason::kDropped);
request.SetSuggestedFilename(download_attr);
request.SetRequestContext(mojom::blink::RequestContextType::DOWNLOAD);
request.SetRequestorOrigin(window->GetSecurityOrigin());
network::mojom::ReferrerPolicy referrer_policy =
request.GetReferrerPolicy();
if (referrer_policy == network::mojom::ReferrerPolicy::kDefault)
referrer_policy = window->GetReferrerPolicy();
Referrer referrer = SecurityPolicy::GenerateReferrer(
referrer_policy, completed_url, window->OutgoingReferrer());
request.SetReferrerString(referrer.referrer);
request.SetReferrerPolicy(referrer.referrer_policy);
frame->DownloadURL(request, network::mojom::blink::RedirectMode::kManual);
return;
}
base::OnceClosure navigate_closure = WTF::BindOnce(
&HTMLAnchorElement::NavigateToHyperlink, WrapWeakPersistent(this),
std::move(request), navigation_policy, event.isTrusted(),
event.PlatformTimeStamp(), std::move(completed_url));
if (navigation_policy == kNavigationPolicyDownload ||
navigation_policy == kNavigationPolicyLinkPreview) {
// We distinguish single/double click with some modifiers.
// See the comment of `EventHandler.delayed_navigation_task_handle_`.
auto task_handle = PostDelayedCancellableTask(
*base::SingleThreadTaskRunner::GetCurrentDefault(), FROM_HERE,
std::move(navigate_closure),
base::Milliseconds(ui::kDoubleClickTimeMs));
frame->GetEventHandler().SetDelayedNavigationTaskHandle(
std::move(task_handle));
} else {
std::move(navigate_closure).Run();
}
}
bool IsEnterKeyKeydownEvent(Event& event) {
auto* keyboard_event = DynamicTo<KeyboardEvent>(event);
return event.type() == event_type_names::kKeydown && keyboard_event &&
keyboard_event->key() == "Enter" && !keyboard_event->repeat();
}
bool IsLinkClick(Event& event) {
auto* mouse_event = DynamicTo<MouseEvent>(event);
if ((event.type() != event_type_names::kClick &&
event.type() != event_type_names::kAuxclick) ||
!mouse_event) {
return false;
}
int16_t button = mouse_event->button();
return (button == static_cast<int16_t>(WebPointerProperties::Button::kLeft) ||
button ==
static_cast<int16_t>(WebPointerProperties::Button::kMiddle));
}
bool HTMLAnchorElement::WillRespondToMouseClickEvents() {
return IsLink() || HTMLElement::WillRespondToMouseClickEvents();
}
bool HTMLAnchorElement::IsInteractiveContent() const {
return IsLink();
}
Node::InsertionNotificationRequest HTMLAnchorElement::InsertedInto(
ContainerNode& insertion_point) {
InsertionNotificationRequest request =
HTMLElement::InsertedInto(insertion_point);
LogAddElementIfIsolatedWorldAndInDocument("a", html_names::kHrefAttr);
if (isConnected()) {
if (auto* sender =
AnchorElementMetricsSender::GetForFrame(GetDocument().GetFrame())) {
sender->AddAnchorElement(*this);
}
}
if (isConnected() && IsLink()) {
static const bool speculative_service_worker_warm_up_enabled =
base::FeatureList::IsEnabled(features::kSpeculativeServiceWorkerWarmUp);
if (speculative_service_worker_warm_up_enabled) {
static const bool warm_up_on_visible =
features::kSpeculativeServiceWorkerWarmUpOnVisible.Get();
static const bool warm_up_on_inserted_into_dom =
features::kSpeculativeServiceWorkerWarmUpOnInsertedIntoDom.Get();
if (warm_up_on_visible || warm_up_on_inserted_into_dom) {
Document& top_document = GetDocument().TopDocument();
if (auto* observer =
AnchorElementObserverForServiceWorker::From(top_document)) {
if (warm_up_on_visible) {
observer->ObserveAnchorElementVisibility(*this);
}
if (warm_up_on_inserted_into_dom) {
observer->MaybeSendNavigationTargetLinks({this});
}
}
}
}
if (auto* document_rules =
DocumentSpeculationRules::FromIfExists(GetDocument())) {
document_rules->LinkInserted(this);
}
}
return request;
}
void HTMLAnchorElement::RemovedFrom(ContainerNode& insertion_point) {
HTMLElement::RemovedFrom(insertion_point);
if (insertion_point.isConnected()) {
if (auto* sender =
AnchorElementMetricsSender::GetForFrame(GetDocument().GetFrame())) {
sender->RemoveAnchorElement(*this);
}
}
if (insertion_point.isConnected() && IsLink()) {
if (auto* document_rules =
DocumentSpeculationRules::FromIfExists(GetDocument())) {
document_rules->LinkRemoved(this);
}
}
}
void HTMLAnchorElement::Trace(Visitor* visitor) const {
visitor->Trace(rel_list_);
HTMLElement::Trace(visitor);
}
} // namespace blink