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