e65a19d0efaf7c76cebe30f6e33b40db23ac897c
[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 "SecurityOrigin.h"
48 #include "SecurityPolicy.h"
49 #include "Settings.h"
50 #include "URLUtils.h"
51 #include <wtf/text/StringBuilder.h>
52
53 namespace WebCore {
54
55 using namespace HTMLNames;
56
57 HTMLAnchorElement::HTMLAnchorElement(const QualifiedName& tagName, Document& document)
58     : HTMLElement(tagName, document)
59     , m_hasRootEditableElementForSelectionOnMouseDown(false)
60     , m_wasShiftKeyDownOnMouseDown(false)
61     , m_linkRelations(0)
62     , m_cachedVisitedLinkHash(0)
63 {
64 }
65
66 Ref<HTMLAnchorElement> HTMLAnchorElement::create(Document& document)
67 {
68     return adoptRef(*new HTMLAnchorElement(aTag, document));
69 }
70
71 Ref<HTMLAnchorElement> HTMLAnchorElement::create(const QualifiedName& tagName, Document& document)
72 {
73     return adoptRef(*new HTMLAnchorElement(tagName, document));
74 }
75
76 HTMLAnchorElement::~HTMLAnchorElement()
77 {
78     clearRootEditableElementForSelectionOnMouseDown();
79 }
80
81 bool HTMLAnchorElement::supportsFocus() const
82 {
83     if (hasEditableStyle())
84         return HTMLElement::supportsFocus();
85     // If not a link we should still be able to focus the element if it has tabIndex.
86     return isLink() || HTMLElement::supportsFocus();
87 }
88
89 bool HTMLAnchorElement::isMouseFocusable() const
90 {
91     // Only allow links with tabIndex or contentEditable to be mouse focusable.
92     if (isLink())
93         return HTMLElement::supportsFocus();
94
95     return HTMLElement::isMouseFocusable();
96 }
97
98 static bool hasNonEmptyBox(RenderBoxModelObject* renderer)
99 {
100     if (!renderer)
101         return false;
102
103     // Before calling absoluteRects, check for the common case where borderBoundingBox
104     // is non-empty, since this is a faster check and almost always returns true.
105     // FIXME: Why do we need to call absoluteRects at all?
106     if (!renderer->borderBoundingBox().isEmpty())
107         return true;
108
109     // FIXME: Since all we are checking is whether the rects are empty, could we just
110     // pass in 0,0 for the layout point instead of calling localToAbsolute?
111     Vector<IntRect> rects;
112     renderer->absoluteRects(rects, flooredLayoutPoint(renderer->localToAbsolute()));
113     for (auto& rect : rects) {
114         if (!rect.isEmpty())
115             return true;
116     }
117
118     return false;
119 }
120
121 bool HTMLAnchorElement::isKeyboardFocusable(KeyboardEvent* event) const
122 {
123     if (!isLink())
124         return HTMLElement::isKeyboardFocusable(event);
125
126     if (!isFocusable())
127         return false;
128     
129     if (!document().frame())
130         return false;
131
132     if (!document().frame()->eventHandler().tabsToLinks(event))
133         return false;
134
135     if (!renderer() && ancestorsOfType<HTMLCanvasElement>(*this).first())
136         return true;
137
138     return hasNonEmptyBox(renderBoxModelObject());
139 }
140
141 static void appendServerMapMousePosition(StringBuilder& url, Event& event)
142 {
143     if (!is<MouseEvent>(event))
144         return;
145     auto& mouseEvent = downcast<MouseEvent>(event);
146
147     ASSERT(mouseEvent.target());
148     auto* target = mouseEvent.target()->toNode();
149     ASSERT(target);
150     if (!is<HTMLImageElement>(*target))
151         return;
152
153     auto& imageElement = downcast<HTMLImageElement>(*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 = EditableLinkDefaultBehavior;
205         if (Settings* settings = document().settings())
206             editableLinkBehavior = settings->editableLinkBehavior();
207             
208         switch (editableLinkBehavior) {
209             default:
210             case EditableLinkDefaultBehavior:
211             case EditableLinkAlwaysLive:
212                 break;
213
214             case EditableLinkNeverLive:
215                 return;
216
217             // Don't set the link to be active if the current selection is in the same editable block as
218             // this link
219             case EditableLinkLiveWhenNotFocused:
220                 if (down && document().frame() && document().frame()->selection().selection().rootEditableElement() == rootEditableElement())
221                     return;
222                 break;
223             
224             case EditableLinkOnlyLiveWithShiftKey:
225                 return;
226         }
227
228     }
229     
230     HTMLElement::setActive(down, pause);
231 }
232
233 void HTMLAnchorElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
234 {
235     if (name == hrefAttr) {
236         bool wasLink = isLink();
237         setIsLink(!value.isNull() && !shouldProhibitLinks(this));
238         if (wasLink != isLink())
239             setNeedsStyleRecalc();
240         if (isLink()) {
241             String parsedURL = stripLeadingAndTrailingHTMLSpaces(value);
242             if (document().isDNSPrefetchEnabled() && document().frame()) {
243                 if (protocolIsInHTTPFamily(parsedURL) || parsedURL.startsWith("//"))
244                     document().frame()->loader().client().prefetchDNS(document().completeURL(parsedURL).host());
245             }
246         }
247         invalidateCachedVisitedLinkHash();
248     } else if (name == nameAttr || name == titleAttr) {
249         // Do nothing.
250     } else if (name == relAttr) {
251         if (SpaceSplitString::spaceSplitStringContainsValue(value, "noreferrer", true))
252             m_linkRelations |= RelationNoReferrer;
253         if (m_relList)
254             m_relList->associatedAttributeValueChanged(value);
255     }
256     else
257         HTMLElement::parseAttribute(name, value);
258 }
259
260 void HTMLAnchorElement::accessKeyAction(bool sendMouseEvents)
261 {
262     dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
263 }
264
265 bool HTMLAnchorElement::isURLAttribute(const Attribute& attribute) const
266 {
267     return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
268 }
269
270 bool HTMLAnchorElement::canStartSelection() const
271 {
272     if (!isLink())
273         return HTMLElement::canStartSelection();
274     return hasEditableStyle();
275 }
276
277 bool HTMLAnchorElement::draggable() const
278 {
279     const AtomicString& value = attributeWithoutSynchronization(draggableAttr);
280     if (equalLettersIgnoringASCIICase(value, "true"))
281         return true;
282     if (equalLettersIgnoringASCIICase(value, "false"))
283         return false;
284     return hasAttributeWithoutSynchronization(hrefAttr);
285 }
286
287 URL HTMLAnchorElement::href() const
288 {
289     return document().completeURL(stripLeadingAndTrailingHTMLSpaces(attributeWithoutSynchronization(hrefAttr)));
290 }
291
292 void HTMLAnchorElement::setHref(const AtomicString& value)
293 {
294     setAttributeWithoutSynchronization(hrefAttr, value);
295 }
296
297 bool HTMLAnchorElement::hasRel(uint32_t relation) const
298 {
299     return m_linkRelations & relation;
300 }
301
302 DOMTokenList& HTMLAnchorElement::relList()
303 {
304     if (!m_relList) 
305         m_relList = std::make_unique<DOMTokenList>(*this, HTMLNames::relAttr);
306     return *m_relList;
307 }
308
309 const AtomicString& HTMLAnchorElement::name() const
310 {
311     return getNameAttribute();
312 }
313
314 int HTMLAnchorElement::tabIndex() const
315 {
316     // Skip the supportsFocus check in HTMLElement.
317     return Element::tabIndex();
318 }
319
320 String HTMLAnchorElement::target() const
321 {
322     return attributeWithoutSynchronization(targetAttr);
323 }
324
325 String HTMLAnchorElement::origin() const
326 {
327     return SecurityOrigin::create(href()).get().toString();
328 }
329
330 String HTMLAnchorElement::text()
331 {
332     return textContent();
333 }
334
335 void HTMLAnchorElement::setText(const String& text)
336 {
337     setTextContent(text, ASSERT_NO_EXCEPTION);
338 }
339
340 bool HTMLAnchorElement::isLiveLink() const
341 {
342     return isLink() && treatLinkAsLiveForEventType(m_wasShiftKeyDownOnMouseDown ? MouseEventWithShiftKey : MouseEventWithoutShiftKey);
343 }
344
345 void HTMLAnchorElement::sendPings(const URL& destinationURL)
346 {
347     if (!hasAttributeWithoutSynchronization(pingAttr) || !document().settings() || !document().settings()->hyperlinkAuditingEnabled())
348         return;
349
350     SpaceSplitString pingURLs(attributeWithoutSynchronization(pingAttr), false);
351     for (unsigned i = 0; i < pingURLs.size(); i++)
352         PingLoader::sendPing(*document().frame(), document().completeURL(pingURLs[i]), destinationURL);
353 }
354
355 void HTMLAnchorElement::handleClick(Event* event)
356 {
357     event->setDefaultHandled();
358
359     Frame* frame = document().frame();
360     if (!frame)
361         return;
362
363     StringBuilder url;
364     url.append(stripLeadingAndTrailingHTMLSpaces(attributeWithoutSynchronization(hrefAttr)));
365     appendServerMapMousePosition(url, *event);
366     URL kurl = document().completeURL(url.toString());
367
368     auto downloadAttribute = nullAtom;
369 #if ENABLE(DOWNLOAD_ATTRIBUTE)
370     if (RuntimeEnabledFeatures::sharedFeatures().downloadAttributeEnabled())
371         downloadAttribute = attributeWithoutSynchronization(downloadAttr);
372 #endif
373
374     frame->loader().urlSelected(kurl, target(), event, LockHistory::No, LockBackForwardList::No, hasRel(RelationNoReferrer) ? NeverSendReferrer : MaybeSendReferrer, document().shouldOpenExternalURLsPolicyToPropagate(), downloadAttribute);
375
376     sendPings(kurl);
377 }
378
379 HTMLAnchorElement::EventType HTMLAnchorElement::eventType(Event* event)
380 {
381     ASSERT(event);
382     if (!is<MouseEvent>(*event))
383         return NonMouseEvent;
384     return downcast<MouseEvent>(*event).shiftKey() ? MouseEventWithShiftKey : MouseEventWithoutShiftKey;
385 }
386
387 bool HTMLAnchorElement::treatLinkAsLiveForEventType(EventType eventType) const
388 {
389     if (!hasEditableStyle())
390         return true;
391
392     Settings* settings = document().settings();
393     if (!settings)
394         return true;
395
396     switch (settings->editableLinkBehavior()) {
397     case EditableLinkDefaultBehavior:
398     case EditableLinkAlwaysLive:
399         return true;
400
401     case EditableLinkNeverLive:
402         return false;
403
404     // If the selection prior to clicking on this link resided in the same editable block as this link,
405     // and the shift key isn't pressed, we don't want to follow the link.
406     case EditableLinkLiveWhenNotFocused:
407         return eventType == MouseEventWithShiftKey || (eventType == MouseEventWithoutShiftKey && rootEditableElementForSelectionOnMouseDown() != rootEditableElement());
408
409     case EditableLinkOnlyLiveWithShiftKey:
410         return eventType == MouseEventWithShiftKey;
411     }
412
413     ASSERT_NOT_REACHED();
414     return false;
415 }
416
417 bool isEnterKeyKeydownEvent(Event* event)
418 {
419     return event->type() == eventNames().keydownEvent && is<KeyboardEvent>(*event) && downcast<KeyboardEvent>(*event).keyIdentifier() == "Enter";
420 }
421
422 bool shouldProhibitLinks(Element* element)
423 {
424     return isInSVGImage(element);
425 }
426
427 bool HTMLAnchorElement::willRespondToMouseClickEvents()
428 {
429     return isLink() || HTMLElement::willRespondToMouseClickEvents();
430 }
431
432 typedef HashMap<const HTMLAnchorElement*, RefPtr<Element>> RootEditableElementMap;
433
434 static RootEditableElementMap& rootEditableElementMap()
435 {
436     static NeverDestroyed<RootEditableElementMap> map;
437     return map;
438 }
439
440 Element* HTMLAnchorElement::rootEditableElementForSelectionOnMouseDown() const
441 {
442     if (!m_hasRootEditableElementForSelectionOnMouseDown)
443         return 0;
444     return rootEditableElementMap().get(this);
445 }
446
447 void HTMLAnchorElement::clearRootEditableElementForSelectionOnMouseDown()
448 {
449     if (!m_hasRootEditableElementForSelectionOnMouseDown)
450         return;
451     rootEditableElementMap().remove(this);
452     m_hasRootEditableElementForSelectionOnMouseDown = false;
453 }
454
455 void HTMLAnchorElement::setRootEditableElementForSelectionOnMouseDown(Element* element)
456 {
457     if (!element) {
458         clearRootEditableElementForSelectionOnMouseDown();
459         return;
460     }
461
462     rootEditableElementMap().set(this, element);
463     m_hasRootEditableElementForSelectionOnMouseDown = true;
464 }
465
466 }