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 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 "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
40namespace WebCore {
41
42using namespace HTMLNames;
43
44HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document* document)
45    : HTMLElement(tagName, document, CreateElement)
46    , m_wasShiftKeyDownOnMouseDown(false)
47    , m_linkRelations(0)
48{
49}
50
51PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document* document)
52{
53    return adoptRef(new HTMLAnchorElement(aTag, document));
54}
55
56PassRefPtr<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.
62static 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
70bool 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
78bool 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
91bool 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
125void 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
238void 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
271void 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
298void HTMLAnchorElement::accessKeyAction(bool sendToAnyElement)
299{
300    // send the mouse button events if the caller specified sendToAnyElement
301    dispatchSimulatedClick(0, sendToAnyElement);
302}
303
304bool HTMLAnchorElement::isURLAttribute(Attribute *attr) const
305{
306    return attr->name() == hrefAttr;
307}
308
309bool 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
317bool 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
328KURL HTMLAnchorElement::href() const
329{
330    return document()->completeURL(deprecatedParseURL(getAttribute(hrefAttr)));
331}
332
333void HTMLAnchorElement::setHref(const AtomicString& value)
334{
335    setAttribute(hrefAttr, value);
336}
337
338bool HTMLAnchorElement::hasRel(uint32_t relation) const
339{
340    return m_linkRelations & relation;
341}
342
343void 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
352const AtomicString& HTMLAnchorElement::name() const
353{
354    return getAttribute(nameAttr);
355}
356
357short HTMLAnchorElement::tabIndex() const
358{
359    // Skip the supportsFocus check in HTMLElement.
360    return Element::tabIndex();
361}
362
363String HTMLAnchorElement::target() const
364{
365    return getAttribute(targetAttr);
366}
367
368String HTMLAnchorElement::hash() const
369{
370    String fragmentIdentifier = href().fragmentIdentifier();
371    return fragmentIdentifier.isEmpty() ? "" : "#" + fragmentIdentifier;
372}
373
374void 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
384String 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
394void 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
426String HTMLAnchorElement::hostname() const
427{
428    return href().host();
429}
430
431void 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
451String HTMLAnchorElement::pathname() const
452{
453    return href().path();
454}
455
456void 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
470String HTMLAnchorElement::port() const
471{
472    return String::number(href().port());
473}
474
475void 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
493String HTMLAnchorElement::protocol() const
494{
495    return href().protocol() + ":";
496}
497
498void HTMLAnchorElement::setProtocol(const String& value)
499{
500    KURL url = href();
501    url.setProtocol(value);
502    setHref(url.string());
503}
504
505String HTMLAnchorElement::search() const
506{
507    String query = href().query();
508    return query.isEmpty() ? "" : "?" + query;
509}
510
511void 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
521String HTMLAnchorElement::text() const
522{
523    return innerText();
524}
525
526String HTMLAnchorElement::toString() const
527{
528    return href().string();
529}
530
531bool 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