777e94ad8c543206bf59dc9f7affbba86a98cc4d
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / html / HTMLAnchorElement.cpp
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/frame/FrameHost.h"
32 #include "core/frame/LocalFrame.h"
33 #include "core/frame/Settings.h"
34 #include "core/frame/UseCounter.h"
35 #include "core/html/HTMLFormElement.h"
36 #include "core/html/HTMLImageElement.h"
37 #include "core/html/parser/HTMLParserIdioms.h"
38 #include "core/loader/FrameLoadRequest.h"
39 #include "core/loader/FrameLoader.h"
40 #include "core/loader/FrameLoaderClient.h"
41 #include "core/loader/FrameLoaderTypes.h"
42 #include "core/loader/PingLoader.h"
43 #include "core/page/Chrome.h"
44 #include "core/page/ChromeClient.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
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:
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);
83     void didChangeHREF() { m_hadHREFChanged = true; }
84     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
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
120 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document& document)
121 {
122     return adoptRef(new HTMLAnchorElement(aTag, document));
123 }
124
125 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(const QualifiedName& tagName, Document& document)
126 {
127     return adoptRef(new HTMLAnchorElement(tagName, document));
128 }
129
130 HTMLAnchorElement::~HTMLAnchorElement()
131 {
132     clearRootEditableElementForSelectionOnMouseDown();
133 }
134
135 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
143 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
153 bool HTMLAnchorElement::isKeyboardFocusable() const
154 {
155     ASSERT(document().isActive());
156
157     if (isFocusable() && Element::supportsFocus())
158         return HTMLElement::isKeyboardFocusable();
159
160     if (isLink() && !document().frameHost()->chrome().client().tabsToLinks())
161         return false;
162     return HTMLElement::isKeyboardFocusable();
163 }
164
165 static void appendServerMapMousePosition(StringBuilder& url, Event* event)
166 {
167     if (!event->isMouseEvent())
168         return;
169
170     ASSERT(event->target());
171     Node* target = event->target()->toNode();
172     ASSERT(target);
173     if (!isHTMLImageElement(*target))
174         return;
175
176     HTMLImageElement& imageElement = toHTMLImageElement(*target);
177     if (!imageElement.isServerMap())
178         return;
179
180     if (!imageElement.renderer() || !imageElement.renderer()->isRenderImage())
181         return;
182     RenderImage* renderer = toRenderImage(imageElement.renderer());
183
184     // FIXME: This should probably pass true for useTransforms.
185     FloatPoint absolutePosition = renderer->absoluteToLocal(FloatPoint(toMouseEvent(event)->pageX(), toMouseEvent(event)->pageY()));
186     int x = absolutePosition.x();
187     int y = absolutePosition.y();
188     url.append('?');
189     url.appendNumber(x);
190     url.append(',');
191     url.appendNumber(y);
192 }
193
194 void HTMLAnchorElement::defaultEventHandler(Event* event)
195 {
196     if (isLink()) {
197         if (focused() && isEnterKeyKeydownEvent(event) && treatLinkAsLiveForEventType(NonMouseEvent)) {
198             event->setDefaultHandled();
199             dispatchSimulatedClick(event);
200             return;
201         }
202
203         prefetchEventHandler()->handleEvent(event);
204
205         if (isLinkClick(event) && treatLinkAsLiveForEventType(eventType(event))) {
206             handleClick(event);
207             prefetchEventHandler()->reset();
208             return;
209         }
210
211         if (rendererIsEditable()) {
212             // This keeps track of the editable block that the selection was in (if it was in one) just before the link was clicked
213             // for the LiveWhenNotFocused editable link behavior
214             if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() != RightButton && document().frame()) {
215                 setRootEditableElementForSelectionOnMouseDown(document().frame()->selection().rootEditableElement());
216                 m_wasShiftKeyDownOnMouseDown = toMouseEvent(event)->shiftKey();
217             } else if (event->type() == EventTypeNames::mouseover) {
218                 // These are cleared on mouseover and not mouseout because their values are needed for drag events,
219                 // but drag events happen after mouse out events.
220                 clearRootEditableElementForSelectionOnMouseDown();
221                 m_wasShiftKeyDownOnMouseDown = false;
222             }
223         }
224     }
225
226     HTMLElement::defaultEventHandler(event);
227 }
228
229 void HTMLAnchorElement::setActive(bool down)
230 {
231     if (rendererIsEditable()) {
232         EditableLinkBehavior editableLinkBehavior = EditableLinkDefaultBehavior;
233         if (Settings* settings = document().settings())
234             editableLinkBehavior = settings->editableLinkBehavior();
235
236         switch (editableLinkBehavior) {
237             default:
238             case EditableLinkDefaultBehavior:
239             case EditableLinkAlwaysLive:
240                 break;
241
242             case EditableLinkNeverLive:
243                 return;
244
245             // Don't set the link to be active if the current selection is in the same editable block as
246             // this link
247             case EditableLinkLiveWhenNotFocused:
248                 if (down && document().frame() && document().frame()->selection().rootEditableElement() == rootEditableElement())
249                     return;
250                 break;
251
252             case EditableLinkOnlyLiveWithShiftKey:
253                 return;
254         }
255
256     }
257
258     ContainerNode::setActive(down);
259 }
260
261 void HTMLAnchorElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
262 {
263     if (name == hrefAttr) {
264         bool wasLink = isLink();
265         setIsLink(!value.isNull());
266         if (wasLink != isLink()) {
267             didAffectSelector(AffectedSelectorLink | AffectedSelectorVisited | AffectedSelectorEnabled);
268             if (wasLink && treeScope().adjustedFocusedElement() == this) {
269                 // We might want to call blur(), but it's dangerous to dispatch
270                 // events here.
271                 document().setNeedsFocusedElementCheck();
272             }
273         }
274         if (isLink()) {
275             String parsedURL = stripLeadingAndTrailingHTMLSpaces(value);
276             if (document().isDNSPrefetchEnabled()) {
277                 if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//"))
278                     prefetchDNS(document().completeURL(parsedURL).host());
279             }
280
281             if (wasLink)
282                 prefetchEventHandler()->didChangeHREF();
283         }
284         invalidateCachedVisitedLinkHash();
285     } else if (name == nameAttr || name == titleAttr) {
286         // Do nothing.
287     } else if (name == relAttr)
288         setRel(value);
289     else
290         HTMLElement::parseAttribute(name, value);
291 }
292
293 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
294 {
295     dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
296 }
297
298 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
299 {
300     return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
301 }
302
303 bool HTMLAnchorElement::hasLegalLinkAttribute(const QualifiedName& name) const
304 {
305     return name == hrefAttr || HTMLElement::hasLegalLinkAttribute(name);
306 }
307
308 bool HTMLAnchorElement::canStartSelection() const
309 {
310     // FIXME: We probably want this same behavior in SVGAElement too
311     if (!isLink())
312         return HTMLElement::canStartSelection();
313     return rendererIsEditable();
314 }
315
316 bool HTMLAnchorElement::draggable() const
317 {
318     // Should be draggable if we have an href attribute.
319     const AtomicString& value = getAttribute(draggableAttr);
320     if (equalIgnoringCase(value, "true"))
321         return true;
322     if (equalIgnoringCase(value, "false"))
323         return false;
324     return hasAttribute(hrefAttr);
325 }
326
327 KURL HTMLAnchorElement::href() const
328 {
329     return document().completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
330 }
331
332 void HTMLAnchorElement::setHref(const AtomicString& value)
333 {
334     setAttribute(hrefAttr, value);
335 }
336
337 KURL HTMLAnchorElement::url() const
338 {
339     return href();
340 }
341
342 void HTMLAnchorElement::setURL(const KURL& url)
343 {
344     setHref(AtomicString(url.string()));
345 }
346
347 String HTMLAnchorElement::input() const
348 {
349     return getAttribute(hrefAttr);
350 }
351
352 void HTMLAnchorElement::setInput(const String& value)
353 {
354     setHref(AtomicString(value));
355 }
356
357 bool HTMLAnchorElement::hasRel(uint32_t relation) const
358 {
359     return m_linkRelations & relation;
360 }
361
362 void HTMLAnchorElement::setRel(const AtomicString& value)
363 {
364     m_linkRelations = 0;
365     SpaceSplitString newLinkRelations(value, true);
366     // FIXME: Add link relations as they are implemented
367     if (newLinkRelations.contains("noreferrer"))
368         m_linkRelations |= RelationNoReferrer;
369 }
370
371 const AtomicString& HTMLAnchorElement::name() const
372 {
373     return getNameAttribute();
374 }
375
376 short HTMLAnchorElement::tabIndex() const
377 {
378     // Skip the supportsFocus check in HTMLElement.
379     return Element::tabIndex();
380 }
381
382 AtomicString HTMLAnchorElement::target() const
383 {
384     return getAttribute(targetAttr);
385 }
386
387
388 String HTMLAnchorElement::text()
389 {
390     return innerText();
391 }
392
393 bool HTMLAnchorElement::isLiveLink() const
394 {
395     return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey);
396 }
397
398 void HTMLAnchorElement::sendPings(const KURL& destinationURL)
399 {
400     const AtomicString& pingValue = getAttribute(pingAttr);
401     if (pingValue.isNull() || !document().settings() || !document().settings()->hyperlinkAuditingEnabled())
402         return;
403
404     UseCounter::count(document(), UseCounter::HTMLAnchorElementPingAttribute);
405
406     SpaceSplitString pingURLs(pingValue, false);
407     for (unsigned i = 0; i < pingURLs.size(); i++)
408         PingLoader::sendPing(document().frame(), document().completeURL(pingURLs[i]), destinationURL);
409 }
410
411 void HTMLAnchorElement::handleClick(Event* event)
412 {
413     event->setDefaultHandled();
414
415     LocalFrame* frame = document().frame();
416     if (!frame)
417         return;
418
419     StringBuilder url;
420     url.append(stripLeadingAndTrailingHTMLSpaces(fastGetAttribute(hrefAttr)));
421     appendServerMapMousePosition(url, event);
422     KURL completedURL = document().completeURL(url.toString());
423
424     // Schedule the ping before the frame load. Prerender in Chrome may kill the renderer as soon as the navigation is
425     // sent out.
426     sendPings(completedURL);
427
428     ResourceRequest request(completedURL);
429     if (prefetchEventHandler()->hasIssuedPreconnect())
430         frame->loader().client()->dispatchWillRequestAfterPreconnect(request);
431     if (hasAttribute(downloadAttr)) {
432         if (!hasRel(RelationNoReferrer)) {
433             String referrer = SecurityPolicy::generateReferrerHeader(document().referrerPolicy(), completedURL, document().outgoingReferrer());
434             if (!referrer.isEmpty())
435                 request.setHTTPReferrer(Referrer(referrer, document().referrerPolicy()));
436         }
437
438         frame->loader().client()->loadURLExternally(request, NavigationPolicyDownload, fastGetAttribute(downloadAttr));
439     } else {
440         FrameLoadRequest frameRequest(&document(), request, target());
441         frameRequest.setTriggeringEvent(event);
442         if (hasRel(RelationNoReferrer))
443             frameRequest.setShouldSendReferrer(NeverSendReferrer);
444         frame->loader().load(frameRequest);
445     }
446 }
447
448 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event)
449 {
450     if (!event->isMouseEvent())
451         return NonMouseEvent;
452     return toMouseEvent(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
453 }
454
455 bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const
456 {
457     if (!rendererIsEditable())
458         return true;
459
460     Settings* settings = document().settings();
461     if (!settings)
462         return true;
463
464     switch (settings->editableLinkBehavior()) {
465     case EditableLinkDefaultBehavior:
466     case EditableLinkAlwaysLive:
467         return true;
468
469     case EditableLinkNeverLive:
470         return false;
471
472     // If the selection prior to clicking on this link resided in the same editable block as this link,
473     // and the shift key isn't pressed, we don't want to follow the link.
474     case EditableLinkLiveWhenNotFocused:
475         return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && rootEditableElementForSelectionOnMouseDown() != rootEditableElement());
476
477     case EditableLinkOnlyLiveWithShiftKey:
478         return eventType == MouseEventWithShiftKey;
479     }
480
481     ASSERT_NOT_REACHED();
482     return false;
483 }
484
485 bool isEnterKeyKeydownEvent(Event* event)
486 {
487     return event->type() == EventTypeNames::keydown && event->isKeyboardEvent() && toKeyboardEvent(event)->keyIdentifier() == "Enter";
488 }
489
490 bool isLinkClick(Event* event)
491 {
492     return event->type() == EventTypeNames::click && (!event->isMouseEvent() || toMouseEvent(event)->button() != RightButton);
493 }
494
495 bool HTMLAnchorElement::willRespondToMouseClickEvents()
496 {
497     return isLink() || HTMLElement::willRespondToMouseClickEvents();
498 }
499
500 typedef HashMap<const HTMLAnchorElement*, RefPtr<Element> > RootEditableElementMap;
501
502 static RootEditableElementMap& rootEditableElementMap()
503 {
504     DEFINE_STATIC_LOCAL(RootEditableElementMap, map, ());
505     return map;
506 }
507
508 Element* HTMLAnchorElement::rootEditableElementForSelectionOnMouseDown() const
509 {
510     if (!m_hasRootEditableElementForSelectionOnMouseDown)
511         return 0;
512     return rootEditableElementMap().get(this);
513 }
514
515 void HTMLAnchorElement::clearRootEditableElementForSelectionOnMouseDown()
516 {
517     if (!m_hasRootEditableElementForSelectionOnMouseDown)
518         return;
519     rootEditableElementMap().remove(this);
520     m_hasRootEditableElementForSelectionOnMouseDown = false;
521 }
522
523 void HTMLAnchorElement::setRootEditableElementForSelectionOnMouseDown(Element* element)
524 {
525     if (!element) {
526         clearRootEditableElementForSelectionOnMouseDown();
527         return;
528     }
529
530     rootEditableElementMap().set(this, element);
531     m_hasRootEditableElementForSelectionOnMouseDown = true;
532 }
533
534 HTMLAnchorElement::PrefetchEventHandler* HTMLAnchorElement::prefetchEventHandler()
535 {
536     if (!m_prefetchEventHandler)
537         m_prefetchEventHandler = PrefetchEventHandler::create(this);
538
539     return m_prefetchEventHandler.get();
540 }
541
542 HTMLAnchorElement::PrefetchEventHandler::PrefetchEventHandler(HTMLAnchorElement* anchorElement)
543     : m_anchorElement(anchorElement)
544 {
545     ASSERT(m_anchorElement);
546
547     reset();
548 }
549
550 void HTMLAnchorElement::PrefetchEventHandler::reset()
551 {
552     m_hadHREFChanged = false;
553     m_mouseOverTimestamp = 0;
554     m_mouseDownTimestamp = 0;
555     m_hadTapUnconfirmed = false;
556     m_tapDownTimestamp = 0;
557     m_hasIssuedPreconnect = false;
558 }
559
560 void HTMLAnchorElement::PrefetchEventHandler::handleEvent(Event* event)
561 {
562     if (!shouldPrefetch(m_anchorElement->href()))
563         return;
564
565     if (event->type() == EventTypeNames::mouseover)
566         handleMouseOver(event);
567     else if (event->type() == EventTypeNames::mouseout)
568         handleMouseOut(event);
569     else if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton)
570         handleLeftMouseDown(event);
571     else if (event->type() == EventTypeNames::gestureshowpress)
572         handleGestureShowPress(event);
573     else if (event->type() == EventTypeNames::gesturetapunconfirmed)
574         handleGestureTapUnconfirmed(event);
575     else if (isLinkClick(event))
576         handleClick(event);
577 }
578
579 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOver(Event* event)
580 {
581     if (m_mouseOverTimestamp == 0.0) {
582         m_mouseOverTimestamp = event->timeStamp();
583
584         blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseOvers", 0, 2);
585
586         prefetch(blink::WebPreconnectMotivationLinkMouseOver);
587     }
588 }
589
590 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOut(Event* event)
591 {
592     if (m_mouseOverTimestamp > 0.0) {
593         double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp);
594         blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_NoClick", mouseOverDuration * 1000, 0, 10000, 100);
595
596         m_mouseOverTimestamp = 0.0;
597     }
598 }
599
600 void HTMLAnchorElement::PrefetchEventHandler::handleLeftMouseDown(Event* event)
601 {
602     m_mouseDownTimestamp = event->timeStamp();
603
604     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseDowns", 0, 2);
605
606     prefetch(blink::WebPreconnectMotivationLinkMouseDown);
607 }
608
609 void HTMLAnchorElement::PrefetchEventHandler::handleGestureTapUnconfirmed(Event* event)
610 {
611     m_hadTapUnconfirmed = true;
612
613     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.TapUnconfirmeds", 0, 2);
614
615     prefetch(blink::WebPreconnectMotivationLinkTapUnconfirmed);
616 }
617
618 void HTMLAnchorElement::PrefetchEventHandler::handleGestureShowPress(Event* event)
619 {
620     m_tapDownTimestamp = event->timeStamp();
621
622     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.TapDowns", 0, 2);
623
624     prefetch(blink::WebPreconnectMotivationLinkTapDown);
625 }
626
627 void HTMLAnchorElement::PrefetchEventHandler::handleClick(Event* event)
628 {
629     bool capturedMouseOver = (m_mouseOverTimestamp > 0.0);
630     if (capturedMouseOver) {
631         double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp);
632
633         blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_Click", mouseOverDuration * 1000, 0, 10000, 100);
634     }
635
636     bool capturedMouseDown = (m_mouseDownTimestamp > 0.0);
637     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseDownFollowedByClick", capturedMouseDown, 2);
638
639     if (capturedMouseDown) {
640         double mouseDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseDownTimestamp);
641
642         blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseDownDuration_Click", mouseDownDuration * 1000, 0, 10000, 100);
643     }
644
645     bool capturedTapDown = (m_tapDownTimestamp > 0.0);
646     if (capturedTapDown) {
647         double tapDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_tapDownTimestamp);
648
649         blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.TapDownDuration_Click", tapDownDuration * 1000, 0, 10000, 100);
650     }
651
652     int flags = (m_hadTapUnconfirmed ? 2 : 0) | (capturedTapDown ? 1 : 0);
653     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.PreTapEventsFollowedByClick", flags, 4);
654 }
655
656 bool HTMLAnchorElement::PrefetchEventHandler::shouldPrefetch(const KURL& url)
657 {
658     if (m_hadHREFChanged)
659         return false;
660
661     if (m_anchorElement->hasEventListeners(EventTypeNames::click))
662         return false;
663
664     if (!url.protocolIsInHTTPFamily())
665         return false;
666
667     Document& document = m_anchorElement->document();
668
669     if (!document.securityOrigin()->canDisplay(url))
670         return false;
671
672     if (url.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(document.url(), url))
673         return false;
674
675     LocalFrame* frame = document.frame();
676     if (!frame)
677         return false;
678
679     // Links which create new window/tab are avoided because they may require user approval interaction.
680     if (!m_anchorElement->target().isEmpty())
681         return false;
682
683     return true;
684 }
685
686 void HTMLAnchorElement::PrefetchEventHandler::prefetch(blink::WebPreconnectMotivation motivation)
687 {
688     const KURL& url = m_anchorElement->href();
689
690     if (!shouldPrefetch(url))
691         return;
692
693     // The precision of current MouseOver trigger is too low to actually trigger preconnects.
694     if (motivation == blink::WebPreconnectMotivationLinkMouseOver)
695         return;
696
697     preconnectToURL(url, motivation);
698     m_hasIssuedPreconnect = true;
699 }
700
701 bool HTMLAnchorElement::isInteractiveContent() const
702 {
703     return isLink();
704 }
705
706 }