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/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 "platform/PlatformMouseEvent.h"
     47 #include "platform/network/DNS.h"
     48 #include "platform/network/ResourceRequest.h"
     49 #include "platform/weborigin/KnownPorts.h"
     50 #include "platform/weborigin/SecurityOrigin.h"
     51 #include "platform/weborigin/SecurityPolicy.h"
     52 #include "public/platform/Platform.h"
     53 #include "public/platform/WebPrescientNetworking.h"
     54 #include "public/platform/WebURL.h"
     55 #include "wtf/text/StringBuilder.h"
     56 
     57 namespace WebCore {
     58 
     59 namespace {
     60 
     61 void preconnectToURL(const KURL& url, blink::WebPreconnectMotivation motivation)
     62 {
     63     blink::WebPrescientNetworking* prescientNetworking = blink::Platform::current()->prescientNetworking();
     64     if (!prescientNetworking)
     65         return;
     66 
     67     prescientNetworking->preconnect(url, motivation);
     68 }
     69 
     70 }
     71 
     72 class HTMLAnchorElement::PrefetchEventHandler FINAL : public NoBaseWillBeGarbageCollected<HTMLAnchorElement::PrefetchEventHandler> {
     73 public:
     74     static PassOwnPtrWillBeRawPtr<PrefetchEventHandler> create(HTMLAnchorElement* anchorElement)
     75     {
     76         return adoptPtrWillBeNoop(new HTMLAnchorElement::PrefetchEventHandler(anchorElement));
     77     }
     78 
     79     void reset();
     80 
     81     void handleEvent(Event* e);
     82     void didChangeHREF() { m_hadHREFChanged = true; }
     83     bool hasIssuedPreconnect() const { return m_hasIssuedPreconnect; }
     84 
     85     void trace(Visitor* visitor) { visitor->trace(m_anchorElement); }
     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 handleGestureShowPress(Event*);
     95     void handleClick(Event* event);
     96 
     97     bool shouldPrefetch(const KURL&);
     98     void prefetch(blink::WebPreconnectMotivation);
     99 
    100     RawPtrWillBeMember<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_linkRelations(0)
    114     , m_cachedVisitedLinkHash(0)
    115 {
    116     ScriptWrappable::init(this);
    117 }
    118 
    119 PassRefPtrWillBeRawPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document& document)
    120 {
    121     return adoptRefWillBeNoop(new HTMLAnchorElement(aTag, document));
    122 }
    123 
    124 HTMLAnchorElement::~HTMLAnchorElement()
    125 {
    126 }
    127 
    128 bool HTMLAnchorElement::supportsFocus() const
    129 {
    130     if (rendererIsEditable())
    131         return HTMLElement::supportsFocus();
    132     // If not a link we should still be able to focus the element if it has tabIndex.
    133     return isLink() || HTMLElement::supportsFocus();
    134 }
    135 
    136 bool HTMLAnchorElement::isMouseFocusable() const
    137 {
    138     // Links are focusable by default, but only allow links with tabindex or contenteditable to be mouse focusable.
    139     // https://bugs.webkit.org/show_bug.cgi?id=26856
    140     if (isLink())
    141         return HTMLElement::supportsFocus();
    142 
    143     return HTMLElement::isMouseFocusable();
    144 }
    145 
    146 bool HTMLAnchorElement::isKeyboardFocusable() const
    147 {
    148     ASSERT(document().isActive());
    149 
    150     if (isFocusable() && Element::supportsFocus())
    151         return HTMLElement::isKeyboardFocusable();
    152 
    153     if (isLink() && !document().frameHost()->chrome().client().tabsToLinks())
    154         return false;
    155     return HTMLElement::isKeyboardFocusable();
    156 }
    157 
    158 static void appendServerMapMousePosition(StringBuilder& url, Event* event)
    159 {
    160     if (!event->isMouseEvent())
    161         return;
    162 
    163     ASSERT(event->target());
    164     Node* target = event->target()->toNode();
    165     ASSERT(target);
    166     if (!isHTMLImageElement(*target))
    167         return;
    168 
    169     HTMLImageElement& imageElement = toHTMLImageElement(*target);
    170     if (!imageElement.isServerMap())
    171         return;
    172 
    173     if (!imageElement.renderer() || !imageElement.renderer()->isRenderImage())
    174         return;
    175     RenderImage* renderer = toRenderImage(imageElement.renderer());
    176 
    177     // FIXME: This should probably pass true for useTransforms.
    178     FloatPoint absolutePosition = renderer->absoluteToLocal(FloatPoint(toMouseEvent(event)->pageX(), toMouseEvent(event)->pageY()));
    179     int x = absolutePosition.x();
    180     int y = absolutePosition.y();
    181     url.append('?');
    182     url.appendNumber(x);
    183     url.append(',');
    184     url.appendNumber(y);
    185 }
    186 
    187 void HTMLAnchorElement::defaultEventHandler(Event* event)
    188 {
    189     if (isLink()) {
    190         if (focused() && isEnterKeyKeydownEvent(event) && isLiveLink()) {
    191             event->setDefaultHandled();
    192             dispatchSimulatedClick(event);
    193             return;
    194         }
    195 
    196         prefetchEventHandler()->handleEvent(event);
    197 
    198         if (isLinkClick(event) && isLiveLink()) {
    199             handleClick(event);
    200             prefetchEventHandler()->reset();
    201             return;
    202         }
    203     }
    204 
    205     HTMLElement::defaultEventHandler(event);
    206 }
    207 
    208 void HTMLAnchorElement::setActive(bool down)
    209 {
    210     if (rendererIsEditable())
    211         return;
    212 
    213     ContainerNode::setActive(down);
    214 }
    215 
    216 void HTMLAnchorElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
    217 {
    218     if (name == hrefAttr) {
    219         bool wasLink = isLink();
    220         setIsLink(!value.isNull());
    221         if (wasLink != isLink()) {
    222             didAffectSelector(AffectedSelectorLink | AffectedSelectorVisited | AffectedSelectorEnabled);
    223             if (wasLink && treeScope().adjustedFocusedElement() == this) {
    224                 // We might want to call blur(), but it's dangerous to dispatch
    225                 // events here.
    226                 document().setNeedsFocusedElementCheck();
    227             }
    228         }
    229         if (isLink()) {
    230             String parsedURL = stripLeadingAndTrailingHTMLSpaces(value);
    231             if (document().isDNSPrefetchEnabled()) {
    232                 if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//"))
    233                     prefetchDNS(document().completeURL(parsedURL).host());
    234             }
    235 
    236             if (wasLink)
    237                 prefetchEventHandler()->didChangeHREF();
    238         }
    239         invalidateCachedVisitedLinkHash();
    240     } else if (name == nameAttr || name == titleAttr) {
    241         // Do nothing.
    242     } else if (name == relAttr)
    243         setRel(value);
    244     else
    245         HTMLElement::parseAttribute(name, value);
    246 }
    247 
    248 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
    249 {
    250     dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
    251 }
    252 
    253 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
    254 {
    255     return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
    256 }
    257 
    258 bool HTMLAnchorElement::hasLegalLinkAttribute(const QualifiedName& name) const
    259 {
    260     return name == hrefAttr || HTMLElement::hasLegalLinkAttribute(name);
    261 }
    262 
    263 bool HTMLAnchorElement::canStartSelection() const
    264 {
    265     if (!isLink())
    266         return HTMLElement::canStartSelection();
    267     return rendererIsEditable();
    268 }
    269 
    270 bool HTMLAnchorElement::draggable() const
    271 {
    272     // Should be draggable if we have an href attribute.
    273     const AtomicString& value = getAttribute(draggableAttr);
    274     if (equalIgnoringCase(value, "true"))
    275         return true;
    276     if (equalIgnoringCase(value, "false"))
    277         return false;
    278     return hasAttribute(hrefAttr);
    279 }
    280 
    281 KURL HTMLAnchorElement::href() const
    282 {
    283     return document().completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
    284 }
    285 
    286 void HTMLAnchorElement::setHref(const AtomicString& value)
    287 {
    288     setAttribute(hrefAttr, value);
    289 }
    290 
    291 KURL HTMLAnchorElement::url() const
    292 {
    293     return href();
    294 }
    295 
    296 void HTMLAnchorElement::setURL(const KURL& url)
    297 {
    298     setHref(AtomicString(url.string()));
    299 }
    300 
    301 String HTMLAnchorElement::input() const
    302 {
    303     return getAttribute(hrefAttr);
    304 }
    305 
    306 void HTMLAnchorElement::setInput(const String& value)
    307 {
    308     setHref(AtomicString(value));
    309 }
    310 
    311 bool HTMLAnchorElement::hasRel(uint32_t relation) const
    312 {
    313     return m_linkRelations & relation;
    314 }
    315 
    316 void HTMLAnchorElement::setRel(const AtomicString& value)
    317 {
    318     m_linkRelations = 0;
    319     SpaceSplitString newLinkRelations(value, true);
    320     // FIXME: Add link relations as they are implemented
    321     if (newLinkRelations.contains("noreferrer"))
    322         m_linkRelations |= RelationNoReferrer;
    323 }
    324 
    325 const AtomicString& HTMLAnchorElement::name() const
    326 {
    327     return getNameAttribute();
    328 }
    329 
    330 short HTMLAnchorElement::tabIndex() const
    331 {
    332     // Skip the supportsFocus check in HTMLElement.
    333     return Element::tabIndex();
    334 }
    335 
    336 AtomicString HTMLAnchorElement::target() const
    337 {
    338     return getAttribute(targetAttr);
    339 }
    340 
    341 bool HTMLAnchorElement::isLiveLink() const
    342 {
    343     return isLink() && !rendererIsEditable();
    344 }
    345 
    346 void HTMLAnchorElement::sendPings(const KURL& destinationURL)
    347 {
    348     const AtomicString& pingValue = getAttribute(pingAttr);
    349     if (pingValue.isNull() || !document().settings() || !document().settings()->hyperlinkAuditingEnabled())
    350         return;
    351 
    352     UseCounter::count(document(), UseCounter::HTMLAnchorElementPingAttribute);
    353 
    354     SpaceSplitString pingURLs(pingValue, false);
    355     for (unsigned i = 0; i < pingURLs.size(); i++)
    356         PingLoader::sendLinkAuditPing(document().frame(), document().completeURL(pingURLs[i]), destinationURL);
    357 }
    358 
    359 void HTMLAnchorElement::handleClick(Event* event)
    360 {
    361     event->setDefaultHandled();
    362 
    363     LocalFrame* frame = document().frame();
    364     if (!frame)
    365         return;
    366 
    367     StringBuilder url;
    368     url.append(stripLeadingAndTrailingHTMLSpaces(fastGetAttribute(hrefAttr)));
    369     appendServerMapMousePosition(url, event);
    370     KURL completedURL = document().completeURL(url.toString());
    371 
    372     // Schedule the ping before the frame load. Prerender in Chrome may kill the renderer as soon as the navigation is
    373     // sent out.
    374     sendPings(completedURL);
    375 
    376     ResourceRequest request(completedURL);
    377     if (prefetchEventHandler()->hasIssuedPreconnect())
    378         frame->loader().client()->dispatchWillRequestAfterPreconnect(request);
    379     if (hasAttribute(downloadAttr)) {
    380         if (!hasRel(RelationNoReferrer)) {
    381             String referrer = SecurityPolicy::generateReferrerHeader(document().referrerPolicy(), completedURL, document().outgoingReferrer());
    382             if (!referrer.isEmpty())
    383                 request.setHTTPReferrer(Referrer(referrer, document().referrerPolicy()));
    384         }
    385 
    386         bool isSameOrigin = completedURL.protocolIsData() || document().securityOrigin()->canRequest(completedURL);
    387         const AtomicString& suggestedName = (isSameOrigin ? fastGetAttribute(downloadAttr) : nullAtom);
    388 
    389         frame->loader().client()->loadURLExternally(request, NavigationPolicyDownload, suggestedName);
    390     } else {
    391         FrameLoadRequest frameRequest(&document(), request, target());
    392         frameRequest.setTriggeringEvent(event);
    393         if (hasRel(RelationNoReferrer))
    394             frameRequest.setShouldSendReferrer(NeverSendReferrer);
    395         frame->loader().load(frameRequest);
    396     }
    397 }
    398 
    399 bool isEnterKeyKeydownEvent(Event* event)
    400 {
    401     return event->type() == EventTypeNames::keydown && event->isKeyboardEvent() && toKeyboardEvent(event)->keyIdentifier() == "Enter";
    402 }
    403 
    404 bool isLinkClick(Event* event)
    405 {
    406     return event->type() == EventTypeNames::click && (!event->isMouseEvent() || toMouseEvent(event)->button() != RightButton);
    407 }
    408 
    409 bool HTMLAnchorElement::willRespondToMouseClickEvents()
    410 {
    411     return isLink() || HTMLElement::willRespondToMouseClickEvents();
    412 }
    413 
    414 HTMLAnchorElement::PrefetchEventHandler* HTMLAnchorElement::prefetchEventHandler()
    415 {
    416     if (!m_prefetchEventHandler)
    417         m_prefetchEventHandler = PrefetchEventHandler::create(this);
    418 
    419     return m_prefetchEventHandler.get();
    420 }
    421 
    422 HTMLAnchorElement::PrefetchEventHandler::PrefetchEventHandler(HTMLAnchorElement* anchorElement)
    423     : m_anchorElement(anchorElement)
    424 {
    425     ASSERT(m_anchorElement);
    426 
    427     reset();
    428 }
    429 
    430 void HTMLAnchorElement::PrefetchEventHandler::reset()
    431 {
    432     m_hadHREFChanged = false;
    433     m_mouseOverTimestamp = 0;
    434     m_mouseDownTimestamp = 0;
    435     m_hadTapUnconfirmed = false;
    436     m_tapDownTimestamp = 0;
    437     m_hasIssuedPreconnect = false;
    438 }
    439 
    440 void HTMLAnchorElement::PrefetchEventHandler::handleEvent(Event* event)
    441 {
    442     if (!shouldPrefetch(m_anchorElement->href()))
    443         return;
    444 
    445     if (event->type() == EventTypeNames::mouseover)
    446         handleMouseOver(event);
    447     else if (event->type() == EventTypeNames::mouseout)
    448         handleMouseOut(event);
    449     else if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton)
    450         handleLeftMouseDown(event);
    451     else if (event->type() == EventTypeNames::gestureshowpress)
    452         handleGestureShowPress(event);
    453     else if (event->type() == EventTypeNames::gesturetapunconfirmed)
    454         handleGestureTapUnconfirmed(event);
    455     else if (isLinkClick(event))
    456         handleClick(event);
    457 }
    458 
    459 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOver(Event* event)
    460 {
    461     if (m_mouseOverTimestamp == 0.0) {
    462         m_mouseOverTimestamp = event->timeStamp();
    463 
    464         blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseOvers", 0, 2);
    465 
    466         prefetch(blink::WebPreconnectMotivationLinkMouseOver);
    467     }
    468 }
    469 
    470 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOut(Event* event)
    471 {
    472     if (m_mouseOverTimestamp > 0.0) {
    473         double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp);
    474         blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_NoClick", mouseOverDuration * 1000, 0, 10000, 100);
    475 
    476         m_mouseOverTimestamp = 0.0;
    477     }
    478 }
    479 
    480 void HTMLAnchorElement::PrefetchEventHandler::handleLeftMouseDown(Event* event)
    481 {
    482     m_mouseDownTimestamp = event->timeStamp();
    483 
    484     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseDowns", 0, 2);
    485 
    486     prefetch(blink::WebPreconnectMotivationLinkMouseDown);
    487 }
    488 
    489 void HTMLAnchorElement::PrefetchEventHandler::handleGestureTapUnconfirmed(Event* event)
    490 {
    491     m_hadTapUnconfirmed = true;
    492 
    493     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.TapUnconfirmeds", 0, 2);
    494 
    495     prefetch(blink::WebPreconnectMotivationLinkTapUnconfirmed);
    496 }
    497 
    498 void HTMLAnchorElement::PrefetchEventHandler::handleGestureShowPress(Event* event)
    499 {
    500     m_tapDownTimestamp = event->timeStamp();
    501 
    502     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.TapDowns", 0, 2);
    503 
    504     prefetch(blink::WebPreconnectMotivationLinkTapDown);
    505 }
    506 
    507 void HTMLAnchorElement::PrefetchEventHandler::handleClick(Event* event)
    508 {
    509     bool capturedMouseOver = (m_mouseOverTimestamp > 0.0);
    510     if (capturedMouseOver) {
    511         double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp);
    512 
    513         blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_Click", mouseOverDuration * 1000, 0, 10000, 100);
    514     }
    515 
    516     bool capturedMouseDown = (m_mouseDownTimestamp > 0.0);
    517     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseDownFollowedByClick", capturedMouseDown, 2);
    518 
    519     if (capturedMouseDown) {
    520         double mouseDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseDownTimestamp);
    521 
    522         blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseDownDuration_Click", mouseDownDuration * 1000, 0, 10000, 100);
    523     }
    524 
    525     bool capturedTapDown = (m_tapDownTimestamp > 0.0);
    526     if (capturedTapDown) {
    527         double tapDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_tapDownTimestamp);
    528 
    529         blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.TapDownDuration_Click", tapDownDuration * 1000, 0, 10000, 100);
    530     }
    531 
    532     int flags = (m_hadTapUnconfirmed ? 2 : 0) | (capturedTapDown ? 1 : 0);
    533     blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.PreTapEventsFollowedByClick", flags, 4);
    534 }
    535 
    536 bool HTMLAnchorElement::PrefetchEventHandler::shouldPrefetch(const KURL& url)
    537 {
    538     if (m_hadHREFChanged)
    539         return false;
    540 
    541     if (m_anchorElement->hasEventListeners(EventTypeNames::click))
    542         return false;
    543 
    544     if (!url.protocolIsInHTTPFamily())
    545         return false;
    546 
    547     Document& document = m_anchorElement->document();
    548 
    549     if (!document.securityOrigin()->canDisplay(url))
    550         return false;
    551 
    552     if (url.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(document.url(), url))
    553         return false;
    554 
    555     LocalFrame* frame = document.frame();
    556     if (!frame)
    557         return false;
    558 
    559     // Links which create new window/tab are avoided because they may require user approval interaction.
    560     if (!m_anchorElement->target().isEmpty())
    561         return false;
    562 
    563     return true;
    564 }
    565 
    566 void HTMLAnchorElement::PrefetchEventHandler::prefetch(blink::WebPreconnectMotivation motivation)
    567 {
    568     const KURL& url = m_anchorElement->href();
    569 
    570     if (!shouldPrefetch(url))
    571         return;
    572 
    573     // The precision of current MouseOver trigger is too low to actually trigger preconnects.
    574     if (motivation == blink::WebPreconnectMotivationLinkMouseOver)
    575         return;
    576 
    577     preconnectToURL(url, motivation);
    578     m_hasIssuedPreconnect = true;
    579 }
    580 
    581 bool HTMLAnchorElement::isInteractiveContent() const
    582 {
    583     return isLink();
    584 }
    585 
    586 void HTMLAnchorElement::trace(Visitor* visitor)
    587 {
    588     visitor->trace(m_prefetchEventHandler);
    589     HTMLElement::trace(visitor);
    590 }
    591 
    592 }
    593