Add SPI callbacks for before and after module execution
authorsbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 24 Apr 2019 22:42:38 +0000 (22:42 +0000)
committersbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 24 Apr 2019 22:42:38 +0000 (22:42 +0000)
https://bugs.webkit.org/show_bug.cgi?id=197244
<rdar://problem/50180511>

Reviewed by Yusuke Suzuki.

This is helpful for clients that want to profile execution of modules
in some way. E.g, if they want to time module execution time.

* API/JSAPIGlobalObject.h:
* API/JSAPIGlobalObject.mm:
(JSC::JSAPIGlobalObject::moduleLoaderEvaluate):
* API/JSContextPrivate.h:
* API/tests/testapi.mm:
(+[JSContextFetchDelegate contextWithBlockForFetch:]):
(-[JSContextFetchDelegate willEvaluateModule:]):
(-[JSContextFetchDelegate didEvaluateModule:]):
(testFetch):
(testFetchWithTwoCycle):
(testFetchWithThreeCycle):
(testLoaderResolvesAbsoluteScriptURL):
(testLoaderRejectsNilScriptURL):
* runtime/JSModuleLoader.cpp:
(JSC::JSModuleLoader::evaluate):
(JSC::JSModuleLoader::evaluateNonVirtual):
* runtime/JSModuleLoader.h:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@244620 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/JavaScriptCore/API/JSAPIGlobalObject.h
Source/JavaScriptCore/API/JSAPIGlobalObject.mm
Source/JavaScriptCore/API/JSContextPrivate.h
Source/JavaScriptCore/API/tests/testapi.mm
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/runtime/JSModuleLoader.cpp
Source/JavaScriptCore/runtime/JSModuleLoader.h

index d9bbcac..339e5e2 100644 (file)
@@ -56,6 +56,7 @@ public:
     static Identifier moduleLoaderResolve(JSGlobalObject*, ExecState*, JSModuleLoader*, JSValue keyValue, JSValue referrerValue, JSValue);
     static JSInternalPromise* moduleLoaderFetch(JSGlobalObject*, ExecState*, JSModuleLoader*, JSValue, JSValue, JSValue);
     static JSObject* moduleLoaderCreateImportMetaProperties(JSGlobalObject*, ExecState*, JSModuleLoader*, JSValue, JSModuleRecord*, JSValue);
+    static JSValue moduleLoaderEvaluate(JSGlobalObject*, ExecState*, JSModuleLoader*, JSValue, JSValue, JSValue);
 
     JSValue loadAndEvaluateJSScriptModule(const JSLockHolder&, JSScript *);
 
index ae62aef..5332280 100644 (file)
@@ -36,6 +36,7 @@
 #import "JSContextInternal.h"
 #import "JSInternalPromise.h"
 #import "JSInternalPromiseDeferred.h"
+#import "JSModuleLoader.h"
 #import "JSNativeStdFunction.h"
 #import "JSPromiseDeferred.h"
 #import "JSScriptInternal.h"
