HTMLAnchorElement.cpp revision ceeeab6161d7d8bf970ecf98affa4f4966df6f01
1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4 *           (C) 2000 Simon Hausmann <hausmann@kde.org>
5 * Copyright (C) 2003, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
6 *           (C) 2006 Graham Dennis (graham.dennis@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
43namespace WebCore {
44
45using namespace HTMLNames;
46
47HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document* document)
48    : HTMLElement(tagName, document)
49    , m_wasShiftKeyDownOnMouseDown(false)
50    , m_linkRelations(0)
51{
52}
53
54PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document* document)
55{
56    return adoptRef(new HTMLAnchorElement(aTag, document));
57}
58
59PassRefPtr<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.
65static 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
73bool 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
81bool 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
94bool 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
111static 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
140void 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
175void 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
207void 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
234void HTMLAnchorElement::accessKeyAction(bool sendToAnyElement)
235{
236    // send the mouse button events if the caller specified sendToAnyElement
237    dispatchSimulatedClick(0, sendToAnyElement);
238}
239
240bool HTMLAnchorElement::isURLAttribute(Attribute *attr) const
241{
242    return attr->name() == hrefAttr;
243}
244
245bool 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
253bool 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
264KURL HTMLAnchorElement::href() const
265{
266    return document()->completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
267}
268
269void HTMLAnchorElement::setHref(const AtomicString& value)
270{
271    setAttribute(hrefAttr, value);
272}
273
274bool HTMLAnchorElement::hasRel(uint32_t relation) const
275{
276    return m_linkRelations & relation;
277}
278
279void 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
288const AtomicString& HTMLAnchorElement::name() const
289{
290    return getAttribute(nameAttr);
291}
292
293short HTMLAnchorElement::tabIndex() const
294{
295    // Skip the supportsFocus check in HTMLElement.
296    return Element::tabIndex();
297}
298
299String HTMLAnchorElement::target() const
300{
301    return getAttribute(targetAttr);
302}
303
304String HTMLAnchorElement::hash() const
305{
306    String fragmentIdentifier = href().fragmentIdentifier();
307    return fragmentIdentifier.isEmpty() ? "" : "#" + fragmentIdentifier;
308}
309
310void 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
320String 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
330void 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
362String HTMLAnchorElement::hostname() const
363{
364    return href().host();
365}
366
367void 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
387String HTMLAnchorElement::pathname() const
388{
389    return href().path();
390}
391
392void 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
406String HTMLAnchorElement::port() const
407{
408    return String::number(href().port());
409}
410
411void HTMLAnchorElement::setPort(const String& value)
412{
413    KURL url = href();
414    if (!url.canSetHostOrPort())
415        return;
416
417    // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
418    // specifically goes against RFC 3986 (p3.2) and
419    // requires setting the port to "0" if it is set to empty string.
420    unsigned port = value.toUInt();
421    if (isDefaultPortForProtocol(port, url.protocol()))
422        url.removePort();
423    else
424        url.setPort(port);
425
426    setHref(url.string());
427}
428
429String HTMLAnchorElement::protocol() const
430{
431    return href().protocol() + ":";
432}
433
434void HTMLAnchorElement::setProtocol(const String& value)
435{
436    KURL url = href();
437    url.setProtocol(value);
438    setHref(url.string());
439}
440
441String HTMLAnchorElement::search() const
442{
443    String query = href().query();
444    return query.isEmpty() ? "" : "?" + query;
445}
446
447String HTMLAnchorElement::origin() const
448{
449    RefPtr<SecurityOrigin> origin = SecurityOrigin::create(href());
450    return origin->toString();
451}
452
453String HTMLAnchorElement::getParameter(const String& name) const
454{
455    ParsedURLParameters parameters;
456    href().copyParsedQueryTo(parameters);
457    return parameters.get(name);
458}
459
460void HTMLAnchorElement::setSearch(const String& value)
461{
462    KURL url = href();
463    String newSearch = (value[0] == '?') ? value.substring(1) : value;
464    // Make sure that '#' in the query does not leak to the hash.
465    url.setQuery(newSearch.replace('#', "%23"));
466
467    setHref(url.string());
468}
469
470String HTMLAnchorElement::text() const
471{
472    return innerText();
473}
474
475String HTMLAnchorElement::toString() const
476{
477    return href().string();
478}
479
480bool HTMLAnchorElement::isLiveLink() const
481{
482    return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey);
483}
484
485void HTMLAnchorElement::sendPings(const KURL& destinationURL)
486{
487    if (!hasAttribute(pingAttr) || !document()->settings()->hyperlinkAuditingEnabled())
488        return;
489
490    SpaceSplitString pingURLs(getAttribute(pingAttr), true);
491    for (unsigned i = 0; i < pingURLs.size(); i++)
492        PingLoader::sendPing(document()->frame(), document()->completeURL(pingURLs[i]), destinationURL);
493}
494
495HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event)
496{
497    if (!event->isMouseEvent())
498        return NonMouseEvent;
499    return static_cast<MouseEvent*>(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
500}
501
502bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const
503{
504    if (!rendererIsEditable())
505        return true;
506
507    Settings* settings = document()->settings();
508    if (!settings)
509        return true;
510
511    switch (settings->editableLinkBehavior()) {
512    case EditableLinkDefaultBehavior:
513    case EditableLinkAlwaysLive:
514        return true;
515
516    case EditableLinkNeverLive:
517        return false;
518
519    // If the selection prior to clicking on this link resided in the same editable block as this link,
520    // and the shift key isn't pressed, we don't want to follow the link.
521    case EditableLinkLiveWhenNotFocused:
522        return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && m_rootEditableElementForSelectionOnMouseDown != rootEditableElement());
523
524    case EditableLinkOnlyLiveWithShiftKey:
525        return eventType == MouseEventWithShiftKey;
526    }
527
528    ASSERT_NOT_REACHED();
529    return false;
530}
531
532bool isEnterKeyKeydownEvent(Event* event)
533{
534#if OS(ANDROID)
535    return event->type() == eventNames().keyupEvent && event->isKeyboardEvent() && static_cast<KeyboardEvent*>(event)->keyIdentifier() == "Enter";
536#else
537    return event->type() == eventNames().keydownEvent && event->isKeyboardEvent() && static_cast<KeyboardEvent*>(event)->keyIdentifier() == "Enter";
538#endif
539}
540
541bool isMiddleMouseButtonEvent(Event* event)
542{
543    return event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == MiddleButton;
544}
545
546bool isLinkClick(Event* event)
547{
548    return event->type() == eventNames().clickEvent && (!event->isMouseEvent() || static_cast<MouseEvent*>(event)->button() != RightButton);
549}
550
551void handleLinkClick(Event* event, Document* document, const String& url, const String& target, bool hideReferrer)
552{
553    event->setDefaultHandled();
554
555    Frame* frame = document->frame();
556    if (!frame)
557        return;
558    frame->loader()->urlSelected(document->completeURL(url), target, event, false, false, hideReferrer ? NoReferrer : SendReferrer);
559}
560
561}
562