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