Skip to content

Commit f4be298

Browse files
committed
Add Promise SPI
https://bugs.webkit.org/show_bug.cgi?id=189809 Reviewed by Saam Barati. Source/JavaScriptCore: The Patch adds new SPI to create promises. It's mostly SPI because I want to see how internal users react to it before we make it public. This patch adds a couple of new Obj-C SPI methods. The first creates a new promise using the same API that JS does where the user provides an executor callback. If an exception is raised in/to that callback the promise is automagically rejected. The other methods create a pre-resolved or rejected promise as this appears to be a common way to initialize a promise. I was also considering adding a second version of executor API where it would catch specific Obj-C exceptions. This would work by taking a Class paramter and checking isKindOfClass: on the exception. I decided against this as nothing else in our API handles Obj-C exceptions. I'm pretty sure the VM will end up in a corrupt state if an Obj-C exception unwinds through JS frames. This patch adds a new C function that will create a "deferred" promise. A deferred promise is a style of creating promise/futures where the resolve and reject functions are passed as outputs of a function. I went with this style for the C SPI because we don't have any concept of forwarding exceptions in the C API. In order to make the C API work I refactored a bit of the promise code so that we can call a static method on JSDeferredPromise and just get the components without allocating an extra cell wrapper. * API/JSContext.mm: (+[JSContext currentCallee]): * API/JSObjectRef.cpp: (JSObjectMakeDeferredPromise): * API/JSObjectRefPrivate.h: * API/JSValue.mm: (+[JSValue valueWithNewPromiseInContext:fromExecutor:]): (+[JSValue valueWithNewPromiseResolvedWithResult:inContext:]): (+[JSValue valueWithNewPromiseRejectedWithReason:inContext:]): * API/JSValuePrivate.h: Added. * API/JSVirtualMachine.mm: * API/JSVirtualMachinePrivate.h: * API/tests/testapi.c: (main): * API/tests/testapi.cpp: (APIContext::operator JSC::ExecState*): (TestAPI::failed const): (TestAPI::check): (TestAPI::basicSymbol): (TestAPI::symbolsTypeof): (TestAPI::symbolsGetPropertyForKey): (TestAPI::symbolsSetPropertyForKey): (TestAPI::symbolsHasPropertyForKey): (TestAPI::symbolsDeletePropertyForKey): (TestAPI::promiseResolveTrue): (TestAPI::promiseRejectTrue): (testCAPIViaCpp): (TestAPI::run): Deleted. * API/tests/testapi.mm: (testObjectiveCAPIMain): (promiseWithExecutor): (promiseRejectOnJSException): (promiseCreateResolved): (promiseCreateRejected): (parallelPromiseResolveTest): (testObjectiveCAPI): * JavaScriptCore.xcodeproj/project.pbxproj: * runtime/JSInternalPromiseDeferred.cpp: (JSC::JSInternalPromiseDeferred::create): * runtime/JSPromise.h: * runtime/JSPromiseConstructor.cpp: (JSC::constructPromise): * runtime/JSPromiseDeferred.cpp: (JSC::JSPromiseDeferred::createDeferredData): (JSC::JSPromiseDeferred::create): (JSC::JSPromiseDeferred::finishCreation): (JSC::newPromiseCapability): Deleted. * runtime/JSPromiseDeferred.h: (JSC::JSPromiseDeferred::promise const): (JSC::JSPromiseDeferred::resolve const): (JSC::JSPromiseDeferred::reject const): Source/WTF: Fix issue where creating a JSContextRef off the main thread before creating initializing the main thread would cause an assertion failure. * wtf/MainThread.cpp: (WTF::isMainThreadIfInitialized): * wtf/MainThread.h: * wtf/mac/MainThreadMac.mm: (WTF::isMainThreadIfInitialized): * wtf/text/cf/StringImplCF.cpp: (WTF::StringImpl::createCFString): Canonical link: https://commits.webkit.org/204827@main git-svn-id: https://svn.webkit.org/repository/webkit/trunk@236372 268f45cc-cd09-0410-ab3c-d52691b4dbfc
1 parent aba3a50 commit f4be298

22 files changed

Lines changed: 799 additions & 191 deletions

‎Source/JavaScriptCore/API/JSContext.mm‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,8 @@ + (JSValue *)currentCallee
158158
{
159159
Thread& thread = Thread::current();
160160
CallbackData *entry = (CallbackData *)thread.m_apiData;
161-
if (!entry)
161+
// calleeValue may be null if we are initializing a promise.
162+
if (!entry || !entry->calleeValue)
162163
return nil;
163164
return [JSValue valueWithJSValueRef:entry->calleeValue inContext:[JSContext currentContext]];
164165
}

