Upstream version 7.35.139.0
[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         bool isSameOrigin = document().securityOrigin()->canRequest(completedURL);
439         const AtomicString& suggestedName = (isSameOrigin ? fastGetAttribute(downloadAttr) : nullAtom);
440
441         frame->loader().client()->loadURLExternally(request, NavigationPolicyDownload, suggestedName);
442     } else {
443         FrameLoadRequest frameRequest(&document(), request, target());
444         frameRequest.setTriggeringEvent(event);
445         if (hasRel(RelationNoReferrer))
446             frameRequest.setShouldSendReferrer(NeverSendReferrer);
447         frame->loader().load(frameRequest);
448     }
449 }
450
451 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event)
452 {
453     if (!event->isMouseEvent())
454         return NonMouseEvent;
455     return toMouseEvent(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
456 }
457
458 bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const
459 {
460     if (!rendererIsEditable())
461         return true;
462
463     Settings* settings = document().settings();
464     if (!settings)
465         return true;
466
467     switch (settings->editableLinkBehavior()) {
468     case EditableLinkDefaultBehavior:
469     case EditableLinkAlwaysLive:
470         return true;
471
472     case EditableLinkNeverLive:
473         return false;
474
475     // If the selection prior to clicking on this link resided in the same editable block as this link,
476     // and the shift key isn't pressed, we don't want to follow the link.
477     case EditableLinkLiveWhenNotFocused:
478         return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && rootEditableElementForSelectionOnMouseDown() != rootEditableElement());
479
480     case EditableLinkOnlyLiveWithShiftKey:
481         return eventType == MouseEventWithShiftKey;
482     }
483
484     ASSERT_NOT_REACHED();
485     return false;
486 }
487
488 bool isEnterKeyKeydownEvent(Event* event)
489 {
490     return event->type() == EventTypeNames::keydown && event->isKeyboardEvent() && toKeyboardEvent(event)->keyIdentifier() == "Enter";
491 }
492
493 bool isLinkClick(Event* event)
494 {
495     return event->type() == EventTypeNames::click && (!event->isMouseEvent() || toMouseEvent(event)->button() != RightButton);
496 }
497
498 bool HTMLAnchorElement::willRespondToMouseClickEvents()
499 {
500     return isLink() || HTMLElement::willRespondToMouseClickEvents();
501 }
502
503 typedef HashMap<const HTMLAnchorElement*, RefPtr<Element> > RootEditableElementMap;
504
505 static RootEditableElementMap& rootEditableElementMap()
506 {
507     DEFINE_STATIC_LOCAL(RootEditableElementMap, map, ());
508     return map;
509 }
510
511 Element* HTMLAnchorElement::rootEditableElementForSelectionOnMouseDown() const
512 {
513     if (!m_hasRootEditableElementForSelectionOnMouseDown)
514         return 0;
515     return rootEditableElementMap().get(this);
516 }
517
518 void HTMLAnchorElement::clearRootEditableElementForSelectionOnMouseDown()
519 {
520     if (!m_hasRootEditableElementForSelectionOnMouseDown)
521         return;
522     rootEditableElementMap().remove(this);
523     m_hasRootEditableElementForSelectionOnMouseDown = false;
524 }
525
526 void HTMLAnchorElement::setRootEditableElementForSelectionOnMouseDown(Element* element)
527 {
528     if (!element) {
529         clearRootEditableElementForSelectionOnMouseDown();
530         return;
531     }
532
533     rootEditableElementMap().set(this, element);
534     m_hasRootEditableElementForSelectionOnMouseDown = true;
535 }
536
537 HTMLAnchorElement::PrefetchEventHandler* HTMLAnchorElement::prefetchEventHandler()
538 {
539     if (!m_prefetchEventHandler)
540         m_prefetchEventHandler = PrefetchEventHandler::create(this);
541
542     return m_prefetchEventHandler.get();
543 }
544
545 HTMLAnchorElement::PrefetchEventHandler::PrefetchEventHandler(HTMLAnchorElement* anchorElement)
546     : m_anchorElement(anchorElement)
547 {
548     ASSERT(m_anchorElement);
549
550     reset();
551 }
552
553 void HTMLAnchorElement::PrefetchEventHandler::reset()
554 {
555     m_hadHREFChanged = false;
556     m_mouseOverTimestamp = 0;
557     m_mouseDownTimestamp = 0;
558     m_hadTapUnconfirmed = false;
559     m_tapDownTimestamp = 0;
560     m_hasIssuedPreconnect = false;
561 }
562
563 void HTMLAnchorElement::PrefetchEventHandler::handleEvent(Event* event)
564 {
565     if (!shouldPrefetch(m_anchorElement->href()))
566         return;
567
568     if (event->type() == EventTypeNames::mouseover)
569         handleMouseOver(event);
570     else if (event->type() == EventTypeNames::mouseout)
571         handleMouseOut(event);
572     else if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton)
573         handleLeftMouseDown(event);
574     else if (event->type() == EventTypeNames::gestureshowpress)
575         handleGestureShowPress(event);
576     else if (event->type() == EventTypeNames::gesturetapunconfirmed)
577         handleGestureTapUnconfirmed(event);
578     else if (isLinkClick(event))
579         handleClick(event);
580 }
581
582 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOver(Event* event)
583 {
584     if (m_mouseOverTimestamp == 0.0) {
585         m_mouseOverTimestamp = event->timeStamp();
586
587         blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseOvers", 0, 2);
588
589         prefetch(blink::WebPreconnectMotivationLinkMouseOver);
590     }
591 }
592
593 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOut(Event* event)
594 {
595     if (m_mouseOverTimestamp > 0.0) {
596         double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp);
597         blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_NoClick", mouseOverDuration * 1000, 0, 10000, 100);
598
599         m_mouseOverTimestamp = 0.0;
600     }
601 }
602
603 void HTMLAnchorElement::PrefetchEventHandler::handleLeftMouseDown(Event* event)
604 {
605     m_mouseDownTimestamp = event->timeStamp();
606
607     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseDowns", 0, 2);
608
609     prefetch(blink::WebPreconnectMotivationLinkMouseDown);
610 }
611
612 void HTMLAnchorElement::PrefetchEventHandler::handleGestureTapUnconfirmed(Event* event)
613 {
614     m_hadTapUnconfirmed = true;
615
616     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.TapUnconfirmeds", 0, 2);
617
618     prefetch(blink::WebPreconnectMotivationLinkTapUnconfirmed);
619 }
620
621 void HTMLAnchorElement::PrefetchEventHandler::handleGestureShowPress(Event* event)
622 {
623     m_tapDownTimestamp = event->timeStamp();
624
625     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.TapDowns", 0, 2);
626
627     prefetch(blink::WebPreconnectMotivationLinkTapDown);
628 }
629
630 void HTMLAnchorElement::PrefetchEventHandler::handleClick(Event* event)
631 {
632     bool capturedMouseOver = (m_mouseOverTimestamp > 0.0);
633     if (capturedMouseOver) {
634         double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp);
635
636         blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_Click", mouseOverDuration * 1000, 0, 10000, 100);
637     }
638
639     bool capturedMouseDown = (m_mouseDownTimestamp > 0.0);
640     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseDownFollowedByClick", capturedMouseDown, 2);
641
642     if (capturedMouseDown) {
643         double mouseDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseDownTimestamp);
644
645         blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseDownDuration_Click", mouseDownDuration * 1000, 0, 10000, 100);
646     }
647
648     bool capturedTapDown = (m_tapDownTimestamp > 0.0);
649     if (capturedTapDown) {
650         double tapDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_tapDownTimestamp);
651
652         blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.TapDownDuration_Click", tapDownDuration * 1000, 0, 10000, 100);
653     }
654
655     int flags = (m_hadTapUnconfirmed ? 2 : 0) | (capturedTapDown ? 1 : 0);
656     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.PreTapEventsFollowedByClick", flags, 4);
657 }
658
659 bool HTMLAnchorElement::PrefetchEventHandler::shouldPrefetch(const KURL& url)
660 {
661     if (m_hadHREFChanged)
662         return false;
663
664     if (m_anchorElement->hasEventListeners(EventTypeNames::click))
665         return false;
666
667     if (!url.protocolIsInHTTPFamily())
668         return false;
669
670     Document& document = m_anchorElement->document();
671
672     if (!document.securityOrigin()->canDisplay(url))
673         return false;
674
675     if (url.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(document.url(), url))
676         return false;
677
678     LocalFrame* frame = document.frame();
679     if (!frame)
680         return false;
681
682     // Links which create new window/tab are avoided because they may require user approval interaction.
683     if (!m_anchorElement->target().isEmpty())
684         return false;
685
686     return true;
687 }
688
689 void HTMLAnchorElement::PrefetchEventHandler::prefetch(blink::WebPreconnectMotivation motivation)
690 {
691     const KURL& url = m_anchorElement->href();
692
693     if (!shouldPrefetch(url))
694         return;
695
696     // The precision of current MouseOver trigger is too low to actually trigger preconnects.
697     if (motivation == blink::WebPreconnectMotivationLinkMouseOver)
698         return;
699
700     preconnectToURL(url, motivation);
701     m_hasIssuedPreconnect = true;
702 }
703
704 bool HTMLAnchorElement::isInteractiveContent() const
705 {
706     return isLink();
707 }
708
709 }