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 "HTMLAnchorElement.h"
     26 
     27 #include "Attribute.h"
     28 #include "EventNames.h"
     29 #include "Frame.h"
     30 #include "FrameLoaderTypes.h"
     31 #include "HTMLImageElement.h"
     32 #include "HTMLNames.h"
     33 #include "HTMLParserIdioms.h"
     34 #include "KeyboardEvent.h"
     35 #include "MouseEvent.h"
     36 #include "Page.h"
     37 #include "PingLoader.h"
     38 #include "RenderImage.h"
     39 #include "ResourceHandle.h"
     40 #include "Settings.h"
     41 #include "UserGestureIndicator.h"
     42 
     43 namespace WebCore {
     44 
     45 using namespace HTMLNames;
     46 
     47 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document* document)
     48     : HTMLElement(tagName, document)
     49     , m_wasShiftKeyDownOnMouseDown(false)
     50     , m_linkRelations(0)
     51 {
     52 }
     53 
     54 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document* document)
     55 {
     56     return adoptRef(new HTMLAnchorElement(aTag, document));
     57 }
     58 
     59 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(const QualifiedName& tagName, Document* document)
     60 {
     61     return adoptRef(new HTMLAnchorElement(tagName, document));
     62 }
     63 
     64 // This function does not allow leading spaces before the port number.
     65 static unsigned parsePortFromStringPosition(const String& value, unsigned portStart, unsigned& portEnd)
     66 {
     67     portEnd = portStart;
     68     while (isASCIIDigit(value[portEnd]))
     69         ++portEnd;
     70     return value.substring(portStart, portEnd - portStart).toUInt();
     71 }
     72 
     73 bool HTMLAnchorElement::supportsFocus() const
     74 {
     75     if (rendererIsEditable())
     76         return HTMLElement::supportsFocus();
     77     // If not a link we should still be able to focus the element if it has tabIndex.
     78     return isLink() || HTMLElement::supportsFocus();
     79 }
     80 
     81 bool HTMLAnchorElement::isMouseFocusable() const
     82 {
     83     // Anchor elements should be mouse focusable, https://bugs.webkit.org/show_bug.cgi?id=26856
     84 #if !PLATFORM(GTK) && !PLATFORM(QT) && !PLATFORM(EFL)
     85     if (isLink())
     86         // Only allow links with tabIndex or contentEditable to be mouse focusable.
     87         return HTMLElement::supportsFocus();
     88 #endif
     89 
     90     // Allow tab index etc to control focus.
     91     return HTMLElement::isMouseFocusable();
     92 }
     93 
     94 bool HTMLAnchorElement::isKeyboardFocusable(KeyboardEvent* event) const
     95 {
     96     if (!isLink())
     97         return HTMLElement::isKeyboardFocusable(event);
     98 
     99     if (!isFocusable())
    100         return false;
    101 
    102     if (!document()->frame())
    103         return false;
    104 
    105     if (!document()->frame()->eventHandler()->tabsToLinks(event))
    106         return false;
    107 
    108     return hasNonEmptyBoundingBox();
    109 }
    110 
    111 static void appendServerMapMousePosition(String& url, Event* event)
    112 {
    113     if (!event->isMouseEvent())
    114         return;
    115 
    116     ASSERT(event->target());
    117     Node* target = event->target()->toNode();
    118     ASSERT(target);
    119     if (!target->hasTagName(imgTag))
    120         return;
    121 
    122     HTMLImageElement* imageElement = static_cast<HTMLImageElement*>(event->target()->toNode());
    123     if (!imageElement || !imageElement->isServerMap())
    124         return;
    125 
    126     RenderImage* renderer = toRenderImage(imageElement->renderer());
    127     if (!renderer)
    128         return;
    129 
    130     // FIXME: This should probably pass true for useTransforms.
    131     FloatPoint absolutePosition = renderer->absoluteToLocal(FloatPoint(static_cast<MouseEvent*>(event)->pageX(), static_cast<MouseEvent*>(event)->pageY()));
    132     int x = absolutePosition.x();
    133     int y = absolutePosition.y();
    134     url += "?";
    135     url += String::number(x);
    136     url += ",";
    137     url += String::number(y);
    138 }
    139 
    140 void HTMLAnchorElement::defaultEventHandler(Event* event)
    141 {
    142     if (isLink()) {
    143         if (focused() && isEnterKeyKeydownEvent(event) && treatLinkAsLiveForEventType(NonMouseEvent)) {
    144             event->setDefaultHandled();
    145             dispatchSimulatedClick(event);
    146             return;
    147         }
    148 
    149         if (isLinkClick(event) && treatLinkAsLiveForEventType(eventType(event))) {
    150             String url = stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr));
    151             appendServerMapMousePosition(url, event);
    152             handleLinkClick(event, document(), url, getAttribute(targetAttr), hasRel(RelationNoReferrer));
    153             sendPings(document()->completeURL(url));
    154             return;
    155         }
    156 
    157         if (rendererIsEditable()) {
    158             // This keeps track of the editable block that the selection was in (if it was in one) just before the link was clicked
    159             // for the LiveWhenNotFocused editable link behavior
    160             if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() != RightButton && document()->frame() && document()->frame()->selection()) {
    161                 m_rootEditableElementForSelectionOnMouseDown = document()->frame()->selection()->rootEditableElement();
    162                 m_wasShiftKeyDownOnMouseDown = static_cast<MouseEvent*>(event)->shiftKey();
    163             } else if (event->type() == eventNames().mouseoverEvent) {
    164                 // These are cleared on mouseover and not mouseout because their values are needed for drag events,
    165                 // but drag events happen after mouse out events.
    166                 m_rootEditableElementForSelectionOnMouseDown = 0;
    167                 m_wasShiftKeyDownOnMouseDown = false;
    168             }
    169         }
    170     }
    171 
    172     HTMLElement::defaultEventHandler(event);
    173 }
    174 
    175 void HTMLAnchorElement::setActive(bool down, bool pause)
    176 {
    177     if (rendererIsEditable()) {
    178         EditableLinkBehavior editableLinkBehavior = EditableLinkDefaultBehavior;
    179         if (Settings* settings = document()->settings())
    180             editableLinkBehavior = settings->editableLinkBehavior();
    181 
    182         switch (editableLinkBehavior) {
    183             default:
    184             case EditableLinkDefaultBehavior:
    185             case EditableLinkAlwaysLive:
    186                 break;
    187 
    188             case EditableLinkNeverLive:
    189                 return;
    190 
    191             // Don't set the link to be active if the current selection is in the same editable block as
    192             // this link
    193             case EditableLinkLiveWhenNotFocused:
    194                 if (down && document()->frame() && document()->frame()->selection()->rootEditableElement() == rootEditableElement())
    195                     return;
    196                 break;
    197 
    198             case EditableLinkOnlyLiveWithShiftKey:
    199                 return;
    200         }
    201 
    202     }
    203 
    204     ContainerNode::setActive(down, pause);
    205 }
    206 
    207 void HTMLAnchorElement::parseMappedAttribute(Attribute* attr)
    208 {
    209     if (attr->name() == hrefAttr) {
    210         bool wasLink = isLink();
    211         setIsLink(!attr->isNull());
    212         if (wasLink != isLink())
    213             setNeedsStyleRecalc();
    214         if (isLink()) {
    215             String parsedURL = stripLeadingAndTrailingHTMLSpaces(attr->value());
    216             if (document()->isDNSPrefetchEnabled()) {
    217                 if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//"))
    218                     ResourceHandle::prepareForURL(document()->completeURL(parsedURL));
    219             }
    220             if (document()->page() && !document()->page()->javaScriptURLsAreAllowed() && protocolIsJavaScript(parsedURL)) {
    221                 clearIsLink();
    222                 attr->setValue(nullAtom);
    223             }
    224         }
    225     } else if (attr->name() == nameAttr ||
    226              attr->name() == titleAttr) {
    227         // Do nothing.
    228     } else if (attr->name() == relAttr)
    229         setRel(attr->value());
    230     else
    231         HTMLElement::parseMappedAttribute(attr);
    232 }
    233 
    234 void HTMLAnchorElement::accessKeyAction(bool sendToAnyElement)
    235 {
    236     // send the mouse button events if the caller specified sendToAnyElement
    237     dispatchSimulatedClick(0, sendToAnyElement);
    238 }
    239 
    240 bool HTMLAnchorElement::isURLAttribute(Attribute *attr) const
    241 {
    242     return attr->name() == hrefAttr;
    243 }
    244 
    245 bool HTMLAnchorElement::canStartSelection() const
    246 {
    247     // FIXME: We probably want this same behavior in SVGAElement too
    248     if (!isLink())
    249         return HTMLElement::canStartSelection();
    250     return rendererIsEditable();
    251 }
    252 
    253 bool HTMLAnchorElement::draggable() const
    254 {
    255     // Should be draggable if we have an href attribute.
    256     const AtomicString& value = getAttribute(draggableAttr);
    257     if (equalIgnoringCase(value, "true"))
    258         return true;
    259     if (equalIgnoringCase(value, "false"))
    260         return false;
    261     return hasAttribute(hrefAttr);
    262 }
    263 
    264 KURL HTMLAnchorElement::href() const
    265 {
    266     return document()->completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
    267 }
    268 
    269 void HTMLAnchorElement::setHref(const AtomicString& value)
    270 {
    271     setAttribute(hrefAttr, value);
    272 }
    273 
    274 bool HTMLAnchorElement::hasRel(uint32_t relation) const
    275 {
    276     return m_linkRelations & relation;
    277 }
    278 
    279 void HTMLAnchorElement::setRel(const String& value)
    280 {
    281     m_linkRelations = 0;
    282     SpaceSplitString newLinkRelations(value, true);
    283     // FIXME: Add link relations as they are implemented
    284     if (newLinkRelations.contains("noreferrer"))
    285         m_linkRelations |= RelationNoReferrer;
    286 }
    287 
    288 const AtomicString& HTMLAnchorElement::name() const
    289 {
    290     return getAttribute(nameAttr);
    291 }
    292 
    293 short HTMLAnchorElement::tabIndex() const
    294 {
    295     // Skip the supportsFocus check in HTMLElement.
    296     return Element::tabIndex();
    297 }
    298 
    299 String HTMLAnchorElement::target() const
    300 {
    301     return getAttribute(targetAttr);
    302 }
    303 
    304 String HTMLAnchorElement::hash() const
    305 {
    306     String fragmentIdentifier = href().fragmentIdentifier();
    307     return fragmentIdentifier.isEmpty() ? "" : "#" + fragmentIdentifier;
    308 }
    309 
    310 void HTMLAnchorElement::setHash(const String& value)
    311 {
    312     KURL url = href();
    313     if (value[0] == '#')
    314         url.setFragmentIdentifier(value.substring(1));
    315     else
    316         url.setFragmentIdentifier(value);
    317     setHref(url.string());
    318 }
    319 
    320 String HTMLAnchorElement::host() const
    321 {
    322     const KURL& url = href();
    323     if (url.hostEnd() == url.pathStart())
    324         return url.host();
    325     if (isDefaultPortForProtocol(url.port(), url.protocol()))
    326         return url.host();
    327     return url.host() + ":" + String::number(url.port());
    328 }
    329 
    330 void HTMLAnchorElement::setHost(const String& value)
    331 {
    332     if (value.isEmpty())
    333         return;
    334     KURL url = href();
    335     if (!url.canSetHostOrPort())
    336         return;
    337 
    338     size_t separator = value.find(':');
    339     if (!separator)
    340         return;
    341 
    342     if (separator == notFound)
    343         url.setHostAndPort(value);
    344     else {
    345         unsigned portEnd;
    346         unsigned port = parsePortFromStringPosition(value, separator + 1, portEnd);
    347         if (!port) {
    348             // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
    349             // specifically goes against RFC 3986 (p3.2) and
    350             // requires setting the port to "0" if it is set to empty string.
    351             url.setHostAndPort(value.substring(0, separator + 1) + "0");
    352         } else {
    353             if (isDefaultPortForProtocol(port, url.protocol()))
    354                 url.setHostAndPort(value.substring(0, separator));
    355             else
    356                 url.setHostAndPort(value.substring(0, portEnd));
    357         }
    358     }
    359     setHref(url.string());
    360 }
    361 
    362 String HTMLAnchorElement::hostname() const
    363 {
    364     return href().host();
    365 }
    366 
    367 void HTMLAnchorElement::setHostname(const String& value)
    368 {
    369     // Before setting new value:
    370     // Remove all leading U+002F SOLIDUS ("/") characters.
    371     unsigned i = 0;
    372     unsigned hostLength = value.length();
    373     while (value[i] == '/')
    374         i++;
    375 
    376     if (i == hostLength)
    377         return;
    378 
    379     KURL url = href();
    380     if (!url.canSetHostOrPort())
    381         return;
    382 
    383     url.setHost(value.substring(i));
    384     setHref(url.string());
    385 }
    386 
    387 String HTMLAnchorElement::pathname() const
    388 {
    389     return href().path();
    390 }
    391 
    392 void HTMLAnchorElement::setPathname(const String& value)
    393 {
    394     KURL url = href();
    395     if (!url.canSetPathname())
    396         return;
    397 
    398     if (value[0] == '/')
    399         url.setPath(value);
    400     else
    401         url.setPath("/" + value);
    402 
    403     setHref(url.string());
    404 }
    405 
    406 String HTMLAnchorElement::port() const
    407 {
    408     if (href().hasPort())
    409         return String::number(href().port());
    410 
    411     return "";
    412 }
    413 
    414 void HTMLAnchorElement::setPort(const String& value)
    415 {
    416     KURL url = href();
    417     if (!url.canSetHostOrPort())
    418         return;
    419 
    420     // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
    421     // specifically goes against RFC 3986 (p3.2) and
    422     // requires setting the port to "0" if it is set to empty string.
    423     unsigned port = value.toUInt();
    424     if (isDefaultPortForProtocol(port, url.protocol()))
    425         url.removePort();
    426     else
    427         url.setPort(port);
    428 
    429     setHref(url.string());
    430 }
    431 
    432 String HTMLAnchorElement::protocol() const
    433 {
    434     return href().protocol() + ":";
    435 }
    436 
    437 void HTMLAnchorElement::setProtocol(const String& value)
    438 {
    439     KURL url = href();
    440     url.setProtocol(value);
    441     setHref(url.string());
    442 }
    443 
    444 String HTMLAnchorElement::search() const
    445 {
    446     String query = href().query();
    447     return query.isEmpty() ? "" : "?" + query;
    448 }
    449 
    450 String HTMLAnchorElement::origin() const
    451 {
    452     RefPtr<SecurityOrigin> origin = SecurityOrigin::create(href());
    453     return origin->toString();
    454 }
    455 
    456 String HTMLAnchorElement::getParameter(const String& name) const
    457 {
    458     ParsedURLParameters parameters;
    459     href().copyParsedQueryTo(parameters);
    460     return parameters.get(name);
    461 }
    462 
    463 void HTMLAnchorElement::setSearch(const String& value)
    464 {
    465     KURL url = href();
    466     String newSearch = (value[0] == '?') ? value.substring(1) : value;
    467     // Make sure that '#' in the query does not leak to the hash.
    468     url.setQuery(newSearch.replace('#', "%23"));
    469 
    470     setHref(url.string());
    471 }
    472 
    473 String HTMLAnchorElement::text() const
    474 {
    475     return innerText();
    476 }
    477 
    478 String HTMLAnchorElement::toString() const
    479 {
    480     return href().string();
    481 }
    482 
    483 bool HTMLAnchorElement::isLiveLink() const
    484 {
    485     return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey);
    486 }
    487 
    488 void HTMLAnchorElement::sendPings(const KURL& destinationURL)
    489 {
    490     if (!hasAttribute(pingAttr) || !document()->settings()->hyperlinkAuditingEnabled())
    491         return;
    492 
    493     SpaceSplitString pingURLs(getAttribute(pingAttr), true);
    494     for (unsigned i = 0; i < pingURLs.size(); i++)
    495         PingLoader::sendPing(document()->frame(), document()->completeURL(pingURLs[i]), destinationURL);
    496 }
    497 
    498 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event)
    499 {
    500     if (!event->isMouseEvent())
    501         return NonMouseEvent;
    502     return static_cast<MouseEvent*>(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
    503 }
    504 
    505 bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const
    506 {
    507     if (!rendererIsEditable())
    508         return true;
    509 
    510     Settings* settings = document()->settings();
    511     if (!settings)
    512         return true;
    513 
    514     switch (settings->editableLinkBehavior()) {
    515     case EditableLinkDefaultBehavior:
    516     case EditableLinkAlwaysLive:
    517         return true;
    518 
    519     case EditableLinkNeverLive:
    520         return false;
    521 
    522     // If the selection prior to clicking on this link resided in the same editable block as this link,
    523     // and the shift key isn't pressed, we don't want to follow the link.
    524     case EditableLinkLiveWhenNotFocused:
    525         return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && m_rootEditableElementForSelectionOnMouseDown != rootEditableElement());
    526 
    527     case EditableLinkOnlyLiveWithShiftKey:
    528         return eventType == MouseEventWithShiftKey;
    529     }
    530 
    531     ASSERT_NOT_REACHED();
    532     return false;
    533 }
    534 
    535 bool isEnterKeyKeydownEvent(Event* event)
    536 {
    537 #if OS(ANDROID)
    538     return event->type() == eventNames().keyupEvent && event->isKeyboardEvent() && static_cast<KeyboardEvent*>(event)->keyIdentifier() == "Enter";
    539 #else
    540     return event->type() == eventNames().keydownEvent && event->isKeyboardEvent() && static_cast<KeyboardEvent*>(event)->keyIdentifier() == "Enter";
    541 #endif
    542 }
    543 
    544 bool isMiddleMouseButtonEvent(Event* event)
    545 {
    546     return event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == MiddleButton;
    547 }
    548 
    549 bool isLinkClick(Event* event)
    550 {
    551     return event->type() == eventNames().clickEvent && (!event->isMouseEvent() || static_cast<MouseEvent*>(event)->button() != RightButton);
    552 }
    553 
    554 void handleLinkClick(Event* event, Document* document, const String& url, const String& target, bool hideReferrer)
    555 {
    556     event->setDefaultHandled();
    557 
    558     Frame* frame = document->frame();
    559     if (!frame)
    560         return;
    561     frame->loader()->urlSelected(document->completeURL(url), target, event, false, false, hideReferrer ? NoReferrer : SendReferrer);
    562 }
    563 
    564 }
    565