1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.javascript;
16
17 import static org.htmlunit.BrowserVersionFeatures.HTMLIMAGE_HTMLELEMENT;
18 import static org.htmlunit.BrowserVersionFeatures.HTMLIMAGE_HTMLUNKNOWNELEMENT;
19
20 import java.io.IOException;
21 import java.util.function.Supplier;
22
23 import org.apache.commons.lang3.function.FailableSupplier;
24 import org.apache.commons.logging.Log;
25 import org.apache.commons.logging.LogFactory;
26 import org.htmlunit.BrowserVersion;
27 import org.htmlunit.WebAssert;
28 import org.htmlunit.WebWindow;
29 import org.htmlunit.corejs.javascript.Context;
30 import org.htmlunit.corejs.javascript.LambdaConstructor;
31 import org.htmlunit.corejs.javascript.LambdaFunction;
32 import org.htmlunit.corejs.javascript.NativePromise;
33 import org.htmlunit.corejs.javascript.Scriptable;
34 import org.htmlunit.corejs.javascript.ScriptableObject;
35 import org.htmlunit.corejs.javascript.TopLevel;
36 import org.htmlunit.corejs.javascript.VarScope;
37 import org.htmlunit.html.DomNode;
38 import org.htmlunit.html.HtmlImage;
39 import org.htmlunit.javascript.host.Window;
40 import org.htmlunit.javascript.host.WindowOrWorkerGlobalScope;
41 import org.htmlunit.javascript.host.html.HTMLElement;
42 import org.htmlunit.javascript.host.html.HTMLUnknownElement;
43
44
45
46
47
48
49
50
51
52
53
54
55
56 public class HtmlUnitScriptable extends ScriptableObject implements Cloneable {
57
58 private static final Log LOG = LogFactory.getLog(HtmlUnitScriptable.class);
59
60 private DomNode domNode_;
61 private String className_;
62
63
64
65
66
67 @Override
68 public String getClassName() {
69 if (className_ != null) {
70 return className_;
71 }
72 if (getPrototype() != null) {
73 return getPrototype().getClassName();
74 }
75 String className = getClass().getSimpleName();
76 if (className.isEmpty()) {
77
78 className = getClass().getSuperclass().getSimpleName();
79 }
80 return className;
81 }
82
83
84
85
86
87
88
89 public void setClassName(final String className) {
90 className_ = className;
91 }
92
93
94
95
96 @Override
97 public void put(final String name, final Scriptable start, final Object value) {
98 try {
99 super.put(name, start, value);
100 }
101 catch (final IllegalArgumentException e) {
102
103 throw JavaScriptEngine.typeError("'set "
104 + name + "' called on an object that does not implement interface " + getClassName());
105 }
106 }
107
108
109
110
111
112
113
114
115
116 @Override
117 public Object get(final String name, final Scriptable start) {
118
119 final Object response = super.get(name, start);
120 if (response == NOT_FOUND && this == start) {
121 return getWithPreemption(name);
122 }
123 return response;
124 }
125
126
127
128
129
130
131
132
133
134
135
136 protected Object getWithPreemption(final String name) {
137 return NOT_FOUND;
138 }
139
140 @Override
141 public boolean has(final int index, final Scriptable start) {
142 final Object found = get(index, start);
143 if (Scriptable.NOT_FOUND != found && !JavaScriptEngine.isUndefined(found)) {
144 return true;
145 }
146 return super.has(index, start);
147 }
148
149
150
151
152
153
154 public DomNode getDomNodeOrDie() {
155 if (domNode_ == null) {
156 throw new IllegalStateException("DomNode has not been set for this HtmlUnitScriptable: "
157 + getClass().getName());
158 }
159 return domNode_;
160 }
161
162
163
164
165
166
167 public DomNode getDomNodeOrNull() {
168 return domNode_;
169 }
170
171
172
173
174
175 public void setDomNode(final DomNode domNode) {
176 setDomNode(domNode, true);
177 }
178
179
180
181
182
183
184
185
186 public void setDomNode(final DomNode domNode, final boolean assignScriptObject) {
187 WebAssert.notNull("domNode", domNode);
188 domNode_ = domNode;
189 if (assignScriptObject) {
190 domNode_.setScriptableObject(this);
191 }
192 }
193
194
195
196
197
198
199
200
201
202 protected HtmlUnitScriptable getScriptableFor(final Object object) {
203 if (object instanceof WebWindow window) {
204 return window.getScriptableObject();
205 }
206
207 final DomNode domNode = (DomNode) object;
208
209 final HtmlUnitScriptable scriptObject = domNode.getScriptableObject();
210 if (scriptObject != null) {
211 return scriptObject;
212 }
213 return makeScriptableFor(domNode);
214 }
215
216
217
218
219
220
221 public HtmlUnitScriptable makeScriptableFor(final DomNode domNode) {
222
223
224 Class<? extends HtmlUnitScriptable> javaScriptClass = null;
225 if (domNode instanceof HtmlImage image && "image".equals(image.getOriginalQualifiedName())
226 && image.wasCreatedByJavascript()) {
227 if (domNode.hasFeature(HTMLIMAGE_HTMLELEMENT)) {
228 javaScriptClass = HTMLElement.class;
229 }
230 else if (domNode.hasFeature(HTMLIMAGE_HTMLUNKNOWNELEMENT)) {
231 javaScriptClass = HTMLUnknownElement.class;
232 }
233 }
234 if (javaScriptClass == null) {
235 final JavaScriptEngine javaScriptEngine =
236 (JavaScriptEngine) getWindow().getWebWindow().getWebClient().getJavaScriptEngine();
237 for (Class<?> c = domNode.getClass(); javaScriptClass == null && c != null; c = c.getSuperclass()) {
238 javaScriptClass = javaScriptEngine.getJavaScriptClass(c);
239 }
240 }
241
242 final HtmlUnitScriptable scriptable;
243 if (javaScriptClass == null) {
244
245 scriptable = new HTMLElement();
246 if (LOG.isDebugEnabled()) {
247 LOG.debug("No JavaScript class found for element <" + domNode.getNodeName() + ">. Using HTMLElement");
248 }
249 }
250 else {
251 try {
252 scriptable = javaScriptClass.getDeclaredConstructor().newInstance();
253 }
254 catch (final Exception e) {
255 throw JavaScriptEngine.throwAsScriptRuntimeEx(e);
256 }
257 }
258
259 scriptable.setParentScope(getParentScope());
260 scriptable.setPrototype(getPrototype(javaScriptClass));
261 scriptable.setDomNode(domNode);
262
263 return scriptable;
264 }
265
266
267
268
269
270
271 @SuppressWarnings("unchecked")
272 public Scriptable getPrototype(final Class<? extends HtmlUnitScriptable> javaScriptClass) {
273 final Scriptable prototype = getWindow().getPrototype(javaScriptClass);
274 if (prototype == null && javaScriptClass != HtmlUnitScriptable.class) {
275 return getPrototype((Class<? extends HtmlUnitScriptable>) javaScriptClass.getSuperclass());
276 }
277 return prototype;
278 }
279
280
281
282
283
284
285
286 @Override
287 public Object getDefaultValue(final Class<?> hint) {
288 if (String.class.equals(hint) || hint == null) {
289 return "[object " + getClassName() + "]";
290 }
291 return super.getDefaultValue(hint);
292 }
293
294
295
296
297
298
299 public Window getWindow() throws RuntimeException {
300 return getWindow(this);
301 }
302
303
304
305
306
307
308
309 protected static Window getWindow(final Scriptable s) throws RuntimeException {
310 if (s instanceof Window window) {
311 return window;
312 }
313
314 final TopLevel topLevel = ScriptableObject.getTopLevelScope(s.getParentScope());
315 if (topLevel.getGlobalThis() instanceof Window window) {
316 return window;
317 }
318 throw new RuntimeException("Unable to find window associated with " + s);
319 }
320
321 protected static WindowOrWorkerGlobalScope getWindowOrWorkerGlobalScope(
322 final Scriptable s) throws RuntimeException {
323 if (s instanceof WindowOrWorkerGlobalScope wow) {
324 return wow;
325 }
326
327 final TopLevel topLevel = ScriptableObject.getTopLevelScope(s.getParentScope());
328 if (topLevel.getGlobalThis() instanceof WindowOrWorkerGlobalScope wow) {
329 return wow;
330 }
331 throw new RuntimeException("Unable to find WindowOrWorkerGlobalScope associated with " + s);
332 }
333
334
335
336
337
338 public BrowserVersion getBrowserVersion() {
339 final DomNode node = getDomNodeOrNull();
340 if (node != null) {
341 return node.getPage().getWebClient().getBrowserVersion();
342 }
343
344 final Window window = getWindow();
345 if (window != null) {
346 final WebWindow webWindow = window.getWebWindow();
347 if (webWindow != null) {
348 return webWindow.getWebClient().getBrowserVersion();
349 }
350 }
351
352 return null;
353 }
354
355
356
357
358 @Override
359 public boolean hasInstance(final Scriptable instance) {
360 if (getPrototype() == null) {
361
362
363 final Object prototype = get("prototype", this);
364 if (!(prototype instanceof ScriptableObject)) {
365 throw JavaScriptEngine.throwAsScriptRuntimeEx(new Exception("Null prototype"));
366 }
367 return ((ScriptableObject) prototype).hasInstance(instance);
368 }
369
370 return super.hasInstance(instance);
371 }
372
373
374
375
376 @Override
377 protected Object equivalentValues(Object value) {
378 if (value instanceof HtmlUnitScriptableProxy<?> proxy) {
379 value = proxy.getDelegee();
380 }
381 return super.equivalentValues(value);
382 }
383
384
385
386
387 @Override
388 public HtmlUnitScriptable clone() {
389 try {
390 return (HtmlUnitScriptable) super.clone();
391 }
392 catch (final Exception e) {
393 throw new IllegalStateException("Clone not supported");
394 }
395 }
396
397 protected NativePromise setupPromise(final FailableSupplier<Object, IOException> resolver) {
398 final VarScope scope = ScriptableObject.getTopLevelScope(getParentScope());
399 final LambdaConstructor ctor = (LambdaConstructor) getProperty(scope, "Promise");
400
401 try {
402 final LambdaFunction resolve = (LambdaFunction) getProperty(ctor, "resolve");
403 return (NativePromise) resolve.call(Context.getCurrentContext(), scope,
404 ctor, new Object[] {resolver.get()});
405 }
406 catch (final IOException e) {
407 final LambdaFunction reject = (LambdaFunction) getProperty(ctor, "reject");
408 return (NativePromise) reject.call(Context.getCurrentContext(), scope, ctor, new Object[] {e.getMessage()});
409 }
410 }
411
412 protected NativePromise setupRejectedPromise(final Supplier<Object> resolver) {
413 final VarScope scope = ScriptableObject.getTopLevelScope(getParentScope());
414 final LambdaConstructor ctor = (LambdaConstructor) getProperty(scope, "Promise");
415 final LambdaFunction reject = (LambdaFunction) getProperty(ctor, "reject");
416 return (NativePromise) reject.call(Context.getCurrentContext(), scope, ctor, new Object[] {resolver.get()});
417 }
418 }