View Javadoc
1   /*
2    * Copyright (c) 2002-2026 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.HTMLINPUT_TYPE_IMAGE_IGNORES_CUSTOM_VALIDITY;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.net.URL;
24  import java.nio.file.Files;
25  import java.util.Map;
26  
27  import org.apache.commons.io.IOUtils;
28  import org.htmlunit.BrowserVersion;
29  import org.htmlunit.ElementNotFoundException;
30  import org.htmlunit.Page;
31  import org.htmlunit.SgmlPage;
32  import org.htmlunit.WebClient;
33  import org.htmlunit.WebRequest;
34  import org.htmlunit.WebResponse;
35  import org.htmlunit.javascript.host.event.Event;
36  import org.htmlunit.util.NameValuePair;
37  import org.htmlunit.util.StringUtils;
38  
39  /**
40   * Wrapper for the HTML element "input".
41   * HtmlUnit does not download the associated image for performance reasons.
42   *
43   * @author Mike Bowler
44   * @author David K. Taylor
45   * @author Christian Sell
46   * @author Marc Guillemot
47   * @author Daniel Gredler
48   * @author Ahmed Ashour
49   * @author Ronald Brill
50   * @author Frank Danek
51   */
52  public class HtmlImageInput extends HtmlInput implements LabelableElement {
53  
54      // For click with x, y position.
55      private boolean wasPositionSpecified_;
56      private int xPosition_;
57      private int yPosition_;
58      private WebResponse imageWebResponse_;
59      private boolean downloaded_;
60  
61      /**
62       * Creates an 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      HtmlImageInput(final String qualifiedName, final SgmlPage page, final Map<String, DomAttr> attributes) {
69          super(qualifiedName, page, attributes);
70      }
71  
72      /**
73       * {@inheritDoc}
74       */
75      @Override
76      public NameValuePair[] getSubmitNameValuePairs() {
77          final String name = getNameAttribute();
78          final String prefix;
79          // a clicked image without name sends parameter x and y
80          if (StringUtils.isEmptyOrNull(name)) {
81              prefix = "";
82          }
83          else {
84              prefix = name + ".";
85          }
86  
87          if (wasPositionSpecified_) {
88              final NameValuePair valueX = new NameValuePair(prefix + 'x', Integer.toString(xPosition_));
89              final NameValuePair valueY = new NameValuePair(prefix + 'y', Integer.toString(yPosition_));
90              return new NameValuePair[] {valueX, valueY};
91          }
92          return new NameValuePair[]{new NameValuePair(getNameAttribute(), getRawValue())};
93      }
94  
95      /**
96       * Submit the form that contains this input. Only a couple of the inputs
97       * support this method so it is made protected here. Those subclasses
98       * that wish to expose it will override and make it public.
99       *
100      * @return the Page that is the result of submitting this page to the server
101      * @throws IOException If an IO error occurs
102      */
103     @Override
104     @SuppressWarnings("unchecked")
105     public Page click() throws IOException {
106         return click(0, 0);
107     }
108 
109     /**
110      * {@inheritDoc}
111      * @throws IOException if an IO error occurred
112      */
113     @Override
114     protected boolean doClickStateUpdate(final boolean shiftKey, final boolean ctrlKey) throws IOException {
115         final HtmlForm form = getEnclosingForm();
116         if (form != null) {
117             form.submit(this);
118             return false;
119         }
120         super.doClickStateUpdate(shiftKey, ctrlKey);
121         return false;
122     }
123 
124     /**
125      * Simulate clicking this input with a pointing device. The x and y coordinates
126      * of the pointing device will be sent to the server.
127      *
128      * @param <P> the page type
129      * @param x the x coordinate of the pointing device at the time of clicking
130      * @param y the y coordinate of the pointing device at the time of clicking
131      * @return the page that is loaded after the click has taken place
132      * @throws IOException If an IO error occurs
133      * @throws ElementNotFoundException If a particular XML element could not be found in the DOM model
134      */
135     public <P extends Page> P click(final int x, final int y) throws IOException, ElementNotFoundException {
136         wasPositionSpecified_ = true;
137         xPosition_ = x;
138         yPosition_ = y;
139         return super.click();
140     }
141 
142     /**
143      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
144      *
145      * Simulates clicking on this element, returning the page in the window that has the focus
146      * after the element has been clicked. Note that the returned page may or may not be the same
147      * as the original page, depending on the type of element being clicked, the presence of JavaScript
148      * action listeners, etc.
149      *
150      * @param event the click event used
151      * @param <P> the page type
152      * @return the page contained in the current window as returned by
153      *         {@link org.htmlunit.WebClient#getCurrentWindow()}
154      * @throws IOException if an IO error occurs
155      */
156     @Override
157     public <P extends Page> P click(final Event event,
158             final boolean shiftKey, final boolean ctrlKey, final boolean altKey,
159             final boolean ignoreVisibility) throws IOException {
160         wasPositionSpecified_ = true;
161         return super.click(event, shiftKey, ctrlKey, altKey, ignoreVisibility);
162     }
163 
164     /**
165      * {@inheritDoc}
166      */
167     @Override
168     public void setValue(final String newValue) {
169         unmarkValueDirty();
170         setDefaultValue(newValue);
171     }
172 
173     /**
174      * {@inheritDoc}
175      */
176     @Override
177     public void setDefaultChecked(final boolean defaultChecked) {
178         // Empty.
179     }
180 
181     /**
182      * {@inheritDoc} Also sets the value to the new default value.
183      * @see SubmittableElement#setDefaultValue(String)
184      */
185     @Override
186     public void setDefaultValue(final String defaultValue) {
187         super.setDefaultValue(defaultValue);
188         setRawValue(defaultValue);
189     }
190 
191     /**
192      * {@inheritDoc}
193      */
194     @Override
195     public boolean willValidate() {
196         if (hasFeature(HTMLINPUT_TYPE_IMAGE_IGNORES_CUSTOM_VALIDITY)) {
197             return false;
198         }
199         return super.willValidate();
200     }
201 
202     /**
203      * {@inheritDoc}
204      */
205     @Override
206     protected boolean isCustomValidityValid() {
207         if (hasFeature(HTMLINPUT_TYPE_IMAGE_IGNORES_CUSTOM_VALIDITY)) {
208             return true;
209         }
210 
211         return super.isCustomValidityValid();
212     }
213 
214     /**
215      * {@inheritDoc}
216      */
217     @Override
218     protected boolean isRequiredSupported() {
219         return false;
220     }
221 
222     /**
223      * {@inheritDoc}
224      */
225     @Override
226     public void setSrcAttribute(final String src) {
227         super.setSrcAttribute(src);
228         downloaded_ = false;
229         imageWebResponse_ = null;
230     }
231 
232     /**
233      * <p>Downloads the image contained by this image element.</p>
234      * <p><span style="color:red">POTENTIAL PERFORMANCE KILLER - DOWNLOADS THE IMAGE - USE AT YOUR OWN RISK</span></p>
235      * <p>If the image has not already been downloaded, this method triggers a download and caches the image.</p>
236      *
237      * @throws IOException if an error occurs while downloading the image
238      */
239     private void downloadImageIfNeeded() throws IOException {
240         if (!downloaded_) {
241             final String src = getSrc();
242             if (!StringUtils.isEmptyString(src)) {
243                 final HtmlPage page = (HtmlPage) getPage();
244                 final WebClient webClient = page.getWebClient();
245 
246                 final BrowserVersion browser = webClient.getBrowserVersion();
247                 final WebRequest request = new WebRequest(new URL(src), browser.getImgAcceptHeader(),
248                                                                 browser.getAcceptEncodingHeader());
249                 request.setCharset(page.getCharset());
250                 request.setRefererHeader(page.getUrl());
251                 imageWebResponse_ = webClient.loadWebResponse(request);
252             }
253 
254             downloaded_ = true;
255         }
256     }
257 
258     /**
259      * Saves this image as the specified file.
260      * @param file the file to save to
261      * @throws IOException if an IO error occurs
262      */
263     public void saveAs(final File file) throws IOException {
264         downloadImageIfNeeded();
265         if (null != imageWebResponse_) {
266             try (OutputStream fos = Files.newOutputStream(file.toPath());
267                     InputStream inputStream = imageWebResponse_.getContentAsStream()) {
268                 IOUtils.copy(inputStream, fos);
269             }
270         }
271     }
272 }