• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 reserved.
6  *           (C) 2006 Graham Dennis (graham.dennis@gmail.com)
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public License
19  * along with this library; see the file COPYING.LIB.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23 
24 #include "config.h"
25 #include "core/html/HTMLAnchorElement.h"
26 
27 #include "core/dom/Attribute.h"
28 #include "core/editing/FrameSelection.h"
29 #include "core/events/KeyboardEvent.h"
30 #include "core/events/MouseEvent.h"
31 #include "core/events/ThreadLocalEventNames.h"
32 #include "core/frame/Frame.h"
33 #include "core/html/HTMLFormElement.h"
34 #include "core/html/HTMLImageElement.h"
35 #include "core/html/parser/HTMLParserIdioms.h"
36 #include "core/loader/FrameLoadRequest.h"
37 #include "core/loader/FrameLoader.h"
38 #include "core/loader/FrameLoaderClient.h"
39 #include "core/loader/FrameLoaderTypes.h"
40 #include "core/loader/PingLoader.h"
41 #include "core/page/Chrome.h"
42 #include "core/page/ChromeClient.h"
43 #include "core/page/Page.h"
44 #include "core/frame/Settings.h"
45 #include "core/rendering/RenderImage.h"
46 #include "core/svg/graphics/SVGImage.h"
47 #include "platform/PlatformMouseEvent.h"
48 #include "platform/network/DNS.h"
49 #include "platform/network/ResourceRequest.h"
50 #include "platform/weborigin/KnownPorts.h"
51 #include "platform/weborigin/SecurityOrigin.h"
52 #include "platform/weborigin/SecurityPolicy.h"
53 #include "public/platform/Platform.h"
54 #include "public/platform/WebPrescientNetworking.h"
55 #include "public/platform/WebURL.h"
56 #include "wtf/text/StringBuilder.h"
57 
58 namespace WebCore {
59 
60 namespace {
61 
preconnectToURL(const KURL & url,blink::WebPreconnectMotivation motivation)62 void preconnectToURL(const KURL& url, blink::WebPreconnectMotivation motivation)
63 {
64     blink::WebPrescientNetworking* prescientNetworking = blink::Platform::current()->prescientNetworking();
65     if (!prescientNetworking)
66         return;
67 
68     prescientNetworking->preconnect(url, motivation);
69 }
70 
71 }
72 
73 class HTMLAnchorElement::PrefetchEventHandler {
74 public:
create(HTMLAnchorElement * anchorElement)75     static PassOwnPtr<PrefetchEventHandler> create(HTMLAnchorElement* anchorElement)
76     {
77         return adoptPtr(new HTMLAnchorElement::PrefetchEventHandler(anchorElement));
78     }
79 
80     void reset();
81 
82     void handleEvent(Event* e);
didChangeHREF()83     void didChangeHREF() { m_hadHREFChanged = true; }
hasIssuedPreconnect() const84     bool hasIssuedPreconnect() const { return m_hasIssuedPreconnect; }
85 
86 private:
87     explicit PrefetchEventHandler(HTMLAnchorElement*);
88 
89     void handleMouseOver(Event* event);
90     void handleMouseOut(Event* event);
91     void handleLeftMouseDown(Event* event);
92     void handleGestureTapUnconfirmed(Event*);
93     void handleGestureShowPress(Event*);
94     void handleClick(Event* event);
95 
96     bool shouldPrefetch(const KURL&);
97     void prefetch(blink::WebPreconnectMotivation);
98 
99     HTMLAnchorElement* m_anchorElement;
100     double m_mouseOverTimestamp;
101     double m_mouseDownTimestamp;
102     double m_tapDownTimestamp;
103     bool m_hadHREFChanged;
104     bool m_hadTapUnconfirmed;
105     bool m_hasIssuedPreconnect;
106 };
107 
108 using namespace HTMLNames;
109 
HTMLAnchorElement(const QualifiedName & tagName,Document & document)110 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document& document)
111     : HTMLElement(tagName, document)
112     , m_hasRootEditableElementForSelectionOnMouseDown(false)
113     , m_wasShiftKeyDownOnMouseDown(false)
114     , m_linkRelations(0)
115     , m_cachedVisitedLinkHash(0)
116 {
117     ScriptWrappable::init(this);
118 }
119 
create(Document & document)120 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document& document)
121 {
122     return adoptRef(new HTMLAnchorElement(aTag, document));
123 }
124 
create(const QualifiedName & tagName,Document & document)125 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(const QualifiedName& tagName, Document& document)
126 {
127     return adoptRef(new HTMLAnchorElement(tagName, document));
128 }
129 
~HTMLAnchorElement()130 HTMLAnchorElement::~HTMLAnchorElement()
131 {
132     clearRootEditableElementForSelectionOnMouseDown();
133 }
134 
supportsFocus() const135 bool HTMLAnchorElement::supportsFocus() const
136 {
137     if (rendererIsEditable())
138         return HTMLElement::supportsFocus();
139     // If not a link we should still be able to focus the element if it has tabIndex.
140     return isLink() || HTMLElement::supportsFocus();
141 }
142 
isMouseFocusable() const143 bool HTMLAnchorElement::isMouseFocusable() const
144 {
145     // Links are focusable by default, but only allow links with tabindex or contenteditable to be mouse focusable.
146     // https://bugs.webkit.org/show_bug.cgi?id=26856
147     if (isLink())
148         return HTMLElement::supportsFocus();
149 
150     return HTMLElement::isMouseFocusable();
151 }
152 
isKeyboardFocusable() const153 bool HTMLAnchorElement::isKeyboardFocusable() const
154 {
155     if (isFocusable() && Element::supportsFocus())
156         return HTMLElement::isKeyboardFocusable();
157 
158     if (isLink()) {
159         Page* page = document().page();
160         if (!page)
161             return false;
162         if (!page->chrome().client().tabsToLinks())
163             return false;
164     }
165     return HTMLElement::isKeyboardFocusable();
166 }
167 
appendServerMapMousePosition(StringBuilder & url,Event * event)168 static void appendServerMapMousePosition(StringBuilder& url, Event* event)
169 {
170     if (!event->isMouseEvent())
171         return;
172 
173     ASSERT(event->target());
174     Node* target = event->target()->toNode();
175     ASSERT(target);
176     if (!target->hasTagName(imgTag))
177         return;
178 
179     HTMLImageElement* imageElement = toHTMLImageElement(event->target()->toNode());
180     if (!imageElement || !imageElement->isServerMap())
181         return;
182 
183     if (!imageElement->renderer() || !imageElement->renderer()->isRenderImage())
184         return;
185     RenderImage* renderer = toRenderImage(imageElement->renderer());
186 
187     // FIXME: This should probably pass true for useTransforms.
188     FloatPoint absolutePosition = renderer->absoluteToLocal(FloatPoint(toMouseEvent(event)->pageX(), toMouseEvent(event)->pageY()));
189     int x = absolutePosition.x();
190     int y = absolutePosition.y();
191     url.append('?');
192     url.appendNumber(x);
193     url.append(',');
194     url.appendNumber(y);
195 }
196 
defaultEventHandler(Event * event)197 void HTMLAnchorElement::defaultEventHandler(Event* event)
198 {
199     if (isLink()) {
200         if (focused() && isEnterKeyKeydownEvent(event) && treatLinkAsLiveForEventType(NonMouseEvent)) {
201             event->setDefaultHandled();
202             dispatchSimulatedClick(event);
203             return;
204         }
205 
206         prefetchEventHandler()->handleEvent(event);
207 
208         if (isLinkClick(event) && treatLinkAsLiveForEventType(eventType(event))) {
209             handleClick(event);
210             prefetchEventHandler()->reset();
211             return;
212         }
213 
214         if (rendererIsEditable()) {
215             // This keeps track of the editable block that the selection was in (if it was in one) just before the link was clicked
216             // for the LiveWhenNotFocused editable link behavior
217             if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() != RightButton && document().frame()) {
218                 setRootEditableElementForSelectionOnMouseDown(document().frame()->selection().rootEditableElement());
219                 m_wasShiftKeyDownOnMouseDown = toMouseEvent(event)->shiftKey();
220             } else if (event->type() == EventTypeNames::mouseover) {
221                 // These are cleared on mouseover and not mouseout because their values are needed for drag events,
222                 // but drag events happen after mouse out events.
223                 clearRootEditableElementForSelectionOnMouseDown();
224                 m_wasShiftKeyDownOnMouseDown = false;
225             }
226         }
227     }
228 
229     HTMLElement::defaultEventHandler(event);
230 }
231 
setActive(bool down)232 void HTMLAnchorElement::setActive(bool down)
233 {
234     if (rendererIsEditable()) {
235         EditableLinkBehavior editableLinkBehavior = EditableLinkDefaultBehavior;
236         if (Settings* settings = document().settings())
237             editableLinkBehavior = settings->editableLinkBehavior();
238 
239         switch (editableLinkBehavior) {
240             default:
241             case EditableLinkDefaultBehavior:
242             case EditableLinkAlwaysLive:
243                 break;
244 
245             case EditableLinkNeverLive:
246                 return;
247 
248             // Don't set the link to be active if the current selection is in the same editable block as
249             // this link
250             case EditableLinkLiveWhenNotFocused:
251                 if (down && document().frame() && document().frame()->selection().rootEditableElement() == rootEditableElement())
252                     return;
253                 break;
254 
255             case EditableLinkOnlyLiveWithShiftKey:
256                 return;
257         }
258 
259     }
260 
261     ContainerNode::setActive(down);
262 }
263 
parseAttribute(const QualifiedName & name,const AtomicString & value)264 void HTMLAnchorElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
265 {
266     if (name == hrefAttr) {
267         bool wasLink = isLink();
268         setIsLink(!value.isNull());
269         if (wasLink != isLink()) {
270             didAffectSelector(AffectedSelectorLink | AffectedSelectorVisited | AffectedSelectorEnabled);
271             if (wasLink && treeScope().adjustedFocusedElement() == this) {
272                 // We might want to call blur(), but it's dangerous to dispatch
273                 // events here.
274                 document().setNeedsFocusedElementCheck();
275             }
276         }
277         if (isLink()) {
278             String parsedURL = stripLeadingAndTrailingHTMLSpaces(value);
279             if (document().isDNSPrefetchEnabled()) {
280                 if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//"))
281                     prefetchDNS(document().completeURL(parsedURL).host());
282             }
283 
284             if (wasLink)
285                 prefetchEventHandler()->didChangeHREF();
286         }
287         invalidateCachedVisitedLinkHash();
288     } else if (name == nameAttr || name == titleAttr) {
289         // Do nothing.
290     } else if (name == relAttr)
291         setRel(value);
292     else
293         HTMLElement::parseAttribute(name, value);
294 }
295 
accessKeyAction(bool sendMouseEvents)296 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
297 {
298     dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
299 }
300 
isURLAttribute(const Attribute & attribute) const301 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
302 {
303     return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
304 }
305 
canStartSelection() const306 bool HTMLAnchorElement::canStartSelection() const
307 {
308     // FIXME: We probably want this same behavior in SVGAElement too
309     if (!isLink())
310         return HTMLElement::canStartSelection();
311     return rendererIsEditable();
312 }
313 
draggable() const314 bool HTMLAnchorElement::draggable() const
315 {
316     // Should be draggable if we have an href attribute.
317     const AtomicString& value = getAttribute(draggableAttr);
318     if (equalIgnoringCase(value, "true"))
319         return true;
320     if (equalIgnoringCase(value, "false"))
321         return false;
322     return hasAttribute(hrefAttr);
323 }
324 
href() const325 KURL HTMLAnchorElement::href() const
326 {
327     return document().completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
328 }
329 
setHref(const AtomicString & value)330 void HTMLAnchorElement::setHref(const AtomicString& value)
331 {
332     setAttribute(hrefAttr, value);
333 }
334 
url() const335 KURL HTMLAnchorElement::url() const
336 {
337     return href();
338 }
339 
setURL(const KURL & url)340 void HTMLAnchorElement::setURL(const KURL& url)
341 {
342     setHref(AtomicString(url.string()));
343 }
344 
input() const345 String HTMLAnchorElement::input() const
346 {
347     return getAttribute(hrefAttr);
348 }
349 
setInput(const String & value)350 void HTMLAnchorElement::setInput(const String& value)
351 {
352     setHref(value);
353 }
354 
hasRel(uint32_t relation) const355 bool HTMLAnchorElement::hasRel(uint32_t relation) const
356 {
357     return m_linkRelations & relation;
358 }
359 
setRel(const String & value)360 void HTMLAnchorElement::setRel(const String& value)
361 {
362     m_linkRelations = 0;
363     SpaceSplitString newLinkRelations(value, true);
364     // FIXME: Add link relations as they are implemented
365     if (newLinkRelations.contains("noreferrer"))
366         m_linkRelations |= RelationNoReferrer;
367 }
368 
name() const369 const AtomicString& HTMLAnchorElement::name() const
370 {
371     return getNameAttribute();
372 }
373 
tabIndex() const374 short HTMLAnchorElement::tabIndex() const
375 {
376     // Skip the supportsFocus check in HTMLElement.
377     return Element::tabIndex();
378 }
379 
target() const380 String HTMLAnchorElement::target() const
381 {
382     return getAttribute(targetAttr);
383 }
384 
385 
text()386 String HTMLAnchorElement::text()
387 {
388     return innerText();
389 }
390 
isLiveLink() const391 bool HTMLAnchorElement::isLiveLink() const
392 {
393     return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey);
394 }
395 
sendPings(const KURL & destinationURL)396 void HTMLAnchorElement::sendPings(const KURL& destinationURL)
397 {
398     if (!hasAttribute(pingAttr) || !document().settings() || !document().settings()->hyperlinkAuditingEnabled())
399         return;
400 
401     SpaceSplitString pingURLs(getAttribute(pingAttr), false);
402     for (unsigned i = 0; i < pingURLs.size(); i++)
403         PingLoader::sendPing(document().frame(), document().completeURL(pingURLs[i]), destinationURL);
404 }
405 
handleClick(Event * event)406 void HTMLAnchorElement::handleClick(Event* event)
407 {
408     event->setDefaultHandled();
409 
410     Frame* frame = document().frame();
411     if (!frame)
412         return;
413 
414     StringBuilder url;
415     url.append(stripLeadingAndTrailingHTMLSpaces(fastGetAttribute(hrefAttr)));
416     appendServerMapMousePosition(url, event);
417     KURL completedURL = document().completeURL(url.toString());
418 
419     // Schedule the ping before the frame load. Prerender in Chrome may kill the renderer as soon as the navigation is
420     // sent out.
421     sendPings(completedURL);
422 
423     ResourceRequest request(completedURL);
424     if (prefetchEventHandler()->hasIssuedPreconnect())
425         frame->loader().client()->dispatchWillRequestAfterPreconnect(request);
426     if (hasAttribute(downloadAttr)) {
427         if (!hasRel(RelationNoReferrer)) {
428             String referrer = SecurityPolicy::generateReferrerHeader(document().referrerPolicy(), completedURL, document().outgoingReferrer());
429             if (!referrer.isEmpty())
430                 request.setHTTPReferrer(referrer);
431         }
432 
433         frame->loader().client()->loadURLExternally(request, NavigationPolicyDownload, fastGetAttribute(downloadAttr));
434     } else {
435         FrameLoadRequest frameRequest(&document(), request, target());
436         frameRequest.setTriggeringEvent(event);
437         if (hasRel(RelationNoReferrer))
438             frameRequest.setShouldSendReferrer(NeverSendReferrer);
439         frame->loader().load(frameRequest);
440     }
441 }
442 
eventType(Event * event)443 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event)
444 {
445     if (!event->isMouseEvent())
446         return NonMouseEvent;
447     return toMouseEvent(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
448 }
449 
treatLinkAsLiveForEventType(EventType eventType) const450 bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const
451 {
452     if (!rendererIsEditable())
453         return true;
454 
455     Settings* settings = document().settings();
456     if (!settings)
457         return true;
458 
459     switch (settings->editableLinkBehavior()) {
460     case EditableLinkDefaultBehavior:
461     case EditableLinkAlwaysLive:
462         return true;
463 
464     case EditableLinkNeverLive:
465         return false;
466 
467     // If the selection prior to clicking on this link resided in the same editable block as this link,
468     // and the shift key isn't pressed, we don't want to follow the link.
469     case EditableLinkLiveWhenNotFocused:
470         return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && rootEditableElementForSelectionOnMouseDown() != rootEditableElement());
471 
472     case EditableLinkOnlyLiveWithShiftKey:
473         return eventType == MouseEventWithShiftKey;
474     }
475 
476     ASSERT_NOT_REACHED();
477     return false;
478 }
479 
isEnterKeyKeydownEvent(Event * event)480 bool isEnterKeyKeydownEvent(Event* event)
481 {
482     return event->type() == EventTypeNames::keydown && event->isKeyboardEvent() && toKeyboardEvent(event)->keyIdentifier() == "Enter";
483 }
484 
isLinkClick(Event * event)485 bool isLinkClick(Event* event)
486 {
487     return event->type() == EventTypeNames::click && (!event->isMouseEvent() || toMouseEvent(event)->button() != RightButton);
488 }
489 
willRespondToMouseClickEvents()490 bool HTMLAnchorElement::willRespondToMouseClickEvents()
491 {
492     return isLink() || HTMLElement::willRespondToMouseClickEvents();
493 }
494 
495 typedef HashMap<const HTMLAnchorElement*, RefPtr<Element> > RootEditableElementMap;
496 
rootEditableElementMap()497 static RootEditableElementMap& rootEditableElementMap()
498 {
499     DEFINE_STATIC_LOCAL(RootEditableElementMap, map, ());
500     return map;
501 }
502 
rootEditableElementForSelectionOnMouseDown() const503 Element* HTMLAnchorElement::rootEditableElementForSelectionOnMouseDown() const
504 {
505     if (!m_hasRootEditableElementForSelectionOnMouseDown)
506         return 0;
507     return rootEditableElementMap().get(this);
508 }
509 
clearRootEditableElementForSelectionOnMouseDown()510 void HTMLAnchorElement::clearRootEditableElementForSelectionOnMouseDown()
511 {
512     if (!m_hasRootEditableElementForSelectionOnMouseDown)
513         return;
514     rootEditableElementMap().remove(this);
515     m_hasRootEditableElementForSelectionOnMouseDown = false;
516 }
517 
setRootEditableElementForSelectionOnMouseDown(Element * element)518 void HTMLAnchorElement::setRootEditableElementForSelectionOnMouseDown(Element* element)
519 {
520     if (!element) {
521         clearRootEditableElementForSelectionOnMouseDown();
522         return;
523     }
524 
525     rootEditableElementMap().set(this, element);
526     m_hasRootEditableElementForSelectionOnMouseDown = true;
527 }
528 
prefetchEventHandler()529 HTMLAnchorElement::PrefetchEventHandler* HTMLAnchorElement::prefetchEventHandler()
530 {
531     if (!m_prefetchEventHandler)
532         m_prefetchEventHandler = PrefetchEventHandler::create(this);
533 
534     return m_prefetchEventHandler.get();
535 }
536 
PrefetchEventHandler(HTMLAnchorElement * anchorElement)537 HTMLAnchorElement::PrefetchEventHandler::PrefetchEventHandler(HTMLAnchorElement* anchorElement)
538     : m_anchorElement(anchorElement)
539 {
540     ASSERT(m_anchorElement);
541 
542     reset();
543 }
544 
reset()545 void HTMLAnchorElement::PrefetchEventHandler::reset()
546 {
547     m_hadHREFChanged = false;
548     m_mouseOverTimestamp = 0;
549     m_mouseDownTimestamp = 0;
550     m_hadTapUnconfirmed = false;
551     m_tapDownTimestamp = 0;
552     m_hasIssuedPreconnect = false;
553 }
554 
handleEvent(Event * event)555 void HTMLAnchorElement::PrefetchEventHandler::handleEvent(Event* event)
556 {
557     if (!shouldPrefetch(m_anchorElement->href()))
558         return;
559 
560     if (event->type() == EventTypeNames::mouseover)
561         handleMouseOver(event);
562     else if (event->type() == EventTypeNames::mouseout)
563         handleMouseOut(event);
564     else if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton)
565         handleLeftMouseDown(event);
566     else if (event->type() == EventTypeNames::gestureshowpress)
567         handleGestureShowPress(event);
568     else if (event->type() == EventTypeNames::gesturetapunconfirmed)
569         handleGestureTapUnconfirmed(event);
570     else if (isLinkClick(event))
571         handleClick(event);
572 }
573 
handleMouseOver(Event * event)574 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOver(Event* event)
575 {
576     if (m_mouseOverTimestamp == 0.0) {
577         m_mouseOverTimestamp = event->timeStamp();
578 
579         blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseOvers", 0, 2);
580 
581         prefetch(blink::WebPreconnectMotivationLinkMouseOver);
582     }
583 }
584 
handleMouseOut(Event * event)585 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOut(Event* event)
586 {
587     if (m_mouseOverTimestamp > 0.0) {
588         double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp);
589         blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_NoClick", mouseOverDuration * 1000, 0, 10000, 100);
590 
591         m_mouseOverTimestamp = 0.0;
592     }
593 }
594 
handleLeftMouseDown(Event * event)595 void HTMLAnchorElement::PrefetchEventHandler::handleLeftMouseDown(Event* event)
596 {
597     m_mouseDownTimestamp = event->timeStamp();
598 
599     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseDowns", 0, 2);
600 
601     prefetch(blink::WebPreconnectMotivationLinkMouseDown);
602 }
603 
handleGestureTapUnconfirmed(Event * event)604 void HTMLAnchorElement::PrefetchEventHandler::handleGestureTapUnconfirmed(Event* event)
605 {
606     m_hadTapUnconfirmed = true;
607 
608     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.TapUnconfirmeds", 0, 2);
609 
610     prefetch(blink::WebPreconnectMotivationLinkTapUnconfirmed);
611 }
612 
handleGestureShowPress(Event * event)613 void HTMLAnchorElement::PrefetchEventHandler::handleGestureShowPress(Event* event)
614 {
615     m_tapDownTimestamp = event->timeStamp();
616 
617     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.TapDowns", 0, 2);
618 
619     prefetch(blink::WebPreconnectMotivationLinkTapDown);
620 }
621 
handleClick(Event * event)622 void HTMLAnchorElement::PrefetchEventHandler::handleClick(Event* event)
623 {
624     bool capturedMouseOver = (m_mouseOverTimestamp > 0.0);
625     if (capturedMouseOver) {
626         double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp);
627 
628         blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_Click", mouseOverDuration * 1000, 0, 10000, 100);
629     }
630 
631     bool capturedMouseDown = (m_mouseDownTimestamp > 0.0);
632     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseDownFollowedByClick", capturedMouseDown, 2);
633 
634     if (capturedMouseDown) {
635         double mouseDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseDownTimestamp);
636 
637         blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseDownDuration_Click", mouseDownDuration * 1000, 0, 10000, 100);
638     }
639 
640     bool capturedTapDown = (m_tapDownTimestamp > 0.0);
641     if (capturedTapDown) {
642         double tapDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_tapDownTimestamp);
643 
644         blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.TapDownDuration_Click", tapDownDuration * 1000, 0, 10000, 100);
645     }
646 
647     int flags = (m_hadTapUnconfirmed ? 2 : 0) | (capturedTapDown ? 1 : 0);
648     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.PreTapEventsFollowedByClick", flags, 4);
649 }
650 
shouldPrefetch(const KURL & url)651 bool HTMLAnchorElement::PrefetchEventHandler::shouldPrefetch(const KURL& url)
652 {
653     if (m_hadHREFChanged)
654         return false;
655 
656     if (m_anchorElement->hasEventListeners(EventTypeNames::click))
657         return false;
658 
659     if (!url.protocolIsInHTTPFamily())
660         return false;
661 
662     Document& document = m_anchorElement->document();
663 
664     if (!document.securityOrigin()->canDisplay(url))
665         return false;
666 
667     if (url.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(document.url(), url))
668         return false;
669 
670     Frame* frame = document.frame();
671     if (!frame)
672         return false;
673 
674     // Links which create new window/tab are avoided because they may require user approval interaction.
675     if (!m_anchorElement->target().isEmpty())
676         return false;
677 
678     return true;
679 }
680 
prefetch(blink::WebPreconnectMotivation motivation)681 void HTMLAnchorElement::PrefetchEventHandler::prefetch(blink::WebPreconnectMotivation motivation)
682 {
683     const KURL& url = m_anchorElement->href();
684 
685     if (!shouldPrefetch(url))
686         return;
687 
688     // The precision of current MouseOver trigger is too low to actually trigger preconnects.
689     if (motivation == blink::WebPreconnectMotivationLinkMouseOver)
690         return;
691 
692     preconnectToURL(url, motivation);
693     m_hasIssuedPreconnect = true;
694 }
695 
isInteractiveContent() const696 bool HTMLAnchorElement::isInteractiveContent() const
697 {
698     return isLink();
699 }
700 
701 }
702