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 "DNS.h"
29#include "EventHandler.h"
30#include "EventNames.h"
31#include "Frame.h"
32#include "FrameLoader.h"
33#include "FrameLoaderClient.h"
34#include "FrameLoaderTypes.h"
35#include "FrameSelection.h"
36#include "HTMLImageElement.h"
37#include "HTMLNames.h"
38#include "HTMLParserIdioms.h"
39#include "KeyboardEvent.h"
40#include "MouseEvent.h"
41#include "PingLoader.h"
42#include "PlatformMouseEvent.h"
43#include "RenderImage.h"
44#include "ResourceRequest.h"
45#include "SecurityOrigin.h"
46#include "SecurityPolicy.h"
47#include "Settings.h"
48#include <wtf/text/StringBuilder.h>
49
50namespace WebCore {
51
52using namespace HTMLNames;
53
54HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document* document)
55    : HTMLElement(tagName, document)
56    , m_hasRootEditableElementForSelectionOnMouseDown(false)
57    , m_wasShiftKeyDownOnMouseDown(false)
58    , m_linkRelations(0)
59    , m_cachedVisitedLinkHash(0)
60{
61}
62
63PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document* document)
64{
65    return adoptRef(new HTMLAnchorElement(aTag, document));
66}
67
68PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(const QualifiedName& tagName, Document* document)
69{
70    return adoptRef(new HTMLAnchorElement(tagName, document));
71}
72
73HTMLAnchorElement::~HTMLAnchorElement()
74{
75    clearRootEditableElementForSelectionOnMouseDown();
76}
77
78// This function does not allow leading spaces before the port number.
79static unsigned parsePortFromStringPosition(const String& value, unsigned portStart, unsigned& portEnd)
80{
81    portEnd = portStart;
82    while (isASCIIDigit(value[portEnd]))
83        ++portEnd;
84    return value.substring(portStart, portEnd - portStart).toUInt();
85}
86
87bool HTMLAnchorElement::supportsFocus() const
88{
89    if (rendererIsEditable())
90        return HTMLElement::supportsFocus();
91    // If not a link we should still be able to focus the element if it has tabIndex.
92    return isLink() || HTMLElement::supportsFocus();
93}
94
95bool HTMLAnchorElement::isMouseFocusable() const
96{
97#if !(PLATFORM(EFL) || PLATFORM(GTK) || PLATFORM(QT))
98    // Only allow links with tabIndex or contentEditable to be mouse focusable.
99    // This is our rule for the Mac platform; on many other platforms we focus any link you click on.
100    if (isLink())
101        return HTMLElement::supportsFocus();
102#endif
103
104    return HTMLElement::isMouseFocusable();
105}
106
107static bool hasNonEmptyBox(RenderBoxModelObject* renderer)
108{
109    if (!renderer)
110        return false;
111
112    // Before calling absoluteRects, check for the common case where borderBoundingBox
113    // is non-empty, since this is a faster check and almost always returns true.
114    // FIXME: Why do we need to call absoluteRects at all?
115    if (!renderer->borderBoundingBox().isEmpty())
116        return true;
117
118    // FIXME: Since all we are checking is whether the rects are empty, could we just
119    // pass in 0,0 for the layout point instead of calling localToAbsolute?
120    Vector<IntRect> rects;
121    renderer->absoluteRects(rects, flooredLayoutPoint(renderer->localToAbsolute()));
122    size_t size = rects.size();
123    for (size_t i = 0; i < size; ++i) {
124        if (!rects[i].isEmpty())
125            return true;
126    }
127
128    return false;
129}
130
131bool HTMLAnchorElement::isKeyboardFocusable(KeyboardEvent* event) const
132{
133    if (!isLink())
134        return HTMLElement::isKeyboardFocusable(event);
135
136    if (!isFocusable())
137        return false;
138
139    if (!document()->frame())
140        return false;
141
142    if (!document()->frame()->eventHandler()->tabsToLinks(event))
143        return false;
144
145    if (isInCanvasSubtree())
146        return true;
147
148    return hasNonEmptyBox(renderBoxModelObject());
149}
150
151static void appendServerMapMousePosition(StringBuilder& url, Event* event)
152{
153    if (!event->isMouseEvent())
154        return;
155
156    ASSERT(event->target());
157    Node* target = event->target()->toNode();
158    ASSERT(target);
159    if (!target->hasTagName(imgTag))
160        return;
161
162    HTMLImageElement* imageElement = static_cast<HTMLImageElement*>(event->target()->toNode());
163    if (!imageElement || !imageElement->isServerMap())
164        return;
165
166    if (!imageElement->renderer() || !imageElement->renderer()->isRenderImage())
167        return;
168    RenderImage* renderer = toRenderImage(imageElement->renderer());
169
170    // FIXME: This should probably pass true for useTransforms.
171    FloatPoint absolutePosition = renderer->absoluteToLocal(FloatPoint(static_cast<MouseEvent*>(event)->pageX(), static_cast<MouseEvent*>(event)->pageY()));
172    int x = absolutePosition.x();
173    int y = absolutePosition.y();
174    url.append('?');
175    url.appendNumber(x);
176    url.append(',');
177    url.appendNumber(y);
178}
179
180void HTMLAnchorElement::defaultEventHandler(Event* event)
181{
182    if (isLink()) {
183        if (focused() && isEnterKeyKeydownEvent(event) && treatLinkAsLiveForEventType(NonMouseEvent)) {
184            event->setDefaultHandled();
185            dispatchSimulatedClick(event);
186            return;
187        }
188
189        if (isLinkClick(event) && treatLinkAsLiveForEventType(eventType(event))) {
190            handleClick(event);
191            return;
192        }
193
194        if (rendererIsEditable()) {
195            // This keeps track of the editable block that the selection was in (if it was in one) just before the link was clicked
196            // for the LiveWhenNotFocused editable link behavior
197            if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() != RightButton && document()->frame() && document()->frame()->selection()) {
198                setRootEditableElementForSelectionOnMouseDown(document()->frame()->selection()->rootEditableElement());
199                m_wasShiftKeyDownOnMouseDown = static_cast<MouseEvent*>(event)->shiftKey();
200            } else if (event->type() == eventNames().mouseoverEvent) {
201                // These are cleared on mouseover and not mouseout because their values are needed for drag events,
202                // but drag events happen after mouse out events.
203                clearRootEditableElementForSelectionOnMouseDown();
204                m_wasShiftKeyDownOnMouseDown = false;
205            }
206        }
207    }
208
209    HTMLElement::defaultEventHandler(event);
210}
211
212void HTMLAnchorElement::setActive(bool down, bool pause)
213{
214    if (rendererIsEditable()) {
215        EditableLinkBehavior editableLinkBehavior = EditableLinkDefaultBehavior;
216        if (Settings* settings = document()->settings())
217            editableLinkBehavior = settings->editableLinkBehavior();
218
219        switch (editableLinkBehavior) {
220            default:
221            case EditableLinkDefaultBehavior:
222            case EditableLinkAlwaysLive:
223                break;
224
225            case EditableLinkNeverLive:
226                return;
227
228            // Don't set the link to be active if the current selection is in the same editable block as
229            // this link
230            case EditableLinkLiveWhenNotFocused:
231                if (down && document()->frame() && document()->frame()->selection()->rootEditableElement() == rootEditableElement())
232                    return;
233                break;
234
235            case EditableLinkOnlyLiveWithShiftKey:
236                return;
237        }
238
239    }
240
241    HTMLElement::setActive(down, pause);
242}
243
244void HTMLAnchorElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
245{
246    if (name == hrefAttr) {
247        bool wasLink = isLink();
248        setIsLink(!value.isNull());
249        if (wasLink != isLink())
250            didAffectSelector(AffectedSelectorLink | AffectedSelectorVisited | AffectedSelectorEnabled);
251        if (isLink()) {
252            String parsedURL = stripLeadingAndTrailingHTMLSpaces(value);
253            if (document()->isDNSPrefetchEnabled()) {
254                if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//"))
255                    prefetchDNS(document()->completeURL(parsedURL).host());
256            }
257        }
258        invalidateCachedVisitedLinkHash();
259    } else if (name == nameAttr || name == titleAttr) {
260        // Do nothing.
261    } else if (name == relAttr)
262        setRel(value);
263    else
264        HTMLElement::parseAttribute(name, value);
265}
266
267void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
268{
269    dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
270}
271
272bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
273{
274    return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
275}
276
277bool HTMLAnchorElement::canStartSelection() const
278{
279    // FIXME: We probably want this same behavior in SVGAElement too
280    if (!isLink())
281        return HTMLElement::canStartSelection();
282    return rendererIsEditable();
283}
284
285bool HTMLAnchorElement::draggable() const
286{
287    // Should be draggable if we have an href attribute.
288    const AtomicString& value = getAttribute(draggableAttr);
289    if (equalIgnoringCase(value, "true"))
290        return true;
291    if (equalIgnoringCase(value, "false"))
292        return false;
293    return hasAttribute(hrefAttr);
294}
295
296KURL HTMLAnchorElement::href() const
297{
298    return document()->completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
299}
300
301void HTMLAnchorElement::setHref(const AtomicString& value)
302{
303    setAttribute(hrefAttr, value);
304}
305
306bool HTMLAnchorElement::hasRel(uint32_t relation) const
307{
308    return m_linkRelations & relation;
309}
310
311void HTMLAnchorElement::setRel(const String& value)
312{
313    m_linkRelations = 0;
314    SpaceSplitString newLinkRelations(value, true);
315    // FIXME: Add link relations as they are implemented
316    if (newLinkRelations.contains("noreferrer"))
317        m_linkRelations |= RelationNoReferrer;
318}
319
320const AtomicString& HTMLAnchorElement::name() const
321{
322    return getNameAttribute();
323}
324
325short HTMLAnchorElement::tabIndex() const
326{
327    // Skip the supportsFocus check in HTMLElement.
328    return Element::tabIndex();
329}
330
331String HTMLAnchorElement::target() const
332{
333    return getAttribute(targetAttr);
334}
335
336String HTMLAnchorElement::hash() const
337{
338    String fragmentIdentifier = href().fragmentIdentifier();
339    if (fragmentIdentifier.isEmpty())
340        return emptyString();
341    return AtomicString(String("#" + fragmentIdentifier));
342}
343
344void HTMLAnchorElement::setHash(const String& value)
345{
346    KURL url = href();
347    if (value[0] == '#')
348        url.setFragmentIdentifier(value.substring(1));
349    else
350        url.setFragmentIdentifier(value);
351    setHref(url.string());
352}
353
354String HTMLAnchorElement::host() const
355{
356    const KURL& url = href();
357    if (url.hostEnd() == url.pathStart())
358        return url.host();
359    if (isDefaultPortForProtocol(url.port(), url.protocol()))
360        return url.host();
361    return url.host() + ":" + String::number(url.port());
362}
363
364void HTMLAnchorElement::setHost(const String& value)
365{
366    if (value.isEmpty())
367        return;
368    KURL url = href();
369    if (!url.canSetHostOrPort())
370        return;
371
372    size_t separator = value.find(':');
373    if (!separator)
374        return;
375
376    if (separator == notFound)
377        url.setHostAndPort(value);
378    else {
379        unsigned portEnd;
380        unsigned port = parsePortFromStringPosition(value, separator + 1, portEnd);
381        if (!port) {
382            // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
383            // specifically goes against RFC 3986 (p3.2) and
384            // requires setting the port to "0" if it is set to empty string.
385            url.setHostAndPort(value.substring(0, separator + 1) + "0");
386        } else {
387            if (isDefaultPortForProtocol(port, url.protocol()))
388                url.setHostAndPort(value.substring(0, separator));
389            else
390                url.setHostAndPort(value.substring(0, portEnd));
391        }
392    }
393    setHref(url.string());
394}
395
396String HTMLAnchorElement::hostname() const
397{
398    return href().host();
399}
400
401void HTMLAnchorElement::setHostname(const String& value)
402{
403    // Before setting new value:
404    // Remove all leading U+002F SOLIDUS ("/") characters.
405    unsigned i = 0;
406    unsigned hostLength = value.length();
407    while (value[i] == '/')
408        i++;
409
410    if (i == hostLength)
411        return;
412
413    KURL url = href();
414    if (!url.canSetHostOrPort())
415        return;
416
417    url.setHost(value.substring(i));
418    setHref(url.string());
419}
420
421String HTMLAnchorElement::pathname() const
422{
423    return href().path();
424}
425
426void HTMLAnchorElement::setPathname(const String& value)
427{
428    KURL url = href();
429    if (!url.canSetPathname())
430        return;
431
432    if (value[0] == '/')
433        url.setPath(value);
434    else
435        url.setPath("/" + value);
436
437    setHref(url.string());
438}
439
440String HTMLAnchorElement::port() const
441{
442    if (href().hasPort())
443        return String::number(href().port());
444
445    return emptyString();
446}
447
448void HTMLAnchorElement::setPort(const String& value)
449{
450    KURL url = href();
451    if (!url.canSetHostOrPort())
452        return;
453
454    // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
455    // specifically goes against RFC 3986 (p3.2) and
456    // requires setting the port to "0" if it is set to empty string.
457    unsigned port = value.toUInt();
458    if (isDefaultPortForProtocol(port, url.protocol()))
459        url.removePort();
460    else
461        url.setPort(port);
462
463    setHref(url.string());
464}
465
466String HTMLAnchorElement::protocol() const
467{
468    return href().protocol() + ":";
469}
470
471void HTMLAnchorElement::setProtocol(const String& value)
472{
473    KURL url = href();
474    url.setProtocol(value);
475    setHref(url.string());
476}
477
478String HTMLAnchorElement::search() const
479{
480    String query = href().query();
481    return query.isEmpty() ? emptyString() : "?" + query;
482}
483
484String HTMLAnchorElement::origin() const
485{
486    RefPtr<SecurityOrigin> origin = SecurityOrigin::create(href());
487    return origin->toString();
488}
489
490void HTMLAnchorElement::setSearch(const String& value)
491{
492    KURL url = href();
493    String newSearch = (value[0] == '?') ? value.substring(1) : value;
494    // Make sure that '#' in the query does not leak to the hash.
495    url.setQuery(newSearch.replaceWithLiteral('#', "%23"));
496
497    setHref(url.string());
498}
499
500String HTMLAnchorElement::text()
501{
502    return innerText();
503}
504
505String HTMLAnchorElement::toString() const
506{
507    return href().string();
508}
509
510bool HTMLAnchorElement::isLiveLink() const
511{
512    return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey);
513}
514
515void HTMLAnchorElement::sendPings(const KURL& destinationURL)
516{
517    if (!hasAttribute(pingAttr) || !document()->settings() || !document()->settings()->hyperlinkAuditingEnabled())
518        return;
519
520    SpaceSplitString pingURLs(getAttribute(pingAttr), false);
521    for (unsigned i = 0; i < pingURLs.size(); i++)
522        PingLoader::sendPing(document()->frame(), document()->completeURL(pingURLs[i]), destinationURL);
523}
524
525void HTMLAnchorElement::handleClick(Event* event)
526{
527    event->setDefaultHandled();
528
529    Frame* frame = document()->frame();
530    if (!frame)
531        return;
532
533    StringBuilder url;
534    url.append(stripLeadingAndTrailingHTMLSpaces(fastGetAttribute(hrefAttr)));
535    appendServerMapMousePosition(url, event);
536    KURL kurl = document()->completeURL(url.toString());
537
538#if ENABLE(DOWNLOAD_ATTRIBUTE)
539    if (hasAttribute(downloadAttr)) {
540        ResourceRequest request(kurl);
541
542        // FIXME: Why are we not calling addExtraFieldsToMainResourceRequest() if this check fails? It sets many important header fields.
543        if (!hasRel(RelationNoReferrer)) {
544            String referrer = SecurityPolicy::generateReferrerHeader(document()->referrerPolicy(), kurl, frame->loader()->outgoingReferrer());
545            if (!referrer.isEmpty())
546                request.setHTTPReferrer(referrer);
547            frame->loader()->addExtraFieldsToMainResourceRequest(request);
548        }
549
550        frame->loader()->client()->startDownload(request, fastGetAttribute(downloadAttr));
551    } else
552#endif
553        frame->loader()->urlSelected(kurl, target(), event, false, false, hasRel(RelationNoReferrer) ? NeverSendReferrer : MaybeSendReferrer);
554
555    sendPings(kurl);
556}
557
558HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event)
559{
560    if (!event->isMouseEvent())
561        return NonMouseEvent;
562    return static_cast<MouseEvent*>(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
563}
564
565bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const
566{
567    if (!rendererIsEditable())
568        return true;
569
570    Settings* settings = document()->settings();
571    if (!settings)
572        return true;
573
574    switch (settings->editableLinkBehavior()) {
575    case EditableLinkDefaultBehavior:
576    case EditableLinkAlwaysLive:
577        return true;
578
579    case EditableLinkNeverLive:
580        return false;
581
582    // If the selection prior to clicking on this link resided in the same editable block as this link,
583    // and the shift key isn't pressed, we don't want to follow the link.
584    case EditableLinkLiveWhenNotFocused:
585        return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && rootEditableElementForSelectionOnMouseDown() != rootEditableElement());
586
587    case EditableLinkOnlyLiveWithShiftKey:
588        return eventType == MouseEventWithShiftKey;
589    }
590
591    ASSERT_NOT_REACHED();
592    return false;
593}
594
595bool isEnterKeyKeydownEvent(Event* event)
596{
597    return event->type() == eventNames().keydownEvent && event->isKeyboardEvent() && static_cast<KeyboardEvent*>(event)->keyIdentifier() == "Enter";
598}
599
600bool isLinkClick(Event* event)
601{
602    return event->type() == eventNames().clickEvent && (!event->isMouseEvent() || static_cast<MouseEvent*>(event)->button() != RightButton);
603}
604
605bool HTMLAnchorElement::willRespondToMouseClickEvents()
606{
607    return isLink() || HTMLElement::willRespondToMouseClickEvents();
608}
609
610#if ENABLE(MICRODATA)
611String HTMLAnchorElement::itemValueText() const
612{
613    return getURLAttribute(hrefAttr);
614}
615
616void HTMLAnchorElement::setItemValueText(const String& value, ExceptionCode&)
617{
618    setAttribute(hrefAttr, value);
619}
620#endif
621
622typedef HashMap<const HTMLAnchorElement*, RefPtr<Element> > RootEditableElementMap;
623
624static RootEditableElementMap& rootEditableElementMap()
625{
626    DEFINE_STATIC_LOCAL(RootEditableElementMap, map, ());
627    return map;
628}
629
630Element* HTMLAnchorElement::rootEditableElementForSelectionOnMouseDown() const
631{
632    if (!m_hasRootEditableElementForSelectionOnMouseDown)
633        return 0;
634    return rootEditableElementMap().get(this);
635}
636
637void HTMLAnchorElement::clearRootEditableElementForSelectionOnMouseDown()
638{
639    if (!m_hasRootEditableElementForSelectionOnMouseDown)
640        return;
641    rootEditableElementMap().remove(this);
642    m_hasRootEditableElementForSelectionOnMouseDown = false;
643}
644
645void HTMLAnchorElement::setRootEditableElementForSelectionOnMouseDown(Element* element)
646{
647    if (!element) {
648        clearRootEditableElementForSelectionOnMouseDown();
649        return;
650    }
651
652    rootEditableElementMap().set(this, element);
653    m_hasRootEditableElementForSelectionOnMouseDown = true;
654}
655
656}
657