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