View Javadoc
1   /*
2    * Copyright (c) 2002-2017 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    * http://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 com.gargoylesoftware.htmlunit.javascript.host.html;
16  
17  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_ALIGN_ACCEPTS_ARBITRARY_VALUES;
18  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_IMAGE_WIDTH_HEIGHT_RETURNS_24x24_0x0;
19  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_IMAGE_WIDTH_HEIGHT_RETURNS_16x16_0x0;
20  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_IMAGE_WIDTH_HEIGHT_RETURNS_28x30_28x30;
21  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
22  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
23  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF;
24  
25  import java.io.IOException;
26  import java.net.MalformedURLException;
27  import java.util.HashMap;
28  import java.util.Locale;
29  import java.util.Map;
30  
31  import org.apache.commons.lang3.StringUtils;
32  import org.xml.sax.helpers.AttributesImpl;
33  
34  import com.gargoylesoftware.htmlunit.BrowserVersion;
35  import com.gargoylesoftware.htmlunit.SgmlPage;
36  import com.gargoylesoftware.htmlunit.html.DomElement;
37  import com.gargoylesoftware.htmlunit.html.DomNode;
38  import com.gargoylesoftware.htmlunit.html.HTMLParser;
39  import com.gargoylesoftware.htmlunit.html.HtmlElement;
40  import com.gargoylesoftware.htmlunit.html.HtmlImage;
41  import com.gargoylesoftware.htmlunit.html.HtmlPage;
42  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
43  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor;
44  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxGetter;
45  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxSetter;
46  
47  import net.sourceforge.htmlunit.corejs.javascript.Context;
48  
49  /**
50   * The JavaScript object {@code HTMLImageElement}.
51   *
52   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
53   * @author <a href="mailto:george@murnock.com">George Murnock</a>
54   * @author Chris Erskine
55   * @author Marc Guillemot
56   * @author Ahmed Ashour
57   * @author Ronald Brill
58   */
59  @JsxClass(domClass = HtmlImage.class)
60  public class HTMLImageElement extends HTMLElement {
61      private static final Map<String, String> NORMALIZED_ALIGN_VALUES;
62      static {
63          NORMALIZED_ALIGN_VALUES = new HashMap<>();
64          NORMALIZED_ALIGN_VALUES.put("center", "center");
65          NORMALIZED_ALIGN_VALUES.put("left", "left");
66          NORMALIZED_ALIGN_VALUES.put("right", "right");
67          NORMALIZED_ALIGN_VALUES.put("bottom", "bottom");
68          NORMALIZED_ALIGN_VALUES.put("middle", "middle");
69          NORMALIZED_ALIGN_VALUES.put("top", "top");
70          NORMALIZED_ALIGN_VALUES.put("absbottom", "absBottom");
71          NORMALIZED_ALIGN_VALUES.put("absmiddle", "absMiddle");
72          NORMALIZED_ALIGN_VALUES.put("baseline", "baseline");
73          NORMALIZED_ALIGN_VALUES.put("texttop", "textTop");
74      }
75  
76      private boolean endTagForbidden_ = true;
77  
78      /**
79       * JavaScript constructor.
80       */
81      @JsxConstructor({CHROME, FF, EDGE})
82      public void jsConstructor() {
83          final SgmlPage page = (SgmlPage) getWindow().getWebWindow().getEnclosedPage();
84          final DomElement fake =
85                  HTMLParser.getFactory(HtmlImage.TAG_NAME).createElement(page, HtmlImage.TAG_NAME, new AttributesImpl());
86          setDomNode(fake);
87      }
88  
89      /**
90       * {@inheritDoc}
91       */
92      @Override
93      public void setDomNode(final DomNode domNode) {
94          super.setDomNode(domNode);
95          if ("image".equalsIgnoreCase(domNode.getLocalName())) {
96              endTagForbidden_ = false;
97          }
98      }
99  
100     /**
101      * Sets the {@code src} attribute.
102      * @param src the {@code src} attribute value
103      */
104     @JsxSetter
105     public void setSrc(final String src) {
106         final HtmlElement img = getDomNodeOrDie();
107         img.setAttribute("src", src);
108     }
109 
110     /**
111      * Returns the value of the {@code src} attribute.
112      * @return the value of the {@code src} attribute
113      */
114     @JsxGetter
115     public String getSrc() {
116         final HtmlImage img = (HtmlImage) getDomNodeOrDie();
117         final String src = img.getSrcAttribute();
118         if ("".equals(src)) {
119             return src;
120         }
121         try {
122             final HtmlPage page = (HtmlPage) img.getPage();
123             return page.getFullyQualifiedUrl(src).toExternalForm();
124         }
125         catch (final MalformedURLException e) {
126             final String msg = "Unable to create fully qualified URL for src attribute of image " + e.getMessage();
127             throw Context.reportRuntimeError(msg);
128         }
129     }
130 
131     /**
132      * Sets the {@code onload} event handler for this element.
133      * @param onload the {@code onload} event handler for this element
134      */
135     @Override
136     public void setOnload(final Object onload) {
137         super.setOnload(onload);
138 
139         // maybe the onload handler was not called so far
140         final HtmlImage img = (HtmlImage) getDomNodeOrDie();
141         img.doOnLoad();
142     }
143 
144     /**
145      * Returns the value of the {@code alt} property.
146      * @return the value of the {@code alt} property
147      */
148     @JsxGetter
149     public String getAlt() {
150         final String alt = getDomNodeOrDie().getAttribute("alt");
151         return alt;
152     }
153 
154     /**
155      * Sets the value of the {@code alt} property.
156      * @param alt the value
157      */
158     @JsxSetter
159     public void setAlt(final String alt) {
160         getDomNodeOrDie().setAttribute("alt", alt);
161     }
162 
163     /**
164      * Gets the {@code border} attribute.
165      * @return the {@code border} attribute
166      */
167     @JsxGetter
168     public String getBorder() {
169         final String border = getDomNodeOrDie().getAttribute("border");
170         return border;
171     }
172 
173     /**
174      * Sets the {@code border} attribute.
175      * @param border the {@code border} attribute
176      */
177     @JsxSetter
178     public void setBorder(final String border) {
179         getDomNodeOrDie().setAttribute("border", border);
180     }
181 
182     /**
183      * Returns the value of the {@code align} property.
184      * @return the value of the {@code align} property
185      */
186     @JsxGetter
187     public String getAlign() {
188         final boolean acceptArbitraryValues = getBrowserVersion().hasFeature(JS_ALIGN_ACCEPTS_ARBITRARY_VALUES);
189 
190         final String align = getDomNodeOrDie().getAttribute("align");
191         if (acceptArbitraryValues) {
192             return align;
193         }
194 
195         final String normalizedValue = NORMALIZED_ALIGN_VALUES.get(align.toLowerCase(Locale.ROOT));
196         if (null != normalizedValue) {
197             return normalizedValue;
198         }
199         return "";
200     }
201 
202     /**
203      * Sets the value of the {@code align} property.
204      * @param align the value of the {@code align} property
205      */
206     @JsxSetter
207     public void setAlign(final String align) {
208         final boolean acceptArbitraryValues = getBrowserVersion().hasFeature(JS_ALIGN_ACCEPTS_ARBITRARY_VALUES);
209         if (acceptArbitraryValues) {
210             getDomNodeOrDie().setAttribute("align", align);
211             return;
212         }
213 
214         final String normalizedValue = NORMALIZED_ALIGN_VALUES.get(align.toLowerCase(Locale.ROOT));
215         if (null != normalizedValue) {
216             getDomNodeOrDie().setAttribute("align", normalizedValue);
217             return;
218         }
219 
220         throw Context.reportRuntimeError("Cannot set the align property to invalid value: '" + align + "'");
221     }
222 
223     /**
224      * Returns the value of the {@code width} property.
225      * @return the value of the {@code width} property
226      */
227     @Override
228     @JsxGetter
229     public int getWidth() {
230         final HtmlImage img = (HtmlImage) getDomNodeOrDie();
231         final String widthAttrib = img.getWidthAttribute();
232 
233         if (DomElement.ATTRIBUTE_NOT_DEFINED != widthAttrib) {
234             try {
235                 return Integer.parseInt(widthAttrib);
236             }
237             catch (final NumberFormatException e) {
238                 // ignore
239             }
240         }
241 
242         final String src = img.getSrcAttribute();
243         if (DomElement.ATTRIBUTE_NOT_DEFINED == src) {
244             final BrowserVersion browserVersion = getBrowserVersion();
245             if (browserVersion.hasFeature(JS_IMAGE_WIDTH_HEIGHT_RETURNS_28x30_28x30)) {
246                 return 28;
247             }
248             if (browserVersion.hasFeature(JS_IMAGE_WIDTH_HEIGHT_RETURNS_16x16_0x0)
249                     || browserVersion.hasFeature(JS_IMAGE_WIDTH_HEIGHT_RETURNS_24x24_0x0)) {
250                 return 0;
251             }
252             return 24;
253         }
254 
255         final BrowserVersion browserVersion = getBrowserVersion();
256         if (browserVersion.hasFeature(JS_IMAGE_WIDTH_HEIGHT_RETURNS_16x16_0x0) && StringUtils.isBlank(src)) {
257             return 0;
258         }
259 
260         try {
261             return img.getWidth();
262         }
263         catch (final IOException e) {
264             if (browserVersion.hasFeature(JS_IMAGE_WIDTH_HEIGHT_RETURNS_28x30_28x30)) {
265                 return 28;
266             }
267             if (browserVersion.hasFeature(JS_IMAGE_WIDTH_HEIGHT_RETURNS_16x16_0x0)) {
268                 return 16;
269             }
270             return 24;
271         }
272     }
273 
274     /**
275      * Sets the value of the {@code width} property.
276      * @param width the value of the {@code width} property
277      */
278     @JsxSetter
279     public void setWidth(final String width) {
280         getDomNodeOrDie().setAttribute("width", width);
281     }
282 
283     /**
284      * Returns the value of the {@code height} property.
285      * @return the value of the {@code height} property
286      */
287     @Override
288     @JsxGetter
289     public int getHeight() {
290         final HtmlImage img = (HtmlImage) getDomNodeOrDie();
291         final String height = img.getHeightAttribute();
292 
293         if (DomElement.ATTRIBUTE_NOT_DEFINED != height) {
294             try {
295                 return Integer.parseInt(height);
296             }
297             catch (final NumberFormatException e) {
298                 // ignore
299             }
300         }
301 
302         final String src = img.getSrcAttribute();
303         if (DomElement.ATTRIBUTE_NOT_DEFINED == src) {
304             final BrowserVersion browserVersion = getBrowserVersion();
305             if (browserVersion.hasFeature(JS_IMAGE_WIDTH_HEIGHT_RETURNS_28x30_28x30)) {
306                 return 30;
307             }
308             if (browserVersion.hasFeature(JS_IMAGE_WIDTH_HEIGHT_RETURNS_16x16_0x0)
309                     || browserVersion.hasFeature(JS_IMAGE_WIDTH_HEIGHT_RETURNS_24x24_0x0)) {
310                 return 0;
311             }
312             return 24;
313         }
314 
315         final BrowserVersion browserVersion = getBrowserVersion();
316         if (browserVersion.hasFeature(JS_IMAGE_WIDTH_HEIGHT_RETURNS_16x16_0x0) && StringUtils.isBlank(src)) {
317             return 0;
318         }
319 
320         try {
321             return img.getHeight();
322         }
323         catch (final IOException e) {
324             if (browserVersion.hasFeature(JS_IMAGE_WIDTH_HEIGHT_RETURNS_28x30_28x30)) {
325                 return 30;
326             }
327             if (browserVersion.hasFeature(JS_IMAGE_WIDTH_HEIGHT_RETURNS_16x16_0x0)) {
328                 return 16;
329             }
330             return 24;
331         }
332     }
333 
334     /**
335      * Sets the value of the {@code height} property.
336      * @param height the value of the {@code height} property
337      */
338     @JsxSetter
339     public void setHeight(final String height) {
340         getDomNodeOrDie().setAttribute("height", height);
341     }
342 
343     /**
344      * {@inheritDoc}
345      */
346     @Override
347     protected boolean isEndTagForbidden() {
348         return endTagForbidden_;
349     }
350 
351     /**
352      * Support for the image.complete property.
353      * @return the value of the {@code complete} property
354      */
355     @JsxGetter
356     public boolean isComplete() {
357         return ((HtmlImage) getDomNodeOrDie()).isComplete();
358     }
359 
360     /**
361      * Returns the value of the {@code naturalWidth} property.
362      * @return the value of the {@code naturalWidth} property
363      */
364     @JsxGetter
365     public int getNaturalWidth() {
366         final HtmlImage img = (HtmlImage) getDomNodeOrDie();
367         try {
368             return img.getWidth();
369         }
370         catch (final IOException e) {
371             return 0;
372         }
373     }
374 
375     /**
376      * Returns the value of the {@code naturalHeight} property.
377      * @return the value of the {@code naturalHeight} property
378      */
379     @JsxGetter
380     public int getNaturalHeight() {
381         final HtmlImage img = (HtmlImage) getDomNodeOrDie();
382         try {
383             return img.getHeight();
384         }
385         catch (final IOException e) {
386             return 0;
387         }
388     }
389 
390     /**
391      * Returns the {@code name} attribute.
392      * @return the {@code name} attribute
393      */
394     @JsxGetter
395     public String getName() {
396         return getDomNodeOrDie().getAttribute("name");
397     }
398 
399     /**
400      * Sets the {@code name} attribute.
401      * @param name the {@code name} attribute
402      */
403     @JsxSetter
404     public void setName(final String name) {
405         getDomNodeOrDie().setAttribute("name", name);
406     }
407 
408 }