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 "Attribute.h" 27#include "Frame.h" 28#include "HTMLImageElement.h" 29#include "HTMLMapElement.h" 30#include "HitTestResult.h" 31#include "Path.h" 32#include "RenderImage.h" 33#include "RenderView.h" 34 35namespace WebCore { 36 37using namespace HTMLNames; 38 39inline HTMLAreaElement::HTMLAreaElement(const QualifiedName& tagName, Document& document) 40 : HTMLAnchorElement(tagName, document) 41 , m_coordsLen(0) 42 , m_lastSize(-1, -1) 43 , m_shape(Unknown) 44{ 45 ASSERT(hasTagName(areaTag)); 46} 47 48PassRefPtr<HTMLAreaElement> HTMLAreaElement::create(const QualifiedName& tagName, Document& document) 49{ 50 return adoptRef(new HTMLAreaElement(tagName, document)); 51} 52 53void HTMLAreaElement::parseAttribute(const QualifiedName& name, const AtomicString& value) 54{ 55 if (name == shapeAttr) { 56 if (equalIgnoringCase(value, "default")) 57 m_shape = Default; 58 else if (equalIgnoringCase(value, "circle")) 59 m_shape = Circle; 60 else if (equalIgnoringCase(value, "poly")) 61 m_shape = Poly; 62 else if (equalIgnoringCase(value, "rect")) 63 m_shape = Rect; 64 invalidateCachedRegion(); 65 } else if (name == coordsAttr) { 66 m_coords = newCoordsArray(value.string(), m_coordsLen); 67 invalidateCachedRegion(); 68 } else if (name == altAttr || name == accesskeyAttr) { 69 // Do nothing. 70 } else 71 HTMLAnchorElement::parseAttribute(name, value); 72} 73 74void HTMLAreaElement::invalidateCachedRegion() 75{ 76 m_lastSize = LayoutSize(-1, -1); 77} 78 79bool HTMLAreaElement::mapMouseEvent(LayoutPoint location, const LayoutSize& size, HitTestResult& result) 80{ 81 if (m_lastSize != size) { 82 m_region = std::make_unique<Path>(getRegion(size)); 83 m_lastSize = size; 84 } 85 86 if (!m_region->contains(location)) 87 return false; 88 89 result.setInnerNode(this); 90 result.setURLElement(this); 91 return true; 92} 93 94// FIXME: We should use RenderElement* instead of RenderObject* once we upstream iOS's DOMUIKitExtensions.{h, mm}. 95Path HTMLAreaElement::computePath(RenderObject* obj) const 96{ 97 if (!obj) 98 return Path(); 99 100 // FIXME: This doesn't work correctly with transforms. 101 FloatPoint absPos = obj->localToAbsolute(); 102 103 // Default should default to the size of the containing object. 104 LayoutSize size = m_lastSize; 105 if (m_shape == Default) 106 size = obj->absoluteOutlineBounds().size(); 107 108 Path p = getRegion(size); 109 float zoomFactor = obj->style().effectiveZoom(); 110 if (zoomFactor != 1.0f) { 111 AffineTransform zoomTransform; 112 zoomTransform.scale(zoomFactor); 113 p.transform(zoomTransform); 114 } 115 116 p.translate(toFloatSize(absPos)); 117 return p; 118} 119 120// FIXME: Use RenderElement* instead of RenderObject* once we upstream iOS's DOMUIKitExtensions.{h, mm}. 121LayoutRect HTMLAreaElement::computeRect(RenderObject* obj) const 122{ 123 return enclosingLayoutRect(computePath(obj).fastBoundingRect()); 124} 125 126Path HTMLAreaElement::getRegion(const LayoutSize& size) const 127{ 128 if (!m_coords && m_shape != Default) 129 return Path(); 130 131 LayoutUnit width = size.width(); 132 LayoutUnit height = size.height(); 133 134 // If element omits the shape attribute, select shape based on number of coordinates. 135 Shape shape = m_shape; 136 if (shape == Unknown) { 137 if (m_coordsLen == 3) 138 shape = Circle; 139 else if (m_coordsLen == 4) 140 shape = Rect; 141 else if (m_coordsLen >= 6) 142 shape = Poly; 143 } 144 145 Path path; 146 switch (shape) { 147 case Poly: 148 if (m_coordsLen >= 6) { 149 int numPoints = m_coordsLen / 2; 150 path.moveTo(FloatPoint(minimumValueForLength(m_coords[0], width), minimumValueForLength(m_coords[1], height))); 151 for (int i = 1; i < numPoints; ++i) 152 path.addLineTo(FloatPoint(minimumValueForLength(m_coords[i * 2], width), minimumValueForLength(m_coords[i * 2 + 1], height))); 153 path.closeSubpath(); 154 } 155 break; 156 case Circle: 157 if (m_coordsLen >= 3) { 158 Length radius = m_coords[2]; 159 int r = std::min(minimumValueForLength(radius, width), minimumValueForLength(radius, height)); 160 path.addEllipse(FloatRect(minimumValueForLength(m_coords[0], width) - r, minimumValueForLength(m_coords[1], height) - r, 2 * r, 2 * r)); 161 } 162 break; 163 case Rect: 164 if (m_coordsLen >= 4) { 165 int x0 = minimumValueForLength(m_coords[0], width); 166 int y0 = minimumValueForLength(m_coords[1], height); 167 int x1 = minimumValueForLength(m_coords[2], width); 168 int y1 = minimumValueForLength(m_coords[3], height); 169 path.addRect(FloatRect(x0, y0, x1 - x0, y1 - y0)); 170 } 171 break; 172 case Default: 173 path.addRect(FloatRect(0, 0, width, height)); 174 break; 175 case Unknown: 176 break; 177 } 178 179 return path; 180} 181 182HTMLImageElement* HTMLAreaElement::imageElement() const 183{ 184 Node* mapElement = parentNode(); 185 if (!mapElement || !isHTMLMapElement(mapElement)) 186 return 0; 187 188 return toHTMLMapElement(mapElement)->imageElement(); 189} 190 191bool HTMLAreaElement::isKeyboardFocusable(KeyboardEvent*) const 192{ 193 return isFocusable(); 194} 195 196bool HTMLAreaElement::isMouseFocusable() const 197{ 198 return isFocusable(); 199} 200 201bool HTMLAreaElement::isFocusable() const 202{ 203 HTMLImageElement* image = imageElement(); 204 if (!image || !image->renderer() || image->renderer()->style().visibility() != VISIBLE) 205 return false; 206 207 return supportsFocus() && Element::tabIndex() >= 0; 208} 209 210void HTMLAreaElement::setFocus(bool shouldBeFocused) 211{ 212 if (focused() == shouldBeFocused) 213 return; 214 215 HTMLAnchorElement::setFocus(shouldBeFocused); 216 217 HTMLImageElement* imageElement = this->imageElement(); 218 if (!imageElement) 219 return; 220 221 auto renderer = imageElement->renderer(); 222 if (!renderer || !renderer->isRenderImage()) 223 return; 224 225 toRenderImage(renderer)->areaElementFocusChanged(this); 226} 227 228void HTMLAreaElement::updateFocusAppearance(bool restorePreviousSelection) 229{ 230 if (!isFocusable()) 231 return; 232 233 HTMLImageElement* imageElement = this->imageElement(); 234 if (!imageElement) 235 return; 236 237 imageElement->updateFocusAppearance(restorePreviousSelection); 238} 239 240bool HTMLAreaElement::supportsFocus() const 241{ 242 // If the AREA element was a link, it should support focus. 243 // The inherited method is not used because it assumes that a render object must exist 244 // for the element to support focus. AREA elements do not have render objects. 245 return isLink(); 246} 247 248String HTMLAreaElement::target() const 249{ 250 return getAttribute(targetAttr); 251} 252 253} 254