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 | |
36 | namespace WebCore { |
37 | |
38 | WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLAreaElement); |
39 | |
40 | using namespace HTMLNames; |
41 | |
42 | inline 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 | |
50 | Ref<HTMLAreaElement> HTMLAreaElement::create(const QualifiedName& tagName, Document& document) |
51 | { |
52 | return adoptRef(*new HTMLAreaElement(tagName, document)); |
53 | } |
54 | |
55 | void 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 | |
78 | void HTMLAreaElement::invalidateCachedRegion() |
79 | { |
80 | m_lastSize = LayoutSize(-1, -1); |
81 | } |
82 | |
83 | bool 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}. |
99 | Path 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 | |
124 | Path 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}. |
130 | LayoutRect HTMLAreaElement::computeRect(RenderObject* obj) const |
131 | { |
132 | return enclosingLayoutRect(computePath(obj).fastBoundingRect()); |
133 | } |
134 | |
135 | Path 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 | |
191 | HTMLImageElement* 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 | |
200 | bool HTMLAreaElement::isKeyboardFocusable(KeyboardEvent*) const |
201 | { |
202 | return isFocusable(); |
203 | } |
204 | |
205 | bool HTMLAreaElement::isMouseFocusable() const |
206 | { |
207 | return isFocusable(); |
208 | } |
209 | |
210 | bool 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 | |
219 | void 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 | |
237 | RefPtr<Element> HTMLAreaElement::focusAppearanceUpdateTarget() |
238 | { |
239 | if (!isFocusable()) |
240 | return nullptr; |
241 | return imageElement(); |
242 | } |
243 | |
244 | bool 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 | |
252 | String HTMLAreaElement::target() const |
253 | { |
254 | return attributeWithoutSynchronization(targetAttr); |
255 | } |
256 | |
257 | } |
258 | |