1 /*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * (C) 2000 Simon Hausmann <hausmann@kde.org>
5 * Copyright (C) 2003, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights
6 * reserved.
7 * (C) 2006 Graham Dennis (graham.dennis@gmail.com)
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Library General Public License for more details.
18 *
19 * You should have received a copy of the GNU Library General Public License
20 * along with this library; see the file COPYING.LIB. If not, write to
21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
23 */
24
25 #include "third_party/blink/renderer/core/html/html_anchor_element.h"
26
27 #include "base/metrics/histogram_macros.h"
28 #include "third_party/blink/public/common/features.h"
29 #include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h"
30 #include "third_party/blink/public/platform/platform.h"
31 #include "third_party/blink/public/platform/web_prescient_networking.h"
32 #include "third_party/blink/renderer/core/editing/editing_utilities.h"
33 #include "third_party/blink/renderer/core/events/keyboard_event.h"
34 #include "third_party/blink/renderer/core/events/mouse_event.h"
35 #include "third_party/blink/renderer/core/frame/ad_tracker.h"
36 #include "third_party/blink/renderer/core/frame/deprecation.h"
37 #include "third_party/blink/renderer/core/frame/local_frame_client.h"
38 #include "third_party/blink/renderer/core/frame/settings.h"
39 #include "third_party/blink/renderer/core/html/anchor_element_metrics.h"
40 #include "third_party/blink/renderer/core/html/anchor_element_metrics_sender.h"
41 #include "third_party/blink/renderer/core/html/html_image_element.h"
42 #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
43 #include "third_party/blink/renderer/core/layout/layout_box.h"
44 #include "third_party/blink/renderer/core/loader/frame_load_request.h"
45 #include "third_party/blink/renderer/core/loader/navigation_policy.h"
46 #include "third_party/blink/renderer/core/loader/ping_loader.h"
47 #include "third_party/blink/renderer/core/page/chrome_client.h"
48 #include "third_party/blink/renderer/core/page/page.h"
49 #include "third_party/blink/renderer/platform/heap/heap.h"
50 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
51 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
52 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
53 #include "third_party/blink/renderer/platform/weborigin/security_policy.h"
54
55 namespace blink {
56
57 namespace {
58
59 // Note: Here it covers download originated from clicking on <a download> link
60 // that results in direct download. Features in this method can also be logged
61 // from browser for download due to navigations to non-web-renderable content.
ShouldInterveneDownloadByFramePolicy(LocalFrame * frame)62 bool ShouldInterveneDownloadByFramePolicy(LocalFrame* frame) {
63 bool should_intervene_download = false;
64 Document& document = *(frame->GetDocument());
65 UseCounter::Count(document, WebFeature::kDownloadPrePolicyCheck);
66 bool has_gesture = LocalFrame::HasTransientUserActivation(frame);
67 if (!has_gesture) {
68 UseCounter::Count(document, WebFeature::kDownloadWithoutUserGesture);
69 }
70 if (frame->IsAdSubframe()) {
71 UseCounter::Count(document, WebFeature::kDownloadInAdFrame);
72 if (!has_gesture) {
73 UseCounter::Count(document,
74 WebFeature::kDownloadInAdFrameWithoutUserGesture);
75 if (base::FeatureList::IsEnabled(
76 blink::features::
77 kBlockingDownloadsInAdFrameWithoutUserActivation))
78 should_intervene_download = true;
79 }
80 }
81 if (document.IsSandboxed(mojom::blink::WebSandboxFlags::kDownloads)) {
82 UseCounter::Count(document, WebFeature::kDownloadInSandbox);
83 if (RuntimeEnabledFeatures::BlockingDownloadsInSandboxEnabled())
84 should_intervene_download = true;
85 }
86 if (!should_intervene_download)
87 UseCounter::Count(document, WebFeature::kDownloadPostPolicyCheck);
88 return should_intervene_download;
89 }
90
91 } // namespace
92
HTMLAnchorElement(Document & document)93 HTMLAnchorElement::HTMLAnchorElement(Document& document)
94 : HTMLAnchorElement(html_names::kATag, document) {}
95
HTMLAnchorElement(const QualifiedName & tag_name,Document & document)96 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tag_name,
97 Document& document)
98 : HTMLElement(tag_name, document),
99 link_relations_(0),
100 cached_visited_link_hash_(0),
101 rel_list_(MakeGarbageCollected<RelList>(this)) {}
102
103 HTMLAnchorElement::~HTMLAnchorElement() = default;
104
SupportsFocus() const105 bool HTMLAnchorElement::SupportsFocus() const {
106 if (HasEditableStyle(*this))
107 return HTMLElement::SupportsFocus();
108 // If not a link we should still be able to focus the element if it has
109 // tabIndex.
110 return IsLink() || HTMLElement::SupportsFocus();
111 }
112
ShouldHaveFocusAppearance() const113 bool HTMLAnchorElement::ShouldHaveFocusAppearance() const {
114 return (GetDocument().LastFocusType() != mojom::blink::FocusType::kMouse) ||
115 HTMLElement::SupportsFocus();
116 }
117
IsMouseFocusable() const118 bool HTMLAnchorElement::IsMouseFocusable() const {
119 if (IsLink())
120 return SupportsFocus();
121
122 return HTMLElement::IsMouseFocusable();
123 }
124
IsKeyboardFocusable() const125 bool HTMLAnchorElement::IsKeyboardFocusable() const {
126 DCHECK(GetDocument().IsActive());
127
128 if (IsFocusable() && Element::SupportsFocus())
129 return HTMLElement::IsKeyboardFocusable();
130
131 if (IsLink() && !GetDocument().GetPage()->GetChromeClient().TabsToLinks())
132 return false;
133 return HTMLElement::IsKeyboardFocusable();
134 }
135
AppendServerMapMousePosition(StringBuilder & url,Event * event)136 static void AppendServerMapMousePosition(StringBuilder& url, Event* event) {
137 auto* mouse_event = DynamicTo<MouseEvent>(event);
138 if (!mouse_event)
139 return;
140
141 DCHECK(event->target());
142 Node* target = event->target()->ToNode();
143 DCHECK(target);
144 auto* image_element = DynamicTo<HTMLImageElement>(target);
145 if (!image_element || !image_element->IsServerMap())
146 return;
147
148 LayoutObject* layout_object = image_element->GetLayoutObject();
149 if (!layout_object || !layout_object->IsBox())
150 return;
151
152 // The coordinates sent in the query string are relative to the height and
153 // width of the image element, ignoring CSS transform/zoom.
154 FloatPoint map_point = layout_object->AbsoluteToLocalFloatPoint(
155 FloatPoint(mouse_event->AbsoluteLocation()));
156
157 // The origin (0,0) is at the upper left of the content area, inside the
158 // padding and border.
159 map_point -=
160 FloatSize(ToLayoutBox(layout_object)->PhysicalContentBoxOffset());
161
162 // CSS zoom is not reflected in the map coordinates.
163 float scale_factor = 1 / layout_object->Style()->EffectiveZoom();
164 map_point.Scale(scale_factor, scale_factor);
165
166 // Negative coordinates are clamped to 0 such that clicks in the left and
167 // top padding/border areas receive an X or Y coordinate of 0.
168 IntPoint clamped_point(RoundedIntPoint(map_point));
169 clamped_point.ClampNegativeToZero();
170
171 url.Append('?');
172 url.AppendNumber(clamped_point.X());
173 url.Append(',');
174 url.AppendNumber(clamped_point.Y());
175 }
176
DefaultEventHandler(Event & event)177 void HTMLAnchorElement::DefaultEventHandler(Event& event) {
178 if (IsLink()) {
179 if (IsFocused() && IsEnterKeyKeydownEvent(event) && IsLiveLink()) {
180 event.SetDefaultHandled();
181 DispatchSimulatedClick(&event);
182 return;
183 }
184
185 if (IsLinkClick(event) && IsLiveLink()) {
186 HandleClick(event);
187 return;
188 }
189 }
190
191 HTMLElement::DefaultEventHandler(event);
192 }
193
HasActivationBehavior() const194 bool HTMLAnchorElement::HasActivationBehavior() const {
195 return true;
196 }
197
SetActive(bool active)198 void HTMLAnchorElement::SetActive(bool active) {
199 if (HasEditableStyle(*this))
200 return;
201
202 HTMLElement::SetActive(active);
203 }
204
AttributeChanged(const AttributeModificationParams & params)205 void HTMLAnchorElement::AttributeChanged(
206 const AttributeModificationParams& params) {
207 HTMLElement::AttributeChanged(params);
208 if (params.reason != AttributeModificationReason::kDirectly)
209 return;
210 if (params.name != html_names::kHrefAttr)
211 return;
212 if (!IsLink() && AdjustedFocusedElementInTreeScope() == this)
213 blur();
214 }
215
ParseAttribute(const AttributeModificationParams & params)216 void HTMLAnchorElement::ParseAttribute(
217 const AttributeModificationParams& params) {
218 if (params.name == html_names::kHrefAttr) {
219 bool was_link = IsLink();
220 SetIsLink(!params.new_value.IsNull());
221 if (was_link || IsLink()) {
222 PseudoStateChanged(CSSSelector::kPseudoLink);
223 PseudoStateChanged(CSSSelector::kPseudoVisited);
224 PseudoStateChanged(CSSSelector::kPseudoWebkitAnyLink);
225 PseudoStateChanged(CSSSelector::kPseudoAnyLink);
226 }
227 if (IsLink()) {
228 String parsed_url = StripLeadingAndTrailingHTMLSpaces(params.new_value);
229 // GetDocument().GetFrame() could be null if this method is called from
230 // DOMParser::parseFromString(), which internally creates a document
231 // and eventually calls this.
232 if (GetDocument().IsDNSPrefetchEnabled() && GetDocument().GetFrame()) {
233 if (ProtocolIs(parsed_url, "http") || ProtocolIs(parsed_url, "https") ||
234 parsed_url.StartsWith("//")) {
235 WebPrescientNetworking* web_prescient_networking =
236 GetDocument().GetFrame()->PrescientNetworking();
237 if (web_prescient_networking) {
238 web_prescient_networking->PrefetchDNS(
239 GetDocument().CompleteURL(parsed_url).Host());
240 }
241 }
242 }
243 }
244 InvalidateCachedVisitedLinkHash();
245 LogUpdateAttributeIfIsolatedWorldAndInDocument("a", params);
246 } else if (params.name == html_names::kNameAttr ||
247 params.name == html_names::kTitleAttr) {
248 // Do nothing.
249 } else if (params.name == html_names::kRelAttr) {
250 SetRel(params.new_value);
251 rel_list_->DidUpdateAttributeValue(params.old_value, params.new_value);
252 } else {
253 HTMLElement::ParseAttribute(params);
254 }
255 }
256
AccessKeyAction(bool send_mouse_events)257 void HTMLAnchorElement::AccessKeyAction(bool send_mouse_events) {
258 DispatchSimulatedClick(
259 nullptr, send_mouse_events ? kSendMouseUpDownEvents : kSendNoEvents);
260 }
261
IsURLAttribute(const Attribute & attribute) const262 bool HTMLAnchorElement::IsURLAttribute(const Attribute& attribute) const {
263 return attribute.GetName().LocalName() == html_names::kHrefAttr ||
264 HTMLElement::IsURLAttribute(attribute);
265 }
266
HasLegalLinkAttribute(const QualifiedName & name) const267 bool HTMLAnchorElement::HasLegalLinkAttribute(const QualifiedName& name) const {
268 return name == html_names::kHrefAttr ||
269 HTMLElement::HasLegalLinkAttribute(name);
270 }
271
CanStartSelection() const272 bool HTMLAnchorElement::CanStartSelection() const {
273 if (!IsLink())
274 return HTMLElement::CanStartSelection();
275 return HasEditableStyle(*this);
276 }
277
draggable() const278 bool HTMLAnchorElement::draggable() const {
279 // Should be draggable if we have an href attribute.
280 const AtomicString& value = FastGetAttribute(html_names::kDraggableAttr);
281 if (EqualIgnoringASCIICase(value, "true"))
282 return true;
283 if (EqualIgnoringASCIICase(value, "false"))
284 return false;
285 return FastHasAttribute(html_names::kHrefAttr);
286 }
287
Href() const288 KURL HTMLAnchorElement::Href() const {
289 return GetDocument().CompleteURL(StripLeadingAndTrailingHTMLSpaces(
290 FastGetAttribute(html_names::kHrefAttr)));
291 }
292
SetHref(const AtomicString & value)293 void HTMLAnchorElement::SetHref(const AtomicString& value) {
294 setAttribute(html_names::kHrefAttr, value);
295 }
296
Url() const297 KURL HTMLAnchorElement::Url() const {
298 return Href();
299 }
300
SetURL(const KURL & url)301 void HTMLAnchorElement::SetURL(const KURL& url) {
302 SetHref(AtomicString(url.GetString()));
303 }
304
Input() const305 String HTMLAnchorElement::Input() const {
306 return FastGetAttribute(html_names::kHrefAttr);
307 }
308
SetInput(const String & value)309 void HTMLAnchorElement::SetInput(const String& value) {
310 SetHref(AtomicString(value));
311 }
312
HasRel(uint32_t relation) const313 bool HTMLAnchorElement::HasRel(uint32_t relation) const {
314 return link_relations_ & relation;
315 }
316
SetRel(const AtomicString & value)317 void HTMLAnchorElement::SetRel(const AtomicString& value) {
318 link_relations_ = 0;
319 SpaceSplitString new_link_relations(value.LowerASCII());
320 // FIXME: Add link relations as they are implemented
321 if (new_link_relations.Contains("noreferrer"))
322 link_relations_ |= kRelationNoReferrer;
323 if (new_link_relations.Contains("noopener"))
324 link_relations_ |= kRelationNoOpener;
325 }
326
GetName() const327 const AtomicString& HTMLAnchorElement::GetName() const {
328 return GetNameAttribute();
329 }
330
DefaultTabIndex() const331 int HTMLAnchorElement::DefaultTabIndex() const {
332 return 0;
333 }
334
IsLiveLink() const335 bool HTMLAnchorElement::IsLiveLink() const {
336 return IsLink() && !HasEditableStyle(*this);
337 }
338
SendPings(const KURL & destination_url) const339 void HTMLAnchorElement::SendPings(const KURL& destination_url) const {
340 const AtomicString& ping_value = FastGetAttribute(html_names::kPingAttr);
341 if (ping_value.IsNull() || !GetDocument().GetSettings() ||
342 !GetDocument().GetSettings()->GetHyperlinkAuditingEnabled()) {
343 return;
344 }
345
346 // Pings should not be sent if MHTML page is loaded.
347 if (GetDocument().Fetcher()->Archive())
348 return;
349
350 if ((ping_value.Contains('\n') || ping_value.Contains('\r') ||
351 ping_value.Contains('\t')) &&
352 ping_value.Contains('<')) {
353 Deprecation::CountDeprecation(
354 GetDocument(), WebFeature::kCanRequestURLHTTPContainingNewline);
355 return;
356 }
357
358 UseCounter::Count(GetDocument(), WebFeature::kHTMLAnchorElementPingAttribute);
359
360 SpaceSplitString ping_urls(ping_value);
361 for (unsigned i = 0; i < ping_urls.size(); i++) {
362 PingLoader::SendLinkAuditPing(GetDocument().GetFrame(),
363 GetDocument().CompleteURL(ping_urls[i]),
364 destination_url);
365 }
366 }
367
HandleClick(Event & event)368 void HTMLAnchorElement::HandleClick(Event& event) {
369 event.SetDefaultHandled();
370
371 LocalFrame* frame = GetDocument().GetFrame();
372 if (!frame)
373 return;
374
375 if (!isConnected()) {
376 UseCounter::Count(GetDocument(),
377 WebFeature::kAnchorClickDispatchForNonConnectedNode);
378 }
379
380 AnchorElementMetrics::MaybeReportClickedMetricsOnClick(this);
381
382 StringBuilder url;
383 url.Append(StripLeadingAndTrailingHTMLSpaces(
384 FastGetAttribute(html_names::kHrefAttr)));
385 AppendServerMapMousePosition(url, &event);
386 KURL completed_url = GetDocument().CompleteURL(url.ToString());
387
388 // Schedule the ping before the frame load. Prerender in Chrome may kill the
389 // renderer as soon as the navigation is sent out.
390 SendPings(completed_url);
391
392 ResourceRequest request(completed_url);
393
394 network::mojom::ReferrerPolicy policy;
395 if (FastHasAttribute(html_names::kReferrerpolicyAttr) &&
396 SecurityPolicy::ReferrerPolicyFromString(
397 FastGetAttribute(html_names::kReferrerpolicyAttr),
398 kSupportReferrerPolicyLegacyKeywords, &policy) &&
399 !HasRel(kRelationNoReferrer)) {
400 UseCounter::Count(GetDocument(),
401 WebFeature::kHTMLAnchorElementReferrerPolicyAttribute);
402 request.SetReferrerPolicy(policy);
403 }
404
405 // Ignore the download attribute if we either can't read the content, or
406 // the event is an alt-click or similar.
407 if (FastHasAttribute(html_names::kDownloadAttr) &&
408 NavigationPolicyFromEvent(&event) != kNavigationPolicyDownload &&
409 GetDocument().GetSecurityOrigin()->CanReadContent(completed_url)) {
410 if (ShouldInterveneDownloadByFramePolicy(frame))
411 return;
412 request.SetSuggestedFilename(
413 static_cast<String>(FastGetAttribute(html_names::kDownloadAttr)));
414 request.SetRequestContext(mojom::RequestContextType::DOWNLOAD);
415 request.SetRequestorOrigin(GetDocument().GetSecurityOrigin());
416 frame->DownloadURL(request, network::mojom::blink::RedirectMode::kManual);
417 return;
418 }
419
420 request.SetRequestContext(mojom::RequestContextType::HYPERLINK);
421 request.SetHasUserGesture(LocalFrame::HasTransientUserActivation(frame));
422 const AtomicString& target = FastGetAttribute(html_names::kTargetAttr);
423 FrameLoadRequest frame_request(&GetDocument(), request);
424 frame_request.SetNavigationPolicy(NavigationPolicyFromEvent(&event));
425 frame_request.SetClientRedirectReason(ClientNavigationReason::kAnchorClick);
426 if (HasRel(kRelationNoReferrer)) {
427 frame_request.SetNoReferrer();
428 frame_request.SetNoOpener();
429 }
430 if (HasRel(kRelationNoOpener))
431 frame_request.SetNoOpener();
432 frame_request.SetTriggeringEventInfo(
433 event.isTrusted() ? TriggeringEventInfo::kFromTrustedEvent
434 : TriggeringEventInfo::kFromUntrustedEvent);
435 frame_request.SetInputStartTime(event.PlatformTimeStamp());
436
437 frame->MaybeLogAdClickNavigation();
438
439 Frame* target_frame =
440 frame->Tree()
441 .FindOrCreateFrameForNavigation(
442 frame_request,
443 target.IsEmpty() ? GetDocument().BaseTarget() : target)
444 .frame;
445
446 // If hrefTranslate is enabled and set restrict processing it
447 // to same frame or navigations with noopener set.
448 if (RuntimeEnabledFeatures::HrefTranslateEnabled(&GetDocument()) &&
449 FastHasAttribute(html_names::kHreftranslateAttr) &&
450 (target_frame == frame || frame_request.GetWindowFeatures().noopener)) {
451 frame_request.SetHrefTranslate(
452 FastGetAttribute(html_names::kHreftranslateAttr));
453 UseCounter::Count(GetDocument(),
454 WebFeature::kHTMLAnchorElementHrefTranslateAttribute);
455 }
456
457 if (target_frame) {
458 // If we also have a pending form submission, make sure this anchor
459 // navigation takes precedence over it, except in the case of href being
460 // a fragment, in which case pending form submissions should go through.
461 // In the case of a target RemoteFrame, don't cancel form submissions
462 // because we can't be sure what the remote document's urlForBinding is.
463 // In the case of href="javascript:", don't cancel form submissions because
464 // we have always let form submissions take precedence in this case.
465 // TODO(crbug.com/1053679): Remove this after making anchor navigations
466 // async like the spec says to do, which will also provide the desired
467 // behavior.
468 if (LocalFrame* target_local_frame = DynamicTo<LocalFrame>(target_frame)) {
469 KURL document_url = target_local_frame->GetDocument()->urlForBinding();
470 bool equal_ignoring_fragment =
471 completed_url.HasFragmentIdentifier() &&
472 EqualIgnoringFragmentIdentifier(completed_url, document_url);
473 if (!equal_ignoring_fragment && !completed_url.ProtocolIsJavaScript())
474 target_frame->CancelFormSubmission();
475 }
476
477 target_frame->Navigate(frame_request, WebFrameLoadType::kStandard);
478 }
479 }
480
IsEnterKeyKeydownEvent(Event & event)481 bool IsEnterKeyKeydownEvent(Event& event) {
482 auto* keyboard_event = DynamicTo<KeyboardEvent>(event);
483 return event.type() == event_type_names::kKeydown && keyboard_event &&
484 keyboard_event->key() == "Enter" && !keyboard_event->repeat();
485 }
486
IsLinkClick(Event & event)487 bool IsLinkClick(Event& event) {
488 auto* mouse_event = DynamicTo<MouseEvent>(event);
489 if ((event.type() != event_type_names::kClick &&
490 event.type() != event_type_names::kAuxclick) ||
491 !mouse_event) {
492 return false;
493 }
494 int16_t button = mouse_event->button();
495 return (button == static_cast<int16_t>(WebPointerProperties::Button::kLeft) ||
496 button ==
497 static_cast<int16_t>(WebPointerProperties::Button::kMiddle));
498 }
499
WillRespondToMouseClickEvents()500 bool HTMLAnchorElement::WillRespondToMouseClickEvents() {
501 return IsLink() || HTMLElement::WillRespondToMouseClickEvents();
502 }
503
IsInteractiveContent() const504 bool HTMLAnchorElement::IsInteractiveContent() const {
505 return IsLink();
506 }
507
InsertedInto(ContainerNode & insertion_point)508 Node::InsertionNotificationRequest HTMLAnchorElement::InsertedInto(
509 ContainerNode& insertion_point) {
510 InsertionNotificationRequest request =
511 HTMLElement::InsertedInto(insertion_point);
512 LogAddElementIfIsolatedWorldAndInDocument("a", html_names::kHrefAttr);
513
514 Document& top_document = GetDocument().TopDocument();
515 if (AnchorElementMetricsSender::HasAnchorElementMetricsSender(top_document))
516 AnchorElementMetricsSender::From(top_document)->AddAnchorElement(*this);
517
518 return request;
519 }
520
Trace(Visitor * visitor)521 void HTMLAnchorElement::Trace(Visitor* visitor) {
522 visitor->Trace(rel_list_);
523 HTMLElement::Trace(visitor);
524 }
525
526 } // namespace blink
527