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