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/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 "platform/PlatformMouseEvent.h" 47 #include "platform/network/DNS.h" 48 #include "platform/network/ResourceRequest.h" 49 #include "platform/weborigin/KnownPorts.h" 50 #include "platform/weborigin/SecurityOrigin.h" 51 #include "platform/weborigin/SecurityPolicy.h" 52 #include "public/platform/Platform.h" 53 #include "public/platform/WebPrescientNetworking.h" 54 #include "public/platform/WebURL.h" 55 #include "wtf/text/StringBuilder.h" 56 57 namespace WebCore { 58 59 namespace { 60 61 void preconnectToURL(const KURL& url, blink::WebPreconnectMotivation motivation) 62 { 63 blink::WebPrescientNetworking* prescientNetworking = blink::Platform::current()->prescientNetworking(); 64 if (!prescientNetworking) 65 return; 66 67 prescientNetworking->preconnect(url, motivation); 68 } 69 70 } 71 72 class HTMLAnchorElement::PrefetchEventHandler FINAL : public NoBaseWillBeGarbageCollected<HTMLAnchorElement::PrefetchEventHandler> { 73 public: 74 static PassOwnPtrWillBeRawPtr<PrefetchEventHandler> create(HTMLAnchorElement* anchorElement) 75 { 76 return adoptPtrWillBeNoop(new HTMLAnchorElement::PrefetchEventHandler(anchorElement)); 77 } 78 79 void reset(); 80 81 void handleEvent(Event* e); 82 void didChangeHREF() { m_hadHREFChanged = true; } 83 bool hasIssuedPreconnect() const { return m_hasIssuedPreconnect; } 84 85 void trace(Visitor* visitor) { visitor->trace(m_anchorElement); } 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 handleGestureShowPress(Event*); 95 void handleClick(Event* event); 96 97 bool shouldPrefetch(const KURL&); 98 void prefetch(blink::WebPreconnectMotivation); 99 100 RawPtrWillBeMember<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_linkRelations(0) 114 , m_cachedVisitedLinkHash(0) 115 { 116 ScriptWrappable::init(this); 117 } 118 119 PassRefPtrWillBeRawPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document& document) 120 { 121 return adoptRefWillBeNoop(new HTMLAnchorElement(aTag, document)); 122 } 123 124 HTMLAnchorElement::~HTMLAnchorElement() 125 { 126 } 127 128 bool HTMLAnchorElement::supportsFocus() const 129 { 130 if (rendererIsEditable()) 131 return HTMLElement::supportsFocus(); 132 // If not a link we should still be able to focus the element if it has tabIndex. 133 return isLink() || HTMLElement::supportsFocus(); 134 } 135 136 bool HTMLAnchorElement::isMouseFocusable() const 137 { 138 // Links are focusable by default, but only allow links with tabindex or contenteditable to be mouse focusable. 139 // https://bugs.webkit.org/show_bug.cgi?id=26856 140 if (isLink()) 141 return HTMLElement::supportsFocus(); 142 143 return HTMLElement::isMouseFocusable(); 144 } 145 146 bool HTMLAnchorElement::isKeyboardFocusable() const 147 { 148 ASSERT(document().isActive()); 149 150 if (isFocusable() && Element::supportsFocus()) 151 return HTMLElement::isKeyboardFocusable(); 152 153 if (isLink() && !document().frameHost()->chrome().client().tabsToLinks()) 154 return false; 155 return HTMLElement::isKeyboardFocusable(); 156 } 157 158 static void appendServerMapMousePosition(StringBuilder& url, Event* event) 159 { 160 if (!event->isMouseEvent()) 161 return; 162 163 ASSERT(event->target()); 164 Node* target = event->target()->toNode(); 165 ASSERT(target); 166 if (!isHTMLImageElement(*target)) 167 return; 168 169 HTMLImageElement& imageElement = toHTMLImageElement(*target); 170 if (!imageElement.isServerMap()) 171 return; 172 173 if (!imageElement.renderer() || !imageElement.renderer()->isRenderImage()) 174 return; 175 RenderImage* renderer = toRenderImage(imageElement.renderer()); 176 177 // FIXME: This should probably pass true for useTransforms. 178 FloatPoint absolutePosition = renderer->absoluteToLocal(FloatPoint(toMouseEvent(event)->pageX(), toMouseEvent(event)->pageY())); 179 int x = absolutePosition.x(); 180 int y = absolutePosition.y(); 181 url.append('?'); 182 url.appendNumber(x); 183 url.append(','); 184 url.appendNumber(y); 185 } 186 187 void HTMLAnchorElement::defaultEventHandler(Event* event) 188 { 189 if (isLink()) { 190 if (focused() && isEnterKeyKeydownEvent(event) && isLiveLink()) { 191 event->setDefaultHandled(); 192 dispatchSimulatedClick(event); 193 return; 194 } 195 196 prefetchEventHandler()->handleEvent(event); 197 198 if (isLinkClick(event) && isLiveLink()) { 199 handleClick(event); 200 prefetchEventHandler()->reset(); 201 return; 202 } 203 } 204 205 HTMLElement::defaultEventHandler(event); 206 } 207 208 void HTMLAnchorElement::setActive(bool down) 209 { 210 if (rendererIsEditable()) 211 return; 212 213 ContainerNode::setActive(down); 214 } 215 216 void HTMLAnchorElement::parseAttribute(const QualifiedName& name, const AtomicString& value) 217 { 218 if (name == hrefAttr) { 219 bool wasLink = isLink(); 220 setIsLink(!value.isNull()); 221 if (wasLink != isLink()) { 222 didAffectSelector(AffectedSelectorLink | AffectedSelectorVisited | AffectedSelectorEnabled); 223 if (wasLink && treeScope().adjustedFocusedElement() == this) { 224 // We might want to call blur(), but it's dangerous to dispatch 225 // events here. 226 document().setNeedsFocusedElementCheck(); 227 } 228 } 229 if (isLink()) { 230 String parsedURL = stripLeadingAndTrailingHTMLSpaces(value); 231 if (document().isDNSPrefetchEnabled()) { 232 if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//")) 233 prefetchDNS(document().completeURL(parsedURL).host()); 234 } 235 236 if (wasLink) 237 prefetchEventHandler()->didChangeHREF(); 238 } 239 invalidateCachedVisitedLinkHash(); 240 } else if (name == nameAttr || name == titleAttr) { 241 // Do nothing. 242 } else if (name == relAttr) 243 setRel(value); 244 else 245 HTMLElement::parseAttribute(name, value); 246 } 247 248 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents) 249 { 250 dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents); 251 } 252 253 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const 254 { 255 return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute); 256 } 257 258 bool HTMLAnchorElement::hasLegalLinkAttribute(const QualifiedName& name) const 259 { 260 return name == hrefAttr || HTMLElement::hasLegalLinkAttribute(name); 261 } 262 263 bool HTMLAnchorElement::canStartSelection() const 264 { 265 if (!isLink()) 266 return HTMLElement::canStartSelection(); 267 return rendererIsEditable(); 268 } 269 270 bool HTMLAnchorElement::draggable() const 271 { 272 // Should be draggable if we have an href attribute. 273 const AtomicString& value = getAttribute(draggableAttr); 274 if (equalIgnoringCase(value, "true")) 275 return true; 276 if (equalIgnoringCase(value, "false")) 277 return false; 278 return hasAttribute(hrefAttr); 279 } 280 281 KURL HTMLAnchorElement::href() const 282 { 283 return document().completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr))); 284 } 285 286 void HTMLAnchorElement::setHref(const AtomicString& value) 287 { 288 setAttribute(hrefAttr, value); 289 } 290 291 KURL HTMLAnchorElement::url() const 292 { 293 return href(); 294 } 295 296 void HTMLAnchorElement::setURL(const KURL& url) 297 { 298 setHref(AtomicString(url.string())); 299 } 300 301 String HTMLAnchorElement::input() const 302 { 303 return getAttribute(hrefAttr); 304 } 305 306 void HTMLAnchorElement::setInput(const String& value) 307 { 308 setHref(AtomicString(value)); 309 } 310 311 bool HTMLAnchorElement::hasRel(uint32_t relation) const 312 { 313 return m_linkRelations & relation; 314 } 315 316 void HTMLAnchorElement::setRel(const AtomicString& value) 317 { 318 m_linkRelations = 0; 319 SpaceSplitString newLinkRelations(value, true); 320 // FIXME: Add link relations as they are implemented 321 if (newLinkRelations.contains("noreferrer")) 322 m_linkRelations |= RelationNoReferrer; 323 } 324 325 const AtomicString& HTMLAnchorElement::name() const 326 { 327 return getNameAttribute(); 328 } 329 330 short HTMLAnchorElement::tabIndex() const 331 { 332 // Skip the supportsFocus check in HTMLElement. 333 return Element::tabIndex(); 334 } 335 336 AtomicString HTMLAnchorElement::target() const 337 { 338 return getAttribute(targetAttr); 339 } 340 341 bool HTMLAnchorElement::isLiveLink() const 342 { 343 return isLink() && !rendererIsEditable(); 344 } 345 346 void HTMLAnchorElement::sendPings(const KURL& destinationURL) 347 { 348 const AtomicString& pingValue = getAttribute(pingAttr); 349 if (pingValue.isNull() || !document().settings() || !document().settings()->hyperlinkAuditingEnabled()) 350 return; 351 352 UseCounter::count(document(), UseCounter::HTMLAnchorElementPingAttribute); 353 354 SpaceSplitString pingURLs(pingValue, false); 355 for (unsigned i = 0; i < pingURLs.size(); i++) 356 PingLoader::sendLinkAuditPing(document().frame(), document().completeURL(pingURLs[i]), destinationURL); 357 } 358 359 void HTMLAnchorElement::handleClick(Event* event) 360 { 361 event->setDefaultHandled(); 362 363 LocalFrame* frame = document().frame(); 364 if (!frame) 365 return; 366 367 StringBuilder url; 368 url.append(stripLeadingAndTrailingHTMLSpaces(fastGetAttribute(hrefAttr))); 369 appendServerMapMousePosition(url, event); 370 KURL completedURL = document().completeURL(url.toString()); 371 372 // Schedule the ping before the frame load. Prerender in Chrome may kill the renderer as soon as the navigation is 373 // sent out. 374 sendPings(completedURL); 375 376 ResourceRequest request(completedURL); 377 if (prefetchEventHandler()->hasIssuedPreconnect()) 378 frame->loader().client()->dispatchWillRequestAfterPreconnect(request); 379 if (hasAttribute(downloadAttr)) { 380 if (!hasRel(RelationNoReferrer)) { 381 String referrer = SecurityPolicy::generateReferrerHeader(document().referrerPolicy(), completedURL, document().outgoingReferrer()); 382 if (!referrer.isEmpty()) 383 request.setHTTPReferrer(Referrer(referrer, document().referrerPolicy())); 384 } 385 386 bool isSameOrigin = completedURL.protocolIsData() || document().securityOrigin()->canRequest(completedURL); 387 const AtomicString& suggestedName = (isSameOrigin ? fastGetAttribute(downloadAttr) : nullAtom); 388 389 frame->loader().client()->loadURLExternally(request, NavigationPolicyDownload, suggestedName); 390 } else { 391 FrameLoadRequest frameRequest(&document(), request, target()); 392 frameRequest.setTriggeringEvent(event); 393 if (hasRel(RelationNoReferrer)) 394 frameRequest.setShouldSendReferrer(NeverSendReferrer); 395 frame->loader().load(frameRequest); 396 } 397 } 398 399 bool isEnterKeyKeydownEvent(Event* event) 400 { 401 return event->type() == EventTypeNames::keydown && event->isKeyboardEvent() && toKeyboardEvent(event)->keyIdentifier() == "Enter"; 402 } 403 404 bool isLinkClick(Event* event) 405 { 406 return event->type() == EventTypeNames::click && (!event->isMouseEvent() || toMouseEvent(event)->button() != RightButton); 407 } 408 409 bool HTMLAnchorElement::willRespondToMouseClickEvents() 410 { 411 return isLink() || HTMLElement::willRespondToMouseClickEvents(); 412 } 413 414 HTMLAnchorElement::PrefetchEventHandler* HTMLAnchorElement::prefetchEventHandler() 415 { 416 if (!m_prefetchEventHandler) 417 m_prefetchEventHandler = PrefetchEventHandler::create(this); 418 419 return m_prefetchEventHandler.get(); 420 } 421 422 HTMLAnchorElement::PrefetchEventHandler::PrefetchEventHandler(HTMLAnchorElement* anchorElement) 423 : m_anchorElement(anchorElement) 424 { 425 ASSERT(m_anchorElement); 426 427 reset(); 428 } 429 430 void HTMLAnchorElement::PrefetchEventHandler::reset() 431 { 432 m_hadHREFChanged = false; 433 m_mouseOverTimestamp = 0; 434 m_mouseDownTimestamp = 0; 435 m_hadTapUnconfirmed = false; 436 m_tapDownTimestamp = 0; 437 m_hasIssuedPreconnect = false; 438 } 439 440 void HTMLAnchorElement::PrefetchEventHandler::handleEvent(Event* event) 441 { 442 if (!shouldPrefetch(m_anchorElement->href())) 443 return; 444 445 if (event->type() == EventTypeNames::mouseover) 446 handleMouseOver(event); 447 else if (event->type() == EventTypeNames::mouseout) 448 handleMouseOut(event); 449 else if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton) 450 handleLeftMouseDown(event); 451 else if (event->type() == EventTypeNames::gestureshowpress) 452 handleGestureShowPress(event); 453 else if (event->type() == EventTypeNames::gesturetapunconfirmed) 454 handleGestureTapUnconfirmed(event); 455 else if (isLinkClick(event)) 456 handleClick(event); 457 } 458 459 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOver(Event* event) 460 { 461 if (m_mouseOverTimestamp == 0.0) { 462 m_mouseOverTimestamp = event->timeStamp(); 463 464 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseOvers", 0, 2); 465 466 prefetch(blink::WebPreconnectMotivationLinkMouseOver); 467 } 468 } 469 470 void HTMLAnchorElement::PrefetchEventHandler::handleMouseOut(Event* event) 471 { 472 if (m_mouseOverTimestamp > 0.0) { 473 double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp); 474 blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_NoClick", mouseOverDuration * 1000, 0, 10000, 100); 475 476 m_mouseOverTimestamp = 0.0; 477 } 478 } 479 480 void HTMLAnchorElement::PrefetchEventHandler::handleLeftMouseDown(Event* event) 481 { 482 m_mouseDownTimestamp = event->timeStamp(); 483 484 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseDowns", 0, 2); 485 486 prefetch(blink::WebPreconnectMotivationLinkMouseDown); 487 } 488 489 void HTMLAnchorElement::PrefetchEventHandler::handleGestureTapUnconfirmed(Event* event) 490 { 491 m_hadTapUnconfirmed = true; 492 493 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.TapUnconfirmeds", 0, 2); 494 495 prefetch(blink::WebPreconnectMotivationLinkTapUnconfirmed); 496 } 497 498 void HTMLAnchorElement::PrefetchEventHandler::handleGestureShowPress(Event* event) 499 { 500 m_tapDownTimestamp = event->timeStamp(); 501 502 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.TapDowns", 0, 2); 503 504 prefetch(blink::WebPreconnectMotivationLinkTapDown); 505 } 506 507 void HTMLAnchorElement::PrefetchEventHandler::handleClick(Event* event) 508 { 509 bool capturedMouseOver = (m_mouseOverTimestamp > 0.0); 510 if (capturedMouseOver) { 511 double mouseOverDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseOverTimestamp); 512 513 blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseOverDuration_Click", mouseOverDuration * 1000, 0, 10000, 100); 514 } 515 516 bool capturedMouseDown = (m_mouseDownTimestamp > 0.0); 517 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.MouseDownFollowedByClick", capturedMouseDown, 2); 518 519 if (capturedMouseDown) { 520 double mouseDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_mouseDownTimestamp); 521 522 blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.MouseDownDuration_Click", mouseDownDuration * 1000, 0, 10000, 100); 523 } 524 525 bool capturedTapDown = (m_tapDownTimestamp > 0.0); 526 if (capturedTapDown) { 527 double tapDownDuration = convertDOMTimeStampToSeconds(event->timeStamp() - m_tapDownTimestamp); 528 529 blink::Platform::current()->histogramCustomCounts("MouseEventPrefetch.TapDownDuration_Click", tapDownDuration * 1000, 0, 10000, 100); 530 } 531 532 int flags = (m_hadTapUnconfirmed ? 2 : 0) | (capturedTapDown ? 1 : 0); 533 blink::Platform::current()->histogramEnumeration("MouseEventPrefetch.PreTapEventsFollowedByClick", flags, 4); 534 } 535 536 bool HTMLAnchorElement::PrefetchEventHandler::shouldPrefetch(const KURL& url) 537 { 538 if (m_hadHREFChanged) 539 return false; 540 541 if (m_anchorElement->hasEventListeners(EventTypeNames::click)) 542 return false; 543 544 if (!url.protocolIsInHTTPFamily()) 545 return false; 546 547 Document& document = m_anchorElement->document(); 548 549 if (!document.securityOrigin()->canDisplay(url)) 550 return false; 551 552 if (url.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(document.url(), url)) 553 return false; 554 555 LocalFrame* frame = document.frame(); 556 if (!frame) 557 return false; 558 559 // Links which create new window/tab are avoided because they may require user approval interaction. 560 if (!m_anchorElement->target().isEmpty()) 561 return false; 562 563 return true; 564 } 565 566 void HTMLAnchorElement::PrefetchEventHandler::prefetch(blink::WebPreconnectMotivation motivation) 567 { 568 const KURL& url = m_anchorElement->href(); 569 570 if (!shouldPrefetch(url)) 571 return; 572 573 // The precision of current MouseOver trigger is too low to actually trigger preconnects. 574 if (motivation == blink::WebPreconnectMotivationLinkMouseOver) 575 return; 576 577 preconnectToURL(url, motivation); 578 m_hasIssuedPreconnect = true; 579 } 580 581 bool HTMLAnchorElement::isInteractiveContent() const 582 { 583 return isLink(); 584 } 585 586 void HTMLAnchorElement::trace(Visitor* visitor) 587 { 588 visitor->trace(m_prefetchEventHandler); 589 HTMLElement::trace(visitor); 590 } 591 592 } 593