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 }