@@ -61,7 +62,7 @@ const GlobalObjectMethodTable JSAPIGlobalObject::s_globalObjectMethodTable = {
     &moduleLoaderResolve, // moduleLoaderResolve
     &moduleLoaderFetch, // moduleLoaderFetch
     &moduleLoaderCreateImportMetaProperties, // moduleLoaderCreateImportMetaProperties
-    nullptr, // moduleLoaderEvaluate
+    moduleLoaderEvaluate, // moduleLoaderEvaluate
     nullptr, // promiseRejectionTracker
     nullptr, // defaultLanguage
     nullptr, // compileStreaming
@@ -234,6 +235,33 @@ JSObject* JSAPIGlobalObject::moduleLoaderCreateImportMetaProperties(JSGlobalObje
     return metaProperties;
 }
 
+JSValue JSAPIGlobalObject::moduleLoaderEvaluate(JSGlobalObject* globalObject, ExecState* exec, JSModuleLoader* moduleLoader, JSValue key, JSValue moduleRecordValue, JSValue scriptFetcher)
+{
+    VM& vm = exec->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    JSContext *context = [JSContext contextWithJSGlobalContextRef:toGlobalRef(globalObject->globalExec())];
+    id <JSModuleLoaderDelegate> moduleLoaderDelegate = [context moduleLoaderDelegate];
+    NSURL *url = nil;
+
+    if ([moduleLoaderDelegate respondsToSelector:@selector(willEvaluateModule:)] || [moduleLoaderDelegate respondsToSelector:@selector(didEvaluateModule:)]) {
+        String moduleKey = key.toWTFString(exec);
+        RETURN_IF_EXCEPTION(scope, { });
+        url = [NSURL URLWithString:static_cast<NSString *>(moduleKey)];
+    }
+
+    if ([moduleLoaderDelegate respondsToSelector:@selector(willEvaluateModule:)])
+        [moduleLoaderDelegate willEvaluateModule:url];
+
+    scope.release();
+    JSValue result = moduleLoader->evaluateNonVirtual(exec, key, moduleRecordValue, scriptFetcher);
+
+    if ([moduleLoaderDelegate respondsToSelector:@selector(didEvaluateModule:)])
+        [moduleLoaderDelegate didEvaluateModule:url];
+
+    return result;
+}
+
 JSValue JSAPIGlobalObject::loadAndEvaluateJSScriptModule(const JSLockHolder&, JSScript *script)
 {
     ASSERT(script.type == kJSScriptTypeModule);
index c32480b..f4656b9 100644 (file)
  */
 - (void)context:(JSContext *)context fetchModuleForIdentifier:(JSValue *)identifier withResolveHandler:(JSValue *)resolve andRejectHandler:(JSValue *)reject;
 
+@optional
+
+/*! @abstract This is called before the module with "key" is evaluated.
+ @param key The module key for the module that is about to be evaluated.
+ */
+- (void)willEvaluateModule:(NSURL *)key;
+
+/*! @abstract This is called after the module with "key" is evaluated.
+ @param key The module key for the module that was just evaluated.
+ */
+- (void)didEvaluateModule:(NSURL *)key;
+
 @end
 
 @interface JSContext(Private)
index 00c767c..e27a339 100644 (file)
@@ -1827,6 +1827,11 @@ typedef void (^FetchBlock)(JSContext *, JSValue *, JSValue *, JSValue *);
 
 + (instancetype)contextWithBlockForFetch:(FetchBlock)block;
 
+@property unsigned willEvaluateModuleCallCount;
+@property unsigned didEvaluateModuleCallCount;
+@property BOOL sawBarJS;
+@property BOOL sawFooJS;
+
 @end
 
 @implementation JSContextFetchDelegate {
@@ -1836,6 +1841,10 @@ typedef void (^FetchBlock)(JSContext *, JSValue *, JSValue *, JSValue *);
 + (instancetype)contextWithBlockForFetch:(FetchBlock)block
 {
     auto *result = [[JSContextFetchDelegate alloc] init];
+    result.willEvaluateModuleCallCount = 0;
+    result.didEvaluateModuleCallCount = 0;
+    result.sawBarJS = NO;
+    result.sawFooJS = NO;
     result->m_fetchBlock = block;
     return result;
 }
@@ -1845,6 +1854,18 @@ typedef void (^FetchBlock)(JSContext *, JSValue *, JSValue *, JSValue *);
     m_fetchBlock(context, identifier, resolve, reject);
 }
 
+- (void)willEvaluateModule:(NSURL *)url
+{
+    self.willEvaluateModuleCallCount += 1;
+    self.sawBarJS |= [url isEqual:[NSURL URLWithString:@"file:///directory/bar.js"]];
+}
+
+- (void)didEvaluateModule:(NSURL *)url
+{
+    self.didEvaluateModuleCallCount += 1;
+    self.sawFooJS |= [url isEqual:[NSURL URLWithString:@"file:///foo.js"]];
+}
+
 @end
 
 static void checkModuleCodeRan(JSContext *context, JSValue *promise, JSValue *expected)
@@ -1898,6 +1919,10 @@ static void testFetch()
         JSValue *promise = [context evaluateScript:@"import('./bar.js');" withSourceURL:[NSURL fileURLWithPath:@"/directory" isDirectory:YES]];
         JSValue *null = [JSValue valueWithNullInContext:context];
         checkModuleCodeRan(context, promise, null);
+        checkResult(@"Context should call willEvaluateModule: twice", context.willEvaluateModuleCallCount == 2);
+        checkResult(@"Context should call didEvaluateModule: twice", context.didEvaluateModuleCallCount == 2);
+        checkResult(@"Context should see bar.js url", !!context.sawBarJS);
+        checkResult(@"Context should see foo.js url", !!context.sawFooJS);
     }
 }
 
@@ -1926,6 +1951,8 @@ static void testFetchWithTwoCycle()
         JSValue *promise = [context evaluateScript:@"import('./bar.js');" withSourceURL:[NSURL fileURLWithPath:@"/directory" isDirectory:YES]];
         JSValue *null = [JSValue valueWithNullInContext:context];
         checkModuleCodeRan(context, promise, null);
+        checkResult(@"Context should call willEvaluateModule: twice", context.willEvaluateModuleCallCount == 2);
+        checkResult(@"Context should call didEvaluateModule: twice", context.didEvaluateModuleCallCount == 2);
     }
 }
 
@@ -1962,6 +1989,10 @@ static void testFetchWithThreeCycle()
         JSValue *promise = [context evaluateScript:@"import('../otherDirectory/baz.js');" withSourceURL:[NSURL fileURLWithPath:@"/directory" isDirectory:YES]];
         JSValue *null = [JSValue valueWithNullInContext:context];
         checkModuleCodeRan(context, promise, null);
