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 "HTMLNames.h"
     28 #include "core/dom/Attribute.h"
     29 #include "core/dom/EventNames.h"
     30 #include "core/dom/KeyboardEvent.h"
     31 #include "core/dom/MouseEvent.h"
     32 #include "core/editing/FrameSelection.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/Frame.h"
     44 #include "core/page/Page.h"
     45 #include "core/page/Settings.h"
     46 #include "core/platform/HistogramSupport.h"
     47 #include "core/platform/PlatformMouseEvent.h"
     48 #include "core/platform/network/DNS.h"
     49 #include "core/platform/network/ResourceRequest.h"
     50 #include "core/rendering/RenderImage.h"
     51 #include "public/platform/Platform.h"
     52 #include "public/platform/WebPrescientNetworking.h"
     53 #include "public/platform/WebURL.h"
     54 #include "weborigin/KnownPorts.h"
     55 #include "weborigin/SecurityOrigin.h"
     56 #include "weborigin/SecurityPolicy.h"
     57 #include "wtf/text/StringBuilder.h"
     58 
     59 namespace WebCore {
     60 
     61 namespace {
     62 
     63 void preconnectToURL(const KURL& url, WebKit::WebPreconnectMotivation motivation)
     64 {
     65     WebKit::WebPrescientNetworking* prescientNetworking = WebKit::Platform::current()->prescientNetworking();
     66     if (!prescientNetworking)
     67         return;
     68 
     69     prescientNetworking->preconnect(url, motivation);
     70 }
     71 
     72 }
     73 
     74 class HTMLAnchorElement::PrefetchEventHandler {
     75 public:
     76     static PassOwnPtr<PrefetchEventHandler> create(HTMLAnchorElement* anchorElement)
     77     {
     78         return adoptPtr(new HTMLAnchorElement::PrefetchEventHandler(anchorElement));
     79     }
     80 
     81     void reset();
     82 
     83     void handleEvent(Event* e);
     84     void didChangeHREF() { m_hadHREFChanged = true; }
     85     bool hasIssuedPreconnect() const { return m_hasIssuedPreconnect; }
     86 
     87 private:
     88     explicit PrefetchEventHandler(HTMLAnchorElement*);
     89 
     90     void handleMouseOver(Event* event);
     91     void handleMouseOut(Event* event);
     92     void handleLeftMouseDown(Event* event);
     93     void handleGestureTapUnconfirmed(Event*);
     94     void handleGestureTapDown(Event*);
     95     void handleClick(Event* event);
     96 
     97     bool shouldPrefetch(const KURL&);
     98     void prefetch(WebKit::WebPreconnectMotivation);
     99 
    100     HTMLAnchorElement* m_anchorElement;
    101     double m_mouseOverTimestamp;
    102     double m_mouseDownTimestamp;
    103     double m_tapDownTimestamp;
    104     bool m_hadHREFChanged;
    105     bool m_hadTapUnconfirmed;
    106     bool m_hasIssuedPreconnect;
    107 };
    108 
    109 using namespace HTMLNames;
    110 
    111 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document* document)
    112     : HTMLElement(tagName, document)
    113     , m_hasRootEditableElementForSelectionOnMouseDown(false)
    114     , m_wasShiftKeyDownOnMouseDown(false)
    115     , m_linkRelations(0)
    116     , m_cachedVisitedLinkHash(0)
    117 {
    118     ScriptWrappable::init(this);
    119 }
    120 
    121 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document* document)
    122 {
    123     return adoptRef(new HTMLAnchorElement(aTag, document));
    124 }
    125 
    126 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(const QualifiedName& tagName, Document* document)
    127 {
    128     return adoptRef(new HTMLAnchorElement(tagName, document));
    129 }
    130 
    131 HTMLAnchorElement::~HTMLAnchorElement()
    132 {
    133     clearRootEditableElementForSelectionOnMouseDown();
    134 }
    135 
    136 // This function does not allow leading spaces before the port number.
    137 static unsigned parsePortFromStringPosition(const String& value, unsigned portStart, unsigned& portEnd)
    138 {
    139     portEnd = portStart;
    140     while (isASCIIDigit(value[portEnd]))
    141         ++portEnd;
    142     return value.substring(portStart, portEnd - portStart).toUInt();
    143 }
    144 
    145 bool HTMLAnchorElement::supportsFocus() const
    146 {
    147     if (rendererIsEditable())
    148         return HTMLElement::supportsFocus();
    149     // If not a link we should still be able to focus the element if it has tabIndex.
    150     return isLink() || HTMLElement::supportsFocus();
    151 }
    152 
    153 bool HTMLAnchorElement::isMouseFocusable() const
    154 {
    155     // Links are focusable by default, but only allow links with tabindex or contenteditable to be mouse focusable.
    156     // https://bugs.webkit.org/show_bug.cgi?id=26856
    157     if (isLink())
    158         return HTMLElement::supportsFocus();
    159 
    160     return HTMLElement::isMouseFocusable();
    161 }
    162 
    163 bool HTMLAnchorElement::isKeyboardFocusable() const
    164 {
    165     if (!isLink())
    166         return HTMLElement::isKeyboardFocusable();
    167 
    168     if (!isFocusable())
    169         return false;
    170 
    171     Page* page = document()->page();
    172     if (!page)
    173         return false;
    174 
    175     if (!page->chrome().client()->tabsToLinks())
    176         return false;
    177 
    178     if (isInCanvasSubtree())
    179         return true;
    180 
    181     return hasNonEmptyBoundingBox();
    182 }
    183 
    184 static void appendServerMapMousePosition(StringBuilder& url, Event* event)
    185 {
    186     if (!event->isMouseEvent())
    187         return;
    188 
    189     ASSERT(event->target());
    190     Node* target = event->target()->toNode();
    191     ASSERT(target);
    192     if (!target->hasTagName(imgTag))
    193         return;
    194 
    195     HTMLImageElement* imageElement = toHTMLImageElement(event->target()->toNode());
    196     if (!imageElement || !imageElement->isServerMap())
    197         return;
    198 
    199     if (!imageElement->renderer() || !imageElement->renderer()->isRenderImage())
    200         return;
    201     RenderImage* renderer = toRenderImage(imageElement->renderer());
    202 
    203     // FIXME: This should probably pass true for useTransforms.
    204     FloatPoint absolutePosition = renderer->absoluteToLocal(FloatPoint(toMouseEvent(event)->pageX(), toMouseEvent(event)->pageY()));
    205     int x = absolutePosition.x();
    206     int y = absolutePosition.y();
    207     url.append('?');
    208     url.appendNumber(x);
    209     url.append(',');
    210     url.appendNumber(y);
    211 }
    212 
    213 void HTMLAnchorElement::defaultEventHandler(Event* event)
    214 {
    215     if (isLink()) {
    216         if (focused() && isEnterKeyKeydownEvent(event) && treatLinkAsLiveForEventType(NonMouseEvent)) {
    217             event->setDefaultHandled();
    218             dispatchSimulatedClick(event);
    219             return;
    220         }
    221 
    222         prefetchEventHandler()->handleEvent(event);
    223 
    224         if (isLinkClick(event) && treatLinkAsLiveForEventType(eventType(event))) {
    225             handleClick(event);
    226             prefetchEventHandler()->reset();
    227             return;
    228         }
    229 
    230         if (rendererIsEditable()) {
    231             // This keeps track of the editable block that the selection was in (if it was in one) just before the link was clicked
    232             // for the LiveWhenNotFocused editable link behavior
    233             if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && toMouseEvent(event)->button() != RightButton && document()->frame() && document()->frame()->selection()) {
    234                 setRootEditableElementForSelectionOnMouseDown(document()->frame()->selection()->rootEditableElement());
    235                 m_wasShiftKeyDownOnMouseDown = toMouseEvent(event)->shiftKey();
    236             } else if (event->type() == eventNames().mouseoverEvent) {
    237                 // These are cleared on mouseover and not mouseout because their values are needed for drag events,
    238                 // but drag events happen after mouse out events.
    239                 clearRootEditableElementForSelectionOnMouseDown();
    240                 m_wasShiftKeyDownOnMouseDown = false;
    241             }
    242         }
    243     }
    244 
    245     HTMLElement::defaultEventHandler(event);
    246 }
    247 
    248 void HTMLAnchorElement::setActive(bool down, bool pause)
    249 {
    250     if (rendererIsEditable()) {
    251         EditableLinkBehavior editableLinkBehavior = EditableLinkDefaultBehavior;
    252         if (Settings* settings = document()->settings())
    253             editableLinkBehavior = settings->editableLinkBehavior();
    254 
    255         switch (editableLinkBehavior) {
    256             default:
    257             case EditableLinkDefaultBehavior:
    258             case EditableLinkAlwaysLive:
    259                 break;
    260 
    261             case EditableLinkNeverLive:
    262                 return;
    263 
    264             // Don't set the link to be active if the current selection is in the same editable block as
    265             // this link
    266             case EditableLinkLiveWhenNotFocused:
    267                 if (down && document()->frame() && document()->frame()->selection()->rootEditableElement() == rootEditableElement())
    268                     return;
    269                 break;
    270 
    271             case EditableLinkOnlyLiveWithShiftKey:
    272                 return;
    273         }
    274 
    275     }
    276 
    277     ContainerNode::setActive(down, pause);
    278 }
    279 
    280 void HTMLAnchorElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
    281 {
    282     if (name == hrefAttr) {
    283         bool wasLink = isLink();
    284         setIsLink(!value.isNull());
    285         if (wasLink != isLink()) {
    286             didAffectSelector(AffectedSelectorLink | AffectedSelectorVisited | AffectedSelectorEnabled);
    287             if (wasLink && treeScope()->adjustedFocusedElement() == this) {
    288                 // We might want to call blur(), but it's dangerous to dispatch
    289                 // events here.
    290                 document()->setNeedsFocusedElementCheck();
    291             }
    292         }
    293         if (isLink()) {
    294             String parsedURL = stripLeadingAndTrailingHTMLSpaces(value);
    295             if (document()->isDNSPrefetchEnabled()) {
    296                 if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//"))
    297                     prefetchDNS(document()->completeURL(parsedURL).host());
    298             }
    299 
    300             if (wasLink)
    301                 prefetchEventHandler()->didChangeHREF();
    302         }
    303         invalidateCachedVisitedLinkHash();
    304     } else if (name == nameAttr || name == titleAttr) {
    305         // Do nothing.
    306     } else if (name == relAttr)
    307         setRel(value);
    308     else
    309         HTMLElement::parseAttribute(name, value);
    310 }
    311 
    312 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
    313 {
    314     dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
    315 }
    316 
    317 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
    318 {
    319     return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
    320 }
    321 
    322 bool HTMLAnchorElement::canStartSelection() const
    323 {
    324     // FIXME: We probably want this same behavior in SVGAElement too
    325     if (!isLink())
    326         return HTMLElement::canStartSelection();
    327     return rendererIsEditable();
    328 }
    329 
    330 bool HTMLAnchorElement::draggable() const
    331 {
    332     // Should be draggable if we have an href attribute.
    333     const AtomicString& value = getAttribute(draggableAttr);
    334     if (equalIgnoringCase(value, "true"))
    335         return true;
    336     if (equalIgnoringCase(value, "false"))
    337         return false;
    338     return hasAttribute(hrefAttr);
    339 }
    340 
    341 KURL HTMLAnchorElement::href() const
    342 {
    343     return document()->completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
    344 }
    345 
    346 void HTMLAnchorElement::setHref(const AtomicString& value)
    347 {
    348     setAttribute(hrefAttr, value);
    349 }
    350 
    351 bool HTMLAnchorElement::hasRel(uint32_t relation) const
    352 {
    353     return m_linkRelations & relation;
    354 }
    355 
    356 void HTMLAnchorElement::setRel(const String& value)
    357 {
    358     m_linkRelations = 0;
    359     SpaceSplitString newLinkRelations(value, true);
    360     // FIXME: Add link relations as they are implemented
    361     if (newLinkRelations.contains("noreferrer"))
    362         m_linkRelations |= RelationNoReferrer;
    363 }
    364 
    365 const AtomicString& HTMLAnchorElement::name() const
    366 {
    367     return getNameAttribute();
    368 }
    369 
    370 short HTMLAnchorElement::tabIndex() const
    371 {
    372     // Skip the supportsFocus check in HTMLElement.
    373     return Element::tabIndex();
    374 }
    375 
    376 String HTMLAnchorElement::target() const
    377 {
    378     return getAttribute(targetAttr);
    379 }
    380 
    381 String HTMLAnchorElement::hash() const
    382 {
    383     String fragmentIdentifier = href().fragmentIdentifier();
    384     if (fragmentIdentifier.isEmpty())
    385         return emptyString();
    386     return AtomicString(String("#" + fragmentIdentifier));
    387 }
    388 
    389 void HTMLAnchorElement::setHash(const String& value)
    390 {
    391     KURL url = href();
    392     if (value[0] == '#')
    393         url.setFragmentIdentifier(value.substring(1));
    394     else
    395         url.setFragmentIdentifier(value);
    396     setHref(url.string());
    397 }
    398 
    399 String HTMLAnchorElement::host() const
    400 {
    401     const KURL& url = href();
    402     if (url.hostEnd() == url.pathStart())
    403         return url.host();
    404     if (isDefaultPortForProtocol(url.port(), url.protocol()))
    405         return url.host();
    406     return url.host() + ":" + String::number(url.port());
    407 }
    408 
    409 void HTMLAnchorElement::setHost(const String& value)
    410 {
    411     if (value.isEmpty())
    412         return;
    413     KURL url = href();
    414     if (!url.canSetHostOrPort())
    415         return;
    416 
    417     size_t separator = value.find(':');
    418     if (!separator)
    419         return;
    420 
    421     if (separator == notFound)
    422         url.setHostAndPort(value);
    423     else {
    424         unsigned portEnd;
    425         unsigned port = parsePortFromStringPosition(value, separator + 1, portEnd);
    426         if (!port) {
    427             // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
    428             // specifically goes against RFC 3986 (p3.2) and
    429             // requires setting the port to "0" if it is set to empty string.
    430             url.setHostAndPort(value.substring(0, separator + 1) + "0");
    431         } else {
    432             if (isDefaultPortForProtocol(port, url.protocol()))
    433                 url.setHostAndPort(value.substring(0, separator));
    434             else
    435                 url.setHostAndPort(value.substring(0, portEnd));
    436         }
    437     }
    438     setHref(url.string());
    439 }
    440 
    441 String HTMLAnchorElement::hostname() const
    442 {
    443     return href().host();
    444 }
    445 
    446 void HTMLAnchorElement::setHostname(const String& value)
    447 {
    448     // Before setting new value:
    449     // Remove all leading U+002F SOLIDUS ("/") characters.
    450     unsigned i = 0;
    451     unsigned hostLength = value.length();
    452     while (value[i] == '/')
    453         i++;
    454 
    455     if (i == hostLength)
    456         return;
    457 
    458     KURL url = href();
    459     if (!url.canSetHostOrPort())
    460         return;
    461 
    462     url.setHost(value.substring(i));
    463     setHref(url.string());
    464 }
    465 
    466 String HTMLAnchorElement::pathname() const
    467 {
    468     return href().path();
    469 }
    470 
    471 void HTMLAnchorElement::setPathname(const String& value)
    472 {
    473     KURL url = href();
    474     if (!url.canSetPathname())
    475         return;
    476 
    477     if (value[0] == '/')
    478         url.setPath(value);
    479     else
    480         url.setPath("/" + value);
    481 
    482     setHref(url.string());
    483 }
    484 
    485 String HTMLAnchorElement::port() const
    486 {
    487     if (href().hasPort())
    488         return String::number(href().port());
    489 
    490     return emptyString();
    491 }
    492 
    493 void HTMLAnchorElement::setPort(const String& value)
    494 {
    495     KURL url = href();
    496     if (!url.canSetHostOrPort())
    497         return;
    498 
    499     // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
    500     // specifically goes against RFC 3986 (p3.2) and
    501     // requires setting the port to "0" if it is set to empty string.
    502     unsigned port = value.toUInt();
    503     if (isDefaultPortForProtocol(port, url.protocol()))
    504         url.removePort();
    505     else
    506         url.setPort(port);
    507 
    508     setHref(url.string());
    509 }
    510 
    511 String HTMLAnchorElement::protocol() const
    512 {
    513     return href().protocol() + ":";
    514 }
    515 
    516 void HTMLAnchorElement::setProtocol(const String& value)
    517 {
    518     KURL url = href();
    519     url.setProtocol(value);
    520     setHref(url.string());
    521 }
    522 
    523 String HTMLAnchorElement::search() const
    524 {
    525     String query = href().query();
    526     return query.isEmpty() ? emptyString() : "?" + query;
    527 }
    528 
    529 String HTMLAnchorElement::origin() const
    530 {
    531     RefPtr<SecurityOrigin> origin = SecurityOrigin::create(href());
    532     return origin->toString();
    533 }
    534 
    535 void HTMLAnchorElement::setSearch(const String& value)
    536 {
    537     KURL url = href();
    538     String newSearch = (value[0] == '?') ? value.substring(1) : value;
    539     // Make sure that '#' in the query does not leak to the hash.
    540     url.setQuery(newSearch.replaceWithLiteral('#', "%23"));
    541 
    542     setHref(url.string());
    543 }
    544 
    545 String HTMLAnchorElement::text()
    546 {
    547     return innerText();
    548 }
    549 
    550 String HTMLAnchorElement::toString() const
    551 {
    552     return href().string();
    553 }
    554 
    555 bool HTMLAnchorElement::isLiveLink() const
    556 {
    557     return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey);
    558 }
    559 
    560 void HTMLAnchorElement::sendPings(const KURL& destinationURL)
    561 {
    562     if (!hasAttribute(pingAttr) || !document()->settings() || !document()->settings()->hyperlinkAuditingEnabled())
    563         return;
    564 
    565     SpaceSplitString pingURLs(getAttribute(pingAttr), false);
    566     for (unsigned i = 0; i < pingURLs.size(); i++)
    567         PingLoader::sendPing(document()->frame(), document()->completeURL(pingURLs[i]), destinationURL);
    568 }
    569 
    570 void HTMLAnchorElement::handleClick(Event* event)
    571 {
    572     event->setDefaultHandled();
    573 
    574     Frame* frame = document()->frame();
    575     if (!frame)
    576         return;
    577 
    578     StringBuilder url;
    579     url.append(stripLeadingAndTrailingHTMLSpaces(fastGetAttribute(hrefAttr)));
    580     appendServerMapMousePosition(url, event);
    581     KURL completedURL = document()->completeURL(url.toString());
    582     if (frame->loader()->client()->shouldAbortNavigationAfterUrlResolve(document()->baseURI(), url.toString(), completedURL))
    583       return;
    584 
    585     ResourceRequest request(completedURL);
    586     if (prefetchEventHandler()->hasIssuedPreconnect())
    587         frame->loader()->client()->dispatchWillRequestAfterPreconnect(request);
    588     if (hasAttribute(downloadAttr)) {
    589         if (!hasRel(RelationNoReferrer)) {
    590             String referrer = SecurityPolicy::generateReferrerHeader(document()->referrerPolicy(), completedURL, frame->loader()->outgoingReferrer());
    591             if (!referrer.isEmpty())
    592                 request.setHTTPReferrer(referrer);
    593         }
    594 
    595         frame->loader()->client()->loadURLExternally(request, NavigationPolicyDownload, fastGetAttribute(downloadAttr));
    596     } else {
    597         FrameLoadRequest frameRequest(document()->securityOrigin(), request, target());
    598         frameRequest.setTriggeringEvent(event);
    599         if (hasRel(RelationNoReferrer))
    600             frameRequest.setShouldSendReferrer(NeverSendReferrer);
    601         frame->loader()->load(frameRequest);
    602     }
    603 
    604     sendPings(completedURL);
    605 }
    606 
    607 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event)
    608 {
    609     if (!event->isMouseEvent())
    610         return NonMouseEvent;
    611     return toMouseEvent(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
    612 }
    613 
    614 bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const
    615 {
    616     if (!rendererIsEditable())
    617         return true;
    618 
    619     Settings* settings = document()->settings();
    620     if (!settings)
    621         return true;
    622 
    623     switch (settings->editableLinkBehavior()) {
    624     case EditableLinkDefaultBehavior:
    625     case EditableLinkAlwaysLive:
    626         return true;
    627 
    628     case EditableLinkNeverLive:
    629         return false;
    630 
    631     // If the selection prior to clicking on this link resided in the same editable block as this link,
    632     // and the shift key isn't pressed, we don't want to follow the link.
    633     case EditableLinkLiveWhenNotFocused:
    634         return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && rootEditableElementForSelectionOnMouseDown() != rootEditableElement());
    635 
    636     case EditableLinkOnlyLiveWithShiftKey:
    637         return eventType == MouseEventWithShiftKey;
    638     }
    639 
    640     ASSERT_NOT_REACHED();
    641     return false;
    642 }
    643 
    644 bool isEnterKeyKeydownEvent(Event* event)
    645 {
    646     return event->type() == eventNames().keydownEvent && event->isKeyboardEvent() && toKeyboardEvent(event)->keyIdentifier() == "Enter";
    647 }
    648 
    649 bool isLinkClick(Event* event)
    650 {
    651     return event->type() == eventNames().clickEvent && (!event->isMouseEvent() || toMouseEvent(event)->button() != RightButton);
    652 }
    653 
    654 bool HTMLAnchorElement::willRespondToMouseClickEvents()
    655 {
    656     return isLink() || HTMLElement::willRespondToMouseClickEvents();
    657 }
    658 
    659 typedef HashMap<const HTMLAnchorElement*, RefPtr<Element> > RootEditableElementMap;
    660 
    661 static RootEditableElementMap& rootEditableElementMap()
    662 {
    663     DEFINE_STATIC_LOCAL(RootEditableElementMap, map, ());
    664     return map;
    665 }
    666 
    667 Element* HTMLAnchorElement::rootEditableElementForSelectionOnMouseDown() const
    668 {
    669     if (!m_hasRootEditableElementForSelectionOnMouseDown)
    670         return 0;
    671     return rootEditableElementMap().get(this);
    672 }
    673 
    674 void HTMLAnchorElement::clearRootEditableElementForSelectionOnMouseDown()
    675 {
    676     if (!m_hasRootEditableElementForSelectionOnMouseDown)
    677         return;
    678     rootEditableElementMap().remove(this);
    679     m_hasRootEditableElementForSelectionOnMouseDown = false;
    680 }
    681 
    682 void HTMLAnchorElement::setRootEditableElementForSelectionOnMouseDown(Element* element)
    683 {
    684     if (!element) {
    685         clearRootEditableElementForSelectionOnMouseDown();
    686         return;
    687     }
    688 
    689     rootEditableElementMap().set(this, element);
    690     m_hasRootEditableElementForSelectionOnMouseDown = true;
    691 }
    692 
    693 HTMLAnchorElement::PrefetchEventHandler* HTMLAnchorElement::prefetchEventHandler()
    694 {
    695     if (!m_prefetchEventHandler)
    696         m_prefetchEventHandler = PrefetchEventHandler::create(this);
    697 
    698     return m_prefetchEventHandler.get();
    699 }
    700 
    701 HTMLAnchorElement::PrefetchEventHandler::PrefetchEventHandler(HTMLAnchorElement* anchorElement)
    702     : m_anchorElement(anchorElement)
    703 {
    704     ASSERT(m_anchorElement);
    705 
    706     reset();
    707 }
    708 
    709 void HTMLAnchorElement::PrefetchEventHandler::reset()
    710 {
    711     m_hadHREFChanged = false;
    712     m_mouseOverTimestamp = 0;
    713     m_mouseDownTimestamp = 0;
    714     m_hadTapUnconfirmed = false;
    715     m_tapDownTimestamp = 0;
    716     m_hasIssuedPreconnect = false;
    717 }
    718 
    719 void HTMLAnchorElement::PrefetchEventHandler::handleEvent(Event* event)
    720 {
    721     if (!shouldPrefetch(m_anchorElement->href()))
    722         return;
    723 
    724     if (event->type() == eventNames().mouseoverEvent)
    725         handleMouseOver(event);
    726     else if (event->type() == eventNames().mouseoutEvent)
    727         handleMouseOut(event);
    728     else if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton)
    729         handleLeftMouseDown(event);
    730     else if (event->type() == eventNames().gesturetapdownEvent)
    731         handleGestureTapDown(event);
    732     else if (event->type() == eventNames().gesturetapunconfirmedEvent)
    733         handleGestureTapUnconfirmed(event);
    734     else if (isLinkClick(event))
    735         handleClick(event);
    736 }
    737 
    738 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOver(Event* event)
    739 {
    740     if (m_mouseOverTimestamp == 0.0) {
    741         m_mouseOverTimestamp = event->timeStamp();
    742 
    743         HistogramSupport::histogramEnumeration("MouseEventPrefetch.MouseOvers", 0, 2);
    744 
    745         prefetch(WebKit::WebPreconnectMotivationLinkMouseOver);
    746     }
    747 }
    748 
    749 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOut(Event* event)
    750 {
    751     if (m_mouseOverTimestamp > 0.0) {
    752         double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp);
    753         HistogramSupport::histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_NoClick", mouseOverDuration * 1000, 0, 10000, 100);
    754 
    755         m_mouseOverTimestamp = 0.0;
    756     }
    757 }
    758 
    759 void HTMLAnchorElement::PrefetchEventHandler::handleLeftMouseDown(Event* event)
    760 {
    761     m_mouseDownTimestamp = event->timeStamp();
    762 
    763     HistogramSupport::histogramEnumeration("MouseEventPrefetch.MouseDowns", 0, 2);
    764 
    765     prefetch(WebKit::WebPreconnectMotivationLinkMouseDown);
    766 }
    767 
    768 void HTMLAnchorElement::PrefetchEventHandler::handleGestureTapUnconfirmed(Event* event)
    769 {
    770     m_hadTapUnconfirmed = true;
    771 
    772     HistogramSupport::histogramEnumeration("MouseEventPrefetch.TapUnconfirmeds", 0, 2);
    773 
    774     prefetch(WebKit::WebPreconnectMotivationLinkTapUnconfirmed);
    775 }
    776 
    777 void HTMLAnchorElement::PrefetchEventHandler::handleGestureTapDown(Event* event)
    778 {
    779     m_tapDownTimestamp = event->timeStamp();
    780 
    781     HistogramSupport::histogramEnumeration("MouseEventPrefetch.TapDowns", 0, 2);
    782 
    783     prefetch(WebKit::WebPreconnectMotivationLinkTapDown);
    784 }
    785 
    786 void HTMLAnchorElement::PrefetchEventHandler::handleClick(Event* event)
    787 {
    788     bool capturedMouseOver = (m_mouseOverTimestamp > 0.0);
    789     if (capturedMouseOver) {
    790         double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp);
    791 
    792         HistogramSupport::histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_Click", mouseOverDuration * 1000, 0, 10000, 100);
    793     }
    794 
    795     bool capturedMouseDown = (m_mouseDownTimestamp > 0.0);
    796     HistogramSupport::histogramEnumeration("MouseEventPrefetch.MouseDownFollowedByClick", capturedMouseDown, 2);
    797 
    798     if (capturedMouseDown) {
    799         double mouseDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseDownTimestamp);
    800 
    801         HistogramSupport::histogramCustomCounts("MouseEventPrefetch.MouseDownDuration_Click", mouseDownDuration * 1000, 0, 10000, 100);
    802     }
    803 
    804     bool capturedTapDown = (m_tapDownTimestamp > 0.0);
    805     if (capturedTapDown) {
    806         double tapDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_tapDownTimestamp);
    807 
    808         HistogramSupport::histogramCustomCounts("MouseEventPrefetch.TapDownDuration_Click", tapDownDuration * 1000, 0, 10000, 100);
    809     }
    810 
    811     int flags = (m_hadTapUnconfirmed ? 2 : 0) | (capturedTapDown ? 1 : 0);
    812     HistogramSupport::histogramEnumeration("MouseEventPrefetch.PreTapEventsFollowedByClick", flags, 4);
    813 }
    814 
    815 bool HTMLAnchorElement::PrefetchEventHandler::shouldPrefetch(const KURL& url)
    816 {
    817     if (m_hadHREFChanged)
    818         return false;
    819 
    820     if (m_anchorElement->hasEventListeners(eventNames().clickEvent))
    821         return false;
    822 
    823     if (!url.protocolIsInHTTPFamily())
    824         return false;
    825 
    826     Document* document = m_anchorElement->document();
    827     if (!document)
    828         return false;
    829 
    830     if (!document->securityOrigin()->canDisplay(url))
    831         return false;
    832 
    833     if (url.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(document->url(), url))
    834         return false;
    835 
    836     Frame* frame = document->frame();
    837     if (!frame)
    838         return false;
    839 
    840     // Links which create new window/tab are avoided because they may require user approval interaction.
    841     if (!m_anchorElement->target().isEmpty())
    842         return false;
    843 
    844     return true;
    845 }
    846 
    847 void HTMLAnchorElement::PrefetchEventHandler::prefetch(WebKit::WebPreconnectMotivation motivation)
    848 {
    849     const KURL& url = m_anchorElement->href();
    850 
    851     if (!shouldPrefetch(url))
    852         return;
    853 
    854     // The precision of current MouseOver trigger is too low to actually trigger preconnects.
    855     if (motivation == WebKit::WebPreconnectMotivationLinkMouseOver)
    856         return;
    857 
    858     preconnectToURL(url, motivation);
    859     m_hasIssuedPreconnect = true;
    860 }
    861 
    862 }
    863