1/* 2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org) 3 * (C) 1999 Antti Koivisto (koivisto@kde.org) 4 * (C) 2000 Simon Hausmann <hausmann@kde.org> 5 * Copyright (C) 2003, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. 6 * (C) 2006 Graham Dennis (graham.dennis@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 43namespace WebCore { 44 45using namespace HTMLNames; 46 47HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document* document) 48 : HTMLElement(tagName, document) 49 , m_wasShiftKeyDownOnMouseDown(false) 50 , m_linkRelations(0) 51{ 52} 53 54PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document* document) 55{ 56 return adoptRef(new HTMLAnchorElement(aTag, document)); 57} 58 59PassRefPtr<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. 65static 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 73bool 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 81bool 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 94bool 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 111static 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 140void 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 175void 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 207void 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 234void HTMLAnchorElement::accessKeyAction(bool sendToAnyElement) 235{ 236 // send the mouse button events if the caller specified sendToAnyElement 237 dispatchSimulatedClick(0, sendToAnyElement); 238} 239 240bool HTMLAnchorElement::isURLAttribute(Attribute *attr) const 241{ 242 return attr->name() == hrefAttr; 243} 244 245bool 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 253bool 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 264KURL HTMLAnchorElement::href() const 265{ 266 return document()->completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr))); 267} 268 269void HTMLAnchorElement::setHref(const AtomicString& value) 270{ 271 setAttribute(hrefAttr, value); 272} 273 274bool HTMLAnchorElement::hasRel(uint32_t relation) const 275{ 276 return m_linkRelations & relation; 277} 278 279void 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 288const AtomicString& HTMLAnchorElement::name() const 289{ 290 return getAttribute(nameAttr); 291} 292 293short HTMLAnchorElement::tabIndex() const 294{ 295 // Skip the supportsFocus check in HTMLElement. 296 return Element::tabIndex(); 297} 298 299String HTMLAnchorElement::target() const 300{ 301 return getAttribute(targetAttr); 302} 303 304String HTMLAnchorElement::hash() const 305{ 306 String fragmentIdentifier = href().fragmentIdentifier(); 307 return fragmentIdentifier.isEmpty() ? "" : "#" + fragmentIdentifier; 308} 309 310void 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 320String 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 330void 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 362String HTMLAnchorElement::hostname() const 363{ 364 return href().host(); 365} 366 367void 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 387String HTMLAnchorElement::pathname() const 388{ 389 return href().path(); 390} 391 392void 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 406String HTMLAnchorElement::port() const 407{ 408 return String::number(href().port()); 409} 410 411void HTMLAnchorElement::setPort(const String& value) 412{ 413 KURL url = href(); 414 if (!url.canSetHostOrPort()) 415 return; 416 417 // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes 418 // specifically goes against RFC 3986 (p3.2) and 419 // requires setting the port to "0" if it is set to empty string. 420 unsigned port = value.toUInt(); 421 if (isDefaultPortForProtocol(port, url.protocol())) 422 url.removePort(); 423 else 424 url.setPort(port); 425 426 setHref(url.string()); 427} 428 429String HTMLAnchorElement::protocol() const 430{ 431 return href().protocol() + ":"; 432} 433 434void HTMLAnchorElement::setProtocol(const String& value) 435{ 436 KURL url = href(); 437 url.setProtocol(value); 438 setHref(url.string()); 439} 440 441String HTMLAnchorElement::search() const 442{ 443 String query = href().query(); 444 return query.isEmpty() ? "" : "?" + query; 445} 446 447String HTMLAnchorElement::origin() const 448{ 449 RefPtr<SecurityOrigin> origin = SecurityOrigin::create(href()); 450 return origin->toString(); 451} 452 453String HTMLAnchorElement::getParameter(const String& name) const 454{ 455 ParsedURLParameters parameters; 456 href().copyParsedQueryTo(parameters); 457 return parameters.get(name); 458} 459 460void HTMLAnchorElement::setSearch(const String& value) 461{ 462 KURL url = href(); 463 String newSearch = (value[0] == '?') ? value.substring(1) : value; 464 // Make sure that '#' in the query does not leak to the hash. 465 url.setQuery(newSearch.replace('#', "%23")); 466 467 setHref(url.string()); 468} 469 470String HTMLAnchorElement::text() const 471{ 472 return innerText(); 473} 474 475String HTMLAnchorElement::toString() const 476{ 477 return href().string(); 478} 479 480bool HTMLAnchorElement::isLiveLink() const 481{ 482 return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey); 483} 484 485void HTMLAnchorElement::sendPings(const KURL& destinationURL) 486{ 487 if (!hasAttribute(pingAttr) || !document()->settings()->hyperlinkAuditingEnabled()) 488 return; 489 490 SpaceSplitString pingURLs(getAttribute(pingAttr), true); 491 for (unsigned i = 0; i < pingURLs.size(); i++) 492 PingLoader::sendPing(document()->frame(), document()->completeURL(pingURLs[i]), destinationURL); 493} 494 495HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event) 496{ 497 if (!event->isMouseEvent()) 498 return NonMouseEvent; 499 return static_cast<MouseEvent*>(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey; 500} 501 502bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const 503{ 504 if (!rendererIsEditable()) 505 return true; 506 507 Settings* settings = document()->settings(); 508 if (!settings) 509 return true; 510 511 switch (settings->editableLinkBehavior()) { 512 case EditableLinkDefaultBehavior: 513 case EditableLinkAlwaysLive: 514 return true; 515 516 case EditableLinkNeverLive: 517 return false; 518 519 // If the selection prior to clicking on this link resided in the same editable block as this link, 520 // and the shift key isn't pressed, we don't want to follow the link. 521 case EditableLinkLiveWhenNotFocused: 522 return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && m_rootEditableElementForSelectionOnMouseDown != rootEditableElement()); 523 524 case EditableLinkOnlyLiveWithShiftKey: 525 return eventType == MouseEventWithShiftKey; 526 } 527 528 ASSERT_NOT_REACHED(); 529 return false; 530} 531 532bool isEnterKeyKeydownEvent(Event* event) 533{ 534 return event->type() == eventNames().keydownEvent && event->isKeyboardEvent() && static_cast<KeyboardEvent*>(event)->keyIdentifier() == "Enter"; 535} 536 537bool isMiddleMouseButtonEvent(Event* event) 538{ 539 return event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == MiddleButton; 540} 541 542bool isLinkClick(Event* event) 543{ 544 return event->type() == eventNames().clickEvent && (!event->isMouseEvent() || static_cast<MouseEvent*>(event)->button() != RightButton); 545} 546 547void handleLinkClick(Event* event, Document* document, const String& url, const String& target, bool hideReferrer) 548{ 549 event->setDefaultHandled(); 550 551 Frame* frame = document->frame(); 552 if (!frame) 553 return; 554 frame->loader()->urlSelected(document->completeURL(url), target, event, false, false, hideReferrer ? NoReferrer : SendReferrer); 555} 556 557} 558