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 "HTMLNames.h" 28 #include "core/dom/Attribute.h" 29 #include "core/dom/EventNames.h" 30 #include "core/dom/KeyboardEvent.h" 31 #include "core/dom/MouseEvent.h" 32 #include "core/editing/FrameSelection.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/Frame.h" 44 #include "core/page/Page.h" 45 #include "core/page/Settings.h" 46 #include "core/platform/HistogramSupport.h" 47 #include "core/platform/PlatformMouseEvent.h" 48 #include "core/platform/network/DNS.h" 49 #include "core/platform/network/ResourceRequest.h" 50 #include "core/rendering/RenderImage.h" 51 #include "public/platform/Platform.h" 52 #include "public/platform/WebPrescientNetworking.h" 53 #include "public/platform/WebURL.h" 54 #include "weborigin/KnownPorts.h" 55 #include "weborigin/SecurityOrigin.h" 56 #include "weborigin/SecurityPolicy.h" 57 #include "wtf/text/StringBuilder.h" 58 59 namespace WebCore { 60 61 namespace { 62 63 void preconnectToURL(const KURL& url, WebKit::WebPreconnectMotivation motivation) 64 { 65 WebKit::WebPrescientNetworking* prescientNetworking = WebKit::Platform::current()->prescientNetworking(); 66 if (!prescientNetworking) 67 return; 68 69 prescientNetworking->preconnect(url, motivation); 70 } 71 72 } 73 74 class HTMLAnchorElement::PrefetchEventHandler { 75 public: 76 static PassOwnPtr<PrefetchEventHandler> create(HTMLAnchorElement* anchorElement) 77 { 78 return adoptPtr(new HTMLAnchorElement::PrefetchEventHandler(anchorElement)); 79 } 80 81 void reset(); 82 83 void handleEvent(Event* e); 84 void didChangeHREF() { m_hadHREFChanged = true; } 85 bool hasIssuedPreconnect() const { return m_hasIssuedPreconnect; } 86 87 private: 88 explicit PrefetchEventHandler(HTMLAnchorElement*); 89 90 void handleMouseOver(Event* event); 91 void handleMouseOut(Event* event); 92 void handleLeftMouseDown(Event* event); 93 void handleGestureTapUnconfirmed(Event*); 94 void handleGestureTapDown(Event*); 95 void handleClick(Event* event); 96 97 bool shouldPrefetch(const KURL&); 98 void prefetch(WebKit::WebPreconnectMotivation); 99 100 HTMLAnchorElement* m_anchorElement; 101 double m_mouseOverTimestamp; 102 double m_mouseDownTimestamp; 103 double m_tapDownTimestamp; 104 bool m_hadHREFChanged; 105 bool m_hadTapUnconfirmed; 106 bool m_hasIssuedPreconnect; 107 }; 108 109 using namespace HTMLNames; 110 111 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document* document) 112 : HTMLElement(tagName, document) 113 , m_hasRootEditableElementForSelectionOnMouseDown(false) 114 , m_wasShiftKeyDownOnMouseDown(false) 115 , m_linkRelations(0) 116 , m_cachedVisitedLinkHash(0) 117 { 118 ScriptWrappable::init(this); 119 } 120 121 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document* document) 122 { 123 return adoptRef(new HTMLAnchorElement(aTag, document)); 124 } 125 126 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(const QualifiedName& tagName, Document* document) 127 { 128 return adoptRef(new HTMLAnchorElement(tagName, document)); 129 } 130 131 HTMLAnchorElement::~HTMLAnchorElement() 132 { 133 clearRootEditableElementForSelectionOnMouseDown(); 134 } 135 136 // This function does not allow leading spaces before the port number. 137 static unsigned parsePortFromStringPosition(const String& value, unsigned portStart, unsigned& portEnd) 138 { 139 portEnd = portStart; 140 while (isASCIIDigit(value[portEnd])) 141 ++portEnd; 142 return value.substring(portStart, portEnd - portStart).toUInt(); 143 } 144 145 bool HTMLAnchorElement::supportsFocus() const 146 { 147 if (rendererIsEditable()) 148 return HTMLElement::supportsFocus(); 149 // If not a link we should still be able to focus the element if it has tabIndex. 150 return isLink() || HTMLElement::supportsFocus(); 151 } 152 153 bool HTMLAnchorElement::isMouseFocusable() const 154 { 155 // Links are focusable by default, but only allow links with tabindex or contenteditable to be mouse focusable. 156 // https://bugs.webkit.org/show_bug.cgi?id=26856 157 if (isLink()) 158 return HTMLElement::supportsFocus(); 159 160 return HTMLElement::isMouseFocusable(); 161 } 162 163 bool HTMLAnchorElement::isKeyboardFocusable() const 164 { 165 if (!isLink()) 166 return HTMLElement::isKeyboardFocusable(); 167 168 if (!isFocusable()) 169 return false; 170 171 Page* page = document()->page(); 172 if (!page) 173 return false; 174 175 if (!page->chrome().client()->tabsToLinks()) 176 return false; 177 178 if (isInCanvasSubtree()) 179 return true; 180 181 return hasNonEmptyBoundingBox(); 182 } 183 184 static void appendServerMapMousePosition(StringBuilder& url, Event* event) 185 { 186 if (!event->isMouseEvent()) 187 return; 188 189 ASSERT(event->target()); 190 Node* target = event->target()->toNode(); 191 ASSERT(target); 192 if (!target->hasTagName(imgTag)) 193 return; 194 195 HTMLImageElement* imageElement = toHTMLImageElement(event->target()->toNode()); 196 if (!imageElement || !imageElement->isServerMap()) 197 return; 198 199 if (!imageElement->renderer() || !imageElement->renderer()->isRenderImage()) 200 return; 201 RenderImage* renderer = toRenderImage(imageElement->renderer()); 202 203 // FIXME: This should probably pass true for useTransforms. 204 FloatPoint absolutePosition = renderer->absoluteToLocal(FloatPoint(toMouseEvent(event)->pageX(), toMouseEvent(event)->pageY())); 205 int x = absolutePosition.x(); 206 int y = absolutePosition.y(); 207 url.append('?'); 208 url.appendNumber(x); 209 url.append(','); 210 url.appendNumber(y); 211 } 212 213 void HTMLAnchorElement::defaultEventHandler(Event* event) 214 { 215 if (isLink()) { 216 if (focused() && isEnterKeyKeydownEvent(event) && treatLinkAsLiveForEventType(NonMouseEvent)) { 217 event->setDefaultHandled(); 218 dispatchSimulatedClick(event); 219 return; 220 } 221 222 prefetchEventHandler()->handleEvent(event); 223 224 if (isLinkClick(event) && treatLinkAsLiveForEventType(eventType(event))) { 225 handleClick(event); 226 prefetchEventHandler()->reset(); 227 return; 228 } 229 230 if (rendererIsEditable()) { 231 // This keeps track of the editable block that the selection was in (if it was in one) just before the link was clicked 232 // for the LiveWhenNotFocused editable link behavior 233 if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && toMouseEvent(event)->button() != RightButton && document()->frame() && document()->frame()->selection()) { 234 setRootEditableElementForSelectionOnMouseDown(document()->frame()->selection()->rootEditableElement()); 235 m_wasShiftKeyDownOnMouseDown = toMouseEvent(event)->shiftKey(); 236 } else if (event->type() == eventNames().mouseoverEvent) { 237 // These are cleared on mouseover and not mouseout because their values are needed for drag events, 238 // but drag events happen after mouse out events. 239 clearRootEditableElementForSelectionOnMouseDown(); 240 m_wasShiftKeyDownOnMouseDown = false; 241 } 242 } 243 } 244 245 HTMLElement::defaultEventHandler(event); 246 } 247 248 void HTMLAnchorElement::setActive(bool down, bool pause) 249 { 250 if (rendererIsEditable()) { 251 EditableLinkBehavior editableLinkBehavior = EditableLinkDefaultBehavior; 252 if (Settings* settings = document()->settings()) 253 editableLinkBehavior = settings->editableLinkBehavior(); 254 255 switch (editableLinkBehavior) { 256 default: 257 case EditableLinkDefaultBehavior: 258 case EditableLinkAlwaysLive: 259 break; 260 261 case EditableLinkNeverLive: 262 return; 263 264 // Don't set the link to be active if the current selection is in the same editable block as 265 // this link 266 case EditableLinkLiveWhenNotFocused: 267 if (down && document()->frame() && document()->frame()->selection()->rootEditableElement() == rootEditableElement()) 268 return; 269 break; 270 271 case EditableLinkOnlyLiveWithShiftKey: 272 return; 273 } 274 275 } 276 277 ContainerNode::setActive(down, pause); 278 } 279 280 void HTMLAnchorElement::parseAttribute(const QualifiedName& name, const AtomicString& value) 281 { 282 if (name == hrefAttr) { 283 bool wasLink = isLink(); 284 setIsLink(!value.isNull()); 285 if (wasLink != isLink()) { 286 didAffectSelector(AffectedSelectorLink | AffectedSelectorVisited | AffectedSelectorEnabled); 287 if (wasLink && treeScope()->adjustedFocusedElement() == this) { 288 // We might want to call blur(), but it's dangerous to dispatch 289 // events here. 290 document()->setNeedsFocusedElementCheck(); 291 } 292 } 293 if (isLink()) { 294 String parsedURL = stripLeadingAndTrailingHTMLSpaces(value); 295 if (document()->isDNSPrefetchEnabled()) { 296 if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//")) 297 prefetchDNS(document()->completeURL(parsedURL).host()); 298 } 299 300 if (wasLink) 301 prefetchEventHandler()->didChangeHREF(); 302 } 303 invalidateCachedVisitedLinkHash(); 304 } else if (name == nameAttr || name == titleAttr) { 305 // Do nothing. 306 } else if (name == relAttr) 307 setRel(value); 308 else 309 HTMLElement::parseAttribute(name, value); 310 } 311 312 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents) 313 { 314 dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents); 315 } 316 317 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const 318 { 319 return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute); 320 } 321 322 bool HTMLAnchorElement::canStartSelection() const 323 { 324 // FIXME: We probably want this same behavior in SVGAElement too 325 if (!isLink()) 326 return HTMLElement::canStartSelection(); 327 return rendererIsEditable(); 328 } 329 330 bool HTMLAnchorElement::draggable() const 331 { 332 // Should be draggable if we have an href attribute. 333 const AtomicString& value = getAttribute(draggableAttr); 334 if (equalIgnoringCase(value, "true")) 335 return true; 336 if (equalIgnoringCase(value, "false")) 337 return false; 338 return hasAttribute(hrefAttr); 339 } 340 341 KURL HTMLAnchorElement::href() const 342 { 343 return document()->completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr))); 344 } 345 346 void HTMLAnchorElement::setHref(const AtomicString& value) 347 { 348 setAttribute(hrefAttr, value); 349 } 350 351 bool HTMLAnchorElement::hasRel(uint32_t relation) const 352 { 353 return m_linkRelations & relation; 354 } 355 356 void HTMLAnchorElement::setRel(const String& value) 357 { 358 m_linkRelations = 0; 359 SpaceSplitString newLinkRelations(value, true); 360 // FIXME: Add link relations as they are implemented 361 if (newLinkRelations.contains("noreferrer")) 362 m_linkRelations |= RelationNoReferrer; 363 } 364 365 const AtomicString& HTMLAnchorElement::name() const 366 { 367 return getNameAttribute(); 368 } 369 370 short HTMLAnchorElement::tabIndex() const 371 { 372 // Skip the supportsFocus check in HTMLElement. 373 return Element::tabIndex(); 374 } 375 376 String HTMLAnchorElement::target() const 377 { 378 return getAttribute(targetAttr); 379 } 380 381 String HTMLAnchorElement::hash() const 382 { 383 String fragmentIdentifier = href().fragmentIdentifier(); 384 if (fragmentIdentifier.isEmpty()) 385 return emptyString(); 386 return AtomicString(String("#" + fragmentIdentifier)); 387 } 388 389 void HTMLAnchorElement::setHash(const String& value) 390 { 391 KURL url = href(); 392 if (value[0] == '#') 393 url.setFragmentIdentifier(value.substring(1)); 394 else 395 url.setFragmentIdentifier(value); 396 setHref(url.string()); 397 } 398 399 String HTMLAnchorElement::host() const 400 { 401 const KURL& url = href(); 402 if (url.hostEnd() == url.pathStart()) 403 return url.host(); 404 if (isDefaultPortForProtocol(url.port(), url.protocol())) 405 return url.host(); 406 return url.host() + ":" + String::number(url.port()); 407 } 408 409 void HTMLAnchorElement::setHost(const String& value) 410 { 411 if (value.isEmpty()) 412 return; 413 KURL url = href(); 414 if (!url.canSetHostOrPort()) 415 return; 416 417 size_t separator = value.find(':'); 418 if (!separator) 419 return; 420 421 if (separator == notFound) 422 url.setHostAndPort(value); 423 else { 424 unsigned portEnd; 425 unsigned port = parsePortFromStringPosition(value, separator + 1, portEnd); 426 if (!port) { 427 // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes 428 // specifically goes against RFC 3986 (p3.2) and 429 // requires setting the port to "0" if it is set to empty string. 430 url.setHostAndPort(value.substring(0, separator + 1) + "0"); 431 } else { 432 if (isDefaultPortForProtocol(port, url.protocol())) 433 url.setHostAndPort(value.substring(0, separator)); 434 else 435 url.setHostAndPort(value.substring(0, portEnd)); 436 } 437 } 438 setHref(url.string()); 439 } 440 441 String HTMLAnchorElement::hostname() const 442 { 443 return href().host(); 444 } 445 446 void HTMLAnchorElement::setHostname(const String& value) 447 { 448 // Before setting new value: 449 // Remove all leading U+002F SOLIDUS ("/") characters. 450 unsigned i = 0; 451 unsigned hostLength = value.length(); 452 while (value[i] == '/') 453 i++; 454 455 if (i == hostLength) 456 return; 457 458 KURL url = href(); 459 if (!url.canSetHostOrPort()) 460 return; 461 462 url.setHost(value.substring(i)); 463 setHref(url.string()); 464 } 465 466 String HTMLAnchorElement::pathname() const 467 { 468 return href().path(); 469 } 470 471 void HTMLAnchorElement::setPathname(const String& value) 472 { 473 KURL url = href(); 474 if (!url.canSetPathname()) 475 return; 476 477 if (value[0] == '/') 478 url.setPath(value); 479 else 480 url.setPath("/" + value); 481 482 setHref(url.string()); 483 } 484 485 String HTMLAnchorElement::port() const 486 { 487 if (href().hasPort()) 488 return String::number(href().port()); 489 490 return emptyString(); 491 } 492 493 void HTMLAnchorElement::setPort(const String& value) 494 { 495 KURL url = href(); 496 if (!url.canSetHostOrPort()) 497 return; 498 499 // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes 500 // specifically goes against RFC 3986 (p3.2) and 501 // requires setting the port to "0" if it is set to empty string. 502 unsigned port = value.toUInt(); 503 if (isDefaultPortForProtocol(port, url.protocol())) 504 url.removePort(); 505 else 506 url.setPort(port); 507 508 setHref(url.string()); 509 } 510 511 String HTMLAnchorElement::protocol() const 512 { 513 return href().protocol() + ":"; 514 } 515 516 void HTMLAnchorElement::setProtocol(const String& value) 517 { 518 KURL url = href(); 519 url.setProtocol(value); 520 setHref(url.string()); 521 } 522 523 String HTMLAnchorElement::search() const 524 { 525 String query = href().query(); 526 return query.isEmpty() ? emptyString() : "?" + query; 527 } 528 529 String HTMLAnchorElement::origin() const 530 { 531 RefPtr<SecurityOrigin> origin = SecurityOrigin::create(href()); 532 return origin->toString(); 533 } 534 535 void HTMLAnchorElement::setSearch(const String& value) 536 { 537 KURL url = href(); 538 String newSearch = (value[0] == '?') ? value.substring(1) : value; 539 // Make sure that '#' in the query does not leak to the hash. 540 url.setQuery(newSearch.replaceWithLiteral('#', "%23")); 541 542 setHref(url.string()); 543 } 544 545 String HTMLAnchorElement::text() 546 { 547 return innerText(); 548 } 549 550 String HTMLAnchorElement::toString() const 551 { 552 return href().string(); 553 } 554 555 bool HTMLAnchorElement::isLiveLink() const 556 { 557 return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey); 558 } 559 560 void HTMLAnchorElement::sendPings(const KURL& destinationURL) 561 { 562 if (!hasAttribute(pingAttr) || !document()->settings() || !document()->settings()->hyperlinkAuditingEnabled()) 563 return; 564 565 SpaceSplitString pingURLs(getAttribute(pingAttr), false); 566 for (unsigned i = 0; i < pingURLs.size(); i++) 567 PingLoader::sendPing(document()->frame(), document()->completeURL(pingURLs[i]), destinationURL); 568 } 569 570 void HTMLAnchorElement::handleClick(Event* event) 571 { 572 event->setDefaultHandled(); 573 574 Frame* frame = document()->frame(); 575 if (!frame) 576 return; 577 578 StringBuilder url; 579 url.append(stripLeadingAndTrailingHTMLSpaces(fastGetAttribute(hrefAttr))); 580 appendServerMapMousePosition(url, event); 581 KURL completedURL = document()->completeURL(url.toString()); 582 if (frame->loader()->client()->shouldAbortNavigationAfterUrlResolve(document()->baseURI(), url.toString(), completedURL)) 583 return; 584 585 ResourceRequest request(completedURL); 586 if (prefetchEventHandler()->hasIssuedPreconnect()) 587 frame->loader()->client()->dispatchWillRequestAfterPreconnect(request); 588 if (hasAttribute(downloadAttr)) { 589 if (!hasRel(RelationNoReferrer)) { 590 String referrer = SecurityPolicy::generateReferrerHeader(document()->referrerPolicy(), completedURL, frame->loader()->outgoingReferrer()); 591 if (!referrer.isEmpty()) 592 request.setHTTPReferrer(referrer); 593 } 594 595 frame->loader()->client()->loadURLExternally(request, NavigationPolicyDownload, fastGetAttribute(downloadAttr)); 596 } else { 597 FrameLoadRequest frameRequest(document()->securityOrigin(), request, target()); 598 frameRequest.setTriggeringEvent(event); 599 if (hasRel(RelationNoReferrer)) 600 frameRequest.setShouldSendReferrer(NeverSendReferrer); 601 frame->loader()->load(frameRequest); 602 } 603 604 sendPings(completedURL); 605 } 606 607 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event) 608 { 609 if (!event->isMouseEvent()) 610 return NonMouseEvent; 611 return toMouseEvent(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey; 612 } 613 614 bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const 615 { 616 if (!rendererIsEditable()) 617 return true; 618 619 Settings* settings = document()->settings(); 620 if (!settings) 621 return true; 622 623 switch (settings->editableLinkBehavior()) { 624 case EditableLinkDefaultBehavior: 625 case EditableLinkAlwaysLive: 626 return true; 627 628 case EditableLinkNeverLive: 629 return false; 630 631 // If the selection prior to clicking on this link resided in the same editable block as this link, 632 // and the shift key isn't pressed, we don't want to follow the link. 633 case EditableLinkLiveWhenNotFocused: 634 return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && rootEditableElementForSelectionOnMouseDown() != rootEditableElement()); 635 636 case EditableLinkOnlyLiveWithShiftKey: 637 return eventType == MouseEventWithShiftKey; 638 } 639 640 ASSERT_NOT_REACHED(); 641 return false; 642 } 643 644 bool isEnterKeyKeydownEvent(Event* event) 645 { 646 return event->type() == eventNames().keydownEvent && event->isKeyboardEvent() && toKeyboardEvent(event)->keyIdentifier() == "Enter"; 647 } 648 649 bool isLinkClick(Event* event) 650 { 651 return event->type() == eventNames().clickEvent && (!event->isMouseEvent() || toMouseEvent(event)->button() != RightButton); 652 } 653 654 bool HTMLAnchorElement::willRespondToMouseClickEvents() 655 { 656 return isLink() || HTMLElement::willRespondToMouseClickEvents(); 657 } 658 659 typedef HashMap<const HTMLAnchorElement*, RefPtr<Element> > RootEditableElementMap; 660 661 static RootEditableElementMap& rootEditableElementMap() 662 { 663 DEFINE_STATIC_LOCAL(RootEditableElementMap, map, ()); 664 return map; 665 } 666 667 Element* HTMLAnchorElement::rootEditableElementForSelectionOnMouseDown() const 668 { 669 if (!m_hasRootEditableElementForSelectionOnMouseDown) 670 return 0; 671 return rootEditableElementMap().get(this); 672 } 673 674 void HTMLAnchorElement::clearRootEditableElementForSelectionOnMouseDown() 675 { 676 if (!m_hasRootEditableElementForSelectionOnMouseDown) 677 return; 678 rootEditableElementMap().remove(this); 679 m_hasRootEditableElementForSelectionOnMouseDown = false; 680 } 681 682 void HTMLAnchorElement::setRootEditableElementForSelectionOnMouseDown(Element* element) 683 { 684 if (!element) { 685 clearRootEditableElementForSelectionOnMouseDown(); 686 return; 687 } 688 689 rootEditableElementMap().set(this, element); 690 m_hasRootEditableElementForSelectionOnMouseDown = true; 691 } 692 693 HTMLAnchorElement::PrefetchEventHandler* HTMLAnchorElement::prefetchEventHandler() 694 { 695 if (!m_prefetchEventHandler) 696 m_prefetchEventHandler = PrefetchEventHandler::create(this); 697 698 return m_prefetchEventHandler.get(); 699 } 700 701 HTMLAnchorElement::PrefetchEventHandler::PrefetchEventHandler(HTMLAnchorElement* anchorElement) 702 : m_anchorElement(anchorElement) 703 { 704 ASSERT(m_anchorElement); 705 706 reset(); 707 } 708 709 void HTMLAnchorElement::PrefetchEventHandler::reset() 710 { 711 m_hadHREFChanged = false; 712 m_mouseOverTimestamp = 0; 713 m_mouseDownTimestamp = 0; 714 m_hadTapUnconfirmed = false; 715 m_tapDownTimestamp = 0; 716 m_hasIssuedPreconnect = false; 717 } 718 719 void HTMLAnchorElement::PrefetchEventHandler::handleEvent(Event* event) 720 { 721 if (!shouldPrefetch(m_anchorElement->href())) 722 return; 723 724 if (event->type() == eventNames().mouseoverEvent) 725 handleMouseOver(event); 726 else if (event->type() == eventNames().mouseoutEvent) 727 handleMouseOut(event); 728 else if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton) 729 handleLeftMouseDown(event); 730 else if (event->type() == eventNames().gesturetapdownEvent) 731 handleGestureTapDown(event); 732 else if (event->type() == eventNames().gesturetapunconfirmedEvent) 733 handleGestureTapUnconfirmed(event); 734 else if (isLinkClick(event)) 735 handleClick(event); 736 } 737 738 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOver(Event* event) 739 { 740 if (m_mouseOverTimestamp == 0.0) { 741 m_mouseOverTimestamp = event->timeStamp(); 742 743 HistogramSupport::histogramEnumeration("MouseEventPrefetch.MouseOvers", 0, 2); 744 745 prefetch(WebKit::WebPreconnectMotivationLinkMouseOver); 746 } 747 } 748 749 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOut(Event* event) 750 { 751 if (m_mouseOverTimestamp > 0.0) { 752 double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp); 753 HistogramSupport::histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_NoClick", mouseOverDuration * 1000, 0, 10000, 100); 754 755 m_mouseOverTimestamp = 0.0; 756 } 757 } 758 759 void HTMLAnchorElement::PrefetchEventHandler::handleLeftMouseDown(Event* event) 760 { 761 m_mouseDownTimestamp = event->timeStamp(); 762 763 HistogramSupport::histogramEnumeration("MouseEventPrefetch.MouseDowns", 0, 2); 764 765 prefetch(WebKit::WebPreconnectMotivationLinkMouseDown); 766 } 767 768 void HTMLAnchorElement::PrefetchEventHandler::handleGestureTapUnconfirmed(Event* event) 769 { 770 m_hadTapUnconfirmed = true; 771 772 HistogramSupport::histogramEnumeration("MouseEventPrefetch.TapUnconfirmeds", 0, 2); 773 774 prefetch(WebKit::WebPreconnectMotivationLinkTapUnconfirmed); 775 } 776 777 void HTMLAnchorElement::PrefetchEventHandler::handleGestureTapDown(Event* event) 778 { 779 m_tapDownTimestamp = event->timeStamp(); 780 781 HistogramSupport::histogramEnumeration("MouseEventPrefetch.TapDowns", 0, 2); 782 783 prefetch(WebKit::WebPreconnectMotivationLinkTapDown); 784 } 785 786 void HTMLAnchorElement::PrefetchEventHandler::handleClick(Event* event) 787 { 788 bool capturedMouseOver = (m_mouseOverTimestamp > 0.0); 789 if (capturedMouseOver) { 790 double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp); 791 792 HistogramSupport::histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_Click", mouseOverDuration * 1000, 0, 10000, 100); 793 } 794 795 bool capturedMouseDown = (m_mouseDownTimestamp > 0.0); 796 HistogramSupport::histogramEnumeration("MouseEventPrefetch.MouseDownFollowedByClick", capturedMouseDown, 2); 797 798 if (capturedMouseDown) { 799 double mouseDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseDownTimestamp); 800 801 HistogramSupport::histogramCustomCounts("MouseEventPrefetch.MouseDownDuration_Click", mouseDownDuration * 1000, 0, 10000, 100); 802 } 803 804 bool capturedTapDown = (m_tapDownTimestamp > 0.0); 805 if (capturedTapDown) { 806 double tapDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_tapDownTimestamp); 807 808 HistogramSupport::histogramCustomCounts("MouseEventPrefetch.TapDownDuration_Click", tapDownDuration * 1000, 0, 10000, 100); 809 } 810 811 int flags = (m_hadTapUnconfirmed ? 2 : 0) | (capturedTapDown ? 1 : 0); 812 HistogramSupport::histogramEnumeration("MouseEventPrefetch.PreTapEventsFollowedByClick", flags, 4); 813 } 814 815 bool HTMLAnchorElement::PrefetchEventHandler::shouldPrefetch(const KURL& url) 816 { 817 if (m_hadHREFChanged) 818 return false; 819 820 if (m_anchorElement->hasEventListeners(eventNames().clickEvent)) 821 return false; 822 823 if (!url.protocolIsInHTTPFamily()) 824 return false; 825 826 Document* document = m_anchorElement->document(); 827 if (!document) 828 return false; 829 830 if (!document->securityOrigin()->canDisplay(url)) 831 return false; 832 833 if (url.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(document->url(), url)) 834 return false; 835 836 Frame* frame = document->frame(); 837 if (!frame) 838 return false; 839 840 // Links which create new window/tab are avoided because they may require user approval interaction. 841 if (!m_anchorElement->target().isEmpty()) 842 return false; 843 844 return true; 845 } 846 847 void HTMLAnchorElement::PrefetchEventHandler::prefetch(WebKit::WebPreconnectMotivation motivation) 848 { 849 const KURL& url = m_anchorElement->href(); 850 851 if (!shouldPrefetch(url)) 852 return; 853 854 // The precision of current MouseOver trigger is too low to actually trigger preconnects. 855 if (motivation == WebKit::WebPreconnectMotivationLinkMouseOver) 856 return; 857 858 preconnectToURL(url, motivation); 859 m_hasIssuedPreconnect = true; 860 } 861 862 } 863