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 if (href().hasPort())
409 return String::number(href().port());
410
411 return "";
412 }
413
setPort(const String & value)414 void HTMLAnchorElement::setPort(const String& value)
415 {
416 KURL url = href();
417 if (!url.canSetHostOrPort())
418 return;
419
420 // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
421 // specifically goes against RFC 3986 (p3.2) and
422 // requires setting the port to "0" if it is set to empty string.
423 unsigned port = value.toUInt();
424 if (isDefaultPortForProtocol(port, url.protocol()))
425 url.removePort();
426 else
427 url.setPort(port);
428
429 setHref(url.string());
430 }
431
protocol() const432 String HTMLAnchorElement::protocol() const
433 {
434 return href().protocol() + ":";
435 }
436
setProtocol(const String & value)437 void HTMLAnchorElement::setProtocol(const String& value)
438 {
439 KURL url = href();
440 url.setProtocol(value);
441 setHref(url.string());
442 }
443
search() const444 String HTMLAnchorElement::search() const
445 {
446 String query = href().query();
447 return query.isEmpty() ? "" : "?" + query;
448 }
449
origin() const450 String HTMLAnchorElement::origin() const
451 {
452 RefPtr<SecurityOrigin> origin = SecurityOrigin::create(href());
453 return origin->toString();
454 }
455
getParameter(const String & name) const456 String HTMLAnchorElement::getParameter(const String& name) const
457 {
458 ParsedURLParameters parameters;
459 href().copyParsedQueryTo(parameters);
460 return parameters.get(name);
461 }
462
setSearch(const String & value)463 void HTMLAnchorElement::setSearch(const String& value)
464 {
465 KURL url = href();
466 String newSearch = (value[0] == '?') ? value.substring(1) : value;
467 // Make sure that '#' in the query does not leak to the hash.
468 url.setQuery(newSearch.replace('#', "%23"));
469
470 setHref(url.string());
471 }
472
text() const473 String HTMLAnchorElement::text() const
474 {
475 return innerText();
476 }
477
toString() const478 String HTMLAnchorElement::toString() const
479 {
480 return href().string();
481 }
482
isLiveLink() const483 bool HTMLAnchorElement::isLiveLink() const
484 {
485 return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey);
486 }
487
sendPings(const KURL & destinationURL)488 void HTMLAnchorElement::sendPings(const KURL& destinationURL)
489 {
490 if (!hasAttribute(pingAttr) || !document()->settings()->hyperlinkAuditingEnabled())
491 return;
492
493 SpaceSplitString pingURLs(getAttribute(pingAttr), true);
494 for (unsigned i = 0; i < pingURLs.size(); i++)
495 PingLoader::sendPing(document()->frame(), document()->completeURL(pingURLs[i]), destinationURL);
496 }
497
eventType(Event * event)498 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event)
499 {
500 if (!event->isMouseEvent())
501 return NonMouseEvent;
502 return static_cast<MouseEvent*>(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
503 }
504
treatLinkAsLiveForEventType(EventType eventType) const505 bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const
506 {
507 if (!rendererIsEditable())
508 return true;
509
510 Settings* settings = document()->settings();
511 if (!settings)
512 return true;
513
514 switch (settings->editableLinkBehavior()) {
515 case EditableLinkDefaultBehavior:
516 case EditableLinkAlwaysLive:
517 return true;
518
519 case EditableLinkNeverLive:
520 return false;
521
522 // If the selection prior to clicking on this link resided in the same editable block as this link,
523 // and the shift key isn't pressed, we don't want to follow the link.
524 case EditableLinkLiveWhenNotFocused:
525 return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && m_rootEditableElementForSelectionOnMouseDown != rootEditableElement());
526
527 case EditableLinkOnlyLiveWithShiftKey:
528 return eventType == MouseEventWithShiftKey;
529 }
530
531 ASSERT_NOT_REACHED();
532 return false;
533 }
534
isEnterKeyKeydownEvent(Event * event)535 bool isEnterKeyKeydownEvent(Event* event)
536 {
537 #if OS(ANDROID)
538 return event->type() == eventNames().keyupEvent && event->isKeyboardEvent() && static_cast<KeyboardEvent*>(event)->keyIdentifier() == "Enter";
539 #else
540 return event->type() == eventNames().keydownEvent && event->isKeyboardEvent() && static_cast<KeyboardEvent*>(event)->keyIdentifier() == "Enter";
541 #endif
542 }
543
isMiddleMouseButtonEvent(Event * event)544 bool isMiddleMouseButtonEvent(Event* event)
545 {
546 return event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == MiddleButton;
547 }
548
isLinkClick(Event * event)549 bool isLinkClick(Event* event)
550 {
551 return event->type() == eventNames().clickEvent && (!event->isMouseEvent() || static_cast<MouseEvent*>(event)->button() != RightButton);
552 }
553
handleLinkClick(Event * event,Document * document,const String & url,const String & target,bool hideReferrer)554 void handleLinkClick(Event* event, Document* document, const String& url, const String& target, bool hideReferrer)
555 {
556 event->setDefaultHandled();
557
558 Frame* frame = document->frame();
559 if (!frame)
560 return;
561 frame->loader()->urlSelected(document->completeURL(url), target, event, false, false, hideReferrer ? NoReferrer : SendReferrer);
562 }
563
564 }
565