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)
113 , m_cachedVisitedLinkHash(0)
115 ScriptWrappable::init(this);
118 PassRefPtrWillBeRawPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document& document)
120 return adoptRefWillBeRefCountedGarbageCollected(new HTMLAnchorElement(aTag, document));
123 HTMLAnchorElement::~HTMLAnchorElement()
127 bool HTMLAnchorElement::supportsFocus() const
129 if (rendererIsEditable())
130 return HTMLElement::supportsFocus();
131 // If not a link we should still be able to focus the element if it has tabIndex.
132 return isLink() || HTMLElement::supportsFocus();
135 bool HTMLAnchorElement::isMouseFocusable() const
137 // Links are focusable by default, but only allow links with tabindex or contenteditable to be mouse focusable.
138 // https://bugs.webkit.org/show_bug.cgi?id=26856
140 return HTMLElement::supportsFocus();
142 return HTMLElement::isMouseFocusable();
145 bool HTMLAnchorElement::isKeyboardFocusable() const
147 ASSERT(document().isActive());
149 if (isFocusable() && Element::supportsFocus())
150 return HTMLElement::isKeyboardFocusable();
152 if (isLink() && !document().frameHost()->chrome().client().tabsToLinks())
154 return HTMLElement::isKeyboardFocusable();
157 static void appendServerMapMousePosition(StringBuilder& url, Event* event)
159 if (!event->isMouseEvent())
162 ASSERT(event->target());
163 Node* target = event->target()->toNode();
165 if (!isHTMLImageElement(*target))
168 HTMLImageElement& imageElement = toHTMLImageElement(*target);
169 if (!imageElement.isServerMap())
172 if (!imageElement.renderer() || !imageElement.renderer()->isRenderImage())
174 RenderImage* renderer = toRenderImage(imageElement.renderer());
176 // FIXME: This should probably pass true for useTransforms.
177 FloatPoint absolutePosition = renderer->absoluteToLocal(FloatPoint(toMouseEvent(event)->pageX(), toMouseEvent(event)->pageY()));
178 int x = absolutePosition.x();
179 int y = absolutePosition.y();
186 void HTMLAnchorElement::defaultEventHandler(Event* event)
189 if (focused() && isEnterKeyKeydownEvent(event) && isLiveLink()) {
190 event->setDefaultHandled();
191 dispatchSimulatedClick(event);
195 prefetchEventHandler()->handleEvent(event);
197 if (isLinkClick(event) && isLiveLink()) {
199 prefetchEventHandler()->reset();
204 HTMLElement::defaultEventHandler(event);
207 void HTMLAnchorElement::setActive(bool down)
209 if (rendererIsEditable())
212 ContainerNode::setActive(down);
215 void HTMLAnchorElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
217 if (name == hrefAttr) {
218 bool wasLink = isLink();
219 setIsLink(!value.isNull());
220 if (wasLink != isLink()) {
221 didAffectSelector(AffectedSelectorLink | AffectedSelectorVisited | AffectedSelectorEnabled);
222 if (wasLink && treeScope().adjustedFocusedElement() == this) {
223 // We might want to call blur(), but it's dangerous to dispatch
225 document().setNeedsFocusedElementCheck();
229 String parsedURL = stripLeadingAndTrailingHTMLSpaces(value);
230 if (document().isDNSPrefetchEnabled()) {
231 if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//"))
232 prefetchDNS(document().completeURL(parsedURL).host());
236 prefetchEventHandler()->didChangeHREF();
238 invalidateCachedVisitedLinkHash();
239 } else if (name == nameAttr || name == titleAttr) {
241 } else if (name == relAttr)
244 HTMLElement::parseAttribute(name, value);
247 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
249 dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
252 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
254 return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
257 bool HTMLAnchorElement::hasLegalLinkAttribute(const QualifiedName& name) const
259 return name == hrefAttr || HTMLElement::hasLegalLinkAttribute(name);
262 bool HTMLAnchorElement::canStartSelection() const
265 return HTMLElement::canStartSelection();
266 return rendererIsEditable();
269 bool HTMLAnchorElement::draggable() const
271 // Should be draggable if we have an href attribute.
272 const AtomicString& value = getAttribute(draggableAttr);
273 if (equalIgnoringCase(value, "true"))
275 if (equalIgnoringCase(value, "false"))
277 return hasAttribute(hrefAttr);
280 KURL HTMLAnchorElement::href() const
282 return document().completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
285 void HTMLAnchorElement::setHref(const AtomicString& value)
287 setAttribute(hrefAttr, value);
290 KURL HTMLAnchorElement::url() const
295 void HTMLAnchorElement::setURL(const KURL& url)
297 setHref(AtomicString(url.string()));
300 String HTMLAnchorElement::input() const
302 return getAttribute(hrefAttr);
305 void HTMLAnchorElement::setInput(const String& value)
307 setHref(AtomicString(value));
310 bool HTMLAnchorElement::hasRel(uint32_t relation) const
312 return m_linkRelations & relation;
315 void HTMLAnchorElement::setRel(const AtomicString& value)
318 SpaceSplitString newLinkRelations(value, true);
319 // FIXME: Add link relations as they are implemented
320 if (newLinkRelations.contains("noreferrer"))
321 m_linkRelations |= RelationNoReferrer;
324 const AtomicString& HTMLAnchorElement::name() const
326 return getNameAttribute();
329 short HTMLAnchorElement::tabIndex() const
331 // Skip the supportsFocus check in HTMLElement.
332 return Element::tabIndex();
335 AtomicString HTMLAnchorElement::target() const
337 return getAttribute(targetAttr);
340 bool HTMLAnchorElement::isLiveLink() const
342 return isLink() && !rendererIsEditable();
345 void HTMLAnchorElement::sendPings(const KURL& destinationURL)
347 const AtomicString& pingValue = getAttribute(pingAttr);
348 if (pingValue.isNull() || !document().settings() || !document().settings()->hyperlinkAuditingEnabled())
351 UseCounter::count(document(), UseCounter::HTMLAnchorElementPingAttribute);
353 SpaceSplitString pingURLs(pingValue, false);
354 for (unsigned i = 0; i < pingURLs.size(); i++)
355 PingLoader::sendPing(document().frame(), document().completeURL(pingURLs[i]), destinationURL);
358 void HTMLAnchorElement::handleClick(Event* event)
360 event->setDefaultHandled();
362 LocalFrame* frame = document().frame();
367 url.append(stripLeadingAndTrailingHTMLSpaces(fastGetAttribute(hrefAttr)));
368 appendServerMapMousePosition(url, event);
369 KURL completedURL = document().completeURL(url.toString());
371 // Schedule the ping before the frame load. Prerender in Chrome may kill the renderer as soon as the navigation is
373 sendPings(completedURL);
375 ResourceRequest request(completedURL);
376 if (prefetchEventHandler()->hasIssuedPreconnect())
377 frame->loader().client()->dispatchWillRequestAfterPreconnect(request);
378 if (hasAttribute(downloadAttr)) {
379 if (!hasRel(RelationNoReferrer)) {
380 String referrer = SecurityPolicy::generateReferrerHeader(document().referrerPolicy(), completedURL, document().outgoingReferrer());
381 if (!referrer.isEmpty())
382 request.setHTTPReferrer(Referrer(referrer, document().referrerPolicy()));
385 bool isSameOrigin = document().securityOrigin()->canRequest(completedURL);
386 const AtomicString& suggestedName = (isSameOrigin ? fastGetAttribute(downloadAttr) : nullAtom);
388 frame->loader().client()->loadURLExternally(request, NavigationPolicyDownload, suggestedName);
390 FrameLoadRequest frameRequest(&document(), request, target());
391 frameRequest.setTriggeringEvent(event);
392 if (hasRel(RelationNoReferrer))
393 frameRequest.setShouldSendReferrer(NeverSendReferrer);
394 frame->loader().load(frameRequest);
398 bool isEnterKeyKeydownEvent(Event* event)
400 return event->type() == EventTypeNames::keydown && event->isKeyboardEvent() && toKeyboardEvent(event)->keyIdentifier() == "Enter";
403 bool isLinkClick(Event* event)
405 return event->type() == EventTypeNames::click && (!event->isMouseEvent() || toMouseEvent(event)->button() != RightButton);
408 bool HTMLAnchorElement::willRespondToMouseClickEvents()
410 return isLink() || HTMLElement::willRespondToMouseClickEvents();
413 HTMLAnchorElement::PrefetchEventHandler* HTMLAnchorElement::prefetchEventHandler()
415 if (!m_prefetchEventHandler)
416 m_prefetchEventHandler = PrefetchEventHandler::create(this);
418 return m_prefetchEventHandler.get();
421 HTMLAnchorElement::PrefetchEventHandler::PrefetchEventHandler(HTMLAnchorElement* anchorElement)
422 : m_anchorElement(anchorElement)
424 ASSERT(m_anchorElement);
429 void HTMLAnchorElement::PrefetchEventHandler::reset()
431 m_hadHREFChanged = false;
432 m_mouseOverTimestamp = 0;
433 m_mouseDownTimestamp = 0;
434 m_hadTapUnconfirmed = false;
435 m_tapDownTimestamp = 0;
436 m_hasIssuedPreconnect = false;
439 void HTMLAnchorElement::PrefetchEventHandler::handleEvent(Event* event)
441 if (!shouldPrefetch(m_anchorElement->href()))
444 if (event->type() == EventTypeNames::mouseover)
445 handleMouseOver(event);
446 else if (event->type() == EventTypeNames::mouseout)
447 handleMouseOut(event);
448 else if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton)
449 handleLeftMouseDown(event);
450 else if (event->type() == EventTypeNames::gestureshowpress)
451 handleGestureShowPress(event);
452 else if (event->type() == EventTypeNames::gesturetapunconfirmed)
453 handleGestureTapUnconfirmed(event);
454 else if (isLinkClick(event))
458 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOver(Event* event)
460 if (m_mouseOverTimestamp == 0.0) {
461 m_mouseOverTimestamp = event->timeStamp();
463 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseOvers", 0, 2);
465 prefetch(blink::WebPreconnectMotivationLinkMouseOver);
469 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOut(Event* event)
471 if (m_mouseOverTimestamp > 0.0) {
472 double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp);
473 blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_NoClick", mouseOverDuration * 1000, 0, 10000, 100);
475 m_mouseOverTimestamp = 0.0;
479 void HTMLAnchorElement::PrefetchEventHandler::handleLeftMouseDown(Event* event)
481 m_mouseDownTimestamp = event->timeStamp();
483 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseDowns", 0, 2);
485 prefetch(blink::WebPreconnectMotivationLinkMouseDown);
488 void HTMLAnchorElement::PrefetchEventHandler::handleGestureTapUnconfirmed(Event* event)
490 m_hadTapUnconfirmed = true;
492 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.TapUnconfirmeds", 0, 2);
494 prefetch(blink::WebPreconnectMotivationLinkTapUnconfirmed);
497 void HTMLAnchorElement::PrefetchEventHandler::handleGestureShowPress(Event* event)
499 m_tapDownTimestamp = event->timeStamp();
501 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.TapDowns", 0, 2);
503 prefetch(blink::WebPreconnectMotivationLinkTapDown);
506 void HTMLAnchorElement::PrefetchEventHandler::handleClick(Event* event)
508 bool capturedMouseOver = (m_mouseOverTimestamp > 0.0);
509 if (capturedMouseOver) {
510 double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp);
512 blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_Click", mouseOverDuration * 1000, 0, 10000, 100);
515 bool capturedMouseDown = (m_mouseDownTimestamp > 0.0);
516 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseDownFollowedByClick", capturedMouseDown, 2);
518 if (capturedMouseDown) {
519 double mouseDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseDownTimestamp);
521 blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseDownDuration_Click", mouseDownDuration * 1000, 0, 10000, 100);
524 bool capturedTapDown = (m_tapDownTimestamp > 0.0);
525 if (capturedTapDown) {
526 double tapDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_tapDownTimestamp);
528 blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.TapDownDuration_Click", tapDownDuration * 1000, 0, 10000, 100);
531 int flags = (m_hadTapUnconfirmed ? 2 : 0) | (capturedTapDown ? 1 : 0);
532 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.PreTapEventsFollowedByClick", flags, 4);
535 bool HTMLAnchorElement::PrefetchEventHandler::shouldPrefetch(const KURL& url)
537 if (m_hadHREFChanged)
540 if (m_anchorElement->hasEventListeners(EventTypeNames::click))
543 if (!url.protocolIsInHTTPFamily())
546 Document& document = m_anchorElement->document();
548 if (!document.securityOrigin()->canDisplay(url))
551 if (url.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(document.url(), url))
554 LocalFrame* frame = document.frame();
558 // Links which create new window/tab are avoided because they may require user approval interaction.
559 if (!m_anchorElement->target().isEmpty())
565 void HTMLAnchorElement::PrefetchEventHandler::prefetch(blink::WebPreconnectMotivation motivation)
567 const KURL& url = m_anchorElement->href();
569 if (!shouldPrefetch(url))
572 // The precision of current MouseOver trigger is too low to actually trigger preconnects.
573 if (motivation == blink::WebPreconnectMotivationLinkMouseOver)
576 preconnectToURL(url, motivation);
577 m_hasIssuedPreconnect = true;
580 bool HTMLAnchorElement::isInteractiveContent() const