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