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"
29 #include "ElementShadow.h"
30 #include "EventNames.h"
32 #include "FrameLoaderClient.h"
33 #include "FrameLoaderTypes.h"
34 #include "HTMLImageElement.h"
35 #include "HTMLNames.h"
36 #include "HTMLParserIdioms.h"
37 #include "KeyboardEvent.h"
38 #include "MouseEvent.h"
39 #include "PingLoader.h"
40 #include "RenderImage.h"
41 #include "SecurityOrigin.h"
42 #include "SecurityPolicy.h"
44 #include <wtf/text/StringBuilder.h>
48 using namespace HTMLNames;
50 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document* document)
51 : HTMLElement(tagName, document)
52 , m_hasRootEditableElementForSelectionOnMouseDown(false)
53 , m_wasShiftKeyDownOnMouseDown(false)
55 , m_cachedVisitedLinkHash(0)
59 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document* document)
61 return adoptRef(new HTMLAnchorElement(aTag, document));
64 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(const QualifiedName& tagName, Document* document)
66 return adoptRef(new HTMLAnchorElement(tagName, document));
69 HTMLAnchorElement::~HTMLAnchorElement()
71 clearRootEditableElementForSelectionOnMouseDown();
74 // This function does not allow leading spaces before the port number.
75 static unsigned parsePortFromStringPosition(const String& value, unsigned portStart, unsigned& portEnd)
78 while (isASCIIDigit(value[portEnd]))
80 return value.substring(portStart, portEnd - portStart).toUInt();
83 bool HTMLAnchorElement::supportsFocus() const
85 if (rendererIsEditable())
86 return HTMLElement::supportsFocus();
87 // If not a link we should still be able to focus the element if it has tabIndex.
88 return isLink() || HTMLElement::supportsFocus();
91 bool HTMLAnchorElement::isMouseFocusable() const
93 // Anchor elements should be mouse focusable, https://bugs.webkit.org/show_bug.cgi?id=26856
94 #if !PLATFORM(GTK) && !PLATFORM(QT) && !PLATFORM(EFL)
96 // Only allow links with tabIndex or contentEditable to be mouse focusable.
97 return HTMLElement::supportsFocus();
100 // Allow tab index etc to control focus.
101 return HTMLElement::isMouseFocusable();
104 bool HTMLAnchorElement::isKeyboardFocusable(KeyboardEvent* event) const
107 return HTMLElement::isKeyboardFocusable(event);
112 if (!document()->frame())
115 if (!document()->frame()->eventHandler()->tabsToLinks(event))
118 if (isInCanvasSubtree())
121 return hasNonEmptyBoundingBox();
124 static void appendServerMapMousePosition(StringBuilder& url, Event* event)
126 if (!event->isMouseEvent())
129 ASSERT(event->target());
130 Node* target = event->target()->toNode();
132 if (!target->hasTagName(imgTag))
135 HTMLImageElement* imageElement = static_cast<HTMLImageElement*>(event->target()->toNode());
136 if (!imageElement || !imageElement->isServerMap())
139 RenderImage* renderer = toRenderImage(imageElement->renderer());
143 // FIXME: This should probably pass true for useTransforms.
144 FloatPoint absolutePosition = renderer->absoluteToLocal(FloatPoint(static_cast<MouseEvent*>(event)->pageX(), static_cast<MouseEvent*>(event)->pageY()));
145 int x = absolutePosition.x();
146 int y = absolutePosition.y();
153 void HTMLAnchorElement::defaultEventHandler(Event* event)
156 if (focused() && isEnterKeyKeydownEvent(event) && treatLinkAsLiveForEventType(NonMouseEvent)) {
157 event->setDefaultHandled();
158 dispatchSimulatedClick(event);
162 if (isLinkClick(event) && treatLinkAsLiveForEventType(eventType(event))) {
167 if (rendererIsEditable()) {
168 // This keeps track of the editable block that the selection was in (if it was in one) just before the link was clicked
169 // for the LiveWhenNotFocused editable link behavior
170 if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() != RightButton && document()->frame() && document()->frame()->selection()) {
171 setRootEditableElementForSelectionOnMouseDown(document()->frame()->selection()->rootEditableElement());
172 m_wasShiftKeyDownOnMouseDown = static_cast<MouseEvent*>(event)->shiftKey();
173 } else if (event->type() == eventNames().mouseoverEvent) {
174 // These are cleared on mouseover and not mouseout because their values are needed for drag events,
175 // but drag events happen after mouse out events.
176 clearRootEditableElementForSelectionOnMouseDown();
177 m_wasShiftKeyDownOnMouseDown = false;
182 HTMLElement::defaultEventHandler(event);
185 void HTMLAnchorElement::setActive(bool down, bool pause)
187 if (rendererIsEditable()) {
188 EditableLinkBehavior editableLinkBehavior = EditableLinkDefaultBehavior;
189 if (Settings* settings = document()->settings())
190 editableLinkBehavior = settings->editableLinkBehavior();
192 switch (editableLinkBehavior) {
194 case EditableLinkDefaultBehavior:
195 case EditableLinkAlwaysLive:
198 case EditableLinkNeverLive:
201 // Don't set the link to be active if the current selection is in the same editable block as
203 case EditableLinkLiveWhenNotFocused:
204 if (down && document()->frame() && document()->frame()->selection()->rootEditableElement() == rootEditableElement())
208 case EditableLinkOnlyLiveWithShiftKey:
214 ContainerNode::setActive(down, pause);
217 void HTMLAnchorElement::parseAttribute(const Attribute& attribute)
219 if (attribute.name() == hrefAttr) {
220 bool wasLink = isLink();
221 setIsLink(!attribute.isNull());
222 if (wasLink != isLink()) {
223 setNeedsStyleRecalc();
224 invalidateParentDistributionIfNecessary(this, SelectRuleFeatureSet::RuleFeatureLink | SelectRuleFeatureSet::RuleFeatureVisited | SelectRuleFeatureSet::RuleFeatureEnabled);
227 String parsedURL = stripLeadingAndTrailingHTMLSpaces(attribute.value());
228 if (document()->isDNSPrefetchEnabled()) {
229 if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//"))
230 prefetchDNS(document()->completeURL(parsedURL).host());
233 invalidateCachedVisitedLinkHash();
234 } else if (attribute.name() == nameAttr || attribute.name() == titleAttr) {
236 } else if (attribute.name() == relAttr)
237 setRel(attribute.value());
239 HTMLElement::parseAttribute(attribute);
242 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
244 // send the mouse button events if the caller specified sendMouseEvents
245 dispatchSimulatedClick(0, sendMouseEvents);
248 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
250 return attribute.name() == hrefAttr || HTMLElement::isURLAttribute(attribute);
253 bool HTMLAnchorElement::canStartSelection() const
255 // FIXME: We probably want this same behavior in SVGAElement too
257 return HTMLElement::canStartSelection();
258 return rendererIsEditable();
261 bool HTMLAnchorElement::draggable() const
263 // Should be draggable if we have an href attribute.
264 const AtomicString& value = getAttribute(draggableAttr);
265 if (equalIgnoringCase(value, "true"))
267 if (equalIgnoringCase(value, "false"))
269 return hasAttribute(hrefAttr);
272 KURL HTMLAnchorElement::href() const
274 return document()->completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
277 void HTMLAnchorElement::setHref(const AtomicString& value)
279 setAttribute(hrefAttr, value);
282 bool HTMLAnchorElement::hasRel(uint32_t relation) const
284 return m_linkRelations & relation;
287 void HTMLAnchorElement::setRel(const String& value)
290 SpaceSplitString newLinkRelations(value, true);
291 // FIXME: Add link relations as they are implemented
292 if (newLinkRelations.contains("noreferrer"))
293 m_linkRelations |= RelationNoReferrer;
296 const AtomicString& HTMLAnchorElement::name() const
298 return getNameAttribute();
301 short HTMLAnchorElement::tabIndex() const
303 // Skip the supportsFocus check in HTMLElement.
304 return Element::tabIndex();
307 String HTMLAnchorElement::target() const
309 return getAttribute(targetAttr);
312 String HTMLAnchorElement::hash() const
314 String fragmentIdentifier = href().fragmentIdentifier();
315 return fragmentIdentifier.isEmpty() ? emptyString() : "#" + fragmentIdentifier;
318 void HTMLAnchorElement::setHash(const String& value)
322 url.setFragmentIdentifier(value.substring(1));
324 url.setFragmentIdentifier(value);
325 setHref(url.string());
328 String HTMLAnchorElement::host() const
330 const KURL& url = href();
331 if (url.hostEnd() == url.pathStart())
333 if (isDefaultPortForProtocol(url.port(), url.protocol()))
335 return url.host() + ":" + String::number(url.port());
338 void HTMLAnchorElement::setHost(const String& value)
343 if (!url.canSetHostOrPort())
346 size_t separator = value.find(':');
350 if (separator == notFound)
351 url.setHostAndPort(value);
354 unsigned port = parsePortFromStringPosition(value, separator + 1, portEnd);
356 // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
357 // specifically goes against RFC 3986 (p3.2) and
358 // requires setting the port to "0" if it is set to empty string.
359 url.setHostAndPort(value.substring(0, separator + 1) + "0");
361 if (isDefaultPortForProtocol(port, url.protocol()))
362 url.setHostAndPort(value.substring(0, separator));
364 url.setHostAndPort(value.substring(0, portEnd));
367 setHref(url.string());
370 String HTMLAnchorElement::hostname() const
372 return href().host();
375 void HTMLAnchorElement::setHostname(const String& value)
377 // Before setting new value:
378 // Remove all leading U+002F SOLIDUS ("/") characters.
380 unsigned hostLength = value.length();
381 while (value[i] == '/')
388 if (!url.canSetHostOrPort())
391 url.setHost(value.substring(i));
392 setHref(url.string());
395 String HTMLAnchorElement::pathname() const
397 return href().path();
400 void HTMLAnchorElement::setPathname(const String& value)
403 if (!url.canSetPathname())
409 url.setPath("/" + value);
411 setHref(url.string());
414 String HTMLAnchorElement::port() const
416 if (href().hasPort())
417 return String::number(href().port());
419 return emptyString();
422 void HTMLAnchorElement::setPort(const String& value)
425 if (!url.canSetHostOrPort())
428 // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
429 // specifically goes against RFC 3986 (p3.2) and
430 // requires setting the port to "0" if it is set to empty string.
431 unsigned port = value.toUInt();
432 if (isDefaultPortForProtocol(port, url.protocol()))
437 setHref(url.string());
440 String HTMLAnchorElement::protocol() const
442 return href().protocol() + ":";
445 void HTMLAnchorElement::setProtocol(const String& value)
448 url.setProtocol(value);
449 setHref(url.string());
452 String HTMLAnchorElement::search() const
454 String query = href().query();
455 return query.isEmpty() ? emptyString() : "?" + query;
458 String HTMLAnchorElement::origin() const
460 RefPtr<SecurityOrigin> origin = SecurityOrigin::create(href());
461 return origin->toString();
464 void HTMLAnchorElement::setSearch(const String& value)
467 String newSearch = (value[0] == '?') ? value.substring(1) : value;
468 // Make sure that '#' in the query does not leak to the hash.
469 url.setQuery(newSearch.replaceWithLiteral('#', "%23"));
471 setHref(url.string());
474 String HTMLAnchorElement::text()
479 String HTMLAnchorElement::toString() const
481 return href().string();
484 bool HTMLAnchorElement::isLiveLink() const
486 return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey);
489 void HTMLAnchorElement::sendPings(const KURL& destinationURL)
491 if (!hasAttribute(pingAttr) || !document()->settings()->hyperlinkAuditingEnabled())
494 SpaceSplitString pingURLs(getAttribute(pingAttr), false);
495 for (unsigned i = 0; i < pingURLs.size(); i++)
496 PingLoader::sendPing(document()->frame(), document()->completeURL(pingURLs[i]), destinationURL);
499 void HTMLAnchorElement::handleClick(Event* event)
501 event->setDefaultHandled();
503 Frame* frame = document()->frame();
508 url.append(stripLeadingAndTrailingHTMLSpaces(fastGetAttribute(hrefAttr)));
509 appendServerMapMousePosition(url, event);
510 KURL kurl = document()->completeURL(url.toString());
512 #if ENABLE(DOWNLOAD_ATTRIBUTE)
513 if (hasAttribute(downloadAttr)) {
514 ResourceRequest request(kurl);
516 if (!hasRel(RelationNoReferrer)) {
517 String referrer = SecurityPolicy::generateReferrerHeader(document()->referrerPolicy(), kurl, frame->loader()->outgoingReferrer());
518 if (!referrer.isEmpty())
519 request.setHTTPReferrer(referrer);
520 frame->loader()->addExtraFieldsToMainResourceRequest(request);
523 frame->loader()->client()->startDownload(request, fastGetAttribute(downloadAttr));
526 frame->loader()->urlSelected(kurl, target(), event, false, false, hasRel(RelationNoReferrer) ? NeverSendReferrer : MaybeSendReferrer);
531 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event)
533 if (!event->isMouseEvent())
534 return NonMouseEvent;
535 return static_cast<MouseEvent*>(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
538 bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const
540 if (!rendererIsEditable())
543 Settings* settings = document()->settings();
547 switch (settings->editableLinkBehavior()) {
548 case EditableLinkDefaultBehavior:
549 case EditableLinkAlwaysLive:
552 case EditableLinkNeverLive:
555 // If the selection prior to clicking on this link resided in the same editable block as this link,
556 // and the shift key isn't pressed, we don't want to follow the link.
557 case EditableLinkLiveWhenNotFocused:
558 return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && rootEditableElementForSelectionOnMouseDown() != rootEditableElement());
560 case EditableLinkOnlyLiveWithShiftKey:
561 return eventType == MouseEventWithShiftKey;
564 ASSERT_NOT_REACHED();
568 bool isEnterKeyKeydownEvent(Event* event)
570 return event->type() == eventNames().keydownEvent && event->isKeyboardEvent() && static_cast<KeyboardEvent*>(event)->keyIdentifier() == "Enter";
573 bool isLinkClick(Event* event)
575 return event->type() == eventNames().clickEvent && (!event->isMouseEvent() || static_cast<MouseEvent*>(event)->button() != RightButton);
578 bool HTMLAnchorElement::willRespondToMouseClickEvents()
580 return isLink() || HTMLElement::willRespondToMouseClickEvents();
583 #if ENABLE(MICRODATA)
584 String HTMLAnchorElement::itemValueText() const
586 return getURLAttribute(hrefAttr);
589 void HTMLAnchorElement::setItemValueText(const String& value, ExceptionCode&)
591 setAttribute(hrefAttr, value);
595 typedef HashMap<const HTMLAnchorElement*, RefPtr<Element> > RootEditableElementMap;
597 static RootEditableElementMap& rootEditableElementMap()
599 DEFINE_STATIC_LOCAL(RootEditableElementMap, map, ());
603 Element* HTMLAnchorElement::rootEditableElementForSelectionOnMouseDown() const
605 if (!m_hasRootEditableElementForSelectionOnMouseDown)
607 return rootEditableElementMap().get(this).get();
610 void HTMLAnchorElement::clearRootEditableElementForSelectionOnMouseDown()
612 if (!m_hasRootEditableElementForSelectionOnMouseDown)
614 rootEditableElementMap().remove(this);
615 m_hasRootEditableElementForSelectionOnMouseDown = false;
618 void HTMLAnchorElement::setRootEditableElementForSelectionOnMouseDown(Element* element)
621 clearRootEditableElementForSelectionOnMouseDown();
625 rootEditableElementMap().set(this, element);
626 m_hasRootEditableElementForSelectionOnMouseDown = true;