78a92b2fa7e253b2f7fbcc5c2f6924948b1c163c
[WebKit-https.git] / Source / WebCore / html / HTMLAnchorElement.cpp
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 "DNS.h"
29 #include "ElementShadow.h"
30 #include "EventNames.h"
31 #include "Frame.h"
32 #include "FrameLoaderClient.h"
33 #include "FrameLoaderTypes.h"
34 #include "HTMLImageElement.h"
35 #include "HTMLNames.h"
36 #include "HTMLParserIdioms.h"
37 #include "KeyboardEvent.h"
38 #include "MouseEvent.h"
39 #include "PingLoader.h"
40 #include "RenderImage.h"
41 #include "SecurityOrigin.h"
42 #include "SecurityPolicy.h"
43 #include "Settings.h"
44 #include <wtf/text/StringBuilder.h>
45
46 namespace WebCore {
47
48 using namespace HTMLNames;
49
50 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document* document)
51     : HTMLElement(tagName, document)
52     , m_hasRootEditableElementForSelectionOnMouseDown(false)
53     , m_wasShiftKeyDownOnMouseDown(false)
54     , m_linkRelations(0)
55     , m_cachedVisitedLinkHash(0)
56 {
57 }
58
59 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(Document* document)
60 {
61     return adoptRef(new HTMLAnchorElement(aTag, document));
62 }
63
64 PassRefPtr<HTMLAnchorElement> HTMLAnchorElement::create(const QualifiedName& tagName, Document* document)
65 {
66     return adoptRef(new HTMLAnchorElement(tagName, document));
67 }
68
69 HTMLAnchorElement::~HTMLAnchorElement()
70 {
71     clearRootEditableElementForSelectionOnMouseDown();
72 }
73
74 // This function does not allow leading spaces before the port number.
75 static unsigned parsePortFromStringPosition(const String& value, unsigned portStart, unsigned& portEnd)
76 {
77     portEnd = portStart;
78     while (isASCIIDigit(value[portEnd]))
79         ++portEnd;
80     return value.substring(portStart, portEnd - portStart).toUInt();
81 }
82
83 bool HTMLAnchorElement::supportsFocus() const
84 {
85     if (rendererIsEditable())
86         return HTMLElement::supportsFocus();
87     // If not a link we should still be able to focus the element if it has tabIndex.
88     return isLink() || HTMLElement::supportsFocus();
89 }
90
91 bool HTMLAnchorElement::isMouseFocusable() const
92 {
93     // Anchor elements should be mouse focusable, https://bugs.webkit.org/show_bug.cgi?id=26856
94 #if !PLATFORM(GTK) && !PLATFORM(QT) && !PLATFORM(EFL)
95     if (isLink())
96         // Only allow links with tabIndex or contentEditable to be mouse focusable.
97         return HTMLElement::supportsFocus();
98 #endif
99
100     // Allow tab index etc to control focus.
101     return HTMLElement::isMouseFocusable();
102 }
103
104 bool HTMLAnchorElement::isKeyboardFocusable(KeyboardEvent* event) const
105 {
106     if (!isLink())
107         return HTMLElement::isKeyboardFocusable(event);
108
109     if (!isFocusable())
110         return false;
111     
112     if (!document()->frame())
113         return false;
114
115     if (!document()->frame()->eventHandler()->tabsToLinks(event))
116         return false;
117
118     if (isInCanvasSubtree())
119         return true;
120
121     return hasNonEmptyBoundingBox();
122 }
123
124 static void appendServerMapMousePosition(StringBuilder& url, Event* event)
125 {
126     if (!event->isMouseEvent())
127         return;
128
129     ASSERT(event->target());
130     Node* target = event->target()->toNode();
131     ASSERT(target);
132     if (!target->hasTagName(imgTag))
133         return;
134
135     HTMLImageElement* imageElement = static_cast<HTMLImageElement*>(event->target()->toNode());
136     if (!imageElement || !imageElement->isServerMap())
137         return;
138
139     RenderImage* renderer = toRenderImage(imageElement->renderer());
140     if (!renderer)
141         return;
142
143     // FIXME: This should probably pass true for useTransforms.
144     FloatPoint absolutePosition = renderer->absoluteToLocal(FloatPoint(static_cast<MouseEvent*>(event)->pageX(), static_cast<MouseEvent*>(event)->pageY()));
145     int x = absolutePosition.x();
146     int y = absolutePosition.y();
147     url.append('?');
148     url.appendNumber(x);
149     url.append(',');
150     url.appendNumber(y);
151 }
152
153 void HTMLAnchorElement::defaultEventHandler(Event* event)
154 {
155     if (isLink()) {
156         if (focused() && isEnterKeyKeydownEvent(event) && treatLinkAsLiveForEventType(NonMouseEvent)) {
157             event->setDefaultHandled();
158             dispatchSimulatedClick(event);
159             return;
160         }
161
162         if (isLinkClick(event) && treatLinkAsLiveForEventType(eventType(event))) {
163             handleClick(event);
164             return;
165         }
166
167         if (rendererIsEditable()) {
168             // This keeps track of the editable block that the selection was in (if it was in one) just before the link was clicked
169             // for the LiveWhenNotFocused editable link behavior
170             if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() != RightButton && document()->frame() && document()->frame()->selection()) {
171                 setRootEditableElementForSelectionOnMouseDown(document()->frame()->selection()->rootEditableElement());
172                 m_wasShiftKeyDownOnMouseDown = static_cast<MouseEvent*>(event)->shiftKey();
173             } else if (event->type() == eventNames().mouseoverEvent) {
174                 // These are cleared on mouseover and not mouseout because their values are needed for drag events,
175                 // but drag events happen after mouse out events.
176                 clearRootEditableElementForSelectionOnMouseDown();
177                 m_wasShiftKeyDownOnMouseDown = false;
178             }
179         }
180     }
181
182     HTMLElement::defaultEventHandler(event);
183 }
184
185 void HTMLAnchorElement::setActive(bool down, bool pause)
186 {
187     if (rendererIsEditable()) {
188         EditableLinkBehavior editableLinkBehavior = EditableLinkDefaultBehavior;
189         if (Settings* settings = document()->settings())
190             editableLinkBehavior = settings->editableLinkBehavior();
191             
192         switch (editableLinkBehavior) {
193             default:
194             case EditableLinkDefaultBehavior:
195             case EditableLinkAlwaysLive:
196                 break;
197
198             case EditableLinkNeverLive:
199                 return;
200
201             // Don't set the link to be active if the current selection is in the same editable block as
202             // this link
203             case EditableLinkLiveWhenNotFocused:
204                 if (down && document()->frame() && document()->frame()->selection()->rootEditableElement() == rootEditableElement())
205                     return;
206                 break;
207             
208             case EditableLinkOnlyLiveWithShiftKey:
209                 return;
210         }
211
212     }
213     
214     ContainerNode::setActive(down, pause);
215 }
216
217 void HTMLAnchorElement::parseAttribute(const Attribute& attribute)
218 {
219     if (attribute.name() == hrefAttr) {
220         bool wasLink = isLink();
221         setIsLink(!attribute.isNull());
222         if (wasLink != isLink()) {
223             setNeedsStyleRecalc();
224             invalidateParentDistributionIfNecessary(this, SelectRuleFeatureSet::RuleFeatureLink | SelectRuleFeatureSet::RuleFeatureVisited);
225         }
226         if (isLink()) {
227             String parsedURL = stripLeadingAndTrailingHTMLSpaces(attribute.value());
228             if (document()->isDNSPrefetchEnabled()) {
229                 if (protocolIs(parsedURL, "http") || protocolIs(parsedURL, "https") || parsedURL.startsWith("//"))
230                     prefetchDNS(document()->completeURL(parsedURL).host());
231             }
232         }
233         invalidateCachedVisitedLinkHash();
234     } else if (attribute.name() == nameAttr || attribute.name() == titleAttr) {
235         // Do nothing.
236     } else if (attribute.name() == relAttr)
237         setRel(attribute.value());
238     else
239         HTMLElement::parseAttribute(attribute);
240 }
241
242 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
243 {
244     // send the mouse button events if the caller specified sendMouseEvents
245     dispatchSimulatedClick(0, sendMouseEvents);
246 }
247
248 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
249 {
250     return attribute.name() == hrefAttr || HTMLElement::isURLAttribute(attribute);
251 }
252
253 bool HTMLAnchorElement::canStartSelection() const
254 {
255     // FIXME: We probably want this same behavior in SVGAElement too
256     if (!isLink())
257         return HTMLElement::canStartSelection();
258     return rendererIsEditable();
259 }
260
261 bool HTMLAnchorElement::draggable() const
262 {
263     // Should be draggable if we have an href attribute.
264     const AtomicString& value = getAttribute(draggableAttr);
265     if (equalIgnoringCase(value, "true"))
266         return true;
267     if (equalIgnoringCase(value, "false"))
268         return false;
269     return hasAttribute(hrefAttr);
270 }
271
272 KURL HTMLAnchorElement::href() const
273 {
274     return document()->completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
275 }
276
277 void HTMLAnchorElement::setHref(const AtomicString& value)
278 {
279     setAttribute(hrefAttr, value);
280 }
281
282 bool HTMLAnchorElement::hasRel(uint32_t relation) const
283 {
284     return m_linkRelations & relation;
285 }
286
287 void HTMLAnchorElement::setRel(const String& value)
288 {
289     m_linkRelations = 0;
290     SpaceSplitString newLinkRelations(value, true);
291     // FIXME: Add link relations as they are implemented
292     if (newLinkRelations.contains("noreferrer"))
293         m_linkRelations |= RelationNoReferrer;
294 }
295
296 const AtomicString& HTMLAnchorElement::name() const
297 {
298     return getNameAttribute();
299 }
300
301 short HTMLAnchorElement::tabIndex() const
302 {
303     // Skip the supportsFocus check in HTMLElement.
304     return Element::tabIndex();
305 }
306
307 String HTMLAnchorElement::target() const
308 {
309     return getAttribute(targetAttr);
310 }
311
312 String HTMLAnchorElement::hash() const
313 {
314     String fragmentIdentifier = href().fragmentIdentifier();
315     return fragmentIdentifier.isEmpty() ? emptyString() : "#" + fragmentIdentifier;
316 }
317
318 void HTMLAnchorElement::setHash(const String& value)
319 {
320     KURL url = href();
321     if (value[0] == '#')
322         url.setFragmentIdentifier(value.substring(1));
323     else
324         url.setFragmentIdentifier(value);
325     setHref(url.string());
326 }
327
328 String HTMLAnchorElement::host() const
329 {
330     const KURL& url = href();
331     if (url.hostEnd() == url.pathStart())
332         return url.host();
333     if (isDefaultPortForProtocol(url.port(), url.protocol()))
334         return url.host();
335     return url.host() + ":" + String::number(url.port());
336 }
337
338 void HTMLAnchorElement::setHost(const String& value)
339 {
340     if (value.isEmpty())
341         return;
342     KURL url = href();
343     if (!url.canSetHostOrPort())
344         return;
345
346     size_t separator = value.find(':');
347     if (!separator)
348         return;
349
350     if (separator == notFound)
351         url.setHostAndPort(value);
352     else {
353         unsigned portEnd;
354         unsigned port = parsePortFromStringPosition(value, separator + 1, portEnd);
355         if (!port) {
356             // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
357             // specifically goes against RFC 3986 (p3.2) and
358             // requires setting the port to "0" if it is set to empty string.
359             url.setHostAndPort(value.substring(0, separator + 1) + "0");
360         } else {
361             if (isDefaultPortForProtocol(port, url.protocol()))
362                 url.setHostAndPort(value.substring(0, separator));
363             else
364                 url.setHostAndPort(value.substring(0, portEnd));
365         }
366     }
367     setHref(url.string());
368 }
369
370 String HTMLAnchorElement::hostname() const
371 {
372     return href().host();
373 }
374
375 void HTMLAnchorElement::setHostname(const String& value)
376 {
377     // Before setting new value:
378     // Remove all leading U+002F SOLIDUS ("/") characters.
379     unsigned i = 0;
380     unsigned hostLength = value.length();
381     while (value[i] == '/')
382         i++;
383
384     if (i == hostLength)
385         return;
386
387     KURL url = href();
388     if (!url.canSetHostOrPort())
389         return;
390
391     url.setHost(value.substring(i));
392     setHref(url.string());
393 }
394
395 String HTMLAnchorElement::pathname() const
396 {
397     return href().path();
398 }
399
400 void HTMLAnchorElement::setPathname(const String& value)
401 {
402     KURL url = href();
403     if (!url.canSetPathname())
404         return;
405
406     if (value[0] == '/')
407         url.setPath(value);
408     else
409         url.setPath("/" + value);
410
411     setHref(url.string());
412 }
413
414 String HTMLAnchorElement::port() const
415 {
416     if (href().hasPort())
417         return String::number(href().port());
418
419     return emptyString();
420 }
421
422 void HTMLAnchorElement::setPort(const String& value)
423 {
424     KURL url = href();
425     if (!url.canSetHostOrPort())
426         return;
427
428     // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
429     // specifically goes against RFC 3986 (p3.2) and
430     // requires setting the port to "0" if it is set to empty string.
431     unsigned port = value.toUInt();
432     if (isDefaultPortForProtocol(port, url.protocol()))
433         url.removePort();
434     else
435         url.setPort(port);
436
437     setHref(url.string());
438 }
439
440 String HTMLAnchorElement::protocol() const
441 {
442     return href().protocol() + ":";
443 }
444
445 void HTMLAnchorElement::setProtocol(const String& value)
446 {
447     KURL url = href();
448     url.setProtocol(value);
449     setHref(url.string());
450 }
451
452 String HTMLAnchorElement::search() const
453 {
454     String query = href().query();
455     return query.isEmpty() ? emptyString() : "?" + query;
456 }
457
458 String HTMLAnchorElement::origin() const
459 {
460     RefPtr<SecurityOrigin> origin = SecurityOrigin::create(href());
461     return origin->toString();
462 }
463
464 void HTMLAnchorElement::setSearch(const String& value)
465 {
466     KURL url = href();
467     String newSearch = (value[0] == '?') ? value.substring(1) : value;
468     // Make sure that '#' in the query does not leak to the hash.
469     url.setQuery(newSearch.replaceWithLiteral('#', "%23"));
470
471     setHref(url.string());
472 }
473
474 String HTMLAnchorElement::text()
475 {
476     return innerText();
477 }
478
479 String HTMLAnchorElement::toString() const
480 {
481     return href().string();
482 }
483
484 bool HTMLAnchorElement::isLiveLink() const
485 {
486     return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey);
487 }
488
489 void HTMLAnchorElement::sendPings(const KURL& destinationURL)
490 {
491     if (!hasAttribute(pingAttr) || !document()->settings()->hyperlinkAuditingEnabled())
492         return;
493
494     SpaceSplitString pingURLs(getAttribute(pingAttr), false);
495     for (unsigned i = 0; i < pingURLs.size(); i++)
496         PingLoader::sendPing(document()->frame(), document()->completeURL(pingURLs[i]), destinationURL);
497 }
498
499 void HTMLAnchorElement::handleClick(Event* event)
500 {
501     event->setDefaultHandled();
502
503     Frame* frame = document()->frame();
504     if (!frame)
505         return;
506
507     StringBuilder url;
508     url.append(stripLeadingAndTrailingHTMLSpaces(fastGetAttribute(hrefAttr)));
509     appendServerMapMousePosition(url, event);
510     KURL kurl = document()->completeURL(url.toString());
511
512 #if ENABLE(DOWNLOAD_ATTRIBUTE)
513     if (hasAttribute(downloadAttr)) {
514         ResourceRequest request(kurl);
515
516         if (!hasRel(RelationNoReferrer)) {
517             String referrer = SecurityPolicy::generateReferrerHeader(document()->referrerPolicy(), kurl, frame->loader()->outgoingReferrer());
518             if (!referrer.isEmpty())
519                 request.setHTTPReferrer(referrer);
520             frame->loader()->addExtraFieldsToMainResourceRequest(request);
521         }
522
523         frame->loader()->client()->startDownload(request, fastGetAttribute(downloadAttr));
524     } else
525 #endif
526         frame->loader()->urlSelected(kurl, target(), event, false, false, hasRel(RelationNoReferrer) ? NeverSendReferrer : MaybeSendReferrer);
527
528     sendPings(kurl);
529 }
530
531 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event)
532 {
533     if (!event->isMouseEvent())
534         return NonMouseEvent;
535     return static_cast<MouseEvent*>(event)->shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
536 }
537
538 bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const
539 {
540     if (!rendererIsEditable())
541         return true;
542
543     Settings* settings = document()->settings();
544     if (!settings)
545         return true;
546
547     switch (settings->editableLinkBehavior()) {
548     case EditableLinkDefaultBehavior:
549     case EditableLinkAlwaysLive:
550         return true;
551
552     case EditableLinkNeverLive:
553         return false;
554
555     // If the selection prior to clicking on this link resided in the same editable block as this link,
556     // and the shift key isn't pressed, we don't want to follow the link.
557     case EditableLinkLiveWhenNotFocused:
558         return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && rootEditableElementForSelectionOnMouseDown() != rootEditableElement());
559
560     case EditableLinkOnlyLiveWithShiftKey:
561         return eventType == MouseEventWithShiftKey;
562     }
563
564     ASSERT_NOT_REACHED();
565     return false;
566 }
567
568 bool isEnterKeyKeydownEvent(Event* event)
569 {
570     return event->type() == eventNames().keydownEvent && event->isKeyboardEvent() && static_cast<KeyboardEvent*>(event)->keyIdentifier() == "Enter";
571 }
572
573 bool isLinkClick(Event* event)
574 {
575     return event->type() == eventNames().clickEvent && (!event->isMouseEvent() || static_cast<MouseEvent*>(event)->button() != RightButton);
576 }
577
578 bool HTMLAnchorElement::willRespondToMouseClickEvents()
579 {
580     return isLink() || HTMLElement::willRespondToMouseClickEvents();
581 }
582
583 #if ENABLE(MICRODATA)
584 String HTMLAnchorElement::itemValueText() const
585 {
586     return getURLAttribute(hrefAttr);
587 }
588
589 void HTMLAnchorElement::setItemValueText(const String& value, ExceptionCode&)
590 {
591     setAttribute(hrefAttr, value);
592 }
593 #endif
594
595 typedef HashMap<const HTMLAnchorElement*, RefPtr<Element> > RootEditableElementMap;
596
597 static RootEditableElementMap& rootEditableElementMap()
598 {
599     DEFINE_STATIC_LOCAL(RootEditableElementMap, map, ());
600     return map;
601 }
602
603 Element* HTMLAnchorElement::rootEditableElementForSelectionOnMouseDown() const
604 {
605     if (!m_hasRootEditableElementForSelectionOnMouseDown)
606         return 0;
607     return rootEditableElementMap().get(this).get();
608 }
609
610 void HTMLAnchorElement::clearRootEditableElementForSelectionOnMouseDown()
611 {
612     if (!m_hasRootEditableElementForSelectionOnMouseDown)
613         return;
614     rootEditableElementMap().remove(this);
615     m_hasRootEditableElementForSelectionOnMouseDown = false;
616 }
617
618 void HTMLAnchorElement::setRootEditableElementForSelectionOnMouseDown(Element* element)
619 {
620     if (!element) {
621         clearRootEditableElementForSelectionOnMouseDown();
622         return;
623     }
624
625     rootEditableElementMap().set(this, element);
626     m_hasRootEditableElementForSelectionOnMouseDown = true;
627 }
628
629 }