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 "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 58namespace blink { 59 60using namespace HTMLNames; 61 62HTMLAnchorElement::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 70PassRefPtrWillBeRawPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document& document) 71{ 72 return adoptRefWillBeNoop(new HTMLAnchorElement(aTag, document)); 73} 74 75HTMLAnchorElement::~HTMLAnchorElement() 76{ 77} 78 79bool 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 87bool HTMLAnchorElement::shouldHaveFocusAppearance() const 88{ 89 return !m_wasFocusedByMouse || HTMLElement::supportsFocus(); 90} 91 92void HTMLAnchorElement::dispatchFocusEvent(Element* oldFocusedElement, FocusType type) 93{ 94 if (type != FocusTypePage) 95 m_wasFocusedByMouse = type == FocusTypeMouse; 96 HTMLElement::dispatchFocusEvent(oldFocusedElement, type); 97} 98 99bool HTMLAnchorElement::isMouseFocusable() const 100{ 101 if (isLink()) 102 return supportsFocus(); 103 104 return HTMLElement::isMouseFocusable(); 105} 106 107bool 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 119static 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 148void 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 166void HTMLAnchorElement::setActive(bool down) 167{ 168 if (hasEditableStyle()) 169 return; 170 171 ContainerNode::setActive(down); 172} 173 174void 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 190void 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 222void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents) 223{ 224 dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents); 225} 226 227bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const 228{ 229 return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute); 230} 231 232bool HTMLAnchorElement::hasLegalLinkAttribute(const QualifiedName& name) const 233{ 234 return name == hrefAttr || HTMLElement::hasLegalLinkAttribute(name); 235} 236 237bool HTMLAnchorElement::canStartSelection() const 238{ 239 if (!isLink()) 240 return HTMLElement::canStartSelection(); 241 return hasEditableStyle(); 242} 243 244bool 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 255KURL HTMLAnchorElement::href() const 256{ 257 return document().completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr))); 258} 259 260void HTMLAnchorElement::setHref(const AtomicString& value) 261{ 262 setAttribute(hrefAttr, value); 263} 264 265KURL HTMLAnchorElement::url() const 266{ 267 return href(); 268} 269 270void HTMLAnchorElement::setURL(const KURL& url) 271{ 272 setHref(AtomicString(url.string())); 273} 274 275String HTMLAnchorElement::input() const 276{ 277 return getAttribute(hrefAttr); 278} 279 280void HTMLAnchorElement::setInput(const String& value) 281{ 282 setHref(AtomicString(value)); 283} 284 285bool HTMLAnchorElement::hasRel(uint32_t relation) const 286{ 287 return m_linkRelations & relation; 288} 289 290void 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 299const AtomicString& HTMLAnchorElement::name() const 300{ 301 return getNameAttribute(); 302} 303 304short HTMLAnchorElement::tabIndex() const 305{ 306 // Skip the supportsFocus check in HTMLElement. 307 return Element::tabIndex(); 308} 309 310bool HTMLAnchorElement::isLiveLink() const 311{ 312 return isLink() && !hasEditableStyle(); 313} 314 315void 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 328void 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 368bool isEnterKeyKeydownEvent(Event* event) 369{ 370 return event->type() == EventTypeNames::keydown && event->isKeyboardEvent() && toKeyboardEvent(event)->keyIdentifier() == "Enter"; 371} 372 373bool isLinkClick(Event* event) 374{ 375 return event->type() == EventTypeNames::click && (!event->isMouseEvent() || toMouseEvent(event)->button() != RightButton); 376} 377 378bool HTMLAnchorElement::willRespondToMouseClickEvents() 379{ 380 return isLink() || HTMLElement::willRespondToMouseClickEvents(); 381} 382 383bool HTMLAnchorElement::isInteractiveContent() const 384{ 385 return isLink(); 386} 387 388Node::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