1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 1999 Antti Koivisto (koivisto (at) kde.org) 4 * (C) 2000 Simon Hausmann <hausmann (at) kde.org> 5 * Copyright (C) 2003, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. 6 * (C) 2006 Graham Dennis (graham.dennis (at) 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 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: 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); 83 void didChangeHREF() { m_hadHREFChanged = true; } 84 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 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 120 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document& document) 121 { 122 return adoptRef(new HTMLAnchorElement(aTag, document)); 123 } 124 125 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(const QualifiedName& tagName, Document& document) 126 { 127 return adoptRef(new HTMLAnchorElement(tagName, document)); 128 } 129 130 HTMLAnchorElement::~HTMLAnchorElement() 131 { 132 clearRootEditableElementForSelectionOnMouseDown(); 133 } 134 135 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 143 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 153 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 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 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 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 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 296 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents) 297 { 298 dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents); 299 } 300 301 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const 302 { 303 return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute); 304 } 305 306 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 314 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 325 KURL HTMLAnchorElement::href() const 326 { 327 return document().completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr))); 328 } 329 330 void HTMLAnchorElement::setHref(const AtomicString& value) 331 { 332 setAttribute(hrefAttr, value); 333 } 334 335 KURL HTMLAnchorElement::url() const 336 { 337 return href(); 338 } 339 340 void HTMLAnchorElement::setURL(const KURL& url) 341 { 342 setHref(AtomicString(url.string())); 343 } 344 345 String HTMLAnchorElement::input() const 346 { 347 return getAttribute(hrefAttr); 348 } 349 350 void HTMLAnchorElement::setInput(const String& value) 351 { 352 setHref(value); 353 } 354 355 bool HTMLAnchorElement::hasRel(uint32_t relation) const 356 { 357 return m_linkRelations & relation; 358 } 359 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 369 const AtomicString& HTMLAnchorElement::name() const 370 { 371 return getNameAttribute(); 372 } 373 374 short HTMLAnchorElement::tabIndex() const 375 { 376 // Skip the supportsFocus check in HTMLElement. 377 return Element::tabIndex(); 378 } 379 380 String HTMLAnchorElement::target() const 381 { 382 return getAttribute(targetAttr); 383 } 384 385 386 String HTMLAnchorElement::text() 387 { 388 return innerText(); 389 } 390 391 bool HTMLAnchorElement::isLiveLink() const 392 { 393 return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey); 394 } 395 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 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 443 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event) 444 { 445 if (!event->isMouseEvent()) 446 return NonMouseEvent; 447 return toMouseEvent(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey; 448 } 449 450 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 480 bool isEnterKeyKeydownEvent(Event* event) 481 { 482 return event->type() == EventTypeNames::keydown && event->isKeyboardEvent() && toKeyboardEvent(event)->keyIdentifier() == "Enter"; 483 } 484 485 bool isLinkClick(Event* event) 486 { 487 return event->type() == EventTypeNames::click && (!event->isMouseEvent() || toMouseEvent(event)->button() != RightButton); 488 } 489 490 bool HTMLAnchorElement::willRespondToMouseClickEvents() 491 { 492 return isLink() || HTMLElement::willRespondToMouseClickEvents(); 493 } 494 495 typedef HashMap<const HTMLAnchorElement*, RefPtr<Element> > RootEditableElementMap; 496 497 static RootEditableElementMap& rootEditableElementMap() 498 { 499 DEFINE_STATIC_LOCAL(RootEditableElementMap, map, ()); 500 return map; 501 } 502 503 Element* HTMLAnchorElement::rootEditableElementForSelectionOnMouseDown() const 504 { 505 if (!m_hasRootEditableElementForSelectionOnMouseDown) 506 return 0; 507 return rootEditableElementMap().get(this); 508 } 509 510 void HTMLAnchorElement::clearRootEditableElementForSelectionOnMouseDown() 511 { 512 if (!m_hasRootEditableElementForSelectionOnMouseDown) 513 return; 514 rootEditableElementMap().remove(this); 515 m_hasRootEditableElementForSelectionOnMouseDown = false; 516 } 517 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 529 HTMLAnchorElement::PrefetchEventHandler* HTMLAnchorElement::prefetchEventHandler() 530 { 531 if (!m_prefetchEventHandler) 532 m_prefetchEventHandler = PrefetchEventHandler::create(this); 533 534 return m_prefetchEventHandler.get(); 535 } 536 537 HTMLAnchorElement::PrefetchEventHandler::PrefetchEventHandler(HTMLAnchorElement* anchorElement) 538 : m_anchorElement(anchorElement) 539 { 540 ASSERT(m_anchorElement); 541 542 reset(); 543 } 544 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 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 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 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 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 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 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 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 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 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 696 bool HTMLAnchorElement::isInteractiveContent() const 697 { 698 return isLink(); 699 } 700 701 } 702