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.HTMLIMAGE_NAME_VALUE_PARAMS;
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.apache.commons.lang3.StringUtils;
29  import org.htmlunit.BrowserVersion;
30  import org.htmlunit.ElementNotFoundException;
31  import org.htmlunit.Page;
32  import org.htmlunit.SgmlPage;
33  import org.htmlunit.WebClient;
34  import org.htmlunit.WebRequest;
35  import org.htmlunit.WebResponse;
36  import org.htmlunit.javascript.host.event.Event;
37  import org.htmlunit.util.NameValuePair;
38  
39  /**
40   * Wrapper for the HTML element "input".
41   * HtmlUnit does not download the associated image for performance reasons.
42   *
43   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
44   * @author David K. Taylor
45   * @author <a href="mailto:cse@dynabean.de">Christian Sell</a>
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.isEmpty(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              if (!prefix.isEmpty() && hasFeature(HTMLIMAGE_NAME_VALUE_PARAMS) && !getRawValue().isEmpty()) {
91                  return new NameValuePair[] {valueX, valueY,
92                      new NameValuePair(getNameAttribute(), getRawValue()) };
93              }
94              return new NameValuePair[] {valueX, valueY};
95          }
96          return new NameValuePair[]{new NameValuePair(getNameAttribute(), getRawValue())};
97      }
98  
99      /**
100      * Submit the form that contains this input. Only a couple of the inputs
101      * support this method so it is made protected here. Those subclasses
102      * that wish to expose it will override and make it public.
103      *
104      * @return the Page that is the result of submitting this page to the server
105      * @exception IOException If an IO error occurs
106      */
107     @Override
108     @SuppressWarnings("unchecked")
109     public Page click() throws IOException {
110         return click(0, 0);
111     }
112 
113     /**
114      * {@inheritDoc}
115      * @throws IOException if an IO error occurred
116      */
117     @Override
118     protected boolean doClickStateUpdate(final boolean shiftKey, final boolean ctrlKey) throws IOException {
119         final HtmlForm form = getEnclosingForm();
120         if (form != null) {
121             form.submit(this);
122             return false;
123         }
124         super.doClickStateUpdate(shiftKey, ctrlKey);
125         return false;
126     }
127 
128     /**
129      * Simulate clicking this input with a pointing device. The x and y coordinates
130      * of the pointing device will be sent to the server.
131      *
132      * @param <P> the page type
133      * @param x the x coordinate of the pointing device at the time of clicking
134      * @param y the y coordinate of the pointing device at the time of clicking
135      * @return the page that is loaded after the click has taken place
136      * @exception IOException If an IO error occurs
137      * @exception ElementNotFoundException If a particular XML element could not be found in the DOM model
138      */
139     public <P extends Page> P click(final int x, final int y) throws IOException, ElementNotFoundException {
140         wasPositionSpecified_ = true;
141         xPosition_ = x;
142         yPosition_ = y;
143         return super.click();
144     }
145 
146     /**
147      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
148      *
149      * Simulates clicking on this element, returning the page in the window that has the focus
150      * after the element has been clicked. Note that the returned page may or may not be the same
151      * as the original page, depending on the type of element being clicked, the presence of JavaScript
152      * action listeners, etc.
153      *
154      * @param event the click event used
155      * @param <P> the page type
156      * @return the page contained in the current window as returned by
157      *         {@link org.htmlunit.WebClient#getCurrentWindow()}
158      * @exception IOException if an IO error occurs
159      */
160     @Override
161     public <P extends Page> P click(final Event event,
162             final boolean shiftKey, final boolean ctrlKey, final boolean altKey,
163             final boolean ignoreVisibility) throws IOException {
164         wasPositionSpecified_ = true;
165         return super.click(event, shiftKey, ctrlKey, altKey, ignoreVisibility);
166     }
167 
168     /**
169      * {@inheritDoc}
170      */
171     @Override
172     public void setValue(final String newValue) {
173         unmarkValueDirty();
174         setDefaultValue(newValue);
175     }
176 
177     /**
178      * {@inheritDoc}
179      */
180     @Override
181     public void setDefaultChecked(final boolean defaultChecked) {
182         // Empty.
183     }
184 
185     /**
186      * {@inheritDoc} Also sets the value to the new default value.
187      * @see SubmittableElement#setDefaultValue(String)
188      */
189     @Override
190     public void setDefaultValue(final String defaultValue) {
191         super.setDefaultValue(defaultValue);
192         setRawValue(defaultValue);
193     }
194 
195     /**
196      * {@inheritDoc}
197      */
198     @Override
199     protected boolean isRequiredSupported() {
200         return false;
201     }
202 
203     /**
204      * {@inheritDoc}
205      */
206     @Override
207     public void setSrcAttribute(final String src) {
208         super.setSrcAttribute(src);
209         downloaded_ = false;
210         imageWebResponse_ = null;
211     }
212 
213     /**
214      * <p>Downloads the image contained by this image element.</p>
215      * <p><span style="color:red">POTENTIAL PERFORMANCE KILLER - DOWNLOADS THE IMAGE - USE AT YOUR OWN RISK</span></p>
216      * <p>If the image has not already been downloaded, this method triggers a download and caches the image.</p>
217      *
218      * @throws IOException if an error occurs while downloading the image
219      */
220     private void downloadImageIfNeeded() throws IOException {
221         if (!downloaded_) {
222             final String src = getSrc();
223             if (!org.htmlunit.util.StringUtils.isEmptyString(src)) {
224                 final HtmlPage page = (HtmlPage) getPage();
225                 final WebClient webClient = page.getWebClient();
226 
227                 final BrowserVersion browser = webClient.getBrowserVersion();
228                 final WebRequest request = new WebRequest(new URL(src), browser.getImgAcceptHeader(),
229                                                                 browser.getAcceptEncodingHeader());
230                 request.setCharset(page.getCharset());
231                 request.setRefererHeader(page.getUrl());
232                 imageWebResponse_ = webClient.loadWebResponse(request);
233             }
234 
235             downloaded_ = true;
236         }
237     }
238 
239     /**
240      * Saves this image as the specified file.
241      * @param file the file to save to
242      * @throws IOException if an IO error occurs
243      */
244     public void saveAs(final File file) throws IOException {
245         downloadImageIfNeeded();
246         if (null != imageWebResponse_) {
247             try (OutputStream fos = Files.newOutputStream(file.toPath());
248                     InputStream inputStream = imageWebResponse_.getContentAsStream()) {
249                 IOUtils.copy(inputStream, fos);
250             }
251         }
252     }
253 }