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 "HTMLAnchorElement.h" 26 27 #include "Attribute.h" 28 #include "EventNames.h" 29 #include "Frame.h" 30 #include "FrameLoaderTypes.h" 31 #include "HTMLImageElement.h" 32 #include "HTMLNames.h" 33 #include "HTMLParserIdioms.h" 34 #include "KeyboardEvent.h" 35 #include "MouseEvent.h" 36 #include "Page.h" 37 #include "PingLoader.h" 38 #include "RenderImage.h" 39 #include "ResourceHandle.h" 40 #include "Settings.h" 41 #include "UserGestureIndicator.h" 42 43 namespace WebCore { 44 45 using namespace HTMLNames; 46 47 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document* document) 48 : HTMLElement(tagName, document) 49 , m_wasShiftKeyDownOnMouseDown(false) 50 , m_linkRelations(0) 51 { 52 } 53 54 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document* document) 55 { 56 return adoptRef(new HTMLAnchorElement(aTag, document)); 57 } 58 59 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(const QualifiedName& tagName, Document* document) 60 { 61 return adoptRef(new HTMLAnchorElement(tagName, document)); 62 } 63 64 // This function does not allow leading spaces before the port number. 65 static unsigned parsePortFromStringPosition(const String& value, unsigned portStart, unsigned& portEnd) 66 { 67 portEnd = portStart; 68 while (isASCIIDigit(value[portEnd])) 69 ++portEnd; 70 return value.substring(portStart, portEnd - portStart).toUInt(); 71 } 72 73 bool HTMLAnchorElement::supportsFocus() const 74 { 75 if (rendererIsEditable()) 76 return HTMLElement::supportsFocus(); 77 // If not a link we should still be able to focus the element if it has tabIndex. 78 return isLink() || HTMLElement::supportsFocus(); 79 } 80 81 bool HTMLAnchorElement::isMouseFocusable() const 82 { 83 // Anchor elements should be mouse focusable, https://bugs.webkit.org/show_bug.cgi?id=26856 84 #if !PLATFORM(GTK) && !PLATFORM(QT) && !PLATFORM(EFL) 85 if (isLink()) 86 // Only allow links with tabIndex or contentEditable to be mouse focusable. 87 return HTMLElement::supportsFocus(); 88 #endif 89 90 // Allow tab index etc to control focus. 91 return HTMLElement::isMouseFocusable(); 92 } 93 94 bool HTMLAnchorElement::isKeyboardFocusable(KeyboardEvent* event) const 95 { 96 if (!isLink()) 97 return HTMLElement::isKeyboardFocusable(event); 98 99 if (!isFocusable()) 100 return false; 101 102 if (!document()->frame()) 103 return false; 104 105 if (!document()->frame()->eventHandler()->tabsToLinks(event)) 106 return false; 107 108 return hasNonEmptyBoundingBox(); 109 } 110 111 static void appendServerMapMousePosition(String& url, Event* event) 112 { 113 if (!event->isMouseEvent()) 114 return; 115 116 ASSERT(event->target()); 117 Node* target = event->target()->toNode(); 118 ASSERT(target); 119 if (!target->hasTagName(imgTag)) 120 return; 121 122 HTMLImageElement* imageElement = static_cast<HTMLImageElement*>(event->target()->toNode()); 123 if (!imageElement || !imageElement->isServerMap()) 124 return; 125 126 RenderImage* renderer = toRenderImage(imageElement->renderer()); 127 if (!renderer) 128 return; 129 130 // FIXME: This should probably pass true for useTransforms. 131 FloatPoint absolutePosition = renderer->absoluteToLocal(FloatPoint(static_cast<MouseEvent*>(event)->pageX(), static_cast<MouseEvent*>(event)->pageY())); 132 int x = absolutePosition.x(); 133 int y = absolutePosition.y(); 134 url += "?"; 135 url += String::number(x); 136 url += ","; 137 url += String::number(y); 138 } 139 140 void HTMLAnchorElement::defaultEventHandler(Event* event) 141 { 142 if (isLink()) { 143 if (focused() && isEnterKeyKeydownEvent(event) && treatLinkAsLiveForEventType(NonMouseEvent)) { 144 event->setDefaultHandled(); 145 dispatchSimulatedClick(event); 146 return; 147 } 148 149 if (isLinkClick(event) && treatLinkAsLiveForEventType(eventType(event))) { 150 String url = stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)); 151 appendServerMapMousePosition(url, event); 152 handleLinkClick(event, document(), url, getAttribute(targetAttr), hasRel(RelationNoReferrer)); 153 sendPings(document()->completeURL(url)); 154 return; 155 } 156 157 if (rendererIsEditable()) { 158 // This keeps track of the editable block that the selection was in (if it was in one) just before the link was clicked 159 // for the LiveWhenNotFocused editable link behavior 160 if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() != RightButton && document()->frame() && document()->frame()->selection()) { 161 m_rootEditableElementForSelectionOnMouseDown = document()->frame()->selection()->rootEditableElement(); 162 m_wasShiftKeyDownOnMouseDown = static_cast<MouseEvent*>(event)->shiftKey(); 163 } else if (event->type() == eventNames().mouseoverEvent) { 164 // These are cleared on mouseover and not mouseout because their values are needed for drag events, 165 // but drag events happen after mouse out events. 166 m_rootEditableElementForSelectionOnMouseDown = 0; 167 m_wasShiftKeyDownOnMouseDown = false; 168 } 169 } 170 } 171 172 HTMLElement::defaultEventHandler(event); 173 } 174 175 void HTMLAnchorElement::setActive(bool down, bool pause) 176 { 177 if (rendererIsEditable()) { 178 EditableLinkBehavior editableLinkBehavior = EditableLinkDefaultBehavior; 179 if (Settings* settings = document()->settings()) 180 editableLinkBehavior = settings->editableLinkBehavior(); 181 182 switch (editableLinkBehavior) { 183 default: 184 case EditableLinkDefaultBehavior: 185 case EditableLinkAlwaysLive: 186 break; 187 188 case EditableLinkNeverLive: 189 return; 190 191 // Don't set the link to be active if the current selection is in the same editable block as 192 // this link 193 case EditableLinkLiveWhenNotFocused: 194 if (down && document()->frame() && document()->frame()->selection()->rootEditableElement() == rootEditableElement()) 195 return; 196 break; 197 198 case EditableLinkOnlyLiveWithShiftKey: 199 return; 200 } 201 202 } 203 204 ContainerNode::setActive(down, pause); 205 } 206 207 void HTMLAnchorElement::parseMappedAttribute(Attribute* attr) 208 { 209 if (attr->name() == hrefAttr) { 210 bool wasLink = isLink(); 211 setIsLink(!attr->isNull()); 212 if (wasLink != isLink()) 213 setNeedsStyleRecalc(); 214 if (isLink()) { 215 String parsedURL = stripLeadingAndTrailingHTMLSpaces(attr->value()); 216 if (document()->isDNSPrefetchEnabled()) { 217 if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//")) 218 ResourceHandle::prepareForURL(document()->completeURL(parsedURL)); 219 } 220 if (document()->page() && !document()->page()->javaScriptURLsAreAllowed() && protocolIsJavaScript(parsedURL)) { 221 clearIsLink(); 222 attr->setValue(nullAtom); 223 } 224 } 225 } else if (attr->name() == nameAttr || 226 attr->name() == titleAttr) { 227 // Do nothing. 228 } else if (attr->name() == relAttr) 229 setRel(attr->value()); 230 else 231 HTMLElement::parseMappedAttribute(attr); 232 } 233 234 void HTMLAnchorElement::accessKeyAction(bool sendToAnyElement) 235 { 236 // send the mouse button events if the caller specified sendToAnyElement 237 dispatchSimulatedClick(0, sendToAnyElement); 238 } 239 240 bool HTMLAnchorElement::isURLAttribute(Attribute *attr) const 241 { 242 return attr->name() == hrefAttr; 243 } 244 245 bool HTMLAnchorElement::canStartSelection() const 246 { 247 // FIXME: We probably want this same behavior in SVGAElement too 248 if (!isLink()) 249 return HTMLElement::canStartSelection(); 250 return rendererIsEditable(); 251 } 252 253 bool HTMLAnchorElement::draggable() const 254 { 255 // Should be draggable if we have an href attribute. 256 const AtomicString& value = getAttribute(draggableAttr); 257 if (equalIgnoringCase(value, "true")) 258 return true; 259 if (equalIgnoringCase(value, "false")) 260 return false; 261 return hasAttribute(hrefAttr); 262 } 263 264 KURL HTMLAnchorElement::href() const 265 { 266 return document()->completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr))); 267 } 268 269 void HTMLAnchorElement::setHref(const AtomicString& value) 270 { 271 setAttribute(hrefAttr, value); 272 } 273 274 bool HTMLAnchorElement::hasRel(uint32_t relation) const 275 { 276 return m_linkRelations & relation; 277 } 278 279 void HTMLAnchorElement::setRel(const String& value) 280 { 281 m_linkRelations = 0; 282 SpaceSplitString newLinkRelations(value, true); 283 // FIXME: Add link relations as they are implemented 284 if (newLinkRelations.contains("noreferrer")) 285 m_linkRelations |= RelationNoReferrer; 286 } 287 288 const AtomicString& HTMLAnchorElement::name() const 289 { 290 return getAttribute(nameAttr); 291 } 292 293 short HTMLAnchorElement::tabIndex() const 294 { 295 // Skip the supportsFocus check in HTMLElement. 296 return Element::tabIndex(); 297 } 298 299 String HTMLAnchorElement::target() const 300 { 301 return getAttribute(targetAttr); 302 } 303 304 String HTMLAnchorElement::hash() const 305 { 306 String fragmentIdentifier = href().fragmentIdentifier(); 307 return fragmentIdentifier.isEmpty() ? "" : "#" + fragmentIdentifier; 308 } 309 310 void HTMLAnchorElement::setHash(const String& value) 311 { 312 KURL url = href(); 313 if (value[0] == '#') 314 url.setFragmentIdentifier(value.substring(1)); 315 else 316 url.setFragmentIdentifier(value); 317 setHref(url.string()); 318 } 319 320 String HTMLAnchorElement::host() const 321 { 322 const KURL& url = href(); 323 if (url.hostEnd() == url.pathStart()) 324 return url.host(); 325 if (isDefaultPortForProtocol(url.port(), url.protocol())) 326 return url.host(); 327 return url.host() + ":" + String::number(url.port()); 328 } 329 330 void HTMLAnchorElement::setHost(const String& value) 331 { 332 if (value.isEmpty()) 333 return; 334 KURL url = href(); 335 if (!url.canSetHostOrPort()) 336 return; 337 338 size_t separator = value.find(':'); 339 if (!separator) 340 return; 341 342 if (separator == notFound) 343 url.setHostAndPort(value); 344 else { 345 unsigned portEnd; 346 unsigned port = parsePortFromStringPosition(value, separator + 1, portEnd); 347 if (!port) { 348 // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes 349 // specifically goes against RFC 3986 (p3.2) and 350 // requires setting the port to "0" if it is set to empty string. 351 url.setHostAndPort(value.substring(0, separator + 1) + "0"); 352 } else { 353 if (isDefaultPortForProtocol(port, url.protocol())) 354 url.setHostAndPort(value.substring(0, separator)); 355 else 356 url.setHostAndPort(value.substring(0, portEnd)); 357 } 358 } 359 setHref(url.string()); 360 } 361 362 String HTMLAnchorElement::hostname() const 363 { 364 return href().host(); 365 } 366 367 void HTMLAnchorElement::setHostname(const String& value) 368 { 369 // Before setting new value: 370 // Remove all leading U+002F SOLIDUS ("/") characters. 371 unsigned i = 0; 372 unsigned hostLength = value.length(); 373 while (value[i] == '/') 374 i++; 375 376 if (i == hostLength) 377 return; 378 379 KURL url = href(); 380 if (!url.canSetHostOrPort()) 381 return; 382 383 url.setHost(value.substring(i)); 384 setHref(url.string()); 385 } 386 387 String HTMLAnchorElement::pathname() const 388 { 389 return href().path(); 390 } 391 392 void HTMLAnchorElement::setPathname(const String& value) 393 { 394 KURL url = href(); 395 if (!url.canSetPathname()) 396 return; 397 398 if (value[0] == '/') 399 url.setPath(value); 400 else 401 url.setPath("/" + value); 402 403 setHref(url.string()); 404 } 405 406 String HTMLAnchorElement::port() const 407 { 408 if (href().hasPort()) 409 return String::number(href().port()); 410 411 return ""; 412 } 413 414 void HTMLAnchorElement::setPort(const String& value) 415 { 416 KURL url = href(); 417 if (!url.canSetHostOrPort()) 418 return; 419 420 // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes 421 // specifically goes against RFC 3986 (p3.2) and 422 // requires setting the port to "0" if it is set to empty string. 423 unsigned port = value.toUInt(); 424 if (isDefaultPortForProtocol(port, url.protocol())) 425 url.removePort(); 426 else 427 url.setPort(port); 428 429 setHref(url.string()); 430 } 431 432 String HTMLAnchorElement::protocol() const 433 { 434 return href().protocol() + ":"; 435 } 436 437 void HTMLAnchorElement::setProtocol(const String& value) 438 { 439 KURL url = href(); 440 url.setProtocol(value); 441 setHref(url.string()); 442 } 443 444 String HTMLAnchorElement::search() const 445 { 446 String query = href().query(); 447 return query.isEmpty() ? "" : "?" + query; 448 } 449 450 String HTMLAnchorElement::origin() const 451 { 452 RefPtr<SecurityOrigin> origin = SecurityOrigin::create(href()); 453 return origin->toString(); 454 } 455 456 String HTMLAnchorElement::getParameter(const String& name) const 457 { 458 ParsedURLParameters parameters; 459 href().copyParsedQueryTo(parameters); 460 return parameters.get(name); 461 } 462 463 void HTMLAnchorElement::setSearch(const String& value) 464 { 465 KURL url = href(); 466 String newSearch = (value[0] == '?') ? value.substring(1) : value; 467 // Make sure that '#' in the query does not leak to the hash. 468 url.setQuery(newSearch.replace('#', "%23")); 469 470 setHref(url.string()); 471 } 472 473 String HTMLAnchorElement::text() const 474 { 475 return innerText(); 476 } 477 478 String HTMLAnchorElement::toString() const 479 { 480 return href().string(); 481 } 482 483 bool HTMLAnchorElement::isLiveLink() const 484 { 485 return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey); 486 } 487 488 void HTMLAnchorElement::sendPings(const KURL& destinationURL) 489 { 490 if (!hasAttribute(pingAttr) || !document()->settings()->hyperlinkAuditingEnabled()) 491 return; 492 493 SpaceSplitString pingURLs(getAttribute(pingAttr), true); 494 for (unsigned i = 0; i < pingURLs.size(); i++) 495 PingLoader::sendPing(document()->frame(), document()->completeURL(pingURLs[i]), destinationURL); 496 } 497 498 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event) 499 { 500 if (!event->isMouseEvent()) 501 return NonMouseEvent; 502 return static_cast<MouseEvent*>(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey; 503 } 504 505 bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const 506 { 507 if (!rendererIsEditable()) 508 return true; 509 510 Settings* settings = document()->settings(); 511 if (!settings) 512 return true; 513 514 switch (settings->editableLinkBehavior()) { 515 case EditableLinkDefaultBehavior: 516 case EditableLinkAlwaysLive: 517 return true; 518 519 case EditableLinkNeverLive: 520 return false; 521 522 // If the selection prior to clicking on this link resided in the same editable block as this link, 523 // and the shift key isn't pressed, we don't want to follow the link. 524 case EditableLinkLiveWhenNotFocused: 525 return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && m_rootEditableElementForSelectionOnMouseDown != rootEditableElement()); 526 527 case EditableLinkOnlyLiveWithShiftKey: 528 return eventType == MouseEventWithShiftKey; 529 } 530 531 ASSERT_NOT_REACHED(); 532 return false; 533 } 534 535 bool isEnterKeyKeydownEvent(Event* event) 536 { 537 #if OS(ANDROID) 538 return event->type() == eventNames().keyupEvent && event->isKeyboardEvent() && static_cast<KeyboardEvent*>(event)->keyIdentifier() == "Enter"; 539 #else 540 return event->type() == eventNames().keydownEvent && event->isKeyboardEvent() && static_cast<KeyboardEvent*>(event)->keyIdentifier() == "Enter"; 541 #endif 542 } 543 544 bool isMiddleMouseButtonEvent(Event* event) 545 { 546 return event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == MiddleButton; 547 } 548 549 bool isLinkClick(Event* event) 550 { 551 return event->type() == eventNames().clickEvent && (!event->isMouseEvent() || static_cast<MouseEvent*>(event)->button() != RightButton); 552 } 553 554 void handleLinkClick(Event* event, Document* document, const String& url, const String& target, bool hideReferrer) 555 { 556 event->setDefaultHandled(); 557 558 Frame* frame = document->frame(); 559 if (!frame) 560 return; 561 frame->loader()->urlSelected(document->completeURL(url), target, event, false, false, hideReferrer ? NoReferrer : SendReferrer); 562 } 563 564 } 565