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.htmlunit.BrowserVersion;
27 import org.htmlunit.ElementNotFoundException;
28 import org.htmlunit.Page;
29 import org.htmlunit.SgmlPage;
30 import org.htmlunit.WebClient;
31 import org.htmlunit.WebRequest;
32 import org.htmlunit.WebResponse;
33 import org.htmlunit.javascript.host.event.Event;
34 import org.htmlunit.util.NameValuePair;
35 import org.htmlunit.util.StringUtils;
36
37 /**
38 * Wrapper for the HTML element "input".
39 * HtmlUnit does not download the associated image for performance reasons.
40 *
41 * @author Mike Bowler
42 * @author David K. Taylor
43 * @author Christian Sell
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.isEmptyOrNull(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 (!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 }