Use NeverDestroyed instead of DEPRECATED_DEFINE_STATIC_LOCAL
[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 "AttributeDOMTokenList.h"
28 #include "ElementIterator.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 "HTMLCanvasElement.h"
37 #include "HTMLImageElement.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 Ref<HTMLAnchorElement> HTMLAnchorElement::create(Document& document)
65 {
66     return adoptRef(*new HTMLAnchorElement(aTag, document));
67 }
68
69 Ref<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 (hasEditableStyle())
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))
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     for (auto& rect : rects) {
124         if (!rect.isEmpty())
125             return true;
126     }
127
128     return false;
129 }
130
131 bool HTMLAnchorElement::isKeyboardFocusable(KeyboardEvent* event) const
132 {
133     if (!isLink())
134         return HTMLElement::isKeyboardFocusable(event);
135
136     if (!isFocusable())
137         return false;
138     
139     if (!document().frame())
140         return false;
141
142     if (!document().frame()->eventHandler().tabsToLinks(event))
143         return false;
144
145     if (!renderer() && ancestorsOfType<HTMLCanvasElement>(*this).first())
146         return true;
147
148     return hasNonEmptyBox(renderBoxModelObject());
149 }
150
151 static void appendServerMapMousePosition(StringBuilder& url, Event* event)
152 {
153     ASSERT(event);
154     if (!is<MouseEvent>(*event))
155         return;
156
157     ASSERT(event->target());
158     Node* target = event->target()->toNode();
159     ASSERT(target);
160     if (!is<HTMLImageElement>(*target))
161         return;
162
163     HTMLImageElement& imageElement = downcast<HTMLImageElement>(*target);
164     if (!imageElement.isServerMap())
165         return;
166
167     if (!is<RenderImage>(imageElement.renderer()))
168         return;
169     auto& renderer = downcast<RenderImage>(*imageElement.renderer());
170
171     // FIXME: This should probably pass true for useTransforms.
172     FloatPoint absolutePosition = renderer.absoluteToLocal(FloatPoint(downcast<MouseEvent>(*event).pageX(), downcast<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 (MouseEvent::canTriggerActivationBehavior(*event) && treatLinkAsLiveForEventType(eventType(event))) {
191             handleClick(event);
192             return;
193         }
194
195         if (hasEditableStyle()) {
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 && is<MouseEvent>(*event) && downcast<MouseEvent>(*event).button() != RightButton && document().frame()) {
199                 setRootEditableElementForSelectionOnMouseDown(document().frame()->selection().selection().rootEditableElement());
200                 m_wasShiftKeyDownOnMouseDown = downcast<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 (hasEditableStyle()) {
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().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             setNeedsStyleRecalc();
252         if (isLink()) {
253             String parsedURL = stripLeadingAndTrailingHTMLSpaces(value);
254             if (document().isDNSPrefetchEnabled() && document().frame()) {
255                 if (protocolIsInHTTPFamily(parsedURL) || parsedURL.startsWith("//"))
256                     document().frame()->loader().client().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         if (SpaceSplitString::spaceSplitStringContainsValue(value, "noreferrer", true))
264             m_linkRelations |= RelationNoReferrer;
265         if (m_relList)
266             m_relList->attributeValueChanged(value);
267     }
268     else
269         HTMLElement::parseAttribute(name, value);
270 }
271
272 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
273 {
274     dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
275 }
276
277 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
278 {
279     return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
280 }
281
282 bool HTMLAnchorElement::canStartSelection() const
283 {
284     if (!isLink())
285         return HTMLElement::canStartSelection();
286     return hasEditableStyle();
287 }
288
289 bool HTMLAnchorElement::draggable() const
290 {
291     // Should be draggable if we have an href attribute.
292     const AtomicString& value = fastGetAttribute(draggableAttr);
293     if (equalIgnoringCase(value, "true"))
294         return true;
295     if (equalIgnoringCase(value, "false"))
296         return false;
297     return hasAttribute(hrefAttr);
298 }
299
300 URL HTMLAnchorElement::href() const
301 {
302     return document().completeURL(stripLeadingAndTrailingHTMLSpaces(getAttribute(hrefAttr)));
303 }
304
305 void HTMLAnchorElement::setHref(const AtomicString& value)
306 {
307     setAttribute(hrefAttr, value);
308 }
309
310 bool HTMLAnchorElement::hasRel(uint32_t relation) const
311 {
312     return m_linkRelations & relation;
313 }
314
315 DOMTokenList& HTMLAnchorElement::relList()
316 {
317     if (!m_relList) 
318         m_relList = std::make_unique<AttributeDOMTokenList>(*this, HTMLNames::relAttr);
319     return *m_relList;
320 }
321
322 const AtomicString& HTMLAnchorElement::name() const
323 {
324     return getNameAttribute();
325 }
326
327 short HTMLAnchorElement::tabIndex() const
328 {
329     // Skip the supportsFocus check in HTMLElement.
330     return Element::tabIndex();
331 }
332
333 String HTMLAnchorElement::target() const
334 {
335     return getAttribute(targetAttr);
336 }
337
338 String HTMLAnchorElement::hash() const
339 {
340     String fragmentIdentifier = href().fragmentIdentifier();
341     if (fragmentIdentifier.isEmpty())
342         return emptyString();
343     return AtomicString(String("#" + fragmentIdentifier));
344 }
345
346 void HTMLAnchorElement::setHash(const String& value)
347 {
348     URL url = href();
349     if (value[0] == '#')
350         url.setFragmentIdentifier(value.substring(1));
351     else
352         url.setFragmentIdentifier(value);
353     setHref(url.string());
354 }
355
356 String HTMLAnchorElement::host() const
357 {
358     const URL& url = href();
359     if (url.hostEnd() == url.pathStart())
360         return url.host();
361     if (isDefaultPortForProtocol(url.port(), url.protocol()))
362         return url.host();
363     return url.host() + ":" + String::number(url.port());
364 }
365
366 void HTMLAnchorElement::setHost(const String& value)
367 {
368     if (value.isEmpty())
369         return;
370     URL url = href();
371     if (!url.canSetHostOrPort())
372         return;
373
374     size_t separator = value.find(':');
375     if (!separator)
376         return;
377
378     if (separator == notFound)
379         url.setHostAndPort(value);
380     else {
381         unsigned portEnd;
382         unsigned port = parsePortFromStringPosition(value, separator + 1, portEnd);
383         if (!port) {
384             // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
385             // specifically goes against RFC 3986 (p3.2) and
386             // requires setting the port to "0" if it is set to empty string.
387             url.setHostAndPort(value.substring(0, separator + 1) + "0");
388         } else {
389             if (isDefaultPortForProtocol(port, url.protocol()))
390                 url.setHostAndPort(value.substring(0, separator));
391             else
392                 url.setHostAndPort(value.substring(0, portEnd));
393         }
394     }
395     setHref(url.string());
396 }
397
398 String HTMLAnchorElement::hostname() const
399 {
400     return href().host();
401 }
402
403 void HTMLAnchorElement::setHostname(const String& value)
404 {
405     // Before setting new value:
406     // Remove all leading U+002F SOLIDUS ("/") characters.
407     unsigned i = 0;
408     unsigned hostLength = value.length();
409     while (value[i] == '/')
410         i++;
411
412     if (i == hostLength)
413         return;
414
415     URL url = href();
416     if (!url.canSetHostOrPort())
417         return;
418
419     url.setHost(value.substring(i));
420     setHref(url.string());
421 }
422
423 String HTMLAnchorElement::pathname() const
424 {
425     return href().path();
426 }
427
428 void HTMLAnchorElement::setPathname(const String& value)
429 {
430     URL url = href();
431     if (!url.canSetPathname())
432         return;
433
434     if (value[0] == '/')
435         url.setPath(value);
436     else
437         url.setPath("/" + value);
438
439     setHref(url.string());
440 }
441
442 String HTMLAnchorElement::port() const
443 {
444     if (href().hasPort())
445         return String::number(href().port());
446
447     return emptyString();
448 }
449
450 void HTMLAnchorElement::setPort(const String& value)
451 {
452     URL url = href();
453     if (!url.canSetHostOrPort())
454         return;
455
456     // http://dev.w3.org/html5/spec/infrastructure.html#url-decomposition-idl-attributes
457     // specifically goes against RFC 3986 (p3.2) and
458     // requires setting the port to "0" if it is set to empty string.
459     unsigned port = value.toUInt();
460     if (isDefaultPortForProtocol(port, url.protocol()))
461         url.removePort();
462     else
463         url.setPort(port);
464
465     setHref(url.string());
466 }
467
468 String HTMLAnchorElement::protocol() const
469 {
470     return href().protocol() + ":";
471 }
472
473 void HTMLAnchorElement::setProtocol(const String& value)
474 {
475     URL url = href();
476     url.setProtocol(value);
477     setHref(url.string());
478 }
479
480 String HTMLAnchorElement::search() const
481 {
482     String query = href().query();
483     return query.isEmpty() ? emptyString() : "?" + query;
484 }
485
486 String HTMLAnchorElement::origin() const
487 {
488     return SecurityOrigin::create(href()).get().toString();
489 }
490
491 void HTMLAnchorElement::setSearch(const String& value)
492 {
493     URL url = href();
494     String newSearch = (value[0] == '?') ? value.substring(1) : value;
495     // Make sure that '#' in the query does not leak to the hash.
496     url.setQuery(newSearch.replaceWithLiteral('#', "%23"));
497
498     setHref(url.string());
499 }
500
501 String HTMLAnchorElement::text()
502 {
503     return textContent();
504 }
505
506 void HTMLAnchorElement::setText(const String& text, ExceptionCode& ec)
507 {
508     setTextContent(text, ec);
509 }
510
511 String HTMLAnchorElement::toString() const
512 {
513     return href().string();
514 }
515
516 bool HTMLAnchorElement::isLiveLink() const
517 {
518     return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey);
519 }
520
521 void HTMLAnchorElement::sendPings(const URL& destinationURL)
522 {
523     if (!fastHasAttribute(pingAttr) || !document().settings() || !document().settings()->hyperlinkAuditingEnabled())
524         return;
525
526     SpaceSplitString pingURLs(fastGetAttribute(pingAttr), false);
527     for (unsigned i = 0; i < pingURLs.size(); i++)
528         PingLoader::sendPing(*document().frame(), document().completeURL(pingURLs[i]), destinationURL);
529 }
530
531 void HTMLAnchorElement::handleClick(Event* event)
532 {
533     event->setDefaultHandled();
534
535     Frame* frame = document().frame();
536     if (!frame)
537         return;
538
539     StringBuilder url;
540     url.append(stripLeadingAndTrailingHTMLSpaces(fastGetAttribute(hrefAttr)));
541     appendServerMapMousePosition(url, event);
542     URL kurl = document().completeURL(url.toString());
543
544 #if ENABLE(DOWNLOAD_ATTRIBUTE)
545     if (hasAttribute(downloadAttr)) {
546         ResourceRequest request(kurl);
547
548         // FIXME: Why are we not calling addExtraFieldsToMainResourceRequest() if this check fails? It sets many important header fields.
549         if (!hasRel(RelationNoReferrer)) {
550             String referrer = SecurityPolicy::generateReferrerHeader(document().referrerPolicy(), kurl, frame->loader().outgoingReferrer());
551             if (!referrer.isEmpty())
552                 request.setHTTPReferrer(referrer);
553             frame->loader().addExtraFieldsToMainResourceRequest(request);
554         }
555
556         frame->loader().client().startDownload(request, fastGetAttribute(downloadAttr));
557     } else
558 #endif
559
560     frame->loader().urlSelected(kurl, target(), event, LockHistory::No, LockBackForwardList::No, hasRel(RelationNoReferrer) ? NeverSendReferrer : MaybeSendReferrer, document().shouldOpenExternalURLsPolicyToPropagate());
561
562     sendPings(kurl);
563 }
564
565 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event)
566 {
567     ASSERT(event);
568     if (!is<MouseEvent>(*event))
569         return NonMouseEvent;
570     return downcast<MouseEvent>(*event).shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
571 }
572
573 bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const
574 {
575     if (!hasEditableStyle())
576         return true;
577
578     Settings* settings = document().settings();
579     if (!settings)
580         return true;
581
582     switch (settings->editableLinkBehavior()) {
583     case EditableLinkDefaultBehavior:
584     case EditableLinkAlwaysLive:
585         return true;
586
587     case EditableLinkNeverLive:
588         return false;
589
590     // If the selection prior to clicking on this link resided in the same editable block as this link,
591     // and the shift key isn't pressed, we don't want to follow the link.
592     case EditableLinkLiveWhenNotFocused:
593         return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && rootEditableElementForSelectionOnMouseDown() != rootEditableElement());
594
595     case EditableLinkOnlyLiveWithShiftKey:
596         return eventType == MouseEventWithShiftKey;
597     }
598
599     ASSERT_NOT_REACHED();
600     return false;
601 }
602
603 bool isEnterKeyKeydownEvent(Event* event)
604 {
605     return event->type() == eventNames().keydownEvent && is<KeyboardEvent>(*event) && downcast<KeyboardEvent>(*event).keyIdentifier() == "Enter";
606 }
607
608 bool shouldProhibitLinks(Element* element)
609 {
610     return isInSVGImage(element);
611 }
612
613 bool HTMLAnchorElement::willRespondToMouseClickEvents()
614 {
615     return isLink() || HTMLElement::willRespondToMouseClickEvents();
616 }
617
618 typedef HashMap<const HTMLAnchorElement*, RefPtr<Element>> RootEditableElementMap;
619
620 static RootEditableElementMap& rootEditableElementMap()
621 {
622     static NeverDestroyed<RootEditableElementMap> map;
623     return map;
624 }
625
626 Element* HTMLAnchorElement::rootEditableElementForSelectionOnMouseDown() const
627 {
628     if (!m_hasRootEditableElementForSelectionOnMouseDown)
629         return 0;
630     return rootEditableElementMap().get(this);
631 }
632
633 void HTMLAnchorElement::clearRootEditableElementForSelectionOnMouseDown()
634 {
635     if (!m_hasRootEditableElementForSelectionOnMouseDown)
636         return;
637     rootEditableElementMap().remove(this);
638     m_hasRootEditableElementForSelectionOnMouseDown = false;
639 }
640
641 void HTMLAnchorElement::setRootEditableElementForSelectionOnMouseDown(Element* element)
642 {
643     if (!element) {
644         clearRootEditableElementForSelectionOnMouseDown();
645         return;
646     }
647
648     rootEditableElementMap().set(this, element);
649     m_hasRootEditableElementForSelectionOnMouseDown = true;
650 }
651
652 }