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 htmlunit Show documentation
Show all versions of htmlunit Show documentation
A headless browser intended for use in testing web-based applications.
/*
* Copyright (c) 2002-2018 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
* http://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 java.util.ArrayList;
import java.util.List;
import java.util.Stack;
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.Function;
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
*/
@JsxClass({CHROME, FF, EDGE})
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 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;
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) {
thisPromise.settle(true, args.length != 0 ? args[0] : Undefined.instance, window);
return thisPromise;
}
};
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) {
thisPromise.settle(false, args.length != 0 ? args[0] : Undefined.instance, window);
return thisPromise;
}
};
final Context cx = Context.getCurrentContext();
try {
// KEY_STARTING_SCOPE maintains a stack of scopes
@SuppressWarnings("unchecked")
Stack stack = (Stack) cx.getThreadLocal(KEY_STARTING_SCOPE);
if (null == stack) {
stack = new Stack<>();
cx.putThreadLocal(KEY_STARTING_SCOPE, stack);
}
stack.push(window);
try {
fun.call(cx, window, window, new Object[] {resolve, reject});
}
finally {
stack.pop();
}
window.getWebWindow().getWebClient().getJavaScriptEngine().processPostponedActions();
}
catch (final JavaScriptException e) {
thisPromise.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;
promise = new Promise(nativeObject.get("then", nativeObject));
}
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;
}
private 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 (BasicJavaScriptJob job : settledJobs_) {
window.getWebWindow().getJobManager().addJob(job, window.getDocument().getPage());
}
settledJobs_ = null;
}
if (dependentPromises_ != null) {
for (Promise promise : dependentPromises_) {
promise.settle(fulfilled, newValue, window);
}
dependentPromises_ = null;
}
}
private void settleAll(final Window window) {
if (race_) {
for (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