‎Source/JavaScriptCore/API/JSObjectRef.cpp‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@
4646
#include "JSFunction.h"
4747
#include "JSGlobalObject.h"
4848
#include "JSObject.h"
49+
#include "JSPromise.h"
50+
#include "JSPromiseDeferred.h"
51+
#include "JSRetainPtr.h"
4952
#include "JSString.h"
5053
#include "JSValueRef.h"
5154
#include "ObjectConstructor.h"
@@ -274,6 +277,30 @@ JSObjectRef JSObjectMakeRegExp(JSContextRef ctx, size_t argumentCount, const JSV
274277
return toRef(result);
275278
}
276279

280+
JSObjectRef JSObjectMakeDeferredPromise(JSContextRef ctx, JSObjectRef* resolve, JSObjectRef* reject, JSValueRef* exception)
281+
{
282+
if (!ctx) {
283+
ASSERT_NOT_REACHED();
284+
return nullptr;
285+
}
286+
287+
ExecState* exec = toJS(ctx);
288+
VM& vm = exec->vm();
289+
JSLockHolder locker(exec);
290+
auto scope = DECLARE_CATCH_SCOPE(vm);
291+
292+
auto* globalObject = exec->lexicalGlobalObject();
293+
JSPromiseDeferred::DeferredData data = JSPromiseDeferred::createDeferredData(exec, globalObject, globalObject->promiseConstructor());
294+
if (handleExceptionIfNeeded(scope, exec, exception) == ExceptionStatus::DidThrow)
295+
return nullptr;
296+
297+
if (resolve)
298+
*resolve = toRef(data.resolve);
299+
if (reject)
300+
*reject = toRef(data.reject);
301+
return toRef(data.promise);
302+
}
303+
277304
JSValueRef JSObjectGetPrototype(JSContextRef ctx, JSObjectRef object)
278305
{
279306
if (!ctx) {

‎Source/JavaScriptCore/API/JSObjectRefPrivate.h‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,17 @@ JS_EXPORT bool JSObjectDeletePrivateProperty(JSContextRef ctx, JSObjectRef objec
7070
JS_EXPORT JSObjectRef JSObjectGetProxyTarget(JSObjectRef);
7171

7272
JS_EXPORT JSGlobalContextRef JSObjectGetGlobalContext(JSObjectRef object);
73+
74+
/*!
75+
@function
76+
@abstract Creates a JavaScript promise object by invoking the provided executor.
77+
@param ctx The execution context to use.
78+
@param resolve A pointer to a JSObjectRef in which to store the resolve function for the new promise. Pass NULL if you do not care to store the resolve callback.
79+
@param reject A pointer to a JSObjectRef in which to store the reject function for the new promise. Pass NULL if you do not care to store the reject callback.
80+
@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception.
81+
@result A JSObject that is a promise or NULL if an exception occurred.
82+
*/
83+
JS_EXPORT JSObjectRef JSObjectMakeDeferredPromise(JSContextRef ctx, JSObjectRef* resolve, JSObjectRef* reject, JSValueRef* exception) JSC_API_AVAILABLE(macosx(JSC_MAC_TBA), ios(JSC_IOS_TBA));
7384

7485
#ifdef __cplusplus
7586
}

‎Source/JavaScriptCore/API/JSValue.mm‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@
3131
#import "Exception.h"
3232
#import "JavaScriptCore.h"
3333
#import "JSContextInternal.h"
34+
#import "JSObjectRefPrivate.h"
3435
#import "JSVirtualMachineInternal.h"
3536
#import "JSValueInternal.h"
37+
#import "JSValuePrivate.h"
3638
#import "JSWrapperMap.h"
3739
#import "ObjcRuntimeExtras.h"
3840
#import "JSCInlines.h"
@@ -153,6 +155,48 @@ + (JSValue *)valueWithNewSymbolFromDescription:(NSString *)description inContext
153155
return [JSValue valueWithJSValueRef:JSValueMakeSymbol([context JSGlobalContextRef], string.get()) inContext:context];
154156
}
155157

158+
+ (JSValue *)valueWithNewPromiseInContext:(JSContext *)context fromExecutor:(void (^)(JSValue *, JSValue *))executor
159+
{
160+
JSObjectRef resolve;
161+
JSObjectRef reject;
162+
JSValueRef exception = nullptr;
163+
JSObjectRef promise = JSObjectMakeDeferredPromise([context JSGlobalContextRef], &resolve, &reject, &exception);
164+
if (exception) {
165+
[context notifyException:exception];
166+
return [JSValue valueWithUndefinedInContext:context];
167+
}
168+
169+
JSValue *result = [JSValue valueWithJSValueRef:promise inContext:context];
170+
JSValue *rejection = [JSValue valueWithJSValueRef:reject inContext:context];
171+
CallbackData callbackData;
172+
const size_t argumentCount = 2;
173+
JSValueRef arguments[argumentCount];
174+
arguments[0] = resolve;
175+
arguments[1] = reject;
176+
177+
[context beginCallbackWithData:&callbackData calleeValue:nullptr thisValue:promise argumentCount:argumentCount arguments:arguments];
178+
executor([JSValue valueWithJSValueRef:resolve inContext:context], rejection);
179+
if (context.exception)
180+
[rejection callWithArguments:@[context.exception]];
181+
[context endCallbackWithData:&callbackData];
182+
183+
return result;
184+
}
185+
186+
+ (JSValue *)valueWithNewPromiseResolvedWithResult:(id)result inContext:(JSContext *)context
187+
{
188+
return [JSValue valueWithNewPromiseInContext:context fromExecutor:^(JSValue *resolve, JSValue *) {
189+
[resolve callWithArguments:@[result]];
190+
}];
191+
}
192+
193+
+ (JSValue *)valueWithNewPromiseRejectedWithReason:(id)reason inContext:(JSContext *)context
194+
{
195+
return [JSValue valueWithNewPromiseInContext:context fromExecutor:^(JSValue *, JSValue *reject) {
196+
[reject callWithArguments:@[reason]];
197+
}];
198+
}
199+
156200
- (id)toObject
157201
{
158202
return valueToObject(_context, m_value);
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright (C) 2018 Apple Inc. All rights reserved.
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions
6+
* are met:
7+
* 1. Redistributions of source code must retain the above copyright
8+
* notice, this list of conditions and the following disclaimer.
9+
* 2. Redistributions in binary form must reproduce the above copyright
10+
* notice, this list of conditions and the following disclaimer in the
11+
* documentation and/or other materials provided with the distribution.
12+
*
13+
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14+
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17+
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18+
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20+
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21+
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24+
*/
25+
26+
#if JSC_OBJC_API_ENABLED
27+
28+
#import <JavaScriptCore/JavaScriptCore.h>
29+
30+
@interface JSValue(JSPrivate)
31+
32+
/*!
33+
@method
34+
@abstract Create a new promise object using the provided executor callback.
35+
@param callback A callback block invoked while the promise object is
36+
being initialized. The resolve and reject parameters are functions that
37+
can be called to notify any pending reactions about the state of the
38+
new promise object.
39+
@param context The JSContext to which the resulting JSValue belongs.
40+
@result The JSValue representing a new promise JavaScript object.
41+
@discussion This method is equivalent to calling the Promise constructor in JavaScript.
42+
the resolve and reject callbacks each normally take a single value, which they
43+
forward to all relevent pending reactions. While inside the executor callback context will act
44+
as if it were in any other callback, except calleeFunction will be <code>nil</code>. This also means
45+
means the new promise object may be accessed via <code>[context thisValue]</code>.
46+
*/
47+
+ (JSValue *)valueWithNewPromiseInContext:(JSContext *)context fromExecutor:(void (^)(JSValue *resolve, JSValue *reject))callback JSC_API_AVAILABLE(macosx(JSC_MAC_TBA), ios(JSC_IOS_TBA));
48+
49+
/*!
50+
@method
51+
@abstract Create a new resolved promise object with the provided value.
52+
@param result The result value to be passed to any reactions.
53+
@param context The JSContext to which the resulting JSValue belongs.
54+
@result The JSValue representing a new promise JavaScript object.
55+
@discussion This method is equivalent to calling <code>[JSValue valueWithNewPromiseFromExecutor:^(JSValue *resolve, JSValue *reject) { [resolve callWithArguments:@[result]]; } inContext:context]</code>
56+
*/
57+
+ (JSValue *)valueWithNewPromiseResolvedWithResult:(id)result inContext:(JSContext *)context JSC_API_AVAILABLE(macosx(JSC_MAC_TBA), ios(JSC_IOS_TBA));
58+
59+
/*!
60+
@method
61+
@abstract Create a new rejected promise object with the provided value.
62+
@param reason The result value to be passed to any reactions.
63+
@param context The JSContext to which the resulting JSValue belongs.
64+
@result The JSValue representing a new promise JavaScript object.
65+
@discussion This method is equivalent to calling <code>[JSValue valueWithNewPromiseFromExecutor:^(JSValue *resolve, JSValue *reject) { [reject callWithArguments:@[reason]]; } inContext:context]</code>
66+
*/
67+
+ (JSValue *)valueWithNewPromiseRejectedWithReason:(id)reason inContext:(JSContext *)context JSC_API_AVAILABLE(macosx(JSC_MAC_TBA), ios(JSC_IOS_TBA));
68+
69+
@end
70+
71+
#endif // JSC_OBJC_API_ENABLED

‎Source/JavaScriptCore/API/JSVirtualMachine.mm‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,13 @@
3232
#import "APICast.h"
3333
#import "DFGWorklist.h"
3434
#import "JSManagedValueInternal.h"
35-
#import "JSVirtualMachine.h"
3635
#import "JSVirtualMachineInternal.h"
36+
#import "JSVirtualMachinePrivate.h"
3737
#import "JSWrapperMap.h"
3838
#import "SigillCrashAnalyzer.h"
3939
#import "SlotVisitorInlines.h"
4040
#import <mutex>
41+
#import <wtf/BlockPtr.h>
4142
#import <wtf/Lock.h>
4243
#import <wtf/spi/cocoa/NSMapTableSPI.h>
4344

‎Source/JavaScriptCore/API/JSVirtualMachinePrivate.h‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
API may not synchronously free memory.
4444
*/
4545

46-
- (void)shrinkFootprintWhenIdle NS_AVAILABLE(10_14, 12_0);
46+
- (void)shrinkFootprintWhenIdle JSC_API_AVAILABLE(macosx(10.14), ios(12.0));
4747

4848
#if ENABLE(DFG_JIT)
4949

@@ -58,7 +58,7 @@
5858
@param numberOfThreads The number of threads the DFG compiler should use going forward
5959
@result The previous number of threads being used by the DFG compiler
6060
*/
61-
+ (NSUInteger)setNumberOfDFGCompilerThreads:(NSUInteger)numberOfThreads NS_AVAILABLE(10_14, 12_0);
61+
+ (NSUInteger)setNumberOfDFGCompilerThreads:(NSUInteger)numberOfThreads JSC_API_AVAILABLE(macosx(10.14), ios(12.0));
6262

6363
/*!
6464
@method
@@ -71,7 +71,7 @@
7171
@param numberOfThreads The number of threads the FTL compiler should use going forward
7272
@result The previous number of threads being used by the FTL compiler
7373
*/
74-
+ (NSUInteger)setNumberOfFTLCompilerThreads:(NSUInteger)numberOfThreads NS_AVAILABLE(10_14, 12_0);
74+
+ (NSUInteger)setNumberOfFTLCompilerThreads:(NSUInteger)numberOfThreads JSC_API_AVAILABLE(macosx(10.14), ios(12.0));
7575

7676
#endif // ENABLE(DFG_JIT)
7777

‎Source/JavaScriptCore/API/tests/testapi.c‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
void testObjectiveCAPI(void);
7171
#endif
7272

73-
int testCAPIViaCpp(void);
73+
int testCAPIViaCpp(const char* filter);
7474

7575
bool assertTrue(bool value, const char* message);
7676

@@ -1380,18 +1380,17 @@ int main(int argc, char* argv[])
13801380
SetErrorMode(0);
13811381
#endif
13821382

1383-
testCompareAndSwap();
1384-
startMultithreadedMultiVMExecutionTest();
1385-
13861383
#if JSC_OBJC_API_ENABLED
13871384
testObjectiveCAPI();
13881385
#endif
1389-
RELEASE_ASSERT(!testCAPIViaCpp());
13901386

1391-
const char *scriptPath = "testapi.js";
1392-
if (argc > 1) {
1393-
scriptPath = argv[1];
1394-
}
1387+
const char* filter = argc > 1 ? argv[1] : NULL;
1388+
RELEASE_ASSERT(!testCAPIViaCpp(filter));
1389+
if (filter)
1390+
return 0;
1391+
1392+
testCompareAndSwap();
1393+
startMultithreadedMultiVMExecutionTest();
13951394

13961395
// Test garbage collection with a fresh context
13971396
context = JSGlobalContextCreateInGroup(NULL, NULL);
@@ -1980,6 +1979,7 @@ int main(int argc, char* argv[])
19801979
JSObjectMakeConstructor(context, nullClass, 0);
19811980
JSClassRelease(nullClass);
19821981

1982+
const char* scriptPath = "testapi.js";
19831983
char* scriptUTF8 = createStringWithContentsOfFile(scriptPath);
19841984
if (!scriptUTF8) {
19851985
printf("FAIL: Test script could not be loaded.\n");

0 commit comments

Comments
 (0)