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