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
58 namespace blink {
59
60 using namespace HTMLNames;
61
HTMLAnchorElement(const QualifiedName & tagName,Document & document)62 HTMLAnchorElement::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
create(Document & document)70 PassRefPtrWillBeRawPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document& document)
71 {
72 return adoptRefWillBeNoop(new HTMLAnchorElement(aTag, document));
73 }
74
~HTMLAnchorElement()75 HTMLAnchorElement::~HTMLAnchorElement()
76 {
77 }
78
supportsFocus() const79 bool 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
shouldHaveFocusAppearance() const87 bool HTMLAnchorElement::shouldHaveFocusAppearance() const
88 {
89 return !m_wasFocusedByMouse || HTMLElement::supportsFocus();
90 }
91
dispatchFocusEvent(Element * oldFocusedElement,FocusType type)92 void HTMLAnchorElement::dispatchFocusEvent(Element* oldFocusedElement, FocusType type)
93 {
94 if (type != FocusTypePage)
95 m_wasFocusedByMouse = type == FocusTypeMouse;
96 HTMLElement::dispatchFocusEvent(oldFocusedElement, type);
97 }
98
isMouseFocusable() const99 bool HTMLAnchorElement::isMouseFocusable() const
100 {
101 if (isLink())
102 return supportsFocus();
103
104 return HTMLElement::isMouseFocusable();
105 }
106
isKeyboardFocusable() const107 bool 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
appendServerMapMousePosition(StringBuilder & url,Event * event)119 static 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
defaultEventHandler(Event * event)148 void 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
setActive(bool down)166 void HTMLAnchorElement::setActive(bool down)
167 {
168 if (hasEditableStyle())
169 return;
170
171 ContainerNode::setActive(down);
172 }
173
attributeWillChange(const QualifiedName & name,const AtomicString & oldValue,const AtomicString & newValue)174 void 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
parseAttribute(const QualifiedName & name,const AtomicString & value)190 void 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
accessKeyAction(bool sendMouseEvents)222 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
223 {
224 dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
225 }
226
isURLAttribute(const Attribute & attribute) const227 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
228 {
229 return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
230 }
231
hasLegalLinkAttribute(const QualifiedName & name) const232 bool HTMLAnchorElement::hasLegalLinkAttribute(const QualifiedName& name) const
233 {
234 return name == hrefAttr || HTMLElement::hasLegalLinkAttribute(name);
235 }
236
canStartSelection() const237 bool HTMLAnchorElement::canStartSelection() const
238 {
239 if (!isLink())
240 return HTMLElement::canStartSelection();
241 return hasEditableStyle();
242 }
243
draggable() const244 bool 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
href() const255 KURL HTMLAnchorElement::href() const
256 {
257 return document().completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
258 }
259
setHref(const AtomicString & value)260 void HTMLAnchorElement::setHref(const AtomicString& value)
261 {
262 setAttribute(hrefAttr, value);
263 }
264
url() const265 KURL HTMLAnchorElement::url() const
266 {
267 return href();
268 }
269
setURL(const KURL & url)270 void HTMLAnchorElement::setURL(const KURL& url)
271 {
272 setHref(AtomicString(url.string()));
273 }
274
input() const275 String HTMLAnchorElement::input() const
276 {
277 return getAttribute(hrefAttr);
278 }
279
setInput(const String & value)280 void HTMLAnchorElement::setInput(const String& value)
281 {
282 setHref(AtomicString(value));
283 }
284
hasRel(uint32_t relation) const285 bool HTMLAnchorElement::hasRel(uint32_t relation) const
286 {
287 return m_linkRelations & relation;
288 }
289
setRel(const AtomicString & value)290 void 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
name() const299 const AtomicString& HTMLAnchorElement::name() const
300 {
301 return getNameAttribute();
302 }
303
tabIndex() const304 short HTMLAnchorElement::tabIndex() const
305 {
306 // Skip the supportsFocus check in HTMLElement.
307 return Element::tabIndex();
308 }
309
isLiveLink() const310 bool HTMLAnchorElement::isLiveLink() const
311 {
312 return isLink() && !hasEditableStyle();
313 }
314
sendPings(const KURL & destinationURL) const315 void 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
handleClick(Event * event)328 void 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
isEnterKeyKeydownEvent(Event * event)368 bool isEnterKeyKeydownEvent(Event* event)
369 {
370 return event->type() == EventTypeNames::keydown && event->isKeyboardEvent() && toKeyboardEvent(event)->keyIdentifier() == "Enter";
371 }
372
isLinkClick(Event * event)373 bool isLinkClick(Event* event)
374 {
375 return event->type() == EventTypeNames::click && (!event->isMouseEvent() || toMouseEvent(event)->button() != RightButton);
376 }
377
willRespondToMouseClickEvents()378 bool HTMLAnchorElement::willRespondToMouseClickEvents()
379 {
380 return isLink() || HTMLElement::willRespondToMouseClickEvents();
381 }
382
isInteractiveContent() const383 bool HTMLAnchorElement::isInteractiveContent() const
384 {
385 return isLink();
386 }
387
insertedInto(ContainerNode * insertionPoint)388 Node::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