diff --git a/test-app/app/src/main/assets/app/mainpage.js b/test-app/app/src/main/assets/app/mainpage.js index 93cabd2be..14a3d0553 100644 --- a/test-app/app/src/main/assets/app/mainpage.js +++ b/test-app/app/src/main/assets/app/mainpage.js @@ -71,4 +71,5 @@ require('./tests/testNativeTimers'); require("./tests/testPostFrameCallback"); require("./tests/console/logTests.js"); require('./tests/testURLImpl.js'); -require('./tests/testURLSearchParamsImpl.js'); \ No newline at end of file +require('./tests/testURLSearchParamsImpl.js'); +require('./tests/testPerformanceNow'); \ No newline at end of file diff --git a/test-app/app/src/main/assets/app/tests/testPerformanceNow.js b/test-app/app/src/main/assets/app/tests/testPerformanceNow.js new file mode 100644 index 000000000..0b4e70363 --- /dev/null +++ b/test-app/app/src/main/assets/app/tests/testPerformanceNow.js @@ -0,0 +1,21 @@ +describe('performance.now()', () => { + it('returns increasing high-resolution time', () => { + const t1 = performance.now(); + const t2 = performance.now(); + expect(typeof t1).toBe('number'); + expect(isNaN(t1)).toBe(false); + expect(t2).not.toBeLessThan(t1); // non-decreasing + // Should be relative (well below 1h after startup) + expect(t1).toBeLessThan(60 * 60 * 1000); + }); + + it('advances over real time', (done) => { + const t1 = performance.now(); + setTimeout(() => { + const t2 = performance.now(); + // 8ms threshold accounts for timer clamping on some devices + expect(t2 - t1).not.toBeLessThan(8); + done(); + }, 10); + }); +}); diff --git a/test-app/runtime/src/main/cpp/Runtime.cpp b/test-app/runtime/src/main/cpp/Runtime.cpp index 55706e1b3..afbd10eb2 100644 --- a/test-app/runtime/src/main/cpp/Runtime.cpp +++ b/test-app/runtime/src/main/cpp/Runtime.cpp @@ -34,6 +34,7 @@ #include "URLImpl.h" #include "URLSearchParamsImpl.h" #include "URLPatternImpl.h" +#include #ifdef APPLICATION_IN_DEBUG // #include "NetworkDomainCallbackHandlers.h" @@ -487,6 +488,10 @@ Isolate* Runtime::PrepareV8Runtime(const string& filesPath, const string& native tns::instrumentation::Frame isolateFrame; auto isolate = Isolate::New(create_params); + // Capture start and realtime origin + // MonotonicallyIncreasingTime returns seconds as double; store for performance.now() + m_startTime = platform->MonotonicallyIncreasingTime(); + m_realtimeOrigin = platform->CurrentClockTimeMillis(); isolateFrame.log("Isolate.New"); s_isolate2RuntimesCache[isolate] = this; @@ -523,6 +528,15 @@ Isolate* Runtime::PrepareV8Runtime(const string& filesPath, const string& native globalTemplate->Set(ArgConverter::ConvertToV8String(isolate, "__exit"), FunctionTemplate::New(isolate, CallbackHandlers::ExitMethodCallback)); globalTemplate->Set(ArgConverter::ConvertToV8String(isolate, "__runtimeVersion"), ArgConverter::ConvertToV8String(isolate, NATIVE_SCRIPT_RUNTIME_VERSION), readOnlyFlags); globalTemplate->Set(ArgConverter::ConvertToV8String(isolate, "__time"), FunctionTemplate::New(isolate, CallbackHandlers::TimeCallback)); + + // performance object (performance.now() + timeOrigin) + { + auto performanceTemplate = ObjectTemplate::New(isolate); + auto nowFunc = FunctionTemplate::New(isolate, Runtime::PerformanceNowCallback); + performanceTemplate->Set(ArgConverter::ConvertToV8String(isolate, "now"), nowFunc); + performanceTemplate->Set(ArgConverter::ConvertToV8String(isolate, "timeOrigin"), Number::New(isolate, m_realtimeOrigin)); + globalTemplate->Set(ArgConverter::ConvertToV8String(isolate, "performance"), performanceTemplate); + } globalTemplate->Set(ArgConverter::ConvertToV8String(isolate, "__releaseNativeCounterpart"), FunctionTemplate::New(isolate, CallbackHandlers::ReleaseNativeCounterpartCallback)); globalTemplate->Set(ArgConverter::ConvertToV8String(isolate, "__markingMode"), Number::New(isolate, m_objectManager->GetMarkingMode()), readOnlyFlags); globalTemplate->Set(ArgConverter::ConvertToV8String(isolate, "__runOnMainThread"), FunctionTemplate::New(isolate, CallbackHandlers::RunOnMainThreadCallback)); @@ -741,6 +755,14 @@ void Runtime::SetManualInstrumentationMode(jstring mode) { } } +void Runtime::PerformanceNowCallback(const v8::FunctionCallbackInfo& args) { + auto isolate = args.GetIsolate(); + auto runtime = Runtime::GetRuntime(isolate); + // Difference in seconds * 1000 for ms + double ms = (platform->MonotonicallyIncreasingTime() - runtime->m_startTime) * 1000.0; + args.GetReturnValue().Set(ms); +} + void Runtime::DestroyRuntime() { s_id2RuntimeCache.erase(m_id); s_isolate2RuntimesCache.erase(m_isolate); diff --git a/test-app/runtime/src/main/cpp/Runtime.h b/test-app/runtime/src/main/cpp/Runtime.h index 0f162d1ff..bedb70e43 100644 --- a/test-app/runtime/src/main/cpp/Runtime.h +++ b/test-app/runtime/src/main/cpp/Runtime.h @@ -108,6 +108,15 @@ class Runtime { bool m_isMainThread; + // High resolution timing origin values + // m_startTime: monotonic clock time captured at isolate creation + // m_realtimeOrigin: wall-clock time origin (milliseconds) captured at isolate creation + double m_startTime {0}; + double m_realtimeOrigin {0}; + + // performance.now() callback + static void PerformanceNowCallback(const v8::FunctionCallbackInfo& args); + v8::Isolate* PrepareV8Runtime(const std::string& filesPath, const std::string& nativeLibsDir, const std::string& packageName, bool isDebuggable, const std::string& callingDir, const std::string& profilerOutputDir, const int maxLogcatObjectSize, const bool forceLog); jobject ConvertJsValueToJavaObject(JEnv& env, const v8::Local& value, int classReturnType); static int GetAndroidVersion();