1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * Copyright (C) 2004, 2005, 2006, 2009, 2011 Apple Inc. All rights reserved.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB. If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22#include "config.h"
23#include "HTMLAreaElement.h"
24
25#include "AffineTransform.h"
26#include "Frame.h"
27#include "HTMLImageElement.h"
28#include "HTMLMapElement.h"
29#include "HTMLParserIdioms.h"
30#include "HitTestResult.h"
31#include "Path.h"
32#include "RenderImage.h"
33#include "RenderView.h"
34#include <wtf/IsoMallocInlines.h>
35
36namespace WebCore {
37
38WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLAreaElement);
39
40using namespace HTMLNames;
41
42inline HTMLAreaElement::HTMLAreaElement(const QualifiedName& tagName, Document& document)
43 : HTMLAnchorElement(tagName, document)
44 , m_lastSize(-1, -1)
45 , m_shape(Unknown)
46{
47 ASSERT(hasTagName(areaTag));
48}
49
50Ref<HTMLAreaElement> HTMLAreaElement::create(const QualifiedName& tagName, Document& document)
51{
52 return adoptRef(*new HTMLAreaElement(tagName, document));
53}
54
55void HTMLAreaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
56{
57 if (name == shapeAttr) {
58 if (equalLettersIgnoringASCIICase(value, "default"))
59 m_shape = Default;
60 else if (equalLettersIgnoringASCIICase(value, "circle") || equalLettersIgnoringASCIICase(value, "circ"))
61 m_shape = Circle;
62 else if (equalLettersIgnoringASCIICase(value, "poly") || equalLettersIgnoringASCIICase(value, "polygon"))
63 m_shape = Poly;
64 else {
65 // The missing value default is the rectangle state.
66 m_shape = Rect;
67 }
68 invalidateCachedRegion();
69 } else if (name == coordsAttr) {
70 m_coords = parseHTMLListOfOfFloatingPointNumberValues(value.string());
71 invalidateCachedRegion();
72 } else if (name == altAttr) {
73 // Do nothing.
74 } else
75 HTMLAnchorElement::parseAttribute(name, value);
76}
77
78void HTMLAreaElement::invalidateCachedRegion()
79{
80 m_lastSize = LayoutSize(-1, -1);
81}
82
83bool HTMLAreaElement::mapMouseEvent(LayoutPoint location, const LayoutSize& size, HitTestResult& result)
84{
85 if (m_lastSize != size) {
86 m_region = std::make_unique<Path>(getRegion(size));
87 m_lastSize = size;
88 }
89
90 if (!m_region->contains(location))
91 return false;
92
93 result.setInnerNode(this);
94 result.setURLElement(this);
95 return true;
96}
97
98// FIXME: We should use RenderElement* instead of RenderObject* once we upstream iOS's DOMUIKitExtensions.{h, mm}.
99Path HTMLAreaElement::computePath(RenderObject* obj) const
100{
101 if (!obj)
102 return Path();
103
104 // FIXME: This doesn't work correctly with transforms.
105 FloatPoint absPos = obj->localToAbsolute();
106
107 // Default should default to the size of the containing object.
108 LayoutSize size = m_lastSize;
109 if (m_shape == Default)
110 size = obj->absoluteOutlineBounds().size();
111
112 Path p = getRegion(size);
113 float zoomFactor = obj->style().effectiveZoom();
114 if (zoomFactor != 1.0f) {
115 AffineTransform zoomTransform;
116 zoomTransform.scale(zoomFactor);
117 p.transform(zoomTransform);
118 }
119
120 p.translate(toFloatSize(absPos));
121 return p;
122}
123
124Path HTMLAreaElement::computePathForFocusRing(const LayoutSize& elementSize) const
125{
126 return getRegion(m_shape == Default ? elementSize : m_lastSize);
127}
128
129// FIXME: Use RenderElement* instead of RenderObject* once we upstream iOS's DOMUIKitExtensions.{h, mm}.
130LayoutRect HTMLAreaElement::computeRect(RenderObject* obj) const
131{
132 return enclosingLayoutRect(computePath(obj).fastBoundingRect());
133}
134
135Path HTMLAreaElement::getRegion(const LayoutSize& size) const
136{
137 if (m_coords.isEmpty() && m_shape != Default)
138 return Path();
139
140 LayoutUnit width = size.width();
141 LayoutUnit height = size.height();
142
143 // If element omits the shape attribute, select shape based on number of coordinates.
144 Shape shape = m_shape;
145 if (shape == Unknown) {
146 if (m_coords.size() == 3)
147 shape = Circle;
148 else if (m_coords.size() == 4)
149 shape = Rect;
150 else if (m_coords.size() >= 6)
151 shape = Poly;
152 }
153
154 Path path;
155 switch (shape) {
156 case Poly:
157 if (m_coords.size() >= 6) {
158 int numPoints = m_coords.size() / 2;
159 path.moveTo(FloatPoint(m_coords[0], m_coords[1]));
160 for (int i = 1; i < numPoints; ++i)
161 path.addLineTo(FloatPoint(m_coords[i * 2], m_coords[i * 2 + 1]));
162 path.closeSubpath();
163 }
164 break;
165 case Circle:
166 if (m_coords.size() >= 3) {
167 double radius = m_coords[2];
168 if (radius > 0)
169 path.addEllipse(FloatRect(m_coords[0] - radius, m_coords[1] - radius, 2 * radius, 2 * radius));
170 }
171 break;
172 case Rect:
173 if (m_coords.size() >= 4) {
174 double x0 = m_coords[0];
175 double y0 = m_coords[1];
176 double x1 = m_coords[2];
177 double y1 = m_coords[3];
178 path.addRect(FloatRect(x0, y0, x1 - x0, y1 - y0));
179 }
180 break;
181 case Default:
182 path.addRect(FloatRect(0, 0, width, height));
183 break;
184 case Unknown:
185 break;
186 }
187
188 return path;
189}
190
191HTMLImageElement* HTMLAreaElement::imageElement() const
192{
193 RefPtr<Node> mapElement = parentNode();
194 if (!is<HTMLMapElement>(mapElement))
195 return nullptr;
196
197 return downcast<HTMLMapElement>(*mapElement).imageElement();
198}
199
200bool HTMLAreaElement::isKeyboardFocusable(KeyboardEvent*) const
201{
202 return isFocusable();
203}
204
205bool HTMLAreaElement::isMouseFocusable() const
206{
207 return isFocusable();
208}
209
210bool HTMLAreaElement::isFocusable() const
211{
212 RefPtr<HTMLImageElement> image = imageElement();
213 if (!image || !image->renderer() || image->renderer()->style().visibility() != Visibility::Visible)
214 return false;
215
216 return supportsFocus() && Element::tabIndex() >= 0;
217}
218
219void HTMLAreaElement::setFocus(bool shouldBeFocused)
220{
221 if (focused() == shouldBeFocused)
222 return;
223
224 HTMLAnchorElement::setFocus(shouldBeFocused);
225
226 RefPtr<HTMLImageElement> imageElement = this->imageElement();
227 if (!imageElement)
228 return;
229
230 auto* renderer = imageElement->renderer();
231 if (!is<RenderImage>(renderer))
232 return;
233
234 downcast<RenderImage>(*renderer).areaElementFocusChanged(this);
235}
236
237RefPtr<Element> HTMLAreaElement::focusAppearanceUpdateTarget()
238{
239 if (!isFocusable())
240 return nullptr;
241 return imageElement();
242}
243
244bool HTMLAreaElement::supportsFocus() const
245{
246 // If the AREA element was a link, it should support focus.
247 // The inherited method is not used because it assumes that a render object must exist
248 // for the element to support focus. AREA elements do not have render objects.
249 return isLink();
250}
251
252String HTMLAreaElement::target() const
253{
254 return attributeWithoutSynchronization(targetAttr);
255}
256
257}
258