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
43 namespace WebCore {
44
45 using namespace HTMLNames;
46
HTMLAnchorElement(const QualifiedName & tagName,Document * document)47 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document* document)
48 : HTMLElement(tagName, document)
49 , m_wasShiftKeyDownOnMouseDown(false)
50 , m_linkRelations(0)
51 {
52 }
53
create(Document * document)54 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document* document)
55 {
56 return adoptRef(new HTMLAnchorElement(aTag, document));
57 }
58
create(const QualifiedName & tagName,Document * document)59 PassRefPtr<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.
parsePortFromStringPosition(const String & value,unsigned portStart,unsigned & portEnd)65 static 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
supportsFocus() const73 bool 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
isMouseFocusable() const81 bool 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
isKeyboardFocusable(KeyboardEvent * event) const94 bool 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
appendServerMapMousePosition(String & url,Event * event)111 static 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
defaultEventHandler(Event * event)140 void 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
setActive(bool down,bool pause)175 void 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
parseMappedAttribute(Attribute * attr)207 void 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
accessKeyAction(bool sendToAnyElement)234 void HTMLAnchorElement::accessKeyAction(bool sendToAnyElement)
235 {
236 // send the mouse button events if the caller specified sendToAnyElement
237 dispatchSimulatedClick(0, sendToAnyElement);
238 }
239
isURLAttribute(Attribute * attr) const240 bool HTMLAnchorElement::isURLAttribute(Attribute *attr) const
241 {
242 return attr->name() == hrefAttr;
243 }
244
canStartSelection() const245 bool 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
draggable() const253 bool 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
href() const264 KURL HTMLAnchorElement::href() const
265 {
266 return document()->completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
267 }
268
setHref(const AtomicString & value)269 void HTMLAnchorElement::setHref(const AtomicString& value)
270 {
271 setAttribute(hrefAttr, value);
272 }
273
hasRel(uint32_t relation) const274 bool HTMLAnchorElement::hasRel(uint32_t relation) const
275 {
276 return m_linkRelations & relation;
277 }
278
setRel(const String & value)279 void 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
name() const288 const AtomicString& HTMLAnchorElement::name() const
289 {
290 return getAttribute(nameAttr);
291 }
292
tabIndex() const293 short HTMLAnchorElement::tabIndex() const
294 {
295 // Skip the supportsFocus check in HTMLElement.
296 return Element::tabIndex();
297 }
298
target() const299 String HTMLAnchorElement::target() const
300 {
301 return getAttribute(targetAttr);
302 }
303
hash() const304 String HTMLAnchorElement::hash() const
305 {
306 String fragmentIdentifier = href().fragmentIdentifier();
307 return fragmentIdentifier.isEmpty() ? "" : "#" + fragmentIdentifier;
308 }
309
setHash(const String & value)310 void 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
host() const320 String 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
setHost(const String & value)330 void 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
hostname() const362 String HTMLAnchorElement::hostname() const
363 {
364 return href().host();
365 }
366
setHostname(const String & value)367 void 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
pathname() const387 String HTMLAnchorElement::pathname() const
388 {
389 return href().path();
390 }
391
setPathname(const String & value)392 void 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
port() const406 String HTMLAnchorElement::port() const
407 {
408 return String::number(href().port());
409 }
410
setPort(const String & value)411 void 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
protocol() const429 String HTMLAnchorElement::protocol() const
430 {
431 return href().protocol() + ":";
432 }
433
setProtocol(const String & value)434 void HTMLAnchorElement::setProtocol(const String& value)
435 {
436 KURL url = href();
437 url.setProtocol(value);
438 setHref(url.string());
439 }
440
search() const441 String HTMLAnchorElement::search() const
442 {
443 String query = href().query();
444 return query.isEmpty() ? "" : "?" + query;
445 }
446
origin() const447 String HTMLAnchorElement::origin() const
448 {
449 RefPtr<SecurityOrigin> origin = SecurityOrigin::create(href());
450 return origin->toString();
451 }
452
getParameter(const String & name) const453 String HTMLAnchorElement::getParameter(const String& name) const
454 {
455 ParsedURLParameters parameters;
456 href().copyParsedQueryTo(parameters);
457 return parameters.get(name);
458 }
459
setSearch(const String & value)460 void 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
text() const470 String HTMLAnchorElement::text() const
471 {
472 return innerText();
473 }
474
toString() const475 String HTMLAnchorElement::toString() const
476 {
477 return href().string();
478 }
479
isLiveLink() const480 bool HTMLAnchorElement::isLiveLink() const
481 {
482 return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey);
483 }
484
sendPings(const KURL & destinationURL)485 void 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
eventType(Event * event)495 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event)
496 {
497 if (!event->isMouseEvent())
498 return NonMouseEvent;
499 return static_cast<MouseEvent*>(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
500 }
501
treatLinkAsLiveForEventType(EventType eventType) const502 bool 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
isEnterKeyKeydownEvent(Event * event)532 bool isEnterKeyKeydownEvent(Event* event)
533 {
534 return event->type() == eventNames().keydownEvent && event->isKeyboardEvent() && static_cast<KeyboardEvent*>(event)->keyIdentifier() == "Enter";
535 }
536
isMiddleMouseButtonEvent(Event * event)537 bool isMiddleMouseButtonEvent(Event* event)
538 {
539 return event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == MiddleButton;
540 }
541
isLinkClick(Event * event)542 bool isLinkClick(Event* event)
543 {
544 return event->type() == eventNames().clickEvent && (!event->isMouseEvent() || static_cast<MouseEvent*>(event)->button() != RightButton);
545 }
546
handleLinkClick(Event * event,Document * document,const String & url,const String & target,bool hideReferrer)547 void handleLinkClick(Event* event, Document* document, const String& url, const String& target, bool hideReferrer)
548 {
549 event->setDefaultHandled();
550
551 Frame* frame = document->frame();
552 if (!frame)
553 return;
554 frame->loader()->urlSelected(document->completeURL(url), target, event, false, false, hideReferrer ? NoReferrer : SendReferrer);
555 }
556
557 }
558