Home | History | Annotate | Download | only in html
      1 /*
      2  * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org)
      3  *           (C) 1999 Antti Koivisto (koivisto (at) kde.org)
      4  *           (C) 2000 Simon Hausmann <hausmann (at) kde.org>
      5  * Copyright (C) 2003, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
      6  *           (C) 2006 Graham Dennis (graham.dennis (at) 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 
     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     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 
    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 
    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 
    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 
    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 
    296 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
    297 {
    298     dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
    299 }
    300 
    301 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
    302 {
    303     return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
    304 }
    305 
    306 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 
    314 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 
    325 KURL HTMLAnchorElement::href() const
    326 {
    327     return document().completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
    328 }
    329 
    330 void HTMLAnchorElement::setHref(const AtomicString& value)
    331 {
    332     setAttribute(hrefAttr, value);
    333 }
    334 
    335 KURL HTMLAnchorElement::url() const
    336 {
    337     return href();
    338 }
    339 
    340 void HTMLAnchorElement::setURL(const KURL& url)
    341 {
    342     setHref(AtomicString(url.string()));
    343 }
    344 
    345 String HTMLAnchorElement::input() const
    346 {
    347     return getAttribute(hrefAttr);
    348 }
    349 
    350 void HTMLAnchorElement::setInput(const String& value)
    351 {
    352     setHref(value);
    353 }
    354 
    355 bool HTMLAnchorElement::hasRel(uint32_t relation) const
    356 {
    357     return m_linkRelations & relation;
    358 }
    359 
    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 
    369 const AtomicString& HTMLAnchorElement::name() const
    370 {
    371     return getNameAttribute();
    372 }
    373 
    374 short HTMLAnchorElement::tabIndex() const
    375 {
    376     // Skip the supportsFocus check in HTMLElement.
    377     return Element::tabIndex();
    378 }
    379 
    380 String HTMLAnchorElement::target() const
    381 {
    382     return getAttribute(targetAttr);
    383 }
    384 
    385 
    386 String HTMLAnchorElement::text()
    387 {
    388     return innerText();
    389 }
    390 
    391 bool HTMLAnchorElement::isLiveLink() const
    392 {
    393     return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey);
    394 }
    395 
    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 
    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 
    443 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event)
    444 {
    445     if (!event->isMouseEvent())
    446         return NonMouseEvent;
    447     return toMouseEvent(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
    448 }
    449 
    450 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 
    480 bool isEnterKeyKeydownEvent(Event* event)
    481 {
    482     return event->type() == EventTypeNames::keydown && event->isKeyboardEvent() && toKeyboardEvent(event)->keyIdentifier() == "Enter";
    483 }
    484 
    485 bool isLinkClick(Event* event)
    486 {
    487     return event->type() == EventTypeNames::click && (!event->isMouseEvent() || toMouseEvent(event)->button() != RightButton);
    488 }
    489 
    490 bool HTMLAnchorElement::willRespondToMouseClickEvents()
    491 {
    492     return isLink() || HTMLElement::willRespondToMouseClickEvents();
    493 }
    494 
    495 typedef HashMap<const HTMLAnchorElement*, RefPtr<Element> > RootEditableElementMap;
    496 
    497 static RootEditableElementMap& rootEditableElementMap()
    498 {
    499     DEFINE_STATIC_LOCAL(RootEditableElementMap, map, ());
    500     return map;
    501 }
    502 
    503 Element* HTMLAnchorElement::rootEditableElementForSelectionOnMouseDown() const
    504 {
    505     if (!m_hasRootEditableElementForSelectionOnMouseDown)
    506         return 0;
    507     return rootEditableElementMap().get(this);
    508 }
    509 
    510 void HTMLAnchorElement::clearRootEditableElementForSelectionOnMouseDown()
    511 {
    512     if (!m_hasRootEditableElementForSelectionOnMouseDown)
    513         return;
    514     rootEditableElementMap().remove(this);
    515     m_hasRootEditableElementForSelectionOnMouseDown = false;
    516 }
    517 
    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 
    529 HTMLAnchorElement::PrefetchEventHandler* HTMLAnchorElement::prefetchEventHandler()
    530 {
    531     if (!m_prefetchEventHandler)
    532         m_prefetchEventHandler = PrefetchEventHandler::create(this);
    533 
    534     return m_prefetchEventHandler.get();
    535 }
    536 
    537 HTMLAnchorElement::PrefetchEventHandler::PrefetchEventHandler(HTMLAnchorElement* anchorElement)
    538     : m_anchorElement(anchorElement)
    539 {
    540     ASSERT(m_anchorElement);
    541 
    542     reset();
    543 }
    544 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    696 bool HTMLAnchorElement::isInteractiveContent() const
    697 {
    698     return isLink();
    699 }
    700 
    701 }
    702