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 Apple Inc. All rights reserved.
6 * (C) 2006 Graham Dennis (graham.dennis@gmail.com)
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.
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.
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.
25 #include "HTMLAnchorElement.h"
28 #include "EventNames.h"
30 #include "FrameLoaderTypes.h"
31 #include "HTMLImageElement.h"
32 #include "HTMLNames.h"
33 #include "KeyboardEvent.h"
34 #include "MappedAttribute.h"
35 #include "MouseEvent.h"
37 #include "RenderImage.h"
42 using namespace HTMLNames
;
44 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName
& tagName
, Document
* document
)
45 : HTMLElement(tagName
, document
, CreateElement
)
46 , m_wasShiftKeyDownOnMouseDown(false)
51 PassRefPtr
<HTMLAnchorElement
> HTMLAnchorElement::create(Document
* document
)
53 return adoptRef(new HTMLAnchorElement(aTag
, document
));
56 PassRefPtr
<HTMLAnchorElement
> HTMLAnchorElement::create(const QualifiedName
& tagName
, Document
* document
)
58 return adoptRef(new HTMLAnchorElement(tagName
, document
));
61 // This function does not allow leading spaces before the port number.
62 static unsigned parsePortFromStringPosition(const String
& value
, unsigned portStart
, unsigned& portEnd
)
65 while (isASCIIDigit(value
[portEnd
]))
67 return value
.substring(portStart
, portEnd
- portStart
).toUInt();
70 bool HTMLAnchorElement::supportsFocus() const
72 if (isContentEditable())
73 return HTMLElement::supportsFocus();
74 // If not a link we should still be able to focus the element if it has tabIndex.
75 return isLink() || HTMLElement::supportsFocus();
78 bool HTMLAnchorElement::isMouseFocusable() const
80 // Anchor elements should be mouse focusable, https://bugs.webkit.org/show_bug.cgi?id=26856
81 #if !PLATFORM(GTK) && !PLATFORM(QT)
83 // Only allow links with tabIndex or contentEditable to be mouse focusable.
84 return HTMLElement::supportsFocus();
87 // Allow tab index etc to control focus.
88 return HTMLElement::isMouseFocusable();
91 bool HTMLAnchorElement::isKeyboardFocusable(KeyboardEvent
* event
) const
94 return HTMLElement::isKeyboardFocusable(event
);
99 if (!document()->frame())
102 if (!document()->frame()->eventHandler()->tabsToLinks(event
))
105 if (!renderer() || !renderer()->isBoxModelObject())
108 // Before calling absoluteRects, check for the common case where the renderer
109 // is non-empty, since this is a faster check and almost always returns true.
110 RenderBoxModelObject
* box
= toRenderBoxModelObject(renderer());
111 if (!box
->borderBoundingBox().isEmpty())
114 Vector
<IntRect
> rects
;
115 FloatPoint absPos
= renderer()->localToAbsolute();
116 renderer()->absoluteRects(rects
, absPos
.x(), absPos
.y());
117 size_t n
= rects
.size();
118 for (size_t i
= 0; i
< n
; ++i
)
119 if (!rects
[i
].isEmpty())
125 void HTMLAnchorElement::defaultEventHandler(Event
* evt
)
127 // React on clicks and on keypresses.
128 // Don't make this KEYUP_EVENT again, it makes khtml follow links it shouldn't,
129 // when pressing Enter in the combo.
130 if (isLink() && (evt
->type() == eventNames().clickEvent
|| (evt
->type() == eventNames().keydownEvent
&& focused()))) {
132 if (evt
->type() == eventNames().clickEvent
&& evt
->isMouseEvent())
133 e
= static_cast<MouseEvent
*>(evt
);
135 KeyboardEvent
* k
= 0;
136 if (evt
->type() == eventNames().keydownEvent
&& evt
->isKeyboardEvent())
137 k
= static_cast<KeyboardEvent
*>(evt
);
139 if (e
&& e
->button() == RightButton
) {
140 HTMLElement::defaultEventHandler(evt
);
144 // If the link is editable, then we need to check the settings to see whether or not to follow the link
145 if (isContentEditable()) {
146 EditableLinkBehavior editableLinkBehavior
= EditableLinkDefaultBehavior
;
147 if (Settings
* settings
= document()->settings())
148 editableLinkBehavior
= settings
->editableLinkBehavior();
150 switch (editableLinkBehavior
) {
151 // Always follow the link (Safari 2.0 behavior)
153 case EditableLinkDefaultBehavior
:
154 case EditableLinkAlwaysLive
:
157 case EditableLinkNeverLive
:
158 HTMLElement::defaultEventHandler(evt
);
161 // If the selection prior to clicking on this link resided in the same editable block as this link,
162 // and the shift key isn't pressed, we don't want to follow the link
163 case EditableLinkLiveWhenNotFocused
:
164 if (e
&& !e
->shiftKey() && m_rootEditableElementForSelectionOnMouseDown
== rootEditableElement()) {
165 HTMLElement::defaultEventHandler(evt
);
170 // Only follow the link if the shift key is down (WinIE/Firefox behavior)
171 case EditableLinkOnlyLiveWithShiftKey
:
172 if (e
&& !e
->shiftKey()) {
173 HTMLElement::defaultEventHandler(evt
);
181 if (k
->keyIdentifier() != "Enter") {
182 HTMLElement::defaultEventHandler(evt
);
185 evt
->setDefaultHandled();
186 dispatchSimulatedClick(evt
);
190 String url
= deprecatedParseURL(getAttribute(hrefAttr
));
192 ASSERT(evt
->target());
193 ASSERT(evt
->target()->toNode());
194 if (evt
->target()->toNode()->hasTagName(imgTag
)) {
195 HTMLImageElement
* img
= static_cast<HTMLImageElement
*>(evt
->target()->toNode());
196 if (img
&& img
->isServerMap()) {
197 RenderImage
* r
= toRenderImage(img
->renderer());
199 // FIXME: broken with transforms
200 FloatPoint absPos
= r
->localToAbsolute();
201 int x
= e
->pageX() - absPos
.x();
202 int y
= e
->pageY() - absPos
.y();
204 url
+= String::number(x
);
206 url
+= String::number(y
);
208 evt
->setDefaultHandled();
209 HTMLElement::defaultEventHandler(evt
);
215 if (!evt
->defaultPrevented() && document()->frame())
216 document()->frame()->loader()->urlSelected(document()->completeURL(url
), getAttribute(targetAttr
), evt
, false, false, true, hasRel(RelationNoReferrer
) ? NoReferrer
: SendReferrer
);
218 evt
->setDefaultHandled();
219 } else if (isLink() && isContentEditable()) {
220 // This keeps track of the editable block that the selection was in (if it was in one) just before the link was clicked
221 // for the LiveWhenNotFocused editable link behavior
222 if (evt
->type() == eventNames().mousedownEvent
&& evt
->isMouseEvent() && static_cast<MouseEvent
*>(evt
)->button() != RightButton
&& document()->frame() && document()->frame()->selection()) {
223 MouseEvent
* e
= static_cast<MouseEvent
*>(evt
);
225 m_rootEditableElementForSelectionOnMouseDown
= document()->frame()->selection()->rootEditableElement();
226 m_wasShiftKeyDownOnMouseDown
= e
&& e
->shiftKey();
227 } else if (evt
->type() == eventNames().mouseoverEvent
) {
228 // These are cleared on mouseover and not mouseout because their values are needed for drag events, but these happen
229 // after mouse out events.
230 m_rootEditableElementForSelectionOnMouseDown
= 0;
231 m_wasShiftKeyDownOnMouseDown
= false;
235 HTMLElement::defaultEventHandler(evt
);
238 void HTMLAnchorElement::setActive(bool down
, bool pause
)
240 if (isContentEditable()) {
241 EditableLinkBehavior editableLinkBehavior
= EditableLinkDefaultBehavior
;
242 if (Settings
* settings
= document()->settings())
243 editableLinkBehavior
= settings
->editableLinkBehavior();
245 switch (editableLinkBehavior
) {
247 case EditableLinkDefaultBehavior
:
248 case EditableLinkAlwaysLive
:
251 case EditableLinkNeverLive
:
254 // Don't set the link to be active if the current selection is in the same editable block as
256 case EditableLinkLiveWhenNotFocused
:
257 if (down
&& document()->frame() && document()->frame()->selection() &&
258 document()->frame()->selection()->rootEditableElement() == rootEditableElement())
262 case EditableLinkOnlyLiveWithShiftKey
:
268 ContainerNode::setActive(down
, pause
);
271 void HTMLAnchorElement::parseMappedAttribute(MappedAttribute
*attr
)
273 if (attr
->name() == hrefAttr
) {
274 bool wasLink
= isLink();
275 setIsLink(!attr
->isNull());
276 if (wasLink
!= isLink())
277 setNeedsStyleRecalc();
279 String parsedURL
= deprecatedParseURL(attr
->value());
280 if (document()->isDNSPrefetchEnabled()) {
281 if (protocolIs(parsedURL
, "http") || protocolIs(parsedURL
, "https") || parsedURL
.startsWith("//"))
282 prefetchDNS(document()->completeURL(parsedURL
).host());
284 if (document()->page() && !document()->page()->javaScriptURLsAreAllowed() && protocolIsJavaScript(parsedURL
)) {
286 attr
->setValue(nullAtom
);
289 } else if (attr
->name() == nameAttr
||
290 attr
->name() == titleAttr
) {
292 } else if (attr
->name() == relAttr
)
293 setRel(attr
->value());
295 HTMLElement::parseMappedAttribute(attr
);
298 void HTMLAnchorElement::accessKeyAction(bool sendToAnyElement
)
300 // send the mouse button events if the caller specified sendToAnyElement
301 dispatchSimulatedClick(0, sendToAnyElement
);
304 bool HTMLAnchorElement::isURLAttribute(Attribute
*attr
) const
306 return attr
->name() == hrefAttr
;
309 bool HTMLAnchorElement::canStartSelection() const
311 // FIXME: We probably want this same behavior in SVGAElement too
313 return HTMLElement::canStartSelection();
314 return isContentEditable();
317 bool HTMLAnchorElement::draggable() const
319 // Should be draggable if we have an href attribute.
320 const AtomicString
& value
= getAttribute(draggableAttr
);
321 if (equalIgnoringCase(value
, "true"))
323 if (equalIgnoringCase(value
, "false"))
325 return hasAttribute(hrefAttr
);
328 KURL
HTMLAnchorElement::href() const
330 return document()->completeURL(deprecatedParseURL(getAttribute(hrefAttr
)));
333 void HTMLAnchorElement::setHref(const AtomicString
& value
)
335 setAttribute(hrefAttr
, value
);
338 bool HTMLAnchorElement::hasRel(uint32_t relation
) const
340 return m_linkRelations
& relation
;
343 void HTMLAnchorElement::setRel(const String
& value
)
346 SpaceSplitString
newLinkRelations(value
, true);
347 // FIXME: Add link relations as they are implemented
348 if (newLinkRelations
.contains("noreferrer"))
349 m_linkRelations
|= RelationNoReferrer
;
352 const AtomicString
& HTMLAnchorElement::name() const
354 return getAttribute(nameAttr
);
357 short HTMLAnchorElement::tabIndex() const
359 // Skip the supportsFocus check in HTMLElement.
360 return Element::tabIndex();
363 String
HTMLAnchorElement::target() const
365 return getAttribute(targetAttr
);
368 String
HTMLAnchorElement::hash() const
370 String fragmentIdentifier
= href().fragmentIdentifier();
371 return fragmentIdentifier
.isEmpty() ? "" : "#" + fragmentIdentifier
;
374 void HTMLAnchorElement::setHash(const String
& value
)
378 url
.setFragmentIdentifier(value
.substring(1));
380 url
.setFragmentIdentifier(value
);
381 setHref(url
.string());
384 String
HTMLAnchorElement::host() const
386 const KURL
& url
= href();
387 if (url
.hostEnd() == url
.pathStart())
389 if (isDefaultPortForProtocol(url
.port(), url
.protocol()))
391 return url
.host() + ":" + String::number(url
.port());
394 void HTMLAnchorElement::setHost(const String
& value
)
399 if (!url
.canSetHostOrPort())
402 int separator
= value
.find(':');
407 url
.setHostAndPort(value
);
410 unsigned port
= parsePortFromStringPosition(value
, separator
+ 1, portEnd
);
412 // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
413 // specifically goes against RFC 3986 (p3.2) and
414 // requires setting the port to "0" if it is set to empty string.
415 url
.setHostAndPort(value
.substring(0, separator
+ 1) + "0");
417 if (isDefaultPortForProtocol(port
, url
.protocol()))
418 url
.setHostAndPort(value
.substring(0, separator
));
420 url
.setHostAndPort(value
.substring(0, portEnd
));
423 setHref(url
.string());
426 String
HTMLAnchorElement::hostname() const
428 return href().host();
431 void HTMLAnchorElement::setHostname(const String
& value
)
433 // Before setting new value:
434 // Remove all leading U+002F SOLIDUS ("/") characters.
436 unsigned hostLength
= value
.length();
437 while (value
[i
] == '/')
444 if (!url
.canSetHostOrPort())
447 url
.setHost(value
.substring(i
));
448 setHref(url
.string());
451 String
HTMLAnchorElement::pathname() const
453 return href().path();
456 void HTMLAnchorElement::setPathname(const String
& value
)
459 if (!url
.canSetPathname())
465 url
.setPath("/" + value
);
467 setHref(url
.string());
470 String
HTMLAnchorElement::port() const
472 return String::number(href().port());
475 void HTMLAnchorElement::setPort(const String
& value
)
478 if (!url
.canSetHostOrPort())
481 // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
482 // specifically goes against RFC 3986 (p3.2) and
483 // requires setting the port to "0" if it is set to empty string.
484 unsigned port
= value
.toUInt();
485 if (isDefaultPortForProtocol(port
, url
.protocol()))
490 setHref(url
.string());
493 String
HTMLAnchorElement::protocol() const
495 return href().protocol() + ":";
498 void HTMLAnchorElement::setProtocol(const String
& value
)
500 int separator
= value
.find(':');
508 // Following Firefox 3.5.2 which removes anything after the first ":"
509 String newProtocol
= value
.substring(0, separator
);
510 if (!isValidProtocol(newProtocol
))
512 url
.setProtocol(newProtocol
);
514 setHref(url
.string());
517 String
HTMLAnchorElement::search() const
519 String query
= href().query();
520 return query
.isEmpty() ? "" : "?" + query
;
523 void HTMLAnchorElement::setSearch(const String
& value
)
526 String newSearch
= (value
[0] == '?') ? value
.substring(1) : value
;
527 // Make sure that '#' in the query does not leak to the hash.
528 url
.setQuery(newSearch
.replace('#', "%23"));
530 setHref(url
.string());
533 String
HTMLAnchorElement::text() const
538 String
HTMLAnchorElement::toString() const
540 return href().string();
543 bool HTMLAnchorElement::isLiveLink() const
547 if (!isContentEditable())
550 EditableLinkBehavior editableLinkBehavior
= EditableLinkDefaultBehavior
;
551 if (Settings
* settings
= document()->settings())
552 editableLinkBehavior
= settings
->editableLinkBehavior();
554 switch (editableLinkBehavior
) {
556 case EditableLinkDefaultBehavior
:
557 case EditableLinkAlwaysLive
:
560 case EditableLinkNeverLive
:
563 // Don't set the link to be live if the current selection is in the same editable block as
564 // this link or if the shift key is down
565 case EditableLinkLiveWhenNotFocused
:
566 return m_wasShiftKeyDownOnMouseDown
|| m_rootEditableElementForSelectionOnMouseDown
!= rootEditableElement();
568 case EditableLinkOnlyLiveWithShiftKey
:
569 return m_wasShiftKeyDownOnMouseDown
;