1/*
2 * Copyright (C) 2004, 2005, 2008 Nikolas Zimmermann <zimmermann@kde.org>
3 * Copyright (C) 2004, 2005, 2007 Rob Buis <buis@kde.org>
4 * Copyright (C) 2007 Eric Seidel <eric@webkit.org>
5 * Copyright (C) 2010-2019 Apple Inc. All rights reserved.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB. If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 */
22
23#include "config.h"
24#include "SVGAElement.h"
25
26#include "Document.h"
27#include "EventHandler.h"
28#include "Frame.h"
29#include "FrameLoader.h"
30#include "FrameLoaderTypes.h"
31#include "HTMLAnchorElement.h"
32#include "HTMLParserIdioms.h"
33#include "KeyboardEvent.h"
34#include "MouseEvent.h"
35#include "PlatformMouseEvent.h"
36#include "RenderSVGInline.h"
37#include "RenderSVGText.h"
38#include "RenderSVGTransformableContainer.h"
39#include "ResourceRequest.h"
40#include "SVGNames.h"
41#include "SVGSMILElement.h"
42#include "XLinkNames.h"
43#include <wtf/IsoMallocInlines.h>
44
45namespace WebCore {
46
47WTF_MAKE_ISO_ALLOCATED_IMPL(SVGAElement);
48
49inline SVGAElement::SVGAElement(const QualifiedName& tagName, Document& document)
50 : SVGGraphicsElement(tagName, document)
51 , SVGExternalResourcesRequired(this)
52 , SVGURIReference(this)
53{
54 ASSERT(hasTagName(SVGNames::aTag));
55
56 static std::once_flag onceFlag;
57 std::call_once(onceFlag, [] {
58 PropertyRegistry::registerProperty<SVGNames::targetAttr, &SVGAElement::m_target>();
59 });
60}
61
62Ref<SVGAElement> SVGAElement::create(const QualifiedName& tagName, Document& document)
63{
64 return adoptRef(*new SVGAElement(tagName, document));
65}
66
67String SVGAElement::title() const
68{
69 // If the xlink:title is set (non-empty string), use it.
70 const AtomicString& title = attributeWithoutSynchronization(XLinkNames::titleAttr);
71 if (!title.isEmpty())
72 return title;
73
74 // Otherwise, use the title of this element.
75 return SVGElement::title();
76}
77
78void SVGAElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
79{
80 if (name == SVGNames::targetAttr) {
81 m_target->setBaseValInternal(value);
82 return;
83 }
84
85 SVGGraphicsElement::parseAttribute(name, value);
86 SVGURIReference::parseAttribute(name, value);
87 SVGExternalResourcesRequired::parseAttribute(name, value);
88}
89
90void SVGAElement::svgAttributeChanged(const QualifiedName& attrName)
91{
92 if (SVGURIReference::isKnownAttribute(attrName)) {
93 bool wasLink = isLink();
94 setIsLink(!href().isNull() && !shouldProhibitLinks(this));
95 if (wasLink != isLink()) {
96 InstanceInvalidationGuard guard(*this);
97 invalidateStyleForSubtree();
98 }
99 return;
100 }
101
102 SVGGraphicsElement::svgAttributeChanged(attrName);
103 SVGExternalResourcesRequired::svgAttributeChanged(attrName);
104}
105
106RenderPtr<RenderElement> SVGAElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
107{
108 if (parentNode() && parentNode()->isSVGElement() && downcast<SVGElement>(*parentNode()).isTextContent())
109 return createRenderer<RenderSVGInline>(*this, WTFMove(style));
110
111 return createRenderer<RenderSVGTransformableContainer>(*this, WTFMove(style));
112}
113
114void SVGAElement::defaultEventHandler(Event& event)
115{
116 if (isLink()) {
117 if (focused() && isEnterKeyKeydownEvent(event)) {
118 event.setDefaultHandled();
119 dispatchSimulatedClick(&event);
120 return;
121 }
122
123 if (MouseEvent::canTriggerActivationBehavior(event)) {
124 String url = stripLeadingAndTrailingHTMLSpaces(href());
125
126 if (url[0] == '#') {
127 auto targetElement = makeRefPtr(treeScope().getElementById(url.substringSharingImpl(1)));
128 if (is<SVGSMILElement>(targetElement)) {
129 downcast<SVGSMILElement>(*targetElement).beginByLinkActivation();
130 event.setDefaultHandled();
131 return;
132 }
133 // Only allow navigation to internal <view> anchors.
134 if (targetElement && !targetElement->hasTagName(SVGNames::viewTag))
135 return;
136 }
137
138 String target = this->target();
139 if (target.isEmpty() && attributeWithoutSynchronization(XLinkNames::showAttr) == "new")
140 target = "_blank";
141 event.setDefaultHandled();
142
143 auto frame = makeRefPtr(document().frame());
144 if (!frame)
145 return;
146 frame->loader().urlSelected(document().completeURL(url), target, &event, LockHistory::No, LockBackForwardList::No, MaybeSendReferrer, document().shouldOpenExternalURLsPolicyToPropagate());
147 return;
148 }
149 }
150
151 SVGGraphicsElement::defaultEventHandler(event);
152}
153
154int SVGAElement::tabIndex() const
155{
156 // Skip the supportsFocus check in SVGElement.
157 return Element::tabIndex();
158}
159
160bool SVGAElement::supportsFocus() const
161{
162 if (hasEditableStyle())
163 return SVGGraphicsElement::supportsFocus();
164 // If not a link we should still be able to focus the element if it has a tabIndex.
165 return isLink() || Element::supportsFocus();
166}
167
168bool SVGAElement::isURLAttribute(const Attribute& attribute) const
169{
170 return SVGURIReference::isKnownAttribute(attribute.name()) || SVGGraphicsElement::isURLAttribute(attribute);
171}
172
173bool SVGAElement::isMouseFocusable() const
174{
175 // Links are focusable by default, but only allow links with tabindex or contenteditable to be mouse focusable.
176 // https://bugs.webkit.org/show_bug.cgi?id=26856
177 if (isLink())
178 return Element::supportsFocus();
179
180 return SVGElement::isMouseFocusable();
181}
182
183bool SVGAElement::isKeyboardFocusable(KeyboardEvent* event) const
184{
185 if (isFocusable() && Element::supportsFocus())
186 return SVGElement::isKeyboardFocusable(event);
187
188 if (isLink())
189 return document().frame()->eventHandler().tabsToLinks(event);
190
191 return SVGElement::isKeyboardFocusable(event);
192}
193
194bool SVGAElement::canStartSelection() const
195{
196 if (!isLink())
197 return SVGElement::canStartSelection();
198
199 return hasEditableStyle();
200}
201
202bool SVGAElement::childShouldCreateRenderer(const Node& child) const
203{
204 // http://www.w3.org/2003/01/REC-SVG11-20030114-errata#linking-text-environment
205 // The 'a' element may contain any element that its parent may contain, except itself.
206 if (child.hasTagName(SVGNames::aTag))
207 return false;
208
209 if (parentElement() && parentElement()->isSVGElement())
210 return parentElement()->childShouldCreateRenderer(child);
211
212 return SVGElement::childShouldCreateRenderer(child);
213}
214
215bool SVGAElement::willRespondToMouseClickEvents()
216{
217 return isLink() || SVGGraphicsElement::willRespondToMouseClickEvents();
218}
219
220SharedStringHash SVGAElement::visitedLinkHash() const
221{
222 ASSERT(isLink());
223 if (!m_storedVisitedLinkHash)
224 m_storedVisitedLinkHash = computeVisitedLinkHash(document().baseURL(), getAttribute(SVGNames::hrefAttr, XLinkNames::hrefAttr));
225 return *m_storedVisitedLinkHash;
226}
227
228} // namespace WebCore
229