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