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