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