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 "bindings/core/v8/V8DOMActivityLogger.h" 28 #include "core/dom/Attribute.h" 29 #include "core/editing/FrameSelection.h" 30 #include "core/events/KeyboardEvent.h" 31 #include "core/events/MouseEvent.h" 32 #include "core/frame/FrameHost.h" 33 #include "core/frame/LocalFrame.h" 34 #include "core/frame/Settings.h" 35 #include "core/frame/UseCounter.h" 36 #include "core/html/HTMLFormElement.h" 37 #include "core/html/HTMLImageElement.h" 38 #include "core/html/parser/HTMLParserIdioms.h" 39 #include "core/loader/FrameLoadRequest.h" 40 #include "core/loader/FrameLoader.h" 41 #include "core/loader/FrameLoaderClient.h" 42 #include "core/loader/FrameLoaderTypes.h" 43 #include "core/loader/PingLoader.h" 44 #include "core/page/Chrome.h" 45 #include "core/page/ChromeClient.h" 46 #include "core/rendering/RenderImage.h" 47 #include "platform/PlatformMouseEvent.h" 48 #include "platform/network/DNS.h" 49 #include "platform/network/ResourceRequest.h" 50 #include "platform/weborigin/KnownPorts.h" 51 #include "platform/weborigin/SecurityOrigin.h" 52 #include "platform/weborigin/SecurityPolicy.h" 53 #include "public/platform/Platform.h" 54 #include "public/platform/WebURL.h" 55 #include "public/platform/WebURLRequest.h" 56 #include "wtf/text/StringBuilder.h" 57 58 namespace blink { 59 60 using namespace HTMLNames; 61 62 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document& document) 63 : HTMLElement(tagName, document) 64 , m_linkRelations(0) 65 , m_cachedVisitedLinkHash(0) 66 , m_wasFocusedByMouse(false) 67 { 68 } 69 70 PassRefPtrWillBeRawPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document& document) 71 { 72 return adoptRefWillBeNoop(new HTMLAnchorElement(aTag, document)); 73 } 74 75 HTMLAnchorElement::~HTMLAnchorElement() 76 { 77 } 78 79 bool HTMLAnchorElement::supportsFocus() const 80 { 81 if (hasEditableStyle()) 82 return HTMLElement::supportsFocus(); 83 // If not a link we should still be able to focus the element if it has tabIndex. 84 return isLink() || HTMLElement::supportsFocus(); 85 } 86 87 bool HTMLAnchorElement::shouldHaveFocusAppearance() const 88 { 89 return !m_wasFocusedByMouse || HTMLElement::supportsFocus(); 90 } 91 92 void HTMLAnchorElement::dispatchFocusEvent(Element* oldFocusedElement, FocusType type) 93 { 94 if (type != FocusTypePage) 95 m_wasFocusedByMouse = type == FocusTypeMouse; 96 HTMLElement::dispatchFocusEvent(oldFocusedElement, type); 97 } 98 99 bool HTMLAnchorElement::isMouseFocusable() const 100 { 101 if (isLink()) 102 return supportsFocus(); 103 104 return HTMLElement::isMouseFocusable(); 105 } 106 107 bool HTMLAnchorElement::isKeyboardFocusable() const 108 { 109 ASSERT(document().isActive()); 110 111 if (isFocusable() && Element::supportsFocus()) 112 return HTMLElement::isKeyboardFocusable(); 113 114 if (isLink() && !document().frameHost()->chrome().client().tabsToLinks()) 115 return false; 116 return HTMLElement::isKeyboardFocusable(); 117 } 118 119 static void appendServerMapMousePosition(StringBuilder& url, Event* event) 120 { 121 if (!event->isMouseEvent()) 122 return; 123 124 ASSERT(event->target()); 125 Node* target = event->target()->toNode(); 126 ASSERT(target); 127 if (!isHTMLImageElement(*target)) 128 return; 129 130 HTMLImageElement& imageElement = toHTMLImageElement(*target); 131 if (!imageElement.isServerMap()) 132 return; 133 134 if (!imageElement.renderer() || !imageElement.renderer()->isRenderImage()) 135 return; 136 RenderImage* renderer = toRenderImage(imageElement.renderer()); 137 138 // FIXME: This should probably pass true for useTransforms. 139 FloatPoint absolutePosition = renderer->absoluteToLocal(FloatPoint(toMouseEvent(event)->pageX(), toMouseEvent(event)->pageY())); 140 int x = absolutePosition.x(); 141 int y = absolutePosition.y(); 142 url.append('?'); 143 url.appendNumber(x); 144 url.append(','); 145 url.appendNumber(y); 146 } 147 148 void HTMLAnchorElement::defaultEventHandler(Event* event) 149 { 150 if (isLink()) { 151 if (focused() && isEnterKeyKeydownEvent(event) && isLiveLink()) { 152 event->setDefaultHandled(); 153 dispatchSimulatedClick(event); 154 return; 155 } 156 157 if (isLinkClick(event) && isLiveLink()) { 158 handleClick(event); 159 return; 160 } 161 } 162 163 HTMLElement::defaultEventHandler(event); 164 } 165 166 void HTMLAnchorElement::setActive(bool down) 167 { 168 if (hasEditableStyle()) 169 return; 170 171 ContainerNode::setActive(down); 172 } 173 174 void HTMLAnchorElement::attributeWillChange(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& newValue) 175 { 176 if (name == hrefAttr && inDocument()) { 177 V8DOMActivityLogger* activityLogger = V8DOMActivityLogger::currentActivityLoggerIfIsolatedWorld(); 178 if (activityLogger) { 179 Vector<String> argv; 180 argv.append("a"); 181 argv.append(hrefAttr.toString()); 182 argv.append(oldValue); 183 argv.append(newValue); 184 activityLogger->logEvent("blinkSetAttribute", argv.size(), argv.data()); 185 } 186 } 187 HTMLElement::attributeWillChange(name, oldValue, newValue); 188 } 189 190 void HTMLAnchorElement::parseAttribute(const QualifiedName& name, const AtomicString& value) 191 { 192 if (name == hrefAttr) { 193 bool wasLink = isLink(); 194 setIsLink(!value.isNull()); 195 if (wasLink || isLink()) { 196 pseudoStateChanged(CSSSelector::PseudoLink); 197 pseudoStateChanged(CSSSelector::PseudoVisited); 198 if (wasLink != isLink()) 199 pseudoStateChanged(CSSSelector::PseudoEnabled); 200 } 201 if (wasLink && !isLink() && treeScope().adjustedFocusedElement() == this) { 202 // We might want to call blur(), but it's dangerous to dispatch 203 // events here. 204 document().setNeedsFocusedElementCheck(); 205 } 206 if (isLink()) { 207 String parsedURL = stripLeadingAndTrailingHTMLSpaces(value); 208 if (document().isDNSPrefetchEnabled()) { 209 if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//")) 210 prefetchDNS(document().completeURL(parsedURL).host()); 211 } 212 } 213 invalidateCachedVisitedLinkHash(); 214 } else if (name == nameAttr || name == titleAttr) { 215 // Do nothing. 216 } else if (name == relAttr) 217 setRel(value); 218 else 219 HTMLElement::parseAttribute(name, value); 220 } 221 222 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents) 223 { 224 dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents); 225 } 226 227 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const 228 { 229 return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute); 230 } 231 232 bool HTMLAnchorElement::hasLegalLinkAttribute(const QualifiedName& name) const 233 { 234 return name == hrefAttr || HTMLElement::hasLegalLinkAttribute(name); 235 } 236 237 bool HTMLAnchorElement::canStartSelection() const 238 { 239 if (!isLink()) 240 return HTMLElement::canStartSelection(); 241 return hasEditableStyle(); 242 } 243 244 bool HTMLAnchorElement::draggable() const 245 { 246 // Should be draggable if we have an href attribute. 247 const AtomicString& value = getAttribute(draggableAttr); 248 if (equalIgnoringCase(value, "true")) 249 return true; 250 if (equalIgnoringCase(value, "false")) 251 return false; 252 return hasAttribute(hrefAttr); 253 } 254 255 KURL HTMLAnchorElement::href() const 256 { 257 return document().completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr))); 258 } 259 260 void HTMLAnchorElement::setHref(const AtomicString& value) 261 { 262 setAttribute(hrefAttr, value); 263 } 264 265 KURL HTMLAnchorElement::url() const 266 { 267 return href(); 268 } 269 270 void HTMLAnchorElement::setURL(const KURL& url) 271 { 272 setHref(AtomicString(url.string())); 273 } 274 275 String HTMLAnchorElement::input() const 276 { 277 return getAttribute(hrefAttr); 278 } 279 280 void HTMLAnchorElement::setInput(const String& value) 281 { 282 setHref(AtomicString(value)); 283 } 284 285 bool HTMLAnchorElement::hasRel(uint32_t relation) const 286 { 287 return m_linkRelations & relation; 288 } 289 290 void HTMLAnchorElement::setRel(const AtomicString& value) 291 { 292 m_linkRelations = 0; 293 SpaceSplitString newLinkRelations(value, true); 294 // FIXME: Add link relations as they are implemented 295 if (newLinkRelations.contains("noreferrer")) 296 m_linkRelations |= RelationNoReferrer; 297 } 298 299 const AtomicString& HTMLAnchorElement::name() const 300 { 301 return getNameAttribute(); 302 } 303 304 short HTMLAnchorElement::tabIndex() const 305 { 306 // Skip the supportsFocus check in HTMLElement. 307 return Element::tabIndex(); 308 } 309 310 bool HTMLAnchorElement::isLiveLink() const 311 { 312 return isLink() && !hasEditableStyle(); 313 } 314 315 void HTMLAnchorElement::sendPings(const KURL& destinationURL) const 316 { 317 const AtomicString& pingValue = getAttribute(pingAttr); 318 if (pingValue.isNull() || !document().settings() || !document().settings()->hyperlinkAuditingEnabled()) 319 return; 320 321 UseCounter::count(document(), UseCounter::HTMLAnchorElementPingAttribute); 322 323 SpaceSplitString pingURLs(pingValue, false); 324 for (unsigned i = 0; i < pingURLs.size(); i++) 325 PingLoader::sendLinkAuditPing(document().frame(), document().completeURL(pingURLs[i]), destinationURL); 326 } 327 328 void HTMLAnchorElement::handleClick(Event* event) 329 { 330 event->setDefaultHandled(); 331 332 LocalFrame* frame = document().frame(); 333 if (!frame) 334 return; 335 336 StringBuilder url; 337 url.append(stripLeadingAndTrailingHTMLSpaces(fastGetAttribute(hrefAttr))); 338 appendServerMapMousePosition(url, event); 339 KURL completedURL = document().completeURL(url.toString()); 340 341 // Schedule the ping before the frame load. Prerender in Chrome may kill the renderer as soon as the navigation is 342 // sent out. 343 sendPings(completedURL); 344 345 ResourceRequest request(completedURL); 346 if (hasAttribute(downloadAttr)) { 347 request.setRequestContext(blink::WebURLRequest::RequestContextDownload); 348 if (!hasRel(RelationNoReferrer)) { 349 String referrer = SecurityPolicy::generateReferrerHeader(document().referrerPolicy(), completedURL, document().outgoingReferrer()); 350 if (!referrer.isEmpty()) 351 request.setHTTPReferrer(Referrer(referrer, document().referrerPolicy())); 352 } 353 354 bool isSameOrigin = completedURL.protocolIsData() || document().securityOrigin()->canRequest(completedURL); 355 const AtomicString& suggestedName = (isSameOrigin ? fastGetAttribute(downloadAttr) : nullAtom); 356 357 frame->loader().client()->loadURLExternally(request, NavigationPolicyDownload, suggestedName); 358 } else { 359 request.setRequestContext(blink::WebURLRequest::RequestContextHyperlink); 360 FrameLoadRequest frameRequest(&document(), request, getAttribute(targetAttr)); 361 frameRequest.setTriggeringEvent(event); 362 if (hasRel(RelationNoReferrer)) 363 frameRequest.setShouldSendReferrer(NeverSendReferrer); 364 frame->loader().load(frameRequest); 365 } 366 } 367 368 bool isEnterKeyKeydownEvent(Event* event) 369 { 370 return event->type() == EventTypeNames::keydown && event->isKeyboardEvent() && toKeyboardEvent(event)->keyIdentifier() == "Enter"; 371 } 372 373 bool isLinkClick(Event* event) 374 { 375 return event->type() == EventTypeNames::click && (!event->isMouseEvent() || toMouseEvent(event)->button() != RightButton); 376 } 377 378 bool HTMLAnchorElement::willRespondToMouseClickEvents() 379 { 380 return isLink() || HTMLElement::willRespondToMouseClickEvents(); 381 } 382 383 bool HTMLAnchorElement::isInteractiveContent() const 384 { 385 return isLink(); 386 } 387 388 Node::InsertionNotificationRequest HTMLAnchorElement::insertedInto(ContainerNode* insertionPoint) 389 { 390 if (insertionPoint->inDocument()) { 391 V8DOMActivityLogger* activityLogger = V8DOMActivityLogger::currentActivityLoggerIfIsolatedWorld(); 392 if (activityLogger) { 393 Vector<String> argv; 394 argv.append("a"); 395 argv.append(fastGetAttribute(hrefAttr)); 396 activityLogger->logEvent("blinkAddElement", argv.size(), argv.data()); 397 } 398 } 399 return HTMLElement::insertedInto(insertionPoint); 400 } 401 402 } 403