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)
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"
27 #include "Attribute.h"
28 #include "EventNames.h"
30 #include "FrameLoaderClient.h"
31 #include "FrameLoaderTypes.h"
32 #include "HTMLImageElement.h"
33 #include "HTMLNames.h"
34 #include "HTMLParserIdioms.h"
35 #include "KeyboardEvent.h"
36 #include "MouseEvent.h"
38 #include "PingLoader.h"
39 #include "RenderImage.h"
40 #include "ResourceHandle.h"
41 #include "SecurityOrigin.h"
46 using namespace HTMLNames;
48 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document* document)
49 : HTMLElement(tagName, document)
50 , m_wasShiftKeyDownOnMouseDown(false)
55 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document* document)
57 return adoptRef(new HTMLAnchorElement(aTag, document));
60 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(const QualifiedName& tagName, Document* document)
62 return adoptRef(new HTMLAnchorElement(tagName, document));
65 // This function does not allow leading spaces before the port number.
66 static unsigned parsePortFromStringPosition(const String& value, unsigned portStart, unsigned& portEnd)
69 while (isASCIIDigit(value[portEnd]))
71 return value.substring(portStart, portEnd - portStart).toUInt();
74 bool HTMLAnchorElement::supportsFocus() const
76 if (rendererIsEditable())
77 return HTMLElement::supportsFocus();
78 // If not a link we should still be able to focus the element if it has tabIndex.
79 return isLink() || HTMLElement::supportsFocus();
82 bool HTMLAnchorElement::isMouseFocusable() const
84 // Anchor elements should be mouse focusable, https://bugs.webkit.org/show_bug.cgi?id=26856
85 #if !PLATFORM(GTK) && !PLATFORM(QT) && !PLATFORM(EFL)
87 // Only allow links with tabIndex or contentEditable to be mouse focusable.
88 return HTMLElement::supportsFocus();
91 // Allow tab index etc to control focus.
92 return HTMLElement::isMouseFocusable();
95 bool HTMLAnchorElement::isKeyboardFocusable(KeyboardEvent* event) const
98 return HTMLElement::isKeyboardFocusable(event);
103 if (!document()->frame())
106 if (!document()->frame()->eventHandler()->tabsToLinks(event))
109 return hasNonEmptyBoundingBox();
112 static void appendServerMapMousePosition(String& url, Event* event)
114 if (!event->isMouseEvent())
117 ASSERT(event->target());
118 Node* target = event->target()->toNode();
120 if (!target->hasTagName(imgTag))
123 HTMLImageElement* imageElement = static_cast<HTMLImageElement*>(event->target()->toNode());
124 if (!imageElement || !imageElement->isServerMap())
127 RenderImage* renderer = toRenderImage(imageElement->renderer());
131 // FIXME: This should probably pass true for useTransforms.
132 FloatPoint absolutePosition = renderer->absoluteToLocal(FloatPoint(static_cast<MouseEvent*>(event)->pageX(), static_cast<MouseEvent*>(event)->pageY()));
133 int x = absolutePosition.x();
134 int y = absolutePosition.y();
136 url += String::number(x);
138 url += String::number(y);
141 void HTMLAnchorElement::defaultEventHandler(Event* event)
144 if (focused() && isEnterKeyKeydownEvent(event) && treatLinkAsLiveForEventType(NonMouseEvent)) {
145 event->setDefaultHandled();
146 dispatchSimulatedClick(event);
150 if (isLinkClick(event) && treatLinkAsLiveForEventType(eventType(event))) {
155 if (rendererIsEditable()) {
156 // This keeps track of the editable block that the selection was in (if it was in one) just before the link was clicked
157 // for the LiveWhenNotFocused editable link behavior
158 if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() != RightButton && document()->frame() && document()->frame()->selection()) {
159 m_rootEditableElementForSelectionOnMouseDown = document()->frame()->selection()->rootEditableElement();
160 m_wasShiftKeyDownOnMouseDown = static_cast<MouseEvent*>(event)->shiftKey();
161 } else if (event->type() == eventNames().mouseoverEvent) {
162 // These are cleared on mouseover and not mouseout because their values are needed for drag events,
163 // but drag events happen after mouse out events.
164 m_rootEditableElementForSelectionOnMouseDown = 0;
165 m_wasShiftKeyDownOnMouseDown = false;
170 HTMLElement::defaultEventHandler(event);
173 void HTMLAnchorElement::setActive(bool down, bool pause)
175 if (rendererIsEditable()) {
176 EditableLinkBehavior editableLinkBehavior = EditableLinkDefaultBehavior;
177 if (Settings* settings = document()->settings())
178 editableLinkBehavior = settings->editableLinkBehavior();
180 switch (editableLinkBehavior) {
182 case EditableLinkDefaultBehavior:
183 case EditableLinkAlwaysLive:
186 case EditableLinkNeverLive:
189 // Don't set the link to be active if the current selection is in the same editable block as
191 case EditableLinkLiveWhenNotFocused:
192 if (down && document()->frame() && document()->frame()->selection()->rootEditableElement() == rootEditableElement())
196 case EditableLinkOnlyLiveWithShiftKey:
202 ContainerNode::setActive(down, pause);
205 void HTMLAnchorElement::parseMappedAttribute(Attribute* attr)
207 if (attr->name() == hrefAttr) {
208 bool wasLink = isLink();
209 setIsLink(!attr->isNull());
210 if (wasLink != isLink())
211 setNeedsStyleRecalc();
213 String parsedURL = stripLeadingAndTrailingHTMLSpaces(attr->value());
214 if (document()->isDNSPrefetchEnabled()) {
215 if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//"))
216 ResourceHandle::prepareForURL(document()->completeURL(parsedURL));
218 if (document()->page() && !document()->page()->javaScriptURLsAreAllowed() && protocolIsJavaScript(parsedURL)) {
220 attr->setValue(nullAtom);
223 } else if (attr->name() == nameAttr ||
224 attr->name() == titleAttr) {
226 } else if (attr->name() == relAttr)
227 setRel(attr->value());
229 HTMLElement::parseMappedAttribute(attr);
232 void HTMLAnchorElement::accessKeyAction(bool sendToAnyElement)
234 // send the mouse button events if the caller specified sendToAnyElement
235 dispatchSimulatedClick(0, sendToAnyElement);
238 bool HTMLAnchorElement::isURLAttribute(Attribute *attr) const
240 return attr->name() == hrefAttr;
243 bool HTMLAnchorElement::canStartSelection() const
245 // FIXME: We probably want this same behavior in SVGAElement too
247 return HTMLElement::canStartSelection();
248 return rendererIsEditable();
251 bool HTMLAnchorElement::draggable() const
253 // Should be draggable if we have an href attribute.
254 const AtomicString& value = getAttribute(draggableAttr);
255 if (equalIgnoringCase(value, "true"))
257 if (equalIgnoringCase(value, "false"))
259 return hasAttribute(hrefAttr);
262 KURL HTMLAnchorElement::href() const
264 return document()->completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
267 void HTMLAnchorElement::setHref(const AtomicString& value)
269 setAttribute(hrefAttr, value);
272 bool HTMLAnchorElement::hasRel(uint32_t relation) const
274 return m_linkRelations & relation;
277 void HTMLAnchorElement::setRel(const String& value)
280 SpaceSplitString newLinkRelations(value, true);
281 // FIXME: Add link relations as they are implemented
282 if (newLinkRelations.contains("noreferrer"))
283 m_linkRelations |= RelationNoReferrer;
286 const AtomicString& HTMLAnchorElement::name() const
288 return getAttribute(nameAttr);
291 short HTMLAnchorElement::tabIndex() const
293 // Skip the supportsFocus check in HTMLElement.
294 return Element::tabIndex();
297 String HTMLAnchorElement::target() const
299 return getAttribute(targetAttr);
302 String HTMLAnchorElement::hash() const
304 String fragmentIdentifier = href().fragmentIdentifier();
305 return fragmentIdentifier.isEmpty() ? emptyString() : "#" + fragmentIdentifier;
308 void HTMLAnchorElement::setHash(const String& value)
312 url.setFragmentIdentifier(value.substring(1));
314 url.setFragmentIdentifier(value);
315 setHref(url.string());
318 String HTMLAnchorElement::host() const
320 const KURL& url = href();
321 if (url.hostEnd() == url.pathStart())
323 if (isDefaultPortForProtocol(url.port(), url.protocol()))
325 return url.host() + ":" + String::number(url.port());
328 void HTMLAnchorElement::setHost(const String& value)
333 if (!url.canSetHostOrPort())
336 size_t separator = value.find(':');
340 if (separator == notFound)
341 url.setHostAndPort(value);
344 unsigned port = parsePortFromStringPosition(value, separator + 1, portEnd);
346 // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
347 // specifically goes against RFC 3986 (p3.2) and
348 // requires setting the port to "0" if it is set to empty string.
349 url.setHostAndPort(value.substring(0, separator + 1) + "0");
351 if (isDefaultPortForProtocol(port, url.protocol()))
352 url.setHostAndPort(value.substring(0, separator));
354 url.setHostAndPort(value.substring(0, portEnd));
357 setHref(url.string());
360 String HTMLAnchorElement::hostname() const
362 return href().host();
365 void HTMLAnchorElement::setHostname(const String& value)
367 // Before setting new value:
368 // Remove all leading U+002F SOLIDUS ("/") characters.
370 unsigned hostLength = value.length();
371 while (value[i] == '/')
378 if (!url.canSetHostOrPort())
381 url.setHost(value.substring(i));
382 setHref(url.string());
385 String HTMLAnchorElement::pathname() const
387 return href().path();
390 void HTMLAnchorElement::setPathname(const String& value)
393 if (!url.canSetPathname())
399 url.setPath("/" + value);
401 setHref(url.string());
404 String HTMLAnchorElement::port() const
406 if (href().hasPort())
407 return String::number(href().port());
409 return emptyString();
412 void HTMLAnchorElement::setPort(const String& value)
415 if (!url.canSetHostOrPort())
418 // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
419 // specifically goes against RFC 3986 (p3.2) and
420 // requires setting the port to "0" if it is set to empty string.
421 unsigned port = value.toUInt();
422 if (isDefaultPortForProtocol(port, url.protocol()))
427 setHref(url.string());
430 String HTMLAnchorElement::protocol() const
432 return href().protocol() + ":";
435 void HTMLAnchorElement::setProtocol(const String& value)
438 url.setProtocol(value);
439 setHref(url.string());
442 String HTMLAnchorElement::search() const
444 String query = href().query();
445 return query.isEmpty() ? emptyString() : "?" + query;
448 String HTMLAnchorElement::origin() const
450 RefPtr<SecurityOrigin> origin = SecurityOrigin::create(href());
451 return origin->toString();
454 String HTMLAnchorElement::getParameter(const String& name) const
456 ParsedURLParameters parameters;
457 href().copyParsedQueryTo(parameters);
458 return parameters.get(name);
461 void HTMLAnchorElement::setSearch(const String& value)
464 String newSearch = (value[0] == '?') ? value.substring(1) : value;
465 // Make sure that '#' in the query does not leak to the hash.
466 url.setQuery(newSearch.replace('#', "%23"));
468 setHref(url.string());
471 String HTMLAnchorElement::text()
476 String HTMLAnchorElement::toString() const
478 return href().string();
481 bool HTMLAnchorElement::isLiveLink() const
483 return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey);
486 void HTMLAnchorElement::sendPings(const KURL& destinationURL)
488 if (!hasAttribute(pingAttr) || !document()->settings()->hyperlinkAuditingEnabled())
491 SpaceSplitString pingURLs(getAttribute(pingAttr), true);
492 for (unsigned i = 0; i < pingURLs.size(); i++)
493 PingLoader::sendPing(document()->frame(), document()->completeURL(pingURLs[i]), destinationURL);
496 void HTMLAnchorElement::handleClick(Event* event)
498 event->setDefaultHandled();
500 Frame* frame = document()->frame();
504 String url = stripLeadingAndTrailingHTMLSpaces(fastGetAttribute(hrefAttr));
505 appendServerMapMousePosition(url, event);
506 KURL kurl = document()->completeURL(url);
508 #if ENABLE(DOWNLOAD_ATTRIBUTE)
509 if (hasAttribute(downloadAttr)) {
510 ResourceRequest request(kurl);
512 if (!hasRel(RelationNoReferrer)) {
513 String referrer = frame->loader()->outgoingReferrer();
514 if (!referrer.isEmpty() && !SecurityOrigin::shouldHideReferrer(kurl, referrer))
515 request.setHTTPReferrer(referrer);
516 frame->loader()->addExtraFieldsToMainResourceRequest(request);
519 frame->loader()->client()->startDownload(request, fastGetAttribute(downloadAttr));
522 frame->loader()->urlSelected(kurl, target(), event, false, false, hasRel(RelationNoReferrer) ? NoReferrer : SendReferrer);
527 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event)
529 if (!event->isMouseEvent())
530 return NonMouseEvent;
531 return static_cast<MouseEvent*>(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
534 bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const
536 if (!rendererIsEditable())
539 Settings* settings = document()->settings();
543 switch (settings->editableLinkBehavior()) {
544 case EditableLinkDefaultBehavior:
545 case EditableLinkAlwaysLive:
548 case EditableLinkNeverLive:
551 // If the selection prior to clicking on this link resided in the same editable block as this link,
552 // and the shift key isn't pressed, we don't want to follow the link.
553 case EditableLinkLiveWhenNotFocused:
554 return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && m_rootEditableElementForSelectionOnMouseDown != rootEditableElement());
556 case EditableLinkOnlyLiveWithShiftKey:
557 return eventType == MouseEventWithShiftKey;
560 ASSERT_NOT_REACHED();
564 bool isEnterKeyKeydownEvent(Event* event)
566 return event->type() == eventNames().keydownEvent && event->isKeyboardEvent() && static_cast<KeyboardEvent*>(event)->keyIdentifier() == "Enter";
569 bool isMiddleMouseButtonEvent(Event* event)
571 return event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == MiddleButton;
574 bool isLinkClick(Event* event)
576 return event->type() == eventNames().clickEvent && (!event->isMouseEvent() || static_cast<MouseEvent*>(event)->button() != RightButton);
579 void handleLinkClick(Event* event, Document* document, const String& url, const String& target, bool hideReferrer)
581 event->setDefaultHandled();
583 Frame* frame = document->frame();
586 frame->loader()->urlSelected(document->completeURL(url), target, event, false, false, hideReferrer ? NoReferrer : SendReferrer);