org.htmlunit.javascript.HtmlUnitScriptable Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xlt Show documentation
Show all versions of xlt Show documentation
XLT (Xceptance LoadTest) is an extensive load and performance test tool developed and maintained by Xceptance.
/*
* Copyright (c) 2002-2024 Gargoyle Software Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.htmlunit.javascript;
import static org.htmlunit.BrowserVersionFeatures.HTMLIMAGE_HTMLELEMENT;
import static org.htmlunit.BrowserVersionFeatures.HTMLIMAGE_HTMLUNKNOWNELEMENT;
import java.io.IOException;
import java.util.Deque;
import java.util.function.Supplier;
import org.apache.commons.lang3.function.FailableSupplier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.htmlunit.BrowserVersion;
import org.htmlunit.SgmlPage;
import org.htmlunit.WebAssert;
import org.htmlunit.WebWindow;
import org.htmlunit.corejs.javascript.Context;
import org.htmlunit.corejs.javascript.LambdaConstructor;
import org.htmlunit.corejs.javascript.LambdaFunction;
import org.htmlunit.corejs.javascript.Scriptable;
import org.htmlunit.corejs.javascript.ScriptableObject;
import org.htmlunit.html.DomNode;
import org.htmlunit.html.HtmlImage;
import org.htmlunit.javascript.host.Window;
import org.htmlunit.javascript.host.html.HTMLElement;
import org.htmlunit.javascript.host.html.HTMLUnknownElement;
/**
* Base class for Rhino host objects in HtmlUnit (not bound to a DOM node).
*
* @author Mike Bowler
* @author David K. Taylor
* @author Marc Guillemot
* @author Chris Erskine
* @author Daniel Gredler
* @author Ahmed Ashour
* @author Ronald Brill
*/
public class HtmlUnitScriptable extends ScriptableObject implements Cloneable {
private static final Log LOG = LogFactory.getLog(HtmlUnitScriptable.class);
private DomNode domNode_;
private String className_;
/**
* Returns the JavaScript class name.
* @return the JavaScript class name
*/
@Override
public String getClassName() {
if (className_ != null) {
return className_;
}
if (getPrototype() != null) {
return getPrototype().getClassName();
}
String className = getClass().getSimpleName();
if (className.isEmpty()) {
// for anonymous class
className = getClass().getSuperclass().getSimpleName();
}
return className;
}
/**
* INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
*
* Sets the class name.
* @param className the class name.
*/
public void setClassName(final String className) {
className_ = className;
}
/**
* {@inheritDoc}
*/
@Override
public void setParentScope(final Scriptable m) {
if (m == this) {
throw new IllegalArgumentException("Object can't be its own parentScope");
}
super.setParentScope(m);
}
/**
* {@inheritDoc}
*/
@Override
public void put(final String name, final Scriptable start, final Object value) {
try {
super.put(name, start, value);
}
catch (final IllegalArgumentException e) {
// is it the right place or should Rhino throw a RuntimeError instead of an IllegalArgumentException?
throw JavaScriptEngine.reportRuntimeError("'set "
+ name + "' called on an object that does not implement interface " + getClassName());
}
}
/**
* Gets a named property from the object.
* Normally HtmlUnit objects don't need to overwrite this method as properties are defined
* on the prototypes from the XML configuration. In some cases where "content" of object
* has priority compared to the properties consider using utility {@link #getWithPreemption(String)}.
* {@inheritDoc}
*/
@Override
public Object get(final String name, final Scriptable start) {
// Try to get property configured on object itself.
Object response = super.get(name, start);
if (response != NOT_FOUND) {
return response;
}
if (this == start) {
response = getWithPreemption(name);
}
if (response == NOT_FOUND && start instanceof Window) {
response = ((Window) start).getWithFallback(name);
}
return response;
}
/**
* Called by {@link #get(String, Scriptable)} to allow retrieval of the property before the prototype
* chain is searched.
*
* IMPORTANT: This method is invoked *very* often by Rhino. If you override this method, the implementation
* needs to be as fast as possible!
*
* @param name the property name
* @return {@link Scriptable#NOT_FOUND} if not found
*/
protected Object getWithPreemption(final String name) {
return NOT_FOUND;
}
@Override
public boolean has(final int index, final Scriptable start) {
final Object found = get(index, start);
if (Scriptable.NOT_FOUND != found && !JavaScriptEngine.isUndefined(found)) {
return true;
}
return super.has(index, start);
}
/**
* Returns the DOM node that corresponds to this JavaScript object or throw
* an exception if one cannot be found.
* @return the DOM node
*/
public DomNode getDomNodeOrDie() {
if (domNode_ == null) {
throw new IllegalStateException("DomNode has not been set for this HtmlUnitScriptable: "
+ getClass().getName());
}
return domNode_;
}
/**
* Returns the DOM node that corresponds to this JavaScript object
* or null if a node hasn't been set.
* @return the DOM node or null
*/
public DomNode getDomNodeOrNull() {
return domNode_;
}
/**
* Sets the DOM node that corresponds to this JavaScript object.
* @param domNode the DOM node
*/
public void setDomNode(final DomNode domNode) {
setDomNode(domNode, true);
}
/**
* INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
*
* Sets the DOM node that corresponds to this JavaScript object.
* @param domNode the DOM node
* @param assignScriptObject If true, call setScriptObject
on domNode
*/
public void setDomNode(final DomNode domNode, final boolean assignScriptObject) {
WebAssert.notNull("domNode", domNode);
domNode_ = domNode;
if (assignScriptObject) {
domNode_.setScriptableObject(this);
}
}
/**
* Returns the JavaScript object that corresponds to the specified object.
* New JavaScript objects will be created as needed. If a JavaScript object
* cannot be created for a domNode then NOT_FOUND will be returned.
*
* @param object a {@link DomNode} or a {@link WebWindow}
* @return the JavaScript object or NOT_FOUND
*/
protected HtmlUnitScriptable getScriptableFor(final Object object) {
if (object instanceof WebWindow) {
return ((WebWindow) object).getScriptableObject();
}
final DomNode domNode = (DomNode) object;
final HtmlUnitScriptable scriptObject = domNode.getScriptableObject();
if (scriptObject != null) {
return scriptObject;
}
return makeScriptableFor(domNode);
}
/**
* Builds a new the JavaScript object that corresponds to the specified object.
* @param domNode the DOM node for which a JS object should be created
* @return the JavaScript object
*/
public HtmlUnitScriptable makeScriptableFor(final DomNode domNode) {
// Get the JS class name for the specified DOM node.
// Walk up the inheritance chain if necessary.
Class extends HtmlUnitScriptable> javaScriptClass = null;
if (domNode instanceof HtmlImage && "image".equals(((HtmlImage) domNode).getOriginalQualifiedName())
&& ((HtmlImage) domNode).wasCreatedByJavascript()) {
if (domNode.hasFeature(HTMLIMAGE_HTMLELEMENT)) {
javaScriptClass = HTMLElement.class;
}
else if (domNode.hasFeature(HTMLIMAGE_HTMLUNKNOWNELEMENT)) {
javaScriptClass = HTMLUnknownElement.class;
}
}
if (javaScriptClass == null) {
final JavaScriptEngine javaScriptEngine =
(JavaScriptEngine) getWindow().getWebWindow().getWebClient().getJavaScriptEngine();
for (Class> c = domNode.getClass(); javaScriptClass == null && c != null; c = c.getSuperclass()) {
javaScriptClass = javaScriptEngine.getJavaScriptClass(c);
}
}
final HtmlUnitScriptable scriptable;
if (javaScriptClass == null) {
// We don't have a specific subclass for this element so create something generic.
scriptable = new HTMLElement();
if (LOG.isDebugEnabled()) {
LOG.debug("No JavaScript class found for element <" + domNode.getNodeName() + ">. Using HTMLElement");
}
}
else {
try {
scriptable = javaScriptClass.newInstance();
}
catch (final Exception e) {
throw JavaScriptEngine.throwAsScriptRuntimeEx(e);
}
}
initParentScope(domNode, scriptable);
scriptable.setPrototype(getPrototype(javaScriptClass));
scriptable.setDomNode(domNode);
return scriptable;
}
/**
* Initialize the parent scope of a newly created scriptable.
* @param domNode the DOM node for the script object
* @param scriptable the script object to initialize
*/
protected void initParentScope(final DomNode domNode, final HtmlUnitScriptable scriptable) {
final SgmlPage page = domNode.getPage();
final WebWindow enclosingWindow = page.getEnclosingWindow();
if (enclosingWindow != null && enclosingWindow.getEnclosedPage() == page) {
scriptable.setParentScope(enclosingWindow.getScriptableObject());
}
else {
scriptable.setParentScope(ScriptableObject.getTopLevelScope(page.getScriptableObject()));
}
}
/**
* Gets the prototype object for the given host class.
* @param javaScriptClass the host class
* @return the prototype
*/
@SuppressWarnings("unchecked")
public Scriptable getPrototype(final Class extends HtmlUnitScriptable> javaScriptClass) {
final Scriptable prototype = getWindow().getPrototype(javaScriptClass);
if (prototype == null && javaScriptClass != HtmlUnitScriptable.class) {
return getPrototype((Class extends HtmlUnitScriptable>) javaScriptClass.getSuperclass());
}
return prototype;
}
/**
* Returns the JavaScript default value of this object. This is the JavaScript equivalent of a toString() in Java.
*
* @param hint a hint as to the format of the default value (ignored in this case)
* @return the default value
*/
@Override
public Object getDefaultValue(final Class> hint) {
if (String.class.equals(hint) || hint == null) {
return "[object " + getClassName() + "]";
}
return super.getDefaultValue(hint);
}
/**
* Gets the window that is the top scope for this object.
* @return the window associated with this object
* @throws RuntimeException if the window cannot be found, which should never occur
*/
public Window getWindow() throws RuntimeException {
return getWindow(this);
}
/**
* Gets the window that is the top scope for the specified object.
* @param s the JavaScript object whose associated window is to be returned
* @return the window associated with the specified JavaScript object
* @throws RuntimeException if the window cannot be found, which should never occur
*/
protected static Window getWindow(final Scriptable s) throws RuntimeException {
final Scriptable top = ScriptableObject.getTopLevelScope(s);
if (top instanceof Window) {
return (Window) top;
}
throw new RuntimeException("Unable to find window associated with " + s);
}
/**
* Gets the scriptable used at starting scope for the execution of current script.
* @return the scope as defined in {@link JavaScriptEngine#callFunction}
* or {@link JavaScriptEngine#execute}.
*/
protected Scriptable getStartingScope() {
@SuppressWarnings("unchecked")
final Deque stack =
(Deque) Context.getCurrentContext().getThreadLocal(JavaScriptEngine.KEY_STARTING_SCOPE);
if (null == stack) {
return null;
}
return stack.peek();
}
/**
* Gets the browser version currently used.
* @return the browser version
*/
public BrowserVersion getBrowserVersion() {
final DomNode node = getDomNodeOrNull();
if (node != null) {
return node.getPage().getWebClient().getBrowserVersion();
}
final Window window = getWindow();
if (window != null) {
final WebWindow webWindow = window.getWebWindow();
if (webWindow != null) {
return webWindow.getWebClient().getBrowserVersion();
}
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasInstance(final Scriptable instance) {
if (getPrototype() == null) {
// to handle cases like "x instanceof HTMLElement",
// but HTMLElement is not in the prototype chain of any element
final Object prototype = get("prototype", this);
if (!(prototype instanceof ScriptableObject)) {
throw JavaScriptEngine.throwAsScriptRuntimeEx(new Exception("Null prototype"));
}
return ((ScriptableObject) prototype).hasInstance(instance);
}
return super.hasInstance(instance);
}
/**
* {@inheritDoc}
*/
@Override
protected Object equivalentValues(Object value) {
if (value instanceof HtmlUnitScriptableProxy>) {
value = ((HtmlUnitScriptableProxy>) value).getDelegee();
}
return super.equivalentValues(value);
}
/**
* {@inheritDoc}
*/
@Override
public HtmlUnitScriptable clone() {
try {
return (HtmlUnitScriptable) super.clone();
}
catch (final Exception e) {
throw new IllegalStateException("Clone not supported");
}
}
protected Object setupPromise(final FailableSupplier