• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
43 namespace WebCore {
44 
45 using namespace HTMLNames;
46 
HTMLAnchorElement(const QualifiedName & tagName,Document * document)47 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document* document)
48     : HTMLElement(tagName, document)
49     , m_wasShiftKeyDownOnMouseDown(false)
50     , m_linkRelations(0)
51 {
52 }
53 
create(Document * document)54 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document* document)
55 {
56     return adoptRef(new HTMLAnchorElement(aTag, document));
57 }
58 
create(const QualifiedName & tagName,Document * document)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.
parsePortFromStringPosition(const String & value,unsigned portStart,unsigned & portEnd)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 
supportsFocus() const73 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 
isMouseFocusable() const81 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 
isKeyboardFocusable(KeyboardEvent * event) const94 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 
appendServerMapMousePosition(String & url,Event * event)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 
defaultEventHandler(Event * event)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 
setActive(bool down,bool pause)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 
parseMappedAttribute(Attribute * attr)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 
accessKeyAction(bool sendToAnyElement)234 void HTMLAnchorElement::accessKeyAction(bool sendToAnyElement)
235 {
236     // send the mouse button events if the caller specified sendToAnyElement
237     dispatchSimulatedClick(0, sendToAnyElement);
238 }
239 
isURLAttribute(Attribute * attr) const240 bool HTMLAnchorElement::isURLAttribute(Attribute *attr) const
241 {
242     return attr->name() == hrefAttr;
243 }
244 
canStartSelection() const245 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 
draggable() const253 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 
href() const264 KURL HTMLAnchorElement::href() const
265 {
266     return document()->completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
267 }
268 
setHref(const AtomicString & value)269 void HTMLAnchorElement::setHref(const AtomicString& value)
270 {
271     setAttribute(hrefAttr, value);
272 }
273 
hasRel(uint32_t relation) const274 bool HTMLAnchorElement::hasRel(uint32_t relation) const
275 {
276     return m_linkRelations & relation;
277 }
278 
setRel(const String & value)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 
name() const288 const AtomicString& HTMLAnchorElement::name() const
289 {
290     return getAttribute(nameAttr);
291 }
292 
tabIndex() const293 short HTMLAnchorElement::tabIndex() const
294 {
295     // Skip the supportsFocus check in HTMLElement.
296     return Element::tabIndex();
297 }
298 
target() const299 String HTMLAnchorElement::target() const
300 {
301     return getAttribute(targetAttr);
302 }
303 
hash() const304 String HTMLAnchorElement::hash() const
305 {
306     String fragmentIdentifier = href().fragmentIdentifier();
307     return fragmentIdentifier.isEmpty() ? "" : "#" + fragmentIdentifier;
308 }
309 
setHash(const String & value)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 
host() const320 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 
setHost(const String & value)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 
hostname() const362 String HTMLAnchorElement::hostname() const
363 {
364     return href().host();
365 }
366 
setHostname(const String & value)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 
pathname() const387 String HTMLAnchorElement::pathname() const
388 {
389     return href().path();
390 }
391 
setPathname(const String & value)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 
port() const406 String HTMLAnchorElement::port() const
407 {
408     return String::number(href().port());
409 }
410 
setPort(const String & value)411 void 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 
protocol() const429 String HTMLAnchorElement::protocol() const
430 {
431     return href().protocol() + ":";
432 }
433 
setProtocol(const String & value)434 void HTMLAnchorElement::setProtocol(const String& value)
435 {
436     KURL url = href();
437     url.setProtocol(value);
438     setHref(url.string());
439 }
440 
search() const441 String HTMLAnchorElement::search() const
442 {
443     String query = href().query();
444     return query.isEmpty() ? "" : "?" + query;
445 }
446 
origin() const447 String HTMLAnchorElement::origin() const
448 {
449     RefPtr<SecurityOrigin> origin = SecurityOrigin::create(href());
450     return origin->toString();
451 }
452 
getParameter(const String & name) const453 String HTMLAnchorElement::getParameter(const String& name) const
454 {
455     ParsedURLParameters parameters;
456     href().copyParsedQueryTo(parameters);
457     return parameters.get(name);
458 }
459 
setSearch(const String & value)460 void 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 
text() const470 String HTMLAnchorElement::text() const
471 {
472     return innerText();
473 }
474 
toString() const475 String HTMLAnchorElement::toString() const
476 {
477     return href().string();
478 }
479 
isLiveLink() const480 bool HTMLAnchorElement::isLiveLink() const
481 {
482     return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey);
483 }
484 
sendPings(const KURL & destinationURL)485 void 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 
eventType(Event * event)495 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event)
496 {
497     if (!event->isMouseEvent())
498         return NonMouseEvent;
499     return static_cast<MouseEvent*>(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
500 }
501 
treatLinkAsLiveForEventType(EventType eventType) const502 bool 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 
isEnterKeyKeydownEvent(Event * event)532 bool isEnterKeyKeydownEvent(Event* event)
533 {
534     return event->type() == eventNames().keydownEvent && event->isKeyboardEvent() && static_cast<KeyboardEvent*>(event)->keyIdentifier() == "Enter";
535 }
536 
isMiddleMouseButtonEvent(Event * event)537 bool isMiddleMouseButtonEvent(Event* event)
538 {
539     return event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == MiddleButton;
540 }
541 
isLinkClick(Event * event)542 bool isLinkClick(Event* event)
543 {
544     return event->type() == eventNames().clickEvent && (!event->isMouseEvent() || static_cast<MouseEvent*>(event)->button() != RightButton);
545 }
546 
handleLinkClick(Event * event,Document * document,const String & url,const String & target,bool hideReferrer)547 void 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