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 "core/html/HTMLAnchorElement.h"
27 #include "core/dom/Attribute.h"
28 #include "core/editing/FrameSelection.h"
29 #include "core/events/KeyboardEvent.h"
30 #include "core/events/MouseEvent.h"
31 #include "core/frame/FrameHost.h"
32 #include "core/frame/LocalFrame.h"
33 #include "core/frame/Settings.h"
34 #include "core/frame/UseCounter.h"
35 #include "core/html/HTMLFormElement.h"
36 #include "core/html/HTMLImageElement.h"
37 #include "core/html/parser/HTMLParserIdioms.h"
38 #include "core/loader/FrameLoadRequest.h"
39 #include "core/loader/FrameLoader.h"
40 #include "core/loader/FrameLoaderClient.h"
41 #include "core/loader/FrameLoaderTypes.h"
42 #include "core/loader/PingLoader.h"
43 #include "core/page/Chrome.h"
44 #include "core/page/ChromeClient.h"
45 #include "core/rendering/RenderImage.h"
46 #include "core/svg/graphics/SVGImage.h"
47 #include "platform/PlatformMouseEvent.h"
48 #include "platform/network/DNS.h"
49 #include "platform/network/ResourceRequest.h"
50 #include "platform/weborigin/KnownPorts.h"
51 #include "platform/weborigin/SecurityOrigin.h"
52 #include "platform/weborigin/SecurityPolicy.h"
53 #include "public/platform/Platform.h"
54 #include "public/platform/WebPrescientNetworking.h"
55 #include "public/platform/WebURL.h"
56 #include "wtf/text/StringBuilder.h"
62 void preconnectToURL(const KURL& url, blink::WebPreconnectMotivation motivation)
64 blink::WebPrescientNetworking* prescientNetworking = blink::Platform::current()->prescientNetworking();
65 if (!prescientNetworking)
68 prescientNetworking->preconnect(url, motivation);
73 class HTMLAnchorElement::PrefetchEventHandler {
75 static PassOwnPtr<PrefetchEventHandler> create(HTMLAnchorElement* anchorElement)
77 return adoptPtr(new HTMLAnchorElement::PrefetchEventHandler(anchorElement));
82 void handleEvent(Event* e);
83 void didChangeHREF() { m_hadHREFChanged = true; }
84 bool hasIssuedPreconnect() const { return m_hasIssuedPreconnect; }
87 explicit PrefetchEventHandler(HTMLAnchorElement*);
89 void handleMouseOver(Event* event);
90 void handleMouseOut(Event* event);
91 void handleLeftMouseDown(Event* event);
92 void handleGestureTapUnconfirmed(Event*);
93 void handleGestureShowPress(Event*);
94 void handleClick(Event* event);
96 bool shouldPrefetch(const KURL&);
97 void prefetch(blink::WebPreconnectMotivation);
99 HTMLAnchorElement* m_anchorElement;
100 double m_mouseOverTimestamp;
101 double m_mouseDownTimestamp;
102 double m_tapDownTimestamp;
103 bool m_hadHREFChanged;
104 bool m_hadTapUnconfirmed;
105 bool m_hasIssuedPreconnect;
108 using namespace HTMLNames;
110 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document& document)
111 : HTMLElement(tagName, document)
112 , m_hasRootEditableElementForSelectionOnMouseDown(false)
113 , m_wasShiftKeyDownOnMouseDown(false)
115 , m_cachedVisitedLinkHash(0)
117 ScriptWrappable::init(this);
120 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document& document)
122 return adoptRef(new HTMLAnchorElement(aTag, document));
125 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(const QualifiedName& tagName, Document& document)
127 return adoptRef(new HTMLAnchorElement(tagName, document));
130 HTMLAnchorElement::~HTMLAnchorElement()
132 clearRootEditableElementForSelectionOnMouseDown();
135 bool HTMLAnchorElement::supportsFocus() const
137 if (rendererIsEditable())
138 return HTMLElement::supportsFocus();
139 // If not a link we should still be able to focus the element if it has tabIndex.
140 return isLink() || HTMLElement::supportsFocus();
143 bool HTMLAnchorElement::isMouseFocusable() const
145 // Links are focusable by default, but only allow links with tabindex or contenteditable to be mouse focusable.
146 // https://bugs.webkit.org/show_bug.cgi?id=26856
148 return HTMLElement::supportsFocus();
150 return HTMLElement::isMouseFocusable();
153 bool HTMLAnchorElement::isKeyboardFocusable() const
155 ASSERT(document().isActive());
157 if (isFocusable() && Element::supportsFocus())
158 return HTMLElement::isKeyboardFocusable();
160 if (isLink() && !document().frameHost()->chrome().client().tabsToLinks())
162 return HTMLElement::isKeyboardFocusable();
165 static void appendServerMapMousePosition(StringBuilder& url, Event* event)
167 if (!event->isMouseEvent())
170 ASSERT(event->target());
171 Node* target = event->target()->toNode();
173 if (!isHTMLImageElement(*target))
176 HTMLImageElement& imageElement = toHTMLImageElement(*target);
177 if (!imageElement.isServerMap())
180 if (!imageElement.renderer() || !imageElement.renderer()->isRenderImage())
182 RenderImage* renderer = toRenderImage(imageElement.renderer());
184 // FIXME: This should probably pass true for useTransforms.
185 FloatPoint absolutePosition = renderer->absoluteToLocal(FloatPoint(toMouseEvent(event)->pageX(), toMouseEvent(event)->pageY()));
186 int x = absolutePosition.x();
187 int y = absolutePosition.y();
194 void HTMLAnchorElement::defaultEventHandler(Event* event)
197 if (focused() && isEnterKeyKeydownEvent(event) && treatLinkAsLiveForEventType(NonMouseEvent)) {
198 event->setDefaultHandled();
199 dispatchSimulatedClick(event);
203 prefetchEventHandler()->handleEvent(event);
205 if (isLinkClick(event) && treatLinkAsLiveForEventType(eventType(event))) {
207 prefetchEventHandler()->reset();
211 if (rendererIsEditable()) {
212 // This keeps track of the editable block that the selection was in (if it was in one) just before the link was clicked
213 // for the LiveWhenNotFocused editable link behavior
214 if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() != RightButton && document().frame()) {
215 setRootEditableElementForSelectionOnMouseDown(document().frame()->selection().rootEditableElement());
216 m_wasShiftKeyDownOnMouseDown = toMouseEvent(event)->shiftKey();
217 } else if (event->type() == EventTypeNames::mouseover) {
218 // These are cleared on mouseover and not mouseout because their values are needed for drag events,
219 // but drag events happen after mouse out events.
220 clearRootEditableElementForSelectionOnMouseDown();
221 m_wasShiftKeyDownOnMouseDown = false;
226 HTMLElement::defaultEventHandler(event);
229 void HTMLAnchorElement::setActive(bool down)
231 if (rendererIsEditable()) {
232 EditableLinkBehavior editableLinkBehavior = EditableLinkDefaultBehavior;
233 if (Settings* settings = document().settings())
234 editableLinkBehavior = settings->editableLinkBehavior();
236 switch (editableLinkBehavior) {
238 case EditableLinkDefaultBehavior:
239 case EditableLinkAlwaysLive:
242 case EditableLinkNeverLive:
245 // Don't set the link to be active if the current selection is in the same editable block as
247 case EditableLinkLiveWhenNotFocused:
248 if (down && document().frame() && document().frame()->selection().rootEditableElement() == rootEditableElement())
252 case EditableLinkOnlyLiveWithShiftKey:
258 ContainerNode::setActive(down);
261 void HTMLAnchorElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
263 if (name == hrefAttr) {
264 bool wasLink = isLink();
265 setIsLink(!value.isNull());
266 if (wasLink != isLink()) {
267 didAffectSelector(AffectedSelectorLink | AffectedSelectorVisited | AffectedSelectorEnabled);
268 if (wasLink && treeScope().adjustedFocusedElement() == this) {
269 // We might want to call blur(), but it's dangerous to dispatch
271 document().setNeedsFocusedElementCheck();
275 String parsedURL = stripLeadingAndTrailingHTMLSpaces(value);
276 if (document().isDNSPrefetchEnabled()) {
277 if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//"))
278 prefetchDNS(document().completeURL(parsedURL).host());
282 prefetchEventHandler()->didChangeHREF();
284 invalidateCachedVisitedLinkHash();
285 } else if (name == nameAttr || name == titleAttr) {
287 } else if (name == relAttr)
290 HTMLElement::parseAttribute(name, value);
293 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
295 dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
298 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
300 return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
303 bool HTMLAnchorElement::hasLegalLinkAttribute(const QualifiedName& name) const
305 return name == hrefAttr || HTMLElement::hasLegalLinkAttribute(name);
308 bool HTMLAnchorElement::canStartSelection() const
310 // FIXME: We probably want this same behavior in SVGAElement too
312 return HTMLElement::canStartSelection();
313 return rendererIsEditable();
316 bool HTMLAnchorElement::draggable() const
318 // Should be draggable if we have an href attribute.
319 const AtomicString& value = getAttribute(draggableAttr);
320 if (equalIgnoringCase(value, "true"))
322 if (equalIgnoringCase(value, "false"))
324 return hasAttribute(hrefAttr);
327 KURL HTMLAnchorElement::href() const
329 return document().completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
332 void HTMLAnchorElement::setHref(const AtomicString& value)
334 setAttribute(hrefAttr, value);
337 KURL HTMLAnchorElement::url() const
342 void HTMLAnchorElement::setURL(const KURL& url)
344 setHref(AtomicString(url.string()));
347 String HTMLAnchorElement::input() const
349 return getAttribute(hrefAttr);
352 void HTMLAnchorElement::setInput(const String& value)
354 setHref(AtomicString(value));
357 bool HTMLAnchorElement::hasRel(uint32_t relation) const
359 return m_linkRelations & relation;
362 void HTMLAnchorElement::setRel(const AtomicString& value)
365 SpaceSplitString newLinkRelations(value, true);
366 // FIXME: Add link relations as they are implemented
367 if (newLinkRelations.contains("noreferrer"))
368 m_linkRelations |= RelationNoReferrer;
371 const AtomicString& HTMLAnchorElement::name() const
373 return getNameAttribute();
376 short HTMLAnchorElement::tabIndex() const
378 // Skip the supportsFocus check in HTMLElement.
379 return Element::tabIndex();
382 AtomicString HTMLAnchorElement::target() const
384 return getAttribute(targetAttr);
388 String HTMLAnchorElement::text()
393 bool HTMLAnchorElement::isLiveLink() const
395 return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey);
398 void HTMLAnchorElement::sendPings(const KURL& destinationURL)
400 const AtomicString& pingValue = getAttribute(pingAttr);
401 if (pingValue.isNull() || !document().settings() || !document().settings()->hyperlinkAuditingEnabled())
404 UseCounter::count(document(), UseCounter::HTMLAnchorElementPingAttribute);
406 SpaceSplitString pingURLs(pingValue, false);
407 for (unsigned i = 0; i < pingURLs.size(); i++)
408 PingLoader::sendPing(document().frame(), document().completeURL(pingURLs[i]), destinationURL);
411 void HTMLAnchorElement::handleClick(Event* event)
413 event->setDefaultHandled();
415 LocalFrame* frame = document().frame();
420 url.append(stripLeadingAndTrailingHTMLSpaces(fastGetAttribute(hrefAttr)));
421 appendServerMapMousePosition(url, event);
422 KURL completedURL = document().completeURL(url.toString());
424 // Schedule the ping before the frame load. Prerender in Chrome may kill the renderer as soon as the navigation is
426 sendPings(completedURL);
428 ResourceRequest request(completedURL);
429 if (prefetchEventHandler()->hasIssuedPreconnect())
430 frame->loader().client()->dispatchWillRequestAfterPreconnect(request);
431 if (hasAttribute(downloadAttr)) {
432 if (!hasRel(RelationNoReferrer)) {
433 String referrer = SecurityPolicy::generateReferrerHeader(document().referrerPolicy(), completedURL, document().outgoingReferrer());
434 if (!referrer.isEmpty())
435 request.setHTTPReferrer(Referrer(referrer, document().referrerPolicy()));
438 frame->loader().client()->loadURLExternally(request, NavigationPolicyDownload, fastGetAttribute(downloadAttr));
440 FrameLoadRequest frameRequest(&document(), request, target());
441 frameRequest.setTriggeringEvent(event);
442 if (hasRel(RelationNoReferrer))
443 frameRequest.setShouldSendReferrer(NeverSendReferrer);
444 frame->loader().load(frameRequest);
448 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event)
450 if (!event->isMouseEvent())
451 return NonMouseEvent;
452 return toMouseEvent(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
455 bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const
457 if (!rendererIsEditable())
460 Settings* settings = document().settings();
464 switch (settings->editableLinkBehavior()) {
465 case EditableLinkDefaultBehavior:
466 case EditableLinkAlwaysLive:
469 case EditableLinkNeverLive:
472 // If the selection prior to clicking on this link resided in the same editable block as this link,
473 // and the shift key isn't pressed, we don't want to follow the link.
474 case EditableLinkLiveWhenNotFocused:
475 return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && rootEditableElementForSelectionOnMouseDown() != rootEditableElement());
477 case EditableLinkOnlyLiveWithShiftKey:
478 return eventType == MouseEventWithShiftKey;
481 ASSERT_NOT_REACHED();
485 bool isEnterKeyKeydownEvent(Event* event)
487 return event->type() == EventTypeNames::keydown && event->isKeyboardEvent() && toKeyboardEvent(event)->keyIdentifier() == "Enter";
490 bool isLinkClick(Event* event)
492 return event->type() == EventTypeNames::click && (!event->isMouseEvent() || toMouseEvent(event)->button() != RightButton);
495 bool HTMLAnchorElement::willRespondToMouseClickEvents()
497 return isLink() || HTMLElement::willRespondToMouseClickEvents();
500 typedef HashMap<const HTMLAnchorElement*, RefPtr<Element> > RootEditableElementMap;
502 static RootEditableElementMap& rootEditableElementMap()
504 DEFINE_STATIC_LOCAL(RootEditableElementMap, map, ());
508 Element* HTMLAnchorElement::rootEditableElementForSelectionOnMouseDown() const
510 if (!m_hasRootEditableElementForSelectionOnMouseDown)
512 return rootEditableElementMap().get(this);
515 void HTMLAnchorElement::clearRootEditableElementForSelectionOnMouseDown()
517 if (!m_hasRootEditableElementForSelectionOnMouseDown)
519 rootEditableElementMap().remove(this);
520 m_hasRootEditableElementForSelectionOnMouseDown = false;
523 void HTMLAnchorElement::setRootEditableElementForSelectionOnMouseDown(Element* element)
526 clearRootEditableElementForSelectionOnMouseDown();
530 rootEditableElementMap().set(this, element);
531 m_hasRootEditableElementForSelectionOnMouseDown = true;
534 HTMLAnchorElement::PrefetchEventHandler* HTMLAnchorElement::prefetchEventHandler()
536 if (!m_prefetchEventHandler)
537 m_prefetchEventHandler = PrefetchEventHandler::create(this);
539 return m_prefetchEventHandler.get();
542 HTMLAnchorElement::PrefetchEventHandler::PrefetchEventHandler(HTMLAnchorElement* anchorElement)
543 : m_anchorElement(anchorElement)
545 ASSERT(m_anchorElement);
550 void HTMLAnchorElement::PrefetchEventHandler::reset()
552 m_hadHREFChanged = false;
553 m_mouseOverTimestamp = 0;
554 m_mouseDownTimestamp = 0;
555 m_hadTapUnconfirmed = false;
556 m_tapDownTimestamp = 0;
557 m_hasIssuedPreconnect = false;
560 void HTMLAnchorElement::PrefetchEventHandler::handleEvent(Event* event)
562 if (!shouldPrefetch(m_anchorElement->href()))
565 if (event->type() == EventTypeNames::mouseover)
566 handleMouseOver(event);
567 else if (event->type() == EventTypeNames::mouseout)
568 handleMouseOut(event);
569 else if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton)
570 handleLeftMouseDown(event);
571 else if (event->type() == EventTypeNames::gestureshowpress)
572 handleGestureShowPress(event);
573 else if (event->type() == EventTypeNames::gesturetapunconfirmed)
574 handleGestureTapUnconfirmed(event);
575 else if (isLinkClick(event))
579 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOver(Event* event)
581 if (m_mouseOverTimestamp == 0.0) {
582 m_mouseOverTimestamp = event->timeStamp();
584 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseOvers", 0, 2);
586 prefetch(blink::WebPreconnectMotivationLinkMouseOver);
590 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOut(Event* event)
592 if (m_mouseOverTimestamp > 0.0) {
593 double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp);
594 blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_NoClick", mouseOverDuration * 1000, 0, 10000, 100);
596 m_mouseOverTimestamp = 0.0;
600 void HTMLAnchorElement::PrefetchEventHandler::handleLeftMouseDown(Event* event)
602 m_mouseDownTimestamp = event->timeStamp();
604 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseDowns", 0, 2);
606 prefetch(blink::WebPreconnectMotivationLinkMouseDown);
609 void HTMLAnchorElement::PrefetchEventHandler::handleGestureTapUnconfirmed(Event* event)
611 m_hadTapUnconfirmed = true;
613 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.TapUnconfirmeds", 0, 2);
615 prefetch(blink::WebPreconnectMotivationLinkTapUnconfirmed);
618 void HTMLAnchorElement::PrefetchEventHandler::handleGestureShowPress(Event* event)
620 m_tapDownTimestamp = event->timeStamp();
622 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.TapDowns", 0, 2);
624 prefetch(blink::WebPreconnectMotivationLinkTapDown);
627 void HTMLAnchorElement::PrefetchEventHandler::handleClick(Event* event)
629 bool capturedMouseOver = (m_mouseOverTimestamp > 0.0);
630 if (capturedMouseOver) {
631 double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp);
633 blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_Click", mouseOverDuration * 1000, 0, 10000, 100);
636 bool capturedMouseDown = (m_mouseDownTimestamp > 0.0);
637 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseDownFollowedByClick", capturedMouseDown, 2);
639 if (capturedMouseDown) {
640 double mouseDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseDownTimestamp);
642 blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseDownDuration_Click", mouseDownDuration * 1000, 0, 10000, 100);
645 bool capturedTapDown = (m_tapDownTimestamp > 0.0);
646 if (capturedTapDown) {
647 double tapDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_tapDownTimestamp);
649 blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.TapDownDuration_Click", tapDownDuration * 1000, 0, 10000, 100);
652 int flags = (m_hadTapUnconfirmed ? 2 : 0) | (capturedTapDown ? 1 : 0);
653 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.PreTapEventsFollowedByClick", flags, 4);
656 bool HTMLAnchorElement::PrefetchEventHandler::shouldPrefetch(const KURL& url)
658 if (m_hadHREFChanged)
661 if (m_anchorElement->hasEventListeners(EventTypeNames::click))
664 if (!url.protocolIsInHTTPFamily())
667 Document& document = m_anchorElement->document();
669 if (!document.securityOrigin()->canDisplay(url))
672 if (url.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(document.url(), url))
675 LocalFrame* frame = document.frame();
679 // Links which create new window/tab are avoided because they may require user approval interaction.
680 if (!m_anchorElement->target().isEmpty())
686 void HTMLAnchorElement::PrefetchEventHandler::prefetch(blink::WebPreconnectMotivation motivation)
688 const KURL& url = m_anchorElement->href();
690 if (!shouldPrefetch(url))
693 // The precision of current MouseOver trigger is too low to actually trigger preconnects.
694 if (motivation == blink::WebPreconnectMotivationLinkMouseOver)
697 preconnectToURL(url, motivation);
698 m_hasIssuedPreconnect = true;
701 bool HTMLAnchorElement::isInteractiveContent() const