View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.html;
16  
17  import static org.htmlunit.BrowserVersionFeatures.CSS_DISPLAY_BLOCK;
18  
19  import java.io.IOException;
20  import java.net.MalformedURLException;
21  import java.net.URL;
22  import java.util.Arrays;
23  import java.util.Map;
24  
25  import org.apache.commons.lang3.StringUtils;
26  import org.apache.commons.lang3.Strings;
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.htmlunit.SgmlPage;
30  import org.htmlunit.WebClient;
31  import org.htmlunit.WebRequest;
32  import org.htmlunit.WebWindow;
33  import org.htmlunit.javascript.host.event.Event;
34  import org.htmlunit.protocol.javascript.JavaScriptURLConnection;
35  import org.htmlunit.util.geometry.Circle2D;
36  import org.htmlunit.util.geometry.Polygon2D;
37  import org.htmlunit.util.geometry.Rectangle2D;
38  import org.htmlunit.util.geometry.Shape2D;
39  
40  /**
41   * Wrapper for the HTML element "area".
42   *
43   * @author Mike Bowler
44   * @author David K. Taylor
45   * @author Christian Sell
46   * @author Marc Guillemot
47   * @author Ahmed Ashour
48   * @author Frank Danek
49   * @author Ronald Brill
50   */
51  public class HtmlArea extends HtmlElement {
52      private static final Log LOG = LogFactory.getLog(HtmlArea.class);
53  
54      /** The HTML tag represented by this element. */
55      public static final String TAG_NAME = "area";
56  
57      private static final String SHAPE_RECT = "rect";
58      private static final String SHAPE_CIRCLE = "circle";
59      private static final String SHAPE_POLY = "poly";
60  
61      /**
62       * Creates a new instance.
63       *
64       * @param qualifiedName the qualified name of the element type to instantiate
65       * @param page the page that contains this element
66       * @param attributes the initial attributes
67       */
68      HtmlArea(final String qualifiedName, final SgmlPage page,
69              final Map<String, DomAttr> attributes) {
70          super(qualifiedName, page, attributes);
71      }
72  
73      /**
74       * {@inheritDoc}
75       */
76      @Override
77      protected boolean doClickStateUpdate(final boolean shiftKey, final boolean ctrlKey) throws IOException {
78          final HtmlPage enclosingPage = (HtmlPage) getPage();
79          final WebClient webClient = enclosingPage.getWebClient();
80  
81          final String href = getHrefAttribute().trim();
82          if (!href.isEmpty()) {
83              final HtmlPage page = (HtmlPage) getPage();
84              if (Strings.CI.startsWith(href, JavaScriptURLConnection.JAVASCRIPT_PREFIX)) {
85                  page.executeJavaScript(
86                      href, "javascript url", getStartLineNumber());
87                  return false;
88              }
89              final URL url;
90              try {
91                  url = enclosingPage.getFullyQualifiedUrl(getHrefAttribute());
92              }
93              catch (final MalformedURLException e) {
94                  throw new IllegalStateException(
95                          "Not a valid url: " + getHrefAttribute(), e);
96              }
97              final WebRequest request = new WebRequest(url, page.getCharset(), page.getUrl());
98              final WebWindow webWindow = enclosingPage.getEnclosingWindow();
99              final String target = enclosingPage.getResolvedTarget(getTargetAttribute());
100             webClient.getPage(webClient.openTargetWindow(webWindow, target, WebClient.TARGET_SELF), request);
101         }
102         return false;
103     }
104 
105     /**
106      * Returns the value of the attribute {@code shape}. Refer to the
107      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
108      * documentation for details on the use of this attribute.
109      *
110      * @return the value of the attribute {@code shape} or an empty string if that attribute isn't defined
111      */
112     public final String getShapeAttribute() {
113         return getAttributeDirect("shape");
114     }
115 
116     /**
117      * Returns the value of the attribute {@code coords}. Refer to the
118      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
119      * documentation for details on the use of this attribute.
120      *
121      * @return the value of the attribute {@code coords} or an empty string if that attribute isn't defined
122      */
123     public final String getCoordsAttribute() {
124         return getAttributeDirect("coords");
125     }
126 
127     /**
128      * Returns the value of the attribute {@code href}. Refer to the
129      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
130      * documentation for details on the use of this attribute.
131      *
132      * @return the value of the attribute {@code href} or an empty string if that attribute isn't defined
133      */
134     public final String getHrefAttribute() {
135         return getAttributeDirect("href");
136     }
137 
138     /**
139      * Returns the value of the attribute {@code nohref}. Refer to the
140      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
141      * documentation for details on the use of this attribute.
142      *
143      * @return the value of the attribute {@code nohref} or an empty string if that attribute isn't defined
144      */
145     public final String getNoHrefAttribute() {
146         return getAttributeDirect("nohref");
147     }
148 
149     /**
150      * Returns the value of the attribute {@code alt}. Refer to the
151      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
152      * documentation for details on the use of this attribute.
153      *
154      * @return the value of the attribute {@code alt} or an empty string if that attribute isn't defined
155      */
156     public final String getAltAttribute() {
157         return getAttributeDirect("alt");
158     }
159 
160     /**
161      * Returns the value of the attribute {@code tabindex}. Refer to the
162      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
163      * documentation for details on the use of this attribute.
164      *
165      * @return the value of the attribute {@code tabindex} or an empty string if that attribute isn't defined
166      */
167     public final String getTabIndexAttribute() {
168         return getAttributeDirect("tabindex");
169     }
170 
171     /**
172      * Returns the value of the attribute {@code accesskey}. Refer to the
173      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
174      * documentation for details on the use of this attribute.
175      *
176      * @return the value of the attribute {@code accesskey} or an empty string if that attribute isn't defined
177      */
178     public final String getAccessKeyAttribute() {
179         return getAttributeDirect("accesskey");
180     }
181 
182     /**
183      * Returns the value of the attribute {@code onfocus}. Refer to the
184      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
185      * documentation for details on the use of this attribute.
186      *
187      * @return the value of the attribute {@code onfocus} or an empty string if that attribute isn't defined
188      */
189     public final String getOnFocusAttribute() {
190         return getAttributeDirect("onfocus");
191     }
192 
193     /**
194      * Returns the value of the attribute {@code onblur}. Refer to the
195      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
196      * documentation for details on the use of this attribute.
197      *
198      * @return the value of the attribute {@code onblur} or an empty string if that attribute isn't defined
199      */
200     public final String getOnBlurAttribute() {
201         return getAttributeDirect("onblur");
202     }
203 
204     /**
205      * Returns the value of the attribute {@code target}. Refer to the
206      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
207      * documentation for details on the use of this attribute.
208      *
209      * @return the value of the attribute {@code target} or an empty string if that attribute isn't defined
210      */
211     public final String getTargetAttribute() {
212         return getAttributeDirect("target");
213     }
214 
215     /**
216      * Indicates if this area contains the specified point.
217      * @param x the x coordinate of the point
218      * @param y the y coordinate of the point
219      * @return {@code true} if the point is contained in this area
220      */
221     boolean containsPoint(final int x, final int y) {
222         final String shape = StringUtils.defaultIfEmpty(getShapeAttribute(), SHAPE_RECT);
223 
224         if ("default".equalsIgnoreCase(shape) && getCoordsAttribute() != null) {
225             return true;
226         }
227 
228         if (SHAPE_RECT.equalsIgnoreCase(shape) && getCoordsAttribute() != null) {
229             final Shape2D rectangle = parseRect();
230             return rectangle.contains(x, y);
231         }
232 
233         if (SHAPE_CIRCLE.equalsIgnoreCase(shape) && getCoordsAttribute() != null) {
234             final Shape2D circle = parseCircle();
235             return circle.contains(x, y);
236         }
237 
238         if (SHAPE_POLY.equalsIgnoreCase(shape) && getCoordsAttribute() != null) {
239             final Shape2D path = parsePoly();
240             return path.contains(x, y);
241         }
242 
243         return false;
244     }
245 
246     /**
247      * {@inheritDoc}
248      */
249     @Override
250     public DisplayStyle getDefaultStyleDisplay() {
251         if (hasFeature(CSS_DISPLAY_BLOCK)) {
252             return DisplayStyle.NONE;
253         }
254         return DisplayStyle.INLINE;
255     }
256 
257     /**
258      * {@inheritDoc}
259      */
260     @Override
261     public boolean isDisplayed() {
262         final DomNode parent = getParentNode();
263         if (parent instanceof HtmlMap && parent.isDisplayed()) {
264             return !isEmpty();
265         }
266         return false;
267     }
268 
269     private Rectangle2D parseRect() {
270         // browsers seem to support comma and blank
271         final String[] coords = org.htmlunit.util.StringUtils.splitAtCommaOrBlank(getCoordsAttribute());
272 
273         double leftX = 0;
274         double topY = 0;
275         double rightX = 0;
276         double bottomY = 0;
277 
278         try {
279             if (coords.length > 0) {
280                 leftX = Double.parseDouble(coords[0].trim());
281             }
282             if (coords.length > 1) {
283                 topY = Double.parseDouble(coords[1].trim());
284             }
285             if (coords.length > 2) {
286                 rightX = Double.parseDouble(coords[2].trim());
287             }
288             if (coords.length > 3) {
289                 bottomY = Double.parseDouble(coords[3].trim());
290             }
291         }
292         catch (final NumberFormatException e) {
293             if (LOG.isWarnEnabled()) {
294                 LOG.warn("Invalid rect coords '" + Arrays.toString(coords) + "'", e);
295             }
296         }
297 
298         return new Rectangle2D(leftX, topY, rightX, bottomY);
299     }
300 
301     private Circle2D parseCircle() {
302         // browsers seem to support comma and blank
303         final String[] coords = org.htmlunit.util.StringUtils.splitAtCommaOrBlank(getCoordsAttribute());
304 
305         double centerX = 0;
306         double centerY = 0;
307         double radius = 0;
308 
309         try {
310             if (coords.length > 0) {
311                 centerX = Double.parseDouble(coords[0].trim());
312             }
313             if (coords.length > 1) {
314                 centerY = Double.parseDouble(coords[1].trim());
315             }
316             if (coords.length > 2) {
317                 radius = Double.parseDouble(coords[2].trim());
318             }
319 
320         }
321         catch (final NumberFormatException e) {
322             if (LOG.isWarnEnabled()) {
323                 LOG.warn("Invalid circle coords '" + Arrays.toString(coords) + "'", e);
324             }
325         }
326 
327         return new Circle2D(centerX, centerY, radius);
328     }
329 
330     private Shape2D parsePoly() {
331         // browsers seem to support comma and blank
332         final String[] coords = org.htmlunit.util.StringUtils.splitAtCommaOrBlank(getCoordsAttribute());
333 
334         try {
335             if (coords.length > 1) {
336                 final Polygon2D path = new Polygon2D(Double.parseDouble(coords[0]), Double.parseDouble(coords[1]));
337 
338                 for (int i = 2; i + 1 < coords.length; i += 2) {
339                     path.lineTo(Double.parseDouble(coords[i]), Double.parseDouble(coords[i + 1]));
340                 }
341                 return path;
342             }
343         }
344         catch (final NumberFormatException e) {
345             if (LOG.isWarnEnabled()) {
346                 LOG.warn("Invalid poly coords '" + Arrays.toString(coords) + "'", e);
347             }
348         }
349 
350         return new Rectangle2D(0, 0, 0, 0);
351     }
352 
353     /**
354      * {@inheritDoc}
355      */
356     @Override
357     public boolean handles(final Event event) {
358         if (Event.TYPE_BLUR.equals(event.getType()) || Event.TYPE_FOCUS.equals(event.getType())) {
359             return true;
360         }
361         return super.handles(event);
362     }
363 
364     private boolean isEmpty() {
365         final String shape = StringUtils.defaultIfEmpty(getShapeAttribute(), SHAPE_RECT);
366 
367         if ("default".equalsIgnoreCase(shape) && getCoordsAttribute() != null) {
368             return false;
369         }
370 
371         if (SHAPE_RECT.equalsIgnoreCase(shape) && getCoordsAttribute() != null) {
372             final Shape2D rectangle = parseRect();
373             return rectangle.isEmpty();
374         }
375 
376         if (SHAPE_CIRCLE.equalsIgnoreCase(shape) && getCoordsAttribute() != null) {
377             final Shape2D circle = parseCircle();
378             return circle.isEmpty();
379         }
380 
381         if (SHAPE_POLY.equalsIgnoreCase(shape) && getCoordsAttribute() != null) {
382             final Shape2D path = parsePoly();
383             return path.isEmpty();
384         }
385 
386         return false;
387     }
388 }