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