+        checkResult(@"Context should call willEvaluateModule: three times", context.willEvaluateModuleCallCount == 3);
+        checkResult(@"Context should call didEvaluateModule: three times", context.didEvaluateModuleCallCount == 3);
+        checkResult(@"Context should see bar.js url", !!context.sawBarJS);
+        checkResult(@"Context should see foo.js url", !!context.sawFooJS);
     }
 }
 
@@ -1983,6 +2014,10 @@ static void testLoaderResolvesAbsoluteScriptURL()
         JSValue *promise = [context evaluateScript:@"import('/directory/bar.js');"];
         JSValue *null = [JSValue valueWithNullInContext:context];
         checkModuleCodeRan(context, promise, null);
+        checkResult(@"Context should call willEvaluateModule: once", context.willEvaluateModuleCallCount == 1);
+        checkResult(@"Context should call didEvaluateModule: once", context.didEvaluateModuleCallCount == 1);
+        checkResult(@"Context should see bar.js url", !!context.sawBarJS);
+        checkResult(@"Context should not see foo.js url", !context.sawFooJS);
     }
 }
 
@@ -1995,6 +2030,10 @@ static void testLoaderRejectsNilScriptURL()
         context.moduleLoaderDelegate = context;
         JSValue *promise = [context evaluateScript:@"import('../otherDirectory/baz.js');"];
         checkModuleWasRejected(context, promise);
+        checkResult(@"Context should call willEvaluateModule: zero times", context.willEvaluateModuleCallCount == 0);
+        checkResult(@"Context should call didEvaluateModule: zero times", context.didEvaluateModuleCallCount == 0);
+        checkResult(@"Context should not see bar.js url", !context.sawBarJS);
+        checkResult(@"Context should not see foo.js url", !context.sawFooJS);
     }
 }
 
index 53e375b..faaa5b9 100644 (file)
@@ -1,3 +1,32 @@
+2019-04-24  Saam Barati  <sbarati@apple.com>
+
+        Add SPI callbacks for before and after module execution
+        https://bugs.webkit.org/show_bug.cgi?id=197244
+        <rdar://problem/50180511>
+
+        Reviewed by Yusuke Suzuki.
+
+        This is helpful for clients that want to profile execution of modules
+        in some way. E.g, if they want to time module execution time.
+
+        * API/JSAPIGlobalObject.h:
+        * API/JSAPIGlobalObject.mm:
+        (JSC::JSAPIGlobalObject::moduleLoaderEvaluate):
+        * API/JSContextPrivate.h:
+        * API/tests/testapi.mm:
+        (+[JSContextFetchDelegate contextWithBlockForFetch:]):
+        (-[JSContextFetchDelegate willEvaluateModule:]):
+        (-[JSContextFetchDelegate didEvaluateModule:]):
+        (testFetch):
+        (testFetchWithTwoCycle):
+        (testFetchWithThreeCycle):
+        (testLoaderResolvesAbsoluteScriptURL):
+        (testLoaderRejectsNilScriptURL):
+        * runtime/JSModuleLoader.cpp:
+        (JSC::JSModuleLoader::evaluate):
+        (JSC::JSModuleLoader::evaluateNonVirtual):
+        * runtime/JSModuleLoader.h:
+
 2019-04-23  Yusuke Suzuki  <ysuzuki@apple.com>
 
         [JSC] Shrink DFG::MinifiedNode
index 9d1c221..4c68b9f 100644 (file)
@@ -340,6 +340,11 @@ JSValue JSModuleLoader::evaluate(ExecState* exec, JSValue key, JSValue moduleRec
     if (globalObject->globalObjectMethodTable()->moduleLoaderEvaluate)
         return globalObject->globalObjectMethodTable()->moduleLoaderEvaluate(globalObject, exec, this, key, moduleRecordValue, scriptFetcher);
 
+    return evaluateNonVirtual(exec, key, moduleRecordValue, scriptFetcher);
+}
+
+JSValue JSModuleLoader::evaluateNonVirtual(ExecState* exec, JSValue, JSValue moduleRecordValue, JSValue)
+{
     if (auto* moduleRecord = jsDynamicCast<AbstractModuleRecord*>(exec->vm(), moduleRecordValue))
         return moduleRecord->evaluate(exec);
     return jsUndefined();
index 33ebc42..bda5143 100644 (file)
@@ -80,6 +80,7 @@ public:
 
     // Additional platform dependent hooked APIs.
     JSValue evaluate(ExecState*, JSValue key, JSValue moduleRecord, JSValue scriptFetcher);
+    JSValue evaluateNonVirtual(ExecState*, JSValue key, JSValue moduleRecord, JSValue scriptFetcher);
 
     // Utility functions.
     JSModuleNamespaceObject* getModuleNamespaceObject(ExecState*, JSValue moduleRecord);