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 "bindings/core/v8/V8DOMActivityLogger.h"
     28 #include "core/dom/Attribute.h"
     29 #include "core/editing/FrameSelection.h"
     30 #include "core/events/KeyboardEvent.h"
     31 #include "core/events/MouseEvent.h"
     32 #include "core/frame/FrameHost.h"
     33 #include "core/frame/LocalFrame.h"
     34 #include "core/frame/Settings.h"
     35 #include "core/frame/UseCounter.h"
     36 #include "core/html/HTMLFormElement.h"
     37 #include "core/html/HTMLImageElement.h"
     38 #include "core/html/parser/HTMLParserIdioms.h"
     39 #include "core/loader/FrameLoadRequest.h"
     40 #include "core/loader/FrameLoader.h"
     41 #include "core/loader/FrameLoaderClient.h"
     42 #include "core/loader/FrameLoaderTypes.h"
     43 #include "core/loader/PingLoader.h"
     44 #include "core/page/Chrome.h"
     45 #include "core/page/ChromeClient.h"
     46 #include "core/rendering/RenderImage.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/WebURL.h"
     55 #include "public/platform/WebURLRequest.h"
     56 #include "wtf/text/StringBuilder.h"
     57 
     58 namespace blink {
     59 
     60 using namespace HTMLNames;
     61 
     62 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document& document)
     63     : HTMLElement(tagName, document)
     64     , m_linkRelations(0)
     65     , m_cachedVisitedLinkHash(0)
     66     , m_wasFocusedByMouse(false)
     67 {
     68 }
     69 
     70 PassRefPtrWillBeRawPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document& document)
     71 {
     72     return adoptRefWillBeNoop(new HTMLAnchorElement(aTag, document));
     73 }
     74 
     75 HTMLAnchorElement::~HTMLAnchorElement()
     76 {
     77 }
     78 
     79 bool HTMLAnchorElement::supportsFocus() const
     80 {
     81     if (hasEditableStyle())
     82         return HTMLElement::supportsFocus();
     83     // If not a link we should still be able to focus the element if it has tabIndex.
     84     return isLink() || HTMLElement::supportsFocus();
     85 }
     86 
     87 bool HTMLAnchorElement::shouldHaveFocusAppearance() const
     88 {
     89     return !m_wasFocusedByMouse || HTMLElement::supportsFocus();
     90 }
     91 
     92 void HTMLAnchorElement::dispatchFocusEvent(Element* oldFocusedElement, FocusType type)
     93 {
     94     if (type != FocusTypePage)
     95         m_wasFocusedByMouse = type == FocusTypeMouse;
     96     HTMLElement::dispatchFocusEvent(oldFocusedElement, type);
     97 }
     98 
     99 bool HTMLAnchorElement::isMouseFocusable() const
    100 {
    101     if (isLink())
    102         return supportsFocus();
    103 
    104     return HTMLElement::isMouseFocusable();
    105 }
    106 
    107 bool HTMLAnchorElement::isKeyboardFocusable() const
    108 {
    109     ASSERT(document().isActive());
    110 
    111     if (isFocusable() && Element::supportsFocus())
    112         return HTMLElement::isKeyboardFocusable();
    113 
    114     if (isLink() && !document().frameHost()->chrome().client().tabsToLinks())
    115         return false;
    116     return HTMLElement::isKeyboardFocusable();
    117 }
    118 
    119 static void appendServerMapMousePosition(StringBuilder& url, Event* event)
    120 {
    121     if (!event->isMouseEvent())
    122         return;
    123 
    124     ASSERT(event->target());
    125     Node* target = event->target()->toNode();
    126     ASSERT(target);
    127     if (!isHTMLImageElement(*target))
    128         return;
    129 
    130     HTMLImageElement& imageElement = toHTMLImageElement(*target);
    131     if (!imageElement.isServerMap())
    132         return;
    133 
    134     if (!imageElement.renderer() || !imageElement.renderer()->isRenderImage())
    135         return;
    136     RenderImage* renderer = toRenderImage(imageElement.renderer());
    137 
    138     // FIXME: This should probably pass true for useTransforms.
    139     FloatPoint absolutePosition = renderer->absoluteToLocal(FloatPoint(toMouseEvent(event)->pageX(), toMouseEvent(event)->pageY()));
    140     int x = absolutePosition.x();
    141     int y = absolutePosition.y();
    142     url.append('?');
    143     url.appendNumber(x);
    144     url.append(',');
    145     url.appendNumber(y);
    146 }
    147 
    148 void HTMLAnchorElement::defaultEventHandler(Event* event)
    149 {
    150     if (isLink()) {
    151         if (focused() && isEnterKeyKeydownEvent(event) && isLiveLink()) {
    152             event->setDefaultHandled();
    153             dispatchSimulatedClick(event);
    154             return;
    155         }
    156 
    157         if (isLinkClick(event) && isLiveLink()) {
    158             handleClick(event);
    159             return;
    160         }
    161     }
    162 
    163     HTMLElement::defaultEventHandler(event);
    164 }
    165 
    166 void HTMLAnchorElement::setActive(bool down)
    167 {
    168     if (hasEditableStyle())
    169         return;
    170 
    171     ContainerNode::setActive(down);
    172 }
    173 
    174 void HTMLAnchorElement::attributeWillChange(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& newValue)
    175 {
    176     if (name == hrefAttr && inDocument()) {
    177         V8DOMActivityLogger* activityLogger = V8DOMActivityLogger::currentActivityLoggerIfIsolatedWorld();
    178         if (activityLogger) {
    179             Vector<String> argv;
    180             argv.append("a");
    181             argv.append(hrefAttr.toString());
    182             argv.append(oldValue);
    183             argv.append(newValue);
    184             activityLogger->logEvent("blinkSetAttribute", argv.size(), argv.data());
    185         }
    186     }
    187     HTMLElement::attributeWillChange(name, oldValue, newValue);
    188 }
    189 
    190 void HTMLAnchorElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
    191 {
    192     if (name == hrefAttr) {
    193         bool wasLink = isLink();
    194         setIsLink(!value.isNull());
    195         if (wasLink || isLink()) {
    196             pseudoStateChanged(CSSSelector::PseudoLink);
    197             pseudoStateChanged(CSSSelector::PseudoVisited);
    198             if (wasLink != isLink())
    199                 pseudoStateChanged(CSSSelector::PseudoEnabled);
    200         }
    201         if (wasLink && !isLink() && treeScope().adjustedFocusedElement() == this) {
    202             // We might want to call blur(), but it's dangerous to dispatch
    203             // events here.
    204             document().setNeedsFocusedElementCheck();
    205         }
    206         if (isLink()) {
    207             String parsedURL = stripLeadingAndTrailingHTMLSpaces(value);
    208             if (document().isDNSPrefetchEnabled()) {
    209                 if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//"))
    210                     prefetchDNS(document().completeURL(parsedURL).host());
    211             }
    212         }
    213         invalidateCachedVisitedLinkHash();
    214     } else if (name == nameAttr || name == titleAttr) {
    215         // Do nothing.
    216     } else if (name == relAttr)
    217         setRel(value);
    218     else
    219         HTMLElement::parseAttribute(name, value);
    220 }
    221 
    222 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
    223 {
    224     dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
    225 }
    226 
    227 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
    228 {
    229     return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
    230 }
    231 
    232 bool HTMLAnchorElement::hasLegalLinkAttribute(const QualifiedName& name) const
    233 {
    234     return name == hrefAttr || HTMLElement::hasLegalLinkAttribute(name);
    235 }
    236 
    237 bool HTMLAnchorElement::canStartSelection() const
    238 {
    239     if (!isLink())
    240         return HTMLElement::canStartSelection();
    241     return hasEditableStyle();
    242 }
    243 
    244 bool HTMLAnchorElement::draggable() const
    245 {
    246     // Should be draggable if we have an href attribute.
    247     const AtomicString& value = getAttribute(draggableAttr);
    248     if (equalIgnoringCase(value, "true"))
    249         return true;
    250     if (equalIgnoringCase(value, "false"))
    251         return false;
    252     return hasAttribute(hrefAttr);
    253 }
    254 
    255 KURL HTMLAnchorElement::href() const
    256 {
    257     return document().completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
    258 }
    259 
    260 void HTMLAnchorElement::setHref(const AtomicString& value)
    261 {
    262     setAttribute(hrefAttr, value);
    263 }
    264 
    265 KURL HTMLAnchorElement::url() const
    266 {
    267     return href();
    268 }
    269 
    270 void HTMLAnchorElement::setURL(const KURL& url)
    271 {
    272     setHref(AtomicString(url.string()));
    273 }
    274 
    275 String HTMLAnchorElement::input() const
    276 {
    277     return getAttribute(hrefAttr);
    278 }
    279 
    280 void HTMLAnchorElement::setInput(const String& value)
    281 {
    282     setHref(AtomicString(value));
    283 }
    284 
    285 bool HTMLAnchorElement::hasRel(uint32_t relation) const
    286 {
    287     return m_linkRelations & relation;
    288 }
    289 
    290 void HTMLAnchorElement::setRel(const AtomicString& value)
    291 {
    292     m_linkRelations = 0;
    293     SpaceSplitString newLinkRelations(value, true);
    294     // FIXME: Add link relations as they are implemented
    295     if (newLinkRelations.contains("noreferrer"))
    296         m_linkRelations |= RelationNoReferrer;
    297 }
    298 
    299 const AtomicString& HTMLAnchorElement::name() const
    300 {
    301     return getNameAttribute();
    302 }
    303 
    304 short HTMLAnchorElement::tabIndex() const
    305 {
    306     // Skip the supportsFocus check in HTMLElement.
    307     return Element::tabIndex();
    308 }
    309 
    310 bool HTMLAnchorElement::isLiveLink() const
    311 {
    312     return isLink() && !hasEditableStyle();
    313 }
    314 
    315 void HTMLAnchorElement::sendPings(const KURL& destinationURL) const
    316 {
    317     const AtomicString& pingValue = getAttribute(pingAttr);
    318     if (pingValue.isNull() || !document().settings() || !document().settings()->hyperlinkAuditingEnabled())
    319         return;
    320 
    321     UseCounter::count(document(), UseCounter::HTMLAnchorElementPingAttribute);
    322 
    323     SpaceSplitString pingURLs(pingValue, false);
    324     for (unsigned i = 0; i < pingURLs.size(); i++)
    325         PingLoader::sendLinkAuditPing(document().frame(), document().completeURL(pingURLs[i]), destinationURL);
    326 }
    327 
    328 void HTMLAnchorElement::handleClick(Event* event)
    329 {
    330     event->setDefaultHandled();
    331 
    332     LocalFrame* frame = document().frame();
    333     if (!frame)
    334         return;
    335 
    336     StringBuilder url;
    337     url.append(stripLeadingAndTrailingHTMLSpaces(fastGetAttribute(hrefAttr)));
    338     appendServerMapMousePosition(url, event);
    339     KURL completedURL = document().completeURL(url.toString());
    340 
    341     // Schedule the ping before the frame load. Prerender in Chrome may kill the renderer as soon as the navigation is
    342     // sent out.
    343     sendPings(completedURL);
    344 
    345     ResourceRequest request(completedURL);
    346     if (hasAttribute(downloadAttr)) {
    347         request.setRequestContext(blink::WebURLRequest::RequestContextDownload);
    348         if (!hasRel(RelationNoReferrer)) {
    349             String referrer = SecurityPolicy::generateReferrerHeader(document().referrerPolicy(), completedURL, document().outgoingReferrer());
    350             if (!referrer.isEmpty())
    351                 request.setHTTPReferrer(Referrer(referrer, document().referrerPolicy()));
    352         }
    353 
    354         bool isSameOrigin = completedURL.protocolIsData() || document().securityOrigin()->canRequest(completedURL);
    355         const AtomicString& suggestedName = (isSameOrigin ? fastGetAttribute(downloadAttr) : nullAtom);
    356 
    357         frame->loader().client()->loadURLExternally(request, NavigationPolicyDownload, suggestedName);
    358     } else {
    359         request.setRequestContext(blink::WebURLRequest::RequestContextHyperlink);
    360         FrameLoadRequest frameRequest(&document(), request, getAttribute(targetAttr));
    361         frameRequest.setTriggeringEvent(event);
    362         if (hasRel(RelationNoReferrer))
    363             frameRequest.setShouldSendReferrer(NeverSendReferrer);
    364         frame->loader().load(frameRequest);
    365     }
    366 }
    367 
    368 bool isEnterKeyKeydownEvent(Event* event)
    369 {
    370     return event->type() == EventTypeNames::keydown && event->isKeyboardEvent() && toKeyboardEvent(event)->keyIdentifier() == "Enter";
    371 }
    372 
    373 bool isLinkClick(Event* event)
    374 {
    375     return event->type() == EventTypeNames::click && (!event->isMouseEvent() || toMouseEvent(event)->button() != RightButton);
    376 }
    377 
    378 bool HTMLAnchorElement::willRespondToMouseClickEvents()
    379 {
    380     return isLink() || HTMLElement::willRespondToMouseClickEvents();
    381 }
    382 
    383 bool HTMLAnchorElement::isInteractiveContent() const
    384 {
    385     return isLink();
    386 }
    387 
    388 Node::InsertionNotificationRequest HTMLAnchorElement::insertedInto(ContainerNode* insertionPoint)
    389 {
    390     if (insertionPoint->inDocument()) {
    391         V8DOMActivityLogger* activityLogger = V8DOMActivityLogger::currentActivityLoggerIfIsolatedWorld();
    392         if (activityLogger) {
    393             Vector<String> argv;
    394             argv.append("a");
    395             argv.append(fastGetAttribute(hrefAttr));
    396             activityLogger->logEvent("blinkAddElement", argv.size(), argv.data());
    397         }
    398     }
    399     return HTMLElement::insertedInto(insertionPoint);
    400 }
    401 
    402 }
    403