1 /*
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)
7 *
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.
12 *
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.
17 *
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.
22 */
23
24 #include "config.h"
25 #include "core/html/HTMLAnchorElement.h"
26
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/events/ThreadLocalEventNames.h"
32 #include "core/frame/Frame.h"
33 #include "core/html/HTMLFormElement.h"
34 #include "core/html/HTMLImageElement.h"
35 #include "core/html/parser/HTMLParserIdioms.h"
36 #include "core/loader/FrameLoadRequest.h"
37 #include "core/loader/FrameLoader.h"
38 #include "core/loader/FrameLoaderClient.h"
39 #include "core/loader/FrameLoaderTypes.h"
40 #include "core/loader/PingLoader.h"
41 #include "core/page/Chrome.h"
42 #include "core/page/ChromeClient.h"
43 #include "core/page/Page.h"
44 #include "core/frame/Settings.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"
57
58 namespace WebCore {
59
60 namespace {
61
preconnectToURL(const KURL & url,blink::WebPreconnectMotivation motivation)62 void preconnectToURL(const KURL& url, blink::WebPreconnectMotivation motivation)
63 {
64 blink::WebPrescientNetworking* prescientNetworking = blink::Platform::current()->prescientNetworking();
65 if (!prescientNetworking)
66 return;
67
68 prescientNetworking->preconnect(url, motivation);
69 }
70
71 }
72
73 class HTMLAnchorElement::PrefetchEventHandler {
74 public:
create(HTMLAnchorElement * anchorElement)75 static PassOwnPtr<PrefetchEventHandler> create(HTMLAnchorElement* anchorElement)
76 {
77 return adoptPtr(new HTMLAnchorElement::PrefetchEventHandler(anchorElement));
78 }
79
80 void reset();
81
82 void handleEvent(Event* e);
didChangeHREF()83 void didChangeHREF() { m_hadHREFChanged = true; }
hasIssuedPreconnect() const84 bool hasIssuedPreconnect() const { return m_hasIssuedPreconnect; }
85
86 private:
87 explicit PrefetchEventHandler(HTMLAnchorElement*);
88
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);
95
96 bool shouldPrefetch(const KURL&);
97 void prefetch(blink::WebPreconnectMotivation);
98
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;
106 };
107
108 using namespace HTMLNames;
109
HTMLAnchorElement(const QualifiedName & tagName,Document & document)110 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document& document)
111 : HTMLElement(tagName, document)
112 , m_hasRootEditableElementForSelectionOnMouseDown(false)
113 , m_wasShiftKeyDownOnMouseDown(false)
114 , m_linkRelations(0)
115 , m_cachedVisitedLinkHash(0)
116 {
117 ScriptWrappable::init(this);
118 }
119
create(Document & document)120 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document& document)
121 {
122 return adoptRef(new HTMLAnchorElement(aTag, document));
123 }
124
create(const QualifiedName & tagName,Document & document)125 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(const QualifiedName& tagName, Document& document)
126 {
127 return adoptRef(new HTMLAnchorElement(tagName, document));
128 }
129
~HTMLAnchorElement()130 HTMLAnchorElement::~HTMLAnchorElement()
131 {
132 clearRootEditableElementForSelectionOnMouseDown();
133 }
134
supportsFocus() const135 bool HTMLAnchorElement::supportsFocus() const
136 {
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();
141 }
142
isMouseFocusable() const143 bool HTMLAnchorElement::isMouseFocusable() const
144 {
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
147 if (isLink())
148 return HTMLElement::supportsFocus();
149
150 return HTMLElement::isMouseFocusable();
151 }
152
isKeyboardFocusable() const153 bool HTMLAnchorElement::isKeyboardFocusable() const
154 {
155 if (isFocusable() && Element::supportsFocus())
156 return HTMLElement::isKeyboardFocusable();
157
158 if (isLink()) {
159 Page* page = document().page();
160 if (!page)
161 return false;
162 if (!page->chrome().client().tabsToLinks())
163 return false;
164 }
165 return HTMLElement::isKeyboardFocusable();
166 }
167
appendServerMapMousePosition(StringBuilder & url,Event * event)168 static void appendServerMapMousePosition(StringBuilder& url, Event* event)
169 {
170 if (!event->isMouseEvent())
171 return;
172
173 ASSERT(event->target());
174 Node* target = event->target()->toNode();
175 ASSERT(target);
176 if (!target->hasTagName(imgTag))
177 return;
178
179 HTMLImageElement* imageElement = toHTMLImageElement(event->target()->toNode());
180 if (!imageElement || !imageElement->isServerMap())
181 return;
182
183 if (!imageElement->renderer() || !imageElement->renderer()->isRenderImage())
184 return;
185 RenderImage* renderer = toRenderImage(imageElement->renderer());
186
187 // FIXME: This should probably pass true for useTransforms.
188 FloatPoint absolutePosition = renderer->absoluteToLocal(FloatPoint(toMouseEvent(event)->pageX(), toMouseEvent(event)->pageY()));
189 int x = absolutePosition.x();
190 int y = absolutePosition.y();
191 url.append('?');
192 url.appendNumber(x);
193 url.append(',');
194 url.appendNumber(y);
195 }
196
defaultEventHandler(Event * event)197 void HTMLAnchorElement::defaultEventHandler(Event* event)
198 {
199 if (isLink()) {
200 if (focused() && isEnterKeyKeydownEvent(event) && treatLinkAsLiveForEventType(NonMouseEvent)) {
201 event->setDefaultHandled();
202 dispatchSimulatedClick(event);
203 return;
204 }
205
206 prefetchEventHandler()->handleEvent(event);
207
208 if (isLinkClick(event) && treatLinkAsLiveForEventType(eventType(event))) {
209 handleClick(event);
210 prefetchEventHandler()->reset();
211 return;
212 }
213
214 if (rendererIsEditable()) {
215 // This keeps track of the editable block that the selection was in (if it was in one) just before the link was clicked
216 // for the LiveWhenNotFocused editable link behavior
217 if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() != RightButton && document().frame()) {
218 setRootEditableElementForSelectionOnMouseDown(document().frame()->selection().rootEditableElement());
219 m_wasShiftKeyDownOnMouseDown = toMouseEvent(event)->shiftKey();
220 } else if (event->type() == EventTypeNames::mouseover) {
221 // These are cleared on mouseover and not mouseout because their values are needed for drag events,
222 // but drag events happen after mouse out events.
223 clearRootEditableElementForSelectionOnMouseDown();
224 m_wasShiftKeyDownOnMouseDown = false;
225 }
226 }
227 }
228
229 HTMLElement::defaultEventHandler(event);
230 }
231
setActive(bool down)232 void HTMLAnchorElement::setActive(bool down)
233 {
234 if (rendererIsEditable()) {
235 EditableLinkBehavior editableLinkBehavior = EditableLinkDefaultBehavior;
236 if (Settings* settings = document().settings())
237 editableLinkBehavior = settings->editableLinkBehavior();
238
239 switch (editableLinkBehavior) {
240 default:
241 case EditableLinkDefaultBehavior:
242 case EditableLinkAlwaysLive:
243 break;
244
245 case EditableLinkNeverLive:
246 return;
247
248 // Don't set the link to be active if the current selection is in the same editable block as
249 // this link
250 case EditableLinkLiveWhenNotFocused:
251 if (down && document().frame() && document().frame()->selection().rootEditableElement() == rootEditableElement())
252 return;
253 break;
254
255 case EditableLinkOnlyLiveWithShiftKey:
256 return;
257 }
258
259 }
260
261 ContainerNode::setActive(down);
262 }
263
parseAttribute(const QualifiedName & name,const AtomicString & value)264 void HTMLAnchorElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
265 {
266 if (name == hrefAttr) {
267 bool wasLink = isLink();
268 setIsLink(!value.isNull());
269 if (wasLink != isLink()) {
270 didAffectSelector(AffectedSelectorLink | AffectedSelectorVisited | AffectedSelectorEnabled);
271 if (wasLink && treeScope().adjustedFocusedElement() == this) {
272 // We might want to call blur(), but it's dangerous to dispatch
273 // events here.
274 document().setNeedsFocusedElementCheck();
275 }
276 }
277 if (isLink()) {
278 String parsedURL = stripLeadingAndTrailingHTMLSpaces(value);
279 if (document().isDNSPrefetchEnabled()) {
280 if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//"))
281 prefetchDNS(document().completeURL(parsedURL).host());
282 }
283
284 if (wasLink)
285 prefetchEventHandler()->didChangeHREF();
286 }
287 invalidateCachedVisitedLinkHash();
288 } else if (name == nameAttr || name == titleAttr) {
289 // Do nothing.
290 } else if (name == relAttr)
291 setRel(value);
292 else
293 HTMLElement::parseAttribute(name, value);
294 }
295
accessKeyAction(bool sendMouseEvents)296 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
297 {
298 dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
299 }
300
isURLAttribute(const Attribute & attribute) const301 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
302 {
303 return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
304 }
305
canStartSelection() const306 bool HTMLAnchorElement::canStartSelection() const
307 {
308 // FIXME: We probably want this same behavior in SVGAElement too
309 if (!isLink())
310 return HTMLElement::canStartSelection();
311 return rendererIsEditable();
312 }
313
draggable() const314 bool HTMLAnchorElement::draggable() const
315 {
316 // Should be draggable if we have an href attribute.
317 const AtomicString& value = getAttribute(draggableAttr);
318 if (equalIgnoringCase(value, "true"))
319 return true;
320 if (equalIgnoringCase(value, "false"))
321 return false;
322 return hasAttribute(hrefAttr);
323 }
324
href() const325 KURL HTMLAnchorElement::href() const
326 {
327 return document().completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
328 }
329
setHref(const AtomicString & value)330 void HTMLAnchorElement::setHref(const AtomicString& value)
331 {
332 setAttribute(hrefAttr, value);
333 }
334
url() const335 KURL HTMLAnchorElement::url() const
336 {
337 return href();
338 }
339
setURL(const KURL & url)340 void HTMLAnchorElement::setURL(const KURL& url)
341 {
342 setHref(AtomicString(url.string()));
343 }
344
input() const345 String HTMLAnchorElement::input() const
346 {
347 return getAttribute(hrefAttr);
348 }
349
setInput(const String & value)350 void HTMLAnchorElement::setInput(const String& value)
351 {
352 setHref(value);
353 }
354
hasRel(uint32_t relation) const355 bool HTMLAnchorElement::hasRel(uint32_t relation) const
356 {
357 return m_linkRelations & relation;
358 }
359
setRel(const String & value)360 void HTMLAnchorElement::setRel(const String& value)
361 {
362 m_linkRelations = 0;
363 SpaceSplitString newLinkRelations(value, true);
364 // FIXME: Add link relations as they are implemented
365 if (newLinkRelations.contains("noreferrer"))
366 m_linkRelations |= RelationNoReferrer;
367 }
368
name() const369 const AtomicString& HTMLAnchorElement::name() const
370 {
371 return getNameAttribute();
372 }
373
tabIndex() const374 short HTMLAnchorElement::tabIndex() const
375 {
376 // Skip the supportsFocus check in HTMLElement.
377 return Element::tabIndex();
378 }
379
target() const380 String HTMLAnchorElement::target() const
381 {
382 return getAttribute(targetAttr);
383 }
384
385
text()386 String HTMLAnchorElement::text()
387 {
388 return innerText();
389 }
390
isLiveLink() const391 bool HTMLAnchorElement::isLiveLink() const
392 {
393 return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey);
394 }
395
sendPings(const KURL & destinationURL)396 void HTMLAnchorElement::sendPings(const KURL& destinationURL)
397 {
398 if (!hasAttribute(pingAttr) || !document().settings() || !document().settings()->hyperlinkAuditingEnabled())
399 return;
400
401 SpaceSplitString pingURLs(getAttribute(pingAttr), false);
402 for (unsigned i = 0; i < pingURLs.size(); i++)
403 PingLoader::sendPing(document().frame(), document().completeURL(pingURLs[i]), destinationURL);
404 }
405
handleClick(Event * event)406 void HTMLAnchorElement::handleClick(Event* event)
407 {
408 event->setDefaultHandled();
409
410 Frame* frame = document().frame();
411 if (!frame)
412 return;
413
414 StringBuilder url;
415 url.append(stripLeadingAndTrailingHTMLSpaces(fastGetAttribute(hrefAttr)));
416 appendServerMapMousePosition(url, event);
417 KURL completedURL = document().completeURL(url.toString());
418
419 // Schedule the ping before the frame load. Prerender in Chrome may kill the renderer as soon as the navigation is
420 // sent out.
421 sendPings(completedURL);
422
423 ResourceRequest request(completedURL);
424 if (prefetchEventHandler()->hasIssuedPreconnect())
425 frame->loader().client()->dispatchWillRequestAfterPreconnect(request);
426 if (hasAttribute(downloadAttr)) {
427 if (!hasRel(RelationNoReferrer)) {
428 String referrer = SecurityPolicy::generateReferrerHeader(document().referrerPolicy(), completedURL, document().outgoingReferrer());
429 if (!referrer.isEmpty())
430 request.setHTTPReferrer(referrer);
431 }
432
433 frame->loader().client()->loadURLExternally(request, NavigationPolicyDownload, fastGetAttribute(downloadAttr));
434 } else {
435 FrameLoadRequest frameRequest(&document(), request, target());
436 frameRequest.setTriggeringEvent(event);
437 if (hasRel(RelationNoReferrer))
438 frameRequest.setShouldSendReferrer(NeverSendReferrer);
439 frame->loader().load(frameRequest);
440 }
441 }
442
eventType(Event * event)443 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event)
444 {
445 if (!event->isMouseEvent())
446 return NonMouseEvent;
447 return toMouseEvent(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
448 }
449
treatLinkAsLiveForEventType(EventType eventType) const450 bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const
451 {
452 if (!rendererIsEditable())
453 return true;
454
455 Settings* settings = document().settings();
456 if (!settings)
457 return true;
458
459 switch (settings->editableLinkBehavior()) {
460 case EditableLinkDefaultBehavior:
461 case EditableLinkAlwaysLive:
462 return true;
463
464 case EditableLinkNeverLive:
465 return false;
466
467 // If the selection prior to clicking on this link resided in the same editable block as this link,
468 // and the shift key isn't pressed, we don't want to follow the link.
469 case EditableLinkLiveWhenNotFocused:
470 return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && rootEditableElementForSelectionOnMouseDown() != rootEditableElement());
471
472 case EditableLinkOnlyLiveWithShiftKey:
473 return eventType == MouseEventWithShiftKey;
474 }
475
476 ASSERT_NOT_REACHED();
477 return false;
478 }
479
isEnterKeyKeydownEvent(Event * event)480 bool isEnterKeyKeydownEvent(Event* event)
481 {
482 return event->type() == EventTypeNames::keydown && event->isKeyboardEvent() && toKeyboardEvent(event)->keyIdentifier() == "Enter";
483 }
484
isLinkClick(Event * event)485 bool isLinkClick(Event* event)
486 {
487 return event->type() == EventTypeNames::click && (!event->isMouseEvent() || toMouseEvent(event)->button() != RightButton);
488 }
489
willRespondToMouseClickEvents()490 bool HTMLAnchorElement::willRespondToMouseClickEvents()
491 {
492 return isLink() || HTMLElement::willRespondToMouseClickEvents();
493 }
494
495 typedef HashMap<const HTMLAnchorElement*, RefPtr<Element> > RootEditableElementMap;
496
rootEditableElementMap()497 static RootEditableElementMap& rootEditableElementMap()
498 {
499 DEFINE_STATIC_LOCAL(RootEditableElementMap, map, ());
500 return map;
501 }
502
rootEditableElementForSelectionOnMouseDown() const503 Element* HTMLAnchorElement::rootEditableElementForSelectionOnMouseDown() const
504 {
505 if (!m_hasRootEditableElementForSelectionOnMouseDown)
506 return 0;
507 return rootEditableElementMap().get(this);
508 }
509
clearRootEditableElementForSelectionOnMouseDown()510 void HTMLAnchorElement::clearRootEditableElementForSelectionOnMouseDown()
511 {
512 if (!m_hasRootEditableElementForSelectionOnMouseDown)
513 return;
514 rootEditableElementMap().remove(this);
515 m_hasRootEditableElementForSelectionOnMouseDown = false;
516 }
517
setRootEditableElementForSelectionOnMouseDown(Element * element)518 void HTMLAnchorElement::setRootEditableElementForSelectionOnMouseDown(Element* element)
519 {
520 if (!element) {
521 clearRootEditableElementForSelectionOnMouseDown();
522 return;
523 }
524
525 rootEditableElementMap().set(this, element);
526 m_hasRootEditableElementForSelectionOnMouseDown = true;
527 }
528
prefetchEventHandler()529 HTMLAnchorElement::PrefetchEventHandler* HTMLAnchorElement::prefetchEventHandler()
530 {
531 if (!m_prefetchEventHandler)
532 m_prefetchEventHandler = PrefetchEventHandler::create(this);
533
534 return m_prefetchEventHandler.get();
535 }
536
PrefetchEventHandler(HTMLAnchorElement * anchorElement)537 HTMLAnchorElement::PrefetchEventHandler::PrefetchEventHandler(HTMLAnchorElement* anchorElement)
538 : m_anchorElement(anchorElement)
539 {
540 ASSERT(m_anchorElement);
541
542 reset();
543 }
544
reset()545 void HTMLAnchorElement::PrefetchEventHandler::reset()
546 {
547 m_hadHREFChanged = false;
548 m_mouseOverTimestamp = 0;
549 m_mouseDownTimestamp = 0;
550 m_hadTapUnconfirmed = false;
551 m_tapDownTimestamp = 0;
552 m_hasIssuedPreconnect = false;
553 }
554
handleEvent(Event * event)555 void HTMLAnchorElement::PrefetchEventHandler::handleEvent(Event* event)
556 {
557 if (!shouldPrefetch(m_anchorElement->href()))
558 return;
559
560 if (event->type() == EventTypeNames::mouseover)
561 handleMouseOver(event);
562 else if (event->type() == EventTypeNames::mouseout)
563 handleMouseOut(event);
564 else if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton)
565 handleLeftMouseDown(event);
566 else if (event->type() == EventTypeNames::gestureshowpress)
567 handleGestureShowPress(event);
568 else if (event->type() == EventTypeNames::gesturetapunconfirmed)
569 handleGestureTapUnconfirmed(event);
570 else if (isLinkClick(event))
571 handleClick(event);
572 }
573
handleMouseOver(Event * event)574 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOver(Event* event)
575 {
576 if (m_mouseOverTimestamp == 0.0) {
577 m_mouseOverTimestamp = event->timeStamp();
578
579 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseOvers", 0, 2);
580
581 prefetch(blink::WebPreconnectMotivationLinkMouseOver);
582 }
583 }
584
handleMouseOut(Event * event)585 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOut(Event* event)
586 {
587 if (m_mouseOverTimestamp > 0.0) {
588 double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp);
589 blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_NoClick", mouseOverDuration * 1000, 0, 10000, 100);
590
591 m_mouseOverTimestamp = 0.0;
592 }
593 }
594
handleLeftMouseDown(Event * event)595 void HTMLAnchorElement::PrefetchEventHandler::handleLeftMouseDown(Event* event)
596 {
597 m_mouseDownTimestamp = event->timeStamp();
598
599 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseDowns", 0, 2);
600
601 prefetch(blink::WebPreconnectMotivationLinkMouseDown);
602 }
603
handleGestureTapUnconfirmed(Event * event)604 void HTMLAnchorElement::PrefetchEventHandler::handleGestureTapUnconfirmed(Event* event)
605 {
606 m_hadTapUnconfirmed = true;
607
608 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.TapUnconfirmeds", 0, 2);
609
610 prefetch(blink::WebPreconnectMotivationLinkTapUnconfirmed);
611 }
612
handleGestureShowPress(Event * event)613 void HTMLAnchorElement::PrefetchEventHandler::handleGestureShowPress(Event* event)
614 {
615 m_tapDownTimestamp = event->timeStamp();
616
617 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.TapDowns", 0, 2);
618
619 prefetch(blink::WebPreconnectMotivationLinkTapDown);
620 }
621
handleClick(Event * event)622 void HTMLAnchorElement::PrefetchEventHandler::handleClick(Event* event)
623 {
624 bool capturedMouseOver = (m_mouseOverTimestamp > 0.0);
625 if (capturedMouseOver) {
626 double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp);
627
628 blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_Click", mouseOverDuration * 1000, 0, 10000, 100);
629 }
630
631 bool capturedMouseDown = (m_mouseDownTimestamp > 0.0);
632 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseDownFollowedByClick", capturedMouseDown, 2);
633
634 if (capturedMouseDown) {
635 double mouseDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseDownTimestamp);
636
637 blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseDownDuration_Click", mouseDownDuration * 1000, 0, 10000, 100);
638 }
639
640 bool capturedTapDown = (m_tapDownTimestamp > 0.0);
641 if (capturedTapDown) {
642 double tapDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_tapDownTimestamp);
643
644 blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.TapDownDuration_Click", tapDownDuration * 1000, 0, 10000, 100);
645 }
646
647 int flags = (m_hadTapUnconfirmed ? 2 : 0) | (capturedTapDown ? 1 : 0);
648 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.PreTapEventsFollowedByClick", flags, 4);
649 }
650
shouldPrefetch(const KURL & url)651 bool HTMLAnchorElement::PrefetchEventHandler::shouldPrefetch(const KURL& url)
652 {
653 if (m_hadHREFChanged)
654 return false;
655
656 if (m_anchorElement->hasEventListeners(EventTypeNames::click))
657 return false;
658
659 if (!url.protocolIsInHTTPFamily())
660 return false;
661
662 Document& document = m_anchorElement->document();
663
664 if (!document.securityOrigin()->canDisplay(url))
665 return false;
666
667 if (url.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(document.url(), url))
668 return false;
669
670 Frame* frame = document.frame();
671 if (!frame)
672 return false;
673
674 // Links which create new window/tab are avoided because they may require user approval interaction.
675 if (!m_anchorElement->target().isEmpty())
676 return false;
677
678 return true;
679 }
680
prefetch(blink::WebPreconnectMotivation motivation)681 void HTMLAnchorElement::PrefetchEventHandler::prefetch(blink::WebPreconnectMotivation motivation)
682 {
683 const KURL& url = m_anchorElement->href();
684
685 if (!shouldPrefetch(url))
686 return;
687
688 // The precision of current MouseOver trigger is too low to actually trigger preconnects.
689 if (motivation == blink::WebPreconnectMotivationLinkMouseOver)
690 return;
691
692 preconnectToURL(url, motivation);
693 m_hasIssuedPreconnect = true;
694 }
695
isInteractiveContent() const696 bool HTMLAnchorElement::isInteractiveContent() const
697 {
698 return isLink();
699 }
700
701 }
702