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 bool isSameOrigin = document().securityOrigin()->canRequest(completedURL);
439 const AtomicString& suggestedName = (isSameOrigin ? fastGetAttribute(downloadAttr) : nullAtom);
441 frame->loader().client()->loadURLExternally(request, NavigationPolicyDownload, suggestedName);
443 FrameLoadRequest frameRequest(&document(), request, target());
444 frameRequest.setTriggeringEvent(event);
445 if (hasRel(RelationNoReferrer))
446 frameRequest.setShouldSendReferrer(NeverSendReferrer);
447 frame->loader().load(frameRequest);
451 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event)
453 if (!event->isMouseEvent())
454 return NonMouseEvent;
455 return toMouseEvent(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
458 bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const
460 if (!rendererIsEditable())
463 Settings* settings = document().settings();
467 switch (settings->editableLinkBehavior()) {
468 case EditableLinkDefaultBehavior:
469 case EditableLinkAlwaysLive:
472 case EditableLinkNeverLive:
475 // If the selection prior to clicking on this link resided in the same editable block as this link,
476 // and the shift key isn't pressed, we don't want to follow the link.
477 case EditableLinkLiveWhenNotFocused:
478 return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && rootEditableElementForSelectionOnMouseDown() != rootEditableElement());
480 case EditableLinkOnlyLiveWithShiftKey:
481 return eventType == MouseEventWithShiftKey;
484 ASSERT_NOT_REACHED();
488 bool isEnterKeyKeydownEvent(Event* event)
490 return event->type() == EventTypeNames::keydown && event->isKeyboardEvent() && toKeyboardEvent(event)->keyIdentifier() == "Enter";
493 bool isLinkClick(Event* event)
495 return event->type() == EventTypeNames::click && (!event->isMouseEvent() || toMouseEvent(event)->button() != RightButton);
498 bool HTMLAnchorElement::willRespondToMouseClickEvents()
500 return isLink() || HTMLElement::willRespondToMouseClickEvents();
503 typedef HashMap<const HTMLAnchorElement*, RefPtr<Element> > RootEditableElementMap;
505 static RootEditableElementMap& rootEditableElementMap()
507 DEFINE_STATIC_LOCAL(RootEditableElementMap, map, ());
511 Element* HTMLAnchorElement::rootEditableElementForSelectionOnMouseDown() const
513 if (!m_hasRootEditableElementForSelectionOnMouseDown)
515 return rootEditableElementMap().get(this);
518 void HTMLAnchorElement::clearRootEditableElementForSelectionOnMouseDown()
520 if (!m_hasRootEditableElementForSelectionOnMouseDown)
522 rootEditableElementMap().remove(this);
523 m_hasRootEditableElementForSelectionOnMouseDown = false;
526 void HTMLAnchorElement::setRootEditableElementForSelectionOnMouseDown(Element* element)
529 clearRootEditableElementForSelectionOnMouseDown();
533 rootEditableElementMap().set(this, element);
534 m_hasRootEditableElementForSelectionOnMouseDown = true;
537 HTMLAnchorElement::PrefetchEventHandler* HTMLAnchorElement::prefetchEventHandler()
539 if (!m_prefetchEventHandler)
540 m_prefetchEventHandler = PrefetchEventHandler::create(this);
542 return m_prefetchEventHandler.get();
545 HTMLAnchorElement::PrefetchEventHandler::PrefetchEventHandler(HTMLAnchorElement* anchorElement)
546 : m_anchorElement(anchorElement)
548 ASSERT(m_anchorElement);
553 void HTMLAnchorElement::PrefetchEventHandler::reset()
555 m_hadHREFChanged = false;
556 m_mouseOverTimestamp = 0;
557 m_mouseDownTimestamp = 0;
558 m_hadTapUnconfirmed = false;
559 m_tapDownTimestamp = 0;
560 m_hasIssuedPreconnect = false;
563 void HTMLAnchorElement::PrefetchEventHandler::handleEvent(Event* event)
565 if (!shouldPrefetch(m_anchorElement->href()))
568 if (event->type() == EventTypeNames::mouseover)
569 handleMouseOver(event);
570 else if (event->type() == EventTypeNames::mouseout)
571 handleMouseOut(event);
572 else if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton)
573 handleLeftMouseDown(event);
574 else if (event->type() == EventTypeNames::gestureshowpress)
575 handleGestureShowPress(event);
576 else if (event->type() == EventTypeNames::gesturetapunconfirmed)
577 handleGestureTapUnconfirmed(event);
578 else if (isLinkClick(event))
582 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOver(Event* event)
584 if (m_mouseOverTimestamp == 0.0) {
585 m_mouseOverTimestamp = event->timeStamp();
587 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseOvers", 0, 2);
589 prefetch(blink::WebPreconnectMotivationLinkMouseOver);
593 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOut(Event* event)
595 if (m_mouseOverTimestamp > 0.0) {
596 double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp);
597 blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_NoClick", mouseOverDuration * 1000, 0, 10000, 100);
599 m_mouseOverTimestamp = 0.0;
603 void HTMLAnchorElement::PrefetchEventHandler::handleLeftMouseDown(Event* event)
605 m_mouseDownTimestamp = event->timeStamp();
607 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseDowns", 0, 2);
609 prefetch(blink::WebPreconnectMotivationLinkMouseDown);
612 void HTMLAnchorElement::PrefetchEventHandler::handleGestureTapUnconfirmed(Event* event)
614 m_hadTapUnconfirmed = true;
616 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.TapUnconfirmeds", 0, 2);
618 prefetch(blink::WebPreconnectMotivationLinkTapUnconfirmed);
621 void HTMLAnchorElement::PrefetchEventHandler::handleGestureShowPress(Event* event)
623 m_tapDownTimestamp = event->timeStamp();
625 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.TapDowns", 0, 2);
627 prefetch(blink::WebPreconnectMotivationLinkTapDown);
630 void HTMLAnchorElement::PrefetchEventHandler::handleClick(Event* event)
632 bool capturedMouseOver = (m_mouseOverTimestamp > 0.0);
633 if (capturedMouseOver) {
634 double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp);
636 blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_Click", mouseOverDuration * 1000, 0, 10000, 100);
639 bool capturedMouseDown = (m_mouseDownTimestamp > 0.0);
640 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseDownFollowedByClick", capturedMouseDown, 2);
642 if (capturedMouseDown) {
643 double mouseDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseDownTimestamp);
645 blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseDownDuration_Click", mouseDownDuration * 1000, 0, 10000, 100);
648 bool capturedTapDown = (m_tapDownTimestamp > 0.0);
649 if (capturedTapDown) {
650 double tapDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_tapDownTimestamp);
652 blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.TapDownDuration_Click", tapDownDuration * 1000, 0, 10000, 100);
655 int flags = (m_hadTapUnconfirmed ? 2 : 0) | (capturedTapDown ? 1 : 0);
656 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.PreTapEventsFollowedByClick", flags, 4);
659 bool HTMLAnchorElement::PrefetchEventHandler::shouldPrefetch(const KURL& url)
661 if (m_hadHREFChanged)
664 if (m_anchorElement->hasEventListeners(EventTypeNames::click))
667 if (!url.protocolIsInHTTPFamily())
670 Document& document = m_anchorElement->document();
672 if (!document.securityOrigin()->canDisplay(url))
675 if (url.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(document.url(), url))
678 LocalFrame* frame = document.frame();
682 // Links which create new window/tab are avoided because they may require user approval interaction.
683 if (!m_anchorElement->target().isEmpty())
689 void HTMLAnchorElement::PrefetchEventHandler::prefetch(blink::WebPreconnectMotivation motivation)
691 const KURL& url = m_anchorElement->href();
693 if (!shouldPrefetch(url))
696 // The precision of current MouseOver trigger is too low to actually trigger preconnects.
697 if (motivation == blink::WebPreconnectMotivationLinkMouseOver)
700 preconnectToURL(url, motivation);
701 m_hasIssuedPreconnect = true;
704 bool HTMLAnchorElement::isInteractiveContent() const