648664c0fab4e16998463cc4f5d4454ed6d8a621
[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-2016 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 "DOMTokenList.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 "RuntimeEnabledFeatures.h"
46 #include "SVGImage.h"
47 #include "ScriptController.h"
48 #include "SecurityOrigin.h"
49 #include "SecurityPolicy.h"
50 #include "Settings.h"
51 #include "URLUtils.h"
52 #include <wtf/IsoMallocInlines.h>
53 #include <wtf/text/StringBuilder.h>
54
55 namespace WebCore {
56
57 WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLAnchorElement);
58
59 using namespace HTMLNames;
60
61 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document& document)
62     : HTMLElement(tagName, document)
63     , m_hasRootEditableElementForSelectionOnMouseDown(false)
64     , m_wasShiftKeyDownOnMouseDown(false)
65     , m_cachedVisitedLinkHash(0)
66 {
67 }
68
69 Ref<HTMLAnchorElement> HTMLAnchorElement::create(Document& document)
70 {
71     return adoptRef(*new HTMLAnchorElement(aTag, document));
72 }
73
74 Ref<HTMLAnchorElement> HTMLAnchorElement::create(const QualifiedName& tagName, Document& document)
75 {
76     return adoptRef(*new HTMLAnchorElement(tagName, document));
77 }
78
79 HTMLAnchorElement::~HTMLAnchorElement()
80 {
81     clearRootEditableElementForSelectionOnMouseDown();
82 }
83
84 bool HTMLAnchorElement::supportsFocus() const
85 {
86     if (hasEditableStyle())
87         return HTMLElement::supportsFocus();
88     // If not a link we should still be able to focus the element if it has tabIndex.
89     return isLink() || HTMLElement::supportsFocus();
90 }
91
92 bool HTMLAnchorElement::isMouseFocusable() const
93 {
94     // Only allow links with tabIndex or contentEditable to be mouse focusable.
95     if (isLink())
96         return HTMLElement::supportsFocus();
97
98     return HTMLElement::isMouseFocusable();
99 }
100
101 static bool hasNonEmptyBox(RenderBoxModelObject* renderer)
102 {
103     if (!renderer)
104         return false;
105
106     // Before calling absoluteRects, check for the common case where borderBoundingBox
107     // is non-empty, since this is a faster check and almost always returns true.
108     // FIXME: Why do we need to call absoluteRects at all?
109     if (!renderer->borderBoundingBox().isEmpty())
110         return true;
111
112     // FIXME: Since all we are checking is whether the rects are empty, could we just
113     // pass in 0,0 for the layout point instead of calling localToAbsolute?
114     Vector<IntRect> rects;
115     renderer->absoluteRects(rects, flooredLayoutPoint(renderer->localToAbsolute()));
116     for (auto& rect : rects) {
117         if (!rect.isEmpty())
118             return true;
119     }
120
121     return false;
122 }
123
124 bool HTMLAnchorElement::isKeyboardFocusable(KeyboardEvent* event) const
125 {
126     if (!isLink())
127         return HTMLElement::isKeyboardFocusable(event);
128
129     if (!isFocusable())
130         return false;
131     
132     if (!document().frame())
133         return false;
134
135     if (!document().frame()->eventHandler().tabsToLinks(event))
136         return false;
137
138     if (!renderer() && ancestorsOfType<HTMLCanvasElement>(*this).first())
139         return true;
140
141     return hasNonEmptyBox(renderBoxModelObject());
142 }
143
144 static void appendServerMapMousePosition(StringBuilder& url, Event& event)
145 {
146     if (!is<MouseEvent>(event))
147         return;
148     auto& mouseEvent = downcast<MouseEvent>(event);
149
150     if (!is<HTMLImageElement>(mouseEvent.target()))
151         return;
152
153     auto& imageElement = downcast<HTMLImageElement>(*mouseEvent.target());
154     if (!imageElement.isServerMap())
155         return;
156
157     auto* renderer = imageElement.renderer();
158     if (!is<RenderImage>(renderer))
159         return;
160
161     // FIXME: This should probably pass UseTransforms in the MapCoordinatesFlags.
162     auto absolutePosition = downcast<RenderImage>(*renderer).absoluteToLocal(FloatPoint(mouseEvent.pageX(), mouseEvent.pageY()));
163     url.append('?');
164     url.appendNumber(std::lround(absolutePosition.x()));
165     url.append(',');
166     url.appendNumber(std::lround(absolutePosition.y()));
167 }
168
169 void HTMLAnchorElement::defaultEventHandler(Event& event)
170 {
171     if (isLink()) {
172         if (focused() && isEnterKeyKeydownEvent(event) && treatLinkAsLiveForEventType(NonMouseEvent)) {
173             event.setDefaultHandled();
174             dispatchSimulatedClick(&event);
175             return;
176         }
177
178         if (MouseEvent::canTriggerActivationBehavior(event) && treatLinkAsLiveForEventType(eventType(event))) {
179             handleClick(event);
180             return;
181         }
182
183         if (hasEditableStyle()) {
184             // This keeps track of the editable block that the selection was in (if it was in one) just before the link was clicked
185             // for the LiveWhenNotFocused editable link behavior
186             if (event.type() == eventNames().mousedownEvent && is<MouseEvent>(event) && downcast<MouseEvent>(event).button() != RightButton && document().frame()) {
187                 setRootEditableElementForSelectionOnMouseDown(document().frame()->selection().selection().rootEditableElement());
188                 m_wasShiftKeyDownOnMouseDown = downcast<MouseEvent>(event).shiftKey();
189             } else if (event.type() == eventNames().mouseoverEvent) {
190                 // These are cleared on mouseover and not mouseout because their values are needed for drag events,
191                 // but drag events happen after mouse out events.
192                 clearRootEditableElementForSelectionOnMouseDown();
193                 m_wasShiftKeyDownOnMouseDown = false;
194             }
195         }
196     }
197
198     HTMLElement::defaultEventHandler(event);
199 }
200
201 void HTMLAnchorElement::setActive(bool down, bool pause)
202 {
203     if (hasEditableStyle()) {
204         EditableLinkBehavior editableLinkBehavior = document().settings().editableLinkBehavior();
205             
206         switch (editableLinkBehavior) {
207             default:
208             case EditableLinkDefaultBehavior:
209             case EditableLinkAlwaysLive:
210                 break;
211
212             case EditableLinkNeverLive:
213                 return;
214
215             // Don't set the link to be active if the current selection is in the same editable block as
216             // this link
217             case EditableLinkLiveWhenNotFocused:
218                 if (down && document().frame() && document().frame()->selection().selection().rootEditableElement() == rootEditableElement())
219                     return;
220                 break;
221             
222             case EditableLinkOnlyLiveWithShiftKey:
223                 return;
224         }
225
226     }
227     
228     HTMLElement::setActive(down, pause);
229 }
230
231 void HTMLAnchorElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
232 {
233     if (name == hrefAttr) {
234         bool wasLink = isLink();
235         setIsLink(!value.isNull() && !shouldProhibitLinks(this));
236         if (wasLink != isLink())
237             invalidateStyleForSubtree();
238         if (isLink()) {
239             String parsedURL = stripLeadingAndTrailingHTMLSpaces(value);
240             if (document().isDNSPrefetchEnabled() && document().frame()) {
241                 if (protocolIsInHTTPFamily(parsedURL) || parsedURL.startsWith("//"))
242                     document().frame()->loader().client().prefetchDNS(document().completeURL(parsedURL).host());
243             }
244         }
245         invalidateCachedVisitedLinkHash();
246     } else if (name == nameAttr || name == titleAttr) {
247         // Do nothing.
248     } else if (name == relAttr) {
249         // Update HTMLAnchorElement::relList() if more rel attributes values are supported.
250         static NeverDestroyed<AtomicString> noReferrer("noreferrer", AtomicString::ConstructFromLiteral);
251         static NeverDestroyed<AtomicString> noOpener("noopener", AtomicString::ConstructFromLiteral);
252         const bool shouldFoldCase = true;
253         SpaceSplitString relValue(value, shouldFoldCase);
254         if (relValue.contains(noReferrer))
255             m_linkRelations |= Relation::NoReferrer;
256         if (relValue.contains(noOpener))
257             m_linkRelations |= Relation::NoOpener;
258         if (m_relList)
259             m_relList->associatedAttributeValueChanged(value);
260     }
261     else
262         HTMLElement::parseAttribute(name, value);
263 }
264
265 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
266 {
267     dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
268 }
269
270 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
271 {
272     return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
273 }
274
275 bool HTMLAnchorElement::canStartSelection() const
276 {
277     if (!isLink())
278         return HTMLElement::canStartSelection();
279     return hasEditableStyle();
280 }
281
282 bool HTMLAnchorElement::draggable() const
283 {
284     const AtomicString& value = attributeWithoutSynchronization(draggableAttr);
285     if (equalLettersIgnoringASCIICase(value, "true"))
286         return true;
287     if (equalLettersIgnoringASCIICase(value, "false"))
288         return false;
289     return hasAttributeWithoutSynchronization(hrefAttr);
290 }
291
292 URL HTMLAnchorElement::href() const
293 {
294     return document().completeURL(stripLeadingAndTrailingHTMLSpaces(attributeWithoutSynchronization(hrefAttr)));
295 }
296
297 void HTMLAnchorElement::setHref(const AtomicString& value)
298 {
299     setAttributeWithoutSynchronization(hrefAttr, value);
300 }
301
302 bool HTMLAnchorElement::hasRel(Relation relation) const
303 {
304     return m_linkRelations.contains(relation);
305 }
306
307 DOMTokenList& HTMLAnchorElement::relList()
308 {
309     if (!m_relList) 
310         m_relList = std::make_unique<DOMTokenList>(*this, HTMLNames::relAttr, [](Document&, StringView token) {
311             return equalIgnoringASCIICase(token, "noreferrer") || equalIgnoringASCIICase(token, "noopener");
312         });
313     return *m_relList;
314 }
315
316 const AtomicString& HTMLAnchorElement::name() const
317 {
318     return getNameAttribute();
319 }
320
321 int HTMLAnchorElement::tabIndex() const
322 {
323     // Skip the supportsFocus check in HTMLElement.
324     return Element::tabIndex();
325 }
326
327 String HTMLAnchorElement::target() const
328 {
329     return attributeWithoutSynchronization(targetAttr);
330 }
331
332 String HTMLAnchorElement::origin() const
333 {
334     return SecurityOrigin::create(href()).get().toString();
335 }
336
337 String HTMLAnchorElement::text()
338 {
339     return textContent();
340 }
341
342 void HTMLAnchorElement::setText(const String& text)
343 {
344     setTextContent(text);
345 }
346
347 bool HTMLAnchorElement::isLiveLink() const
348 {
349     return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey);
350 }
351
352 void HTMLAnchorElement::sendPings(const URL& destinationURL)
353 {
354     if (!document().frame())
355         return;
356
357     if (!hasAttributeWithoutSynchronization(pingAttr) || !document().settings().hyperlinkAuditingEnabled())
358         return;
359
360     SpaceSplitString pingURLs(attributeWithoutSynchronization(pingAttr), false);
361     for (unsigned i = 0; i < pingURLs.size(); i++)
362         PingLoader::sendPing(*document().frame(), document().completeURL(pingURLs[i]), destinationURL);
363 }
364
365 void HTMLAnchorElement::handleClick(Event& event)
366 {
367     event.setDefaultHandled();
368
369     RefPtr<Frame> frame = document().frame();
370     if (!frame)
371         return;
372
373     StringBuilder url;
374     url.append(stripLeadingAndTrailingHTMLSpaces(attributeWithoutSynchronization(hrefAttr)));
375     appendServerMapMousePosition(url, event);
376     URL completedURL = document().completeURL(url.toString());
377
378     String downloadAttribute;
379 #if ENABLE(DOWNLOAD_ATTRIBUTE)
380     if (RuntimeEnabledFeatures::sharedFeatures().downloadAttributeEnabled()) {
381         // Ignore the download attribute completely if the href URL is cross origin.
382         bool isSameOrigin = completedURL.protocolIsData() || document().securityOrigin().canRequest(completedURL);
383         if (isSameOrigin)
384             downloadAttribute = ResourceResponse::sanitizeSuggestedFilename(attributeWithoutSynchronization(downloadAttr));
385         else if (hasAttributeWithoutSynchronization(downloadAttr))
386             document().addConsoleMessage(MessageSource::Security, MessageLevel::Warning, "The download attribute on anchor was ignored because its href URL has a different security origin.");
387     }
388 #endif
389
390     ShouldSendReferrer shouldSendReferrer = hasRel(Relation::NoReferrer) ? NeverSendReferrer : MaybeSendReferrer;
391     auto newFrameOpenerPolicy = hasRel(Relation::NoOpener) ? std::make_optional(NewFrameOpenerPolicy::Suppress) : std::nullopt;
392     frame->loader().urlSelected(completedURL, target(), &event, LockHistory::No, LockBackForwardList::No, shouldSendReferrer, document().shouldOpenExternalURLsPolicyToPropagate(), newFrameOpenerPolicy, downloadAttribute);
393
394     sendPings(completedURL);
395 }
396
397 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event& event)
398 {
399     if (!is<MouseEvent>(event))
400         return NonMouseEvent;
401     return downcast<MouseEvent>(event).shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
402 }
403
404 bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const
405 {
406     if (!hasEditableStyle())
407         return true;
408
409     switch (document().settings().editableLinkBehavior()) {
410     case EditableLinkDefaultBehavior:
411     case EditableLinkAlwaysLive:
412         return true;
413
414     case EditableLinkNeverLive:
415         return false;
416
417     // If the selection prior to clicking on this link resided in the same editable block as this link,
418     // and the shift key isn't pressed, we don't want to follow the link.
419     case EditableLinkLiveWhenNotFocused:
420         return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && rootEditableElementForSelectionOnMouseDown() != rootEditableElement());
421
422     case EditableLinkOnlyLiveWithShiftKey:
423         return eventType == MouseEventWithShiftKey;
424     }
425
426     ASSERT_NOT_REACHED();
427     return false;
428 }
429
430 bool isEnterKeyKeydownEvent(Event& event)
431 {
432     return event.type() == eventNames().keydownEvent && is<KeyboardEvent>(event) && downcast<KeyboardEvent>(event).keyIdentifier() == "Enter";
433 }
434
435 bool shouldProhibitLinks(Element* element)
436 {
437     return isInSVGImage(element);
438 }
439
440 bool HTMLAnchorElement::willRespondToMouseClickEvents()
441 {
442     return isLink() || HTMLElement::willRespondToMouseClickEvents();
443 }
444
445 typedef HashMap<const HTMLAnchorElement*, RefPtr<Element>> RootEditableElementMap;
446
447 static RootEditableElementMap& rootEditableElementMap()
448 {
449     static NeverDestroyed<RootEditableElementMap> map;
450     return map;
451 }
452
453 Element* HTMLAnchorElement::rootEditableElementForSelectionOnMouseDown() const
454 {
455     if (!m_hasRootEditableElementForSelectionOnMouseDown)
456         return 0;
457     return rootEditableElementMap().get(this);
458 }
459
460 void HTMLAnchorElement::clearRootEditableElementForSelectionOnMouseDown()
461 {
462     if (!m_hasRootEditableElementForSelectionOnMouseDown)
463         return;
464     rootEditableElementMap().remove(this);
465     m_hasRootEditableElementForSelectionOnMouseDown = false;
466 }
467
468 void HTMLAnchorElement::setRootEditableElementForSelectionOnMouseDown(Element* element)
469 {
470     if (!element) {
471         clearRootEditableElementForSelectionOnMouseDown();
472         return;
473     }
474
475     rootEditableElementMap().set(this, element);
476     m_hasRootEditableElementForSelectionOnMouseDown = true;
477 }
478
479 }