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