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 "core/html/HTMLAnchorElement.h"
26
27#include "bindings/core/v8/V8DOMActivityLogger.h"
28#include "core/dom/Attribute.h"
29#include "core/editing/FrameSelection.h"
30#include "core/events/KeyboardEvent.h"
31#include "core/events/MouseEvent.h"
32#include "core/frame/FrameHost.h"
33#include "core/frame/LocalFrame.h"
34#include "core/frame/Settings.h"
35#include "core/frame/UseCounter.h"
36#include "core/html/HTMLFormElement.h"
37#include "core/html/HTMLImageElement.h"
38#include "core/html/parser/HTMLParserIdioms.h"
39#include "core/loader/FrameLoadRequest.h"
40#include "core/loader/FrameLoader.h"
41#include "core/loader/FrameLoaderClient.h"
42#include "core/loader/FrameLoaderTypes.h"
43#include "core/loader/PingLoader.h"
44#include "core/page/Chrome.h"
45#include "core/page/ChromeClient.h"
46#include "core/rendering/RenderImage.h"
47#include "platform/PlatformMouseEvent.h"
48#include "platform/network/DNS.h"
49#include "platform/network/ResourceRequest.h"
50#include "platform/weborigin/KnownPorts.h"
51#include "platform/weborigin/SecurityOrigin.h"
52#include "platform/weborigin/SecurityPolicy.h"
53#include "public/platform/Platform.h"
54#include "public/platform/WebURL.h"
55#include "public/platform/WebURLRequest.h"
56#include "wtf/text/StringBuilder.h"
57
58namespace blink {
59
60using namespace HTMLNames;
61
62HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document& document)
63    : HTMLElement(tagName, document)
64    , m_linkRelations(0)
65    , m_cachedVisitedLinkHash(0)
66    , m_wasFocusedByMouse(false)
67{
68}
69
70PassRefPtrWillBeRawPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document& document)
71{
72    return adoptRefWillBeNoop(new HTMLAnchorElement(aTag, document));
73}
74
75HTMLAnchorElement::~HTMLAnchorElement()
76{
77}
78
79bool HTMLAnchorElement::supportsFocus() const
80{
81    if (hasEditableStyle())
82        return HTMLElement::supportsFocus();
83    // If not a link we should still be able to focus the element if it has tabIndex.
84    return isLink() || HTMLElement::supportsFocus();
85}
86
87bool HTMLAnchorElement::shouldHaveFocusAppearance() const
88{
89    return !m_wasFocusedByMouse || HTMLElement::supportsFocus();
90}
91
92void HTMLAnchorElement::dispatchFocusEvent(Element* oldFocusedElement, FocusType type)
93{
94    if (type != FocusTypePage)
95        m_wasFocusedByMouse = type == FocusTypeMouse;
96    HTMLElement::dispatchFocusEvent(oldFocusedElement, type);
97}
98
99bool HTMLAnchorElement::isMouseFocusable() const
100{
101    if (isLink())
102        return supportsFocus();
103
104    return HTMLElement::isMouseFocusable();
105}
106
107bool HTMLAnchorElement::isKeyboardFocusable() const
108{
109    ASSERT(document().isActive());
110
111    if (isFocusable() && Element::supportsFocus())
112        return HTMLElement::isKeyboardFocusable();
113
114    if (isLink() && !document().frameHost()->chrome().client().tabsToLinks())
115        return false;
116    return HTMLElement::isKeyboardFocusable();
117}
118
119static void appendServerMapMousePosition(StringBuilder& url, Event* event)
120{
121    if (!event->isMouseEvent())
122        return;
123
124    ASSERT(event->target());
125    Node* target = event->target()->toNode();
126    ASSERT(target);
127    if (!isHTMLImageElement(*target))
128        return;
129
130    HTMLImageElement& imageElement = toHTMLImageElement(*target);
131    if (!imageElement.isServerMap())
132        return;
133
134    if (!imageElement.renderer() || !imageElement.renderer()->isRenderImage())
135        return;
136    RenderImage* renderer = toRenderImage(imageElement.renderer());
137
138    // FIXME: This should probably pass true for useTransforms.
139    FloatPoint absolutePosition = renderer->absoluteToLocal(FloatPoint(toMouseEvent(event)->pageX(), toMouseEvent(event)->pageY()));
140    int x = absolutePosition.x();
141    int y = absolutePosition.y();
142    url.append('?');
143    url.appendNumber(x);
144    url.append(',');
145    url.appendNumber(y);
146}
147
148void HTMLAnchorElement::defaultEventHandler(Event* event)
149{
150    if (isLink()) {
151        if (focused() && isEnterKeyKeydownEvent(event) && isLiveLink()) {
152            event->setDefaultHandled();
153            dispatchSimulatedClick(event);
154            return;
155        }
156
157        if (isLinkClick(event) && isLiveLink()) {
158            handleClick(event);
159            return;
160        }
161    }
162
163    HTMLElement::defaultEventHandler(event);
164}
165
166void HTMLAnchorElement::setActive(bool down)
167{
168    if (hasEditableStyle())
169        return;
170
171    ContainerNode::setActive(down);
172}
173
174void HTMLAnchorElement::attributeWillChange(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& newValue)
175{
176    if (name == hrefAttr && inDocument()) {
177        V8DOMActivityLogger* activityLogger = V8DOMActivityLogger::currentActivityLoggerIfIsolatedWorld();
178        if (activityLogger) {
179            Vector<String> argv;
180            argv.append("a");
181            argv.append(hrefAttr.toString());
182            argv.append(oldValue);
183            argv.append(newValue);
184            activityLogger->logEvent("blinkSetAttribute", argv.size(), argv.data());
185        }
186    }
187    HTMLElement::attributeWillChange(name, oldValue, newValue);
188}
189
190void HTMLAnchorElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
191{
192    if (name == hrefAttr) {
193        bool wasLink = isLink();
194        setIsLink(!value.isNull());
195        if (wasLink || isLink()) {
196            pseudoStateChanged(CSSSelector::PseudoLink);
197            pseudoStateChanged(CSSSelector::PseudoVisited);
198            if (wasLink != isLink())
199                pseudoStateChanged(CSSSelector::PseudoEnabled);
200        }
201        if (wasLink && !isLink() && treeScope().adjustedFocusedElement() == this) {
202            // We might want to call blur(), but it's dangerous to dispatch
203            // events here.
204            document().setNeedsFocusedElementCheck();
205        }
206        if (isLink()) {
207            String parsedURL = stripLeadingAndTrailingHTMLSpaces(value);
208            if (document().isDNSPrefetchEnabled()) {
209                if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//"))
210                    prefetchDNS(document().completeURL(parsedURL).host());
211            }
212        }
213        invalidateCachedVisitedLinkHash();
214    } else if (name == nameAttr || name == titleAttr) {
215        // Do nothing.
216    } else if (name == relAttr)
217        setRel(value);
218    else
219        HTMLElement::parseAttribute(name, value);
220}
221
222void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
223{
224    dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
225}
226
227bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
228{
229    return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
230}
231
232bool HTMLAnchorElement::hasLegalLinkAttribute(const QualifiedName& name) const
233{
234    return name == hrefAttr || HTMLElement::hasLegalLinkAttribute(name);
235}
236
237bool HTMLAnchorElement::canStartSelection() const
238{
239    if (!isLink())
240        return HTMLElement::canStartSelection();
241    return hasEditableStyle();
242}
243
244bool HTMLAnchorElement::draggable() const
245{
246    // Should be draggable if we have an href attribute.
247    const AtomicString& value = getAttribute(draggableAttr);
248    if (equalIgnoringCase(value, "true"))
249        return true;
250    if (equalIgnoringCase(value, "false"))
251        return false;
252    return hasAttribute(hrefAttr);
253}
254
255KURL HTMLAnchorElement::href() const
256{
257    return document().completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
258}
259
260void HTMLAnchorElement::setHref(const AtomicString& value)
261{
262    setAttribute(hrefAttr, value);
263}
264
265KURL HTMLAnchorElement::url() const
266{
267    return href();
268}
269
270void HTMLAnchorElement::setURL(const KURL& url)
271{
272    setHref(AtomicString(url.string()));
273}
274
275String HTMLAnchorElement::input() const
276{
277    return getAttribute(hrefAttr);
278}
279
280void HTMLAnchorElement::setInput(const String& value)
281{
282    setHref(AtomicString(value));
283}
284
285bool HTMLAnchorElement::hasRel(uint32_t relation) const
286{
287    return m_linkRelations & relation;
288}
289
290void HTMLAnchorElement::setRel(const AtomicString& value)
291{
292    m_linkRelations = 0;
293    SpaceSplitString newLinkRelations(value, true);
294    // FIXME: Add link relations as they are implemented
295    if (newLinkRelations.contains("noreferrer"))
296        m_linkRelations |= RelationNoReferrer;
297}
298
299const AtomicString& HTMLAnchorElement::name() const
300{
301    return getNameAttribute();
302}
303
304short HTMLAnchorElement::tabIndex() const
305{
306    // Skip the supportsFocus check in HTMLElement.
307    return Element::tabIndex();
308}
309
310bool HTMLAnchorElement::isLiveLink() const
311{
312    return isLink() && !hasEditableStyle();
313}
314
315void HTMLAnchorElement::sendPings(const KURL& destinationURL) const
316{
317    const AtomicString& pingValue = getAttribute(pingAttr);
318    if (pingValue.isNull() || !document().settings() || !document().settings()->hyperlinkAuditingEnabled())
319        return;
320
321    UseCounter::count(document(), UseCounter::HTMLAnchorElementPingAttribute);
322
323    SpaceSplitString pingURLs(pingValue, false);
324    for (unsigned i = 0; i < pingURLs.size(); i++)
325        PingLoader::sendLinkAuditPing(document().frame(), document().completeURL(pingURLs[i]), destinationURL);
326}
327
328void HTMLAnchorElement::handleClick(Event* event)
329{
330    event->setDefaultHandled();
331
332    LocalFrame* frame = document().frame();
333    if (!frame)
334        return;
335
336    StringBuilder url;
337    url.append(stripLeadingAndTrailingHTMLSpaces(fastGetAttribute(hrefAttr)));
338    appendServerMapMousePosition(url, event);
339    KURL completedURL = document().completeURL(url.toString());
340
341    // Schedule the ping before the frame load. Prerender in Chrome may kill the renderer as soon as the navigation is
342    // sent out.
343    sendPings(completedURL);
344
345    ResourceRequest request(completedURL);
346    if (hasAttribute(downloadAttr)) {
347        request.setRequestContext(blink::WebURLRequest::RequestContextDownload);
348        if (!hasRel(RelationNoReferrer)) {
349            String referrer = SecurityPolicy::generateReferrerHeader(document().referrerPolicy(), completedURL, document().outgoingReferrer());
350            if (!referrer.isEmpty())
351                request.setHTTPReferrer(Referrer(referrer, document().referrerPolicy()));
352        }
353
354        bool isSameOrigin = completedURL.protocolIsData() || document().securityOrigin()->canRequest(completedURL);
355        const AtomicString& suggestedName = (isSameOrigin ? fastGetAttribute(downloadAttr) : nullAtom);
356
357        frame->loader().client()->loadURLExternally(request, NavigationPolicyDownload, suggestedName);
358    } else {
359        request.setRequestContext(blink::WebURLRequest::RequestContextHyperlink);
360        FrameLoadRequest frameRequest(&document(), request, getAttribute(targetAttr));
361        frameRequest.setTriggeringEvent(event);
362        if (hasRel(RelationNoReferrer))
363            frameRequest.setShouldSendReferrer(NeverSendReferrer);
364        frame->loader().load(frameRequest);
365    }
366}
367
368bool isEnterKeyKeydownEvent(Event* event)
369{
370    return event->type() == EventTypeNames::keydown && event->isKeyboardEvent() && toKeyboardEvent(event)->keyIdentifier() == "Enter";
371}
372
373bool isLinkClick(Event* event)
374{
375    return event->type() == EventTypeNames::click && (!event->isMouseEvent() || toMouseEvent(event)->button() != RightButton);
376}
377
378bool HTMLAnchorElement::willRespondToMouseClickEvents()
379{
380    return isLink() || HTMLElement::willRespondToMouseClickEvents();
381}
382
383bool HTMLAnchorElement::isInteractiveContent() const
384{
385    return isLink();
386}
387
388Node::InsertionNotificationRequest HTMLAnchorElement::insertedInto(ContainerNode* insertionPoint)
389{
390    if (insertionPoint->inDocument()) {
391        V8DOMActivityLogger* activityLogger = V8DOMActivityLogger::currentActivityLoggerIfIsolatedWorld();
392        if (activityLogger) {
393            Vector<String> argv;
394            argv.append("a");
395            argv.append(fastGetAttribute(hrefAttr));
396            activityLogger->logEvent("blinkAddElement", argv.size(), argv.data());
397        }
398    }
399    return HTMLElement::insertedInto(insertionPoint);
400}
401
402}
403