com.gargoylesoftware.htmlunit.javascript.host.Promise 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-2021 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 com.gargoylesoftware.htmlunit.javascript.host;
import static com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine.KEY_STARTING_SCOPE;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF78;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.javascript.HtmlUnitContextFactory;
import com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine;
import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable;
import com.gargoylesoftware.htmlunit.javascript.background.BasicJavaScriptJob;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxStaticFunction;
import net.sourceforge.htmlunit.corejs.javascript.BaseFunction;
import net.sourceforge.htmlunit.corejs.javascript.Context;
import net.sourceforge.htmlunit.corejs.javascript.ContextAction;
import net.sourceforge.htmlunit.corejs.javascript.Function;
import net.sourceforge.htmlunit.corejs.javascript.IteratorLikeIterable;
import net.sourceforge.htmlunit.corejs.javascript.JavaScriptException;
import net.sourceforge.htmlunit.corejs.javascript.NativeArray;
import net.sourceforge.htmlunit.corejs.javascript.NativeObject;
import net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime;
import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
import net.sourceforge.htmlunit.corejs.javascript.ScriptableObject;
import net.sourceforge.htmlunit.corejs.javascript.TopLevel;
import net.sourceforge.htmlunit.corejs.javascript.Undefined;
/**
* A JavaScript object for {@code Promise}.
*
* @author Ahmed Ashour
* @author Marc Guillemot
* @author Ronald Brill
* @author Rural Hunter
*/
@JsxClass({CHROME, EDGE, FF, FF78})
public class Promise extends SimpleScriptable {
private enum PromiseState { PENDING, FULFILLED, REJECTED }
private PromiseState state_ = PromiseState.PENDING;
private Object value_;
private boolean race_;
private Promise[] all_;
private List settledJobs_;
private List dependentPromises_;
/**
* Default constructor.
*/
public Promise() {
}
/**
* Facility constructor.
* @param window the owning window
*/
public Promise(final Window window) {
setParentScope(window);
setPrototype(window.getPrototype(Promise.class));
}
/**
* Constructor new promise with the given {@code object}.
*
* @param object the object
*/
@JsxConstructor
public Promise(final Object object) {
if (!(object instanceof Function)) {
throw ScriptRuntime.typeError("Promise resolver '"
+ ScriptRuntime.toString(object) + "' is not a function");
}
final Function fun = (Function) object;
final Window window = getWindow(fun);
this.setParentScope(window);
this.setPrototype(window.getPrototype(this.getClass()));
final Promise thisPromise = this;
callThenableFunction(fun, window, thisPromise, window);
}
private static void callThenableFunction(final Function fun, final Window window,
final Promise promise, final Scriptable thisObj) {
final Function resolve = new BaseFunction(window, ScriptableObject.getFunctionPrototype(window)) {
@Override
public Object call(final Context cx, final Scriptable scope, final Scriptable thisObj,
final Object[] args) {
promise.settle(true, args.length == 0 ? Undefined.instance : args[0], window);
return promise;
}
};
final Function reject = new BaseFunction(window, ScriptableObject.getFunctionPrototype(window)) {
@Override
public Object call(final Context cx, final Scriptable scope, final Scriptable thisObj,
final Object[] args) {
promise.settle(false, args.length == 0 ? Undefined.instance : args[0], window);
return promise;
}
};
final Context cx = Context.getCurrentContext();
try {
// KEY_STARTING_SCOPE maintains a stack of scopes
@SuppressWarnings("unchecked")
Deque stack = (Deque) cx.getThreadLocal(KEY_STARTING_SCOPE);
if (null == stack) {
stack = new ArrayDeque<>();
cx.putThreadLocal(KEY_STARTING_SCOPE, stack);
}
stack.push(window);
try {
fun.call(cx, window, thisObj, new Object[] {resolve, reject});
}
finally {
stack.pop();
}
window.getWebWindow().getWebClient().getJavaScriptEngine().processPostponedActions();
}
catch (final JavaScriptException e) {
promise.settle(false, e.getValue(), window);
}
}
/**
* Returns a {@link Promise} object that is resolved with the given value.
*
* @param context the context
* @param thisObj this object
* @param args the arguments
* @param function the function
* @return a {@link Promise}
*/
@JsxStaticFunction
public static Promise resolve(final Context context, final Scriptable thisObj, final Object[] args,
final Function function) {
return create(thisObj, args, PromiseState.FULFILLED);
}
/**
* Returns a {@link Promise} object that is rejected with the given value.
*
* @param context the context
* @param thisObj this object
* @param args the arguments
* @param function the function
* @return a {@link Promise}
*/
@JsxStaticFunction
public static Promise reject(final Context context, final Scriptable thisObj, final Object[] args,
final Function function) {
return create(thisObj, args, PromiseState.REJECTED);
}
private static Promise create(final Scriptable thisObj, final Object[] args, final PromiseState state) {
// fulfilled promises are returned
if (args.length != 0 && args[0] instanceof Promise && state == PromiseState.FULFILLED) {
return (Promise) args[0];
}
final Promise promise;
if (args.length > 0) {
final Object arg = args[0];
if (arg instanceof NativeObject) {
final NativeObject nativeObject = (NativeObject) arg;
final Object thenFunction = nativeObject.get("then", nativeObject);
if (thenFunction == NOT_FOUND) {
promise = new Promise();
promise.value_ = arg;
promise.state_ = state;
}
else {
promise = new Promise(thenFunction);
}
}
else {
promise = new Promise();
promise.value_ = arg;
promise.state_ = state;
}
}
else {
promise = new Promise();
promise.value_ = Undefined.instance;
promise.state_ = state;
}
promise.setParentScope(thisObj.getParentScope());
promise.setPrototype(getWindow(thisObj).getPrototype(promise.getClass()));
return promise;
}
void settle(final boolean fulfilled, final Object newValue, final Window window) {
if (state_ != PromiseState.PENDING) {
return;
}
if (all_ != null) {
settleAll(window);
return;
}
settleThis(fulfilled, newValue, window);
}
private void settleThis(final boolean fulfilled, final Object newValue, final Window window) {
value_ = newValue;
if (fulfilled) {
state_ = PromiseState.FULFILLED;
}
else {
state_ = PromiseState.REJECTED;
}
if (settledJobs_ != null) {
for (final BasicJavaScriptJob job : settledJobs_) {
window.getWebWindow().getJobManager().addJob(job, window.getDocument().getPage());
}
settledJobs_ = null;
}
if (dependentPromises_ != null) {
for (final Promise promise : dependentPromises_) {
promise.settle(fulfilled, newValue, window);
}
dependentPromises_ = null;
}
}
private void settleAll(final Window window) {
if (race_) {
for (final Promise promise : all_) {
if (promise.state_ == PromiseState.REJECTED) {
settleThis(false, promise.value_, window);
return;
}
else if (promise.state_ == PromiseState.FULFILLED) {
settleThis(true, promise.value_, window);
return;
}
}
return;
}
final ArrayList