Skip to content

Commit 3083d3a

Browse files
committed
expose micotask checkpoint, add class.extend, fix type conversions
1 parent 21f15f0 commit 3083d3a

File tree

11 files changed

+228
-14
lines changed

11 files changed

+228
-14
lines changed

NativeScript/ffi/CFunction.mm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#include "ObjCBridge.h"
44
#include "ffi/NativeScriptException.h"
55
#include "ffi/Tasks.h"
6-
#ifdef ENABLE_JS_ENGINE
6+
#ifdef ENABLE_JS_RUNTIME
77
#include "jsr.h"
88
#endif
99

@@ -79,7 +79,7 @@
7979
}
8080
}
8181

82-
#ifdef ENABLE_JS_ENGINE
82+
#ifdef ENABLE_JS_RUNTIME
8383
if (strcmp(name, "UIApplicationMain") == 0 || strcmp(name, "NSApplicationMain") == 0) {
8484
void **avaluesPtr = new void*[cif->argc];
8585
memcpy(avaluesPtr, avalues, cif->argc * sizeof(void*));

NativeScript/ffi/Class.mm

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,13 @@ void defineProtocolMembers(napi_env env, ObjCClassMemberMap& members, napi_value
554554

555555
bridgeState->classesByPointer[nativeClass] = this;
556556

557+
// Add the 'extend' static method to all native classes (not NativeObject)
558+
if (!isNativeObject) {
559+
napi_value extendMethod;
560+
napi_create_function(env, "extend", NAPI_AUTO_LENGTH, ClassBuilder::ExtendCallback, nullptr, &extendMethod);
561+
napi_set_named_property(env, constructor, "extend", extendMethod);
562+
}
563+
557564
if (!hasMembers) return;
558565

559566
ObjCClassMember::defineMembers(env, members, offset, constructor);

NativeScript/ffi/ClassBuilder.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ class ClassBuilder : public ObjCClass {
2424
napi_value func = nullptr);
2525

2626
void build();
27+
28+
// Static callback for extending native classes
29+
static napi_value ExtendCallback(napi_env env, napi_callback_info info);
2730

2831
MethodMap exposedMethods;
2932
std::unordered_set<ObjCProtocol*> protocols;

NativeScript/ffi/ClassBuilder.mm

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,4 +259,110 @@
259259
}
260260
}
261261

262+
// Static method to extend native classes with JavaScript-defined methods
263+
napi_value ClassBuilder::ExtendCallback(napi_env env, napi_callback_info info) {
264+
size_t argc = 2;
265+
napi_value args[2];
266+
napi_value thisArg;
267+
268+
napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr);
269+
270+
if (argc < 1) {
271+
napi_throw_error(env, nullptr, "extend() requires at least one parameter with method definitions");
272+
return nullptr;
273+
}
274+
275+
// Validate that the first argument is an object
276+
napi_valuetype argType;
277+
napi_typeof(env, args[0], &argType);
278+
if (argType != napi_object) {
279+
napi_throw_error(env, nullptr, "extend() first parameter must be an object");
280+
return nullptr;
281+
}
282+
283+
// Get the native class from 'this' (the constructor function)
284+
Class baseNativeClass = nullptr;
285+
napi_unwrap(env, thisArg, (void**)&baseNativeClass);
286+
287+
if (baseNativeClass == nullptr) {
288+
napi_throw_error(env, nullptr, "extend() can only be called on native class constructors");
289+
return nullptr;
290+
}
291+
292+
// Create a unique class name
293+
napi_value baseClassName;
294+
napi_get_named_property(env, thisArg, "name", &baseClassName);
295+
static char baseClassNameBuf[512];
296+
napi_get_value_string_utf8(env, baseClassName, baseClassNameBuf, 512, nullptr);
297+
298+
std::string newClassName = baseClassNameBuf;
299+
newClassName += "_Extended_";
300+
newClassName += std::to_string(rand());
301+
302+
// Create the new constructor function that extends the base
303+
napi_value newConstructor;
304+
napi_define_class(env, newClassName.c_str(), newClassName.length(),
305+
[](napi_env env, napi_callback_info info) -> napi_value {
306+
// Constructor implementation - delegate to base class
307+
napi_value thisArg;
308+
napi_get_cb_info(env, info, nullptr, nullptr, &thisArg, nullptr);
309+
return thisArg;
310+
}, nullptr, 0, nullptr, &newConstructor);
311+
312+
// Set up JavaScript inheritance from the base class
313+
napi_inherits(env, newConstructor, thisArg);
314+
315+
// Get prototype for adding methods
316+
napi_value newPrototype;
317+
napi_get_named_property(env, newConstructor, "prototype", &newPrototype);
318+
319+
// Add methods from the first parameter to the prototype
320+
napi_value methodNames;
321+
napi_get_all_property_names(env, args[0], napi_key_own_only, napi_key_skip_symbols,
322+
napi_key_numbers_to_strings, &methodNames);
323+
324+
uint32_t methodCount = 0;
325+
napi_get_array_length(env, methodNames, &methodCount);
326+
327+
for (uint32_t i = 0; i < methodCount; i++) {
328+
napi_value methodName, methodFunc;
329+
napi_get_element(env, methodNames, i, &methodName);
330+
331+
static char methodNameBuf[512];
332+
napi_get_value_string_utf8(env, methodName, methodNameBuf, 512, nullptr);
333+
std::string name = methodNameBuf;
334+
335+
napi_get_named_property(env, args[0], name.c_str(), &methodFunc);
336+
337+
// Add the method to the prototype
338+
napi_set_named_property(env, newPrototype, name.c_str(), methodFunc);
339+
}
340+
341+
// Handle optional second parameter for protocols
342+
if (argc >= 2) {
343+
napi_valuetype secondArgType;
344+
napi_typeof(env, args[1], &secondArgType);
345+
if (secondArgType == napi_object) {
346+
napi_value protocols;
347+
bool hasProtocols = false;
348+
napi_has_named_property(env, args[1], "protocols", &hasProtocols);
349+
350+
if (hasProtocols) {
351+
napi_get_named_property(env, args[1], "protocols", &protocols);
352+
napi_set_named_property(env, newConstructor, "ObjCProtocols", protocols);
353+
}
354+
}
355+
}
356+
357+
// Use ClassBuilder to create the native class and bridge the methods
358+
ClassBuilder* builder = new ClassBuilder(env, newConstructor);
359+
builder->build();
360+
361+
// Register the builder in the bridge state
362+
auto bridgeState = ObjCBridgeState::InstanceData(env);
363+
bridgeState->classesByPointer[builder->nativeClass] = builder;
364+
365+
return newConstructor;
366+
}
367+
262368
} // namespace nativescript

NativeScript/ffi/Closure.mm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
#include "ffi/NativeScriptException.h"
99
#include "js_native_api.h"
1010
#include "js_native_api_types.h"
11-
#ifdef ENABLE_JS_ENGINE
11+
#ifdef ENABLE_JS_RUNTIME
1212
#include "jsr.h"
1313
#endif
1414
#include "node_api_util.h"
@@ -25,7 +25,7 @@ inline void JSCallbackInner(Closure* closure, napi_value func, napi_value thisAr
2525
size_t argc, bool* done, void* ret) {
2626
napi_env env = closure->env;
2727

28-
#ifdef ENABLE_JS_ENGINE
28+
#ifdef ENABLE_JS_RUNTIME
2929
NapiScope scope(env);
3030
#endif
3131

NativeScript/ffi/Interop.mm

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,10 @@ napi_value interop_stringFromCString(napi_env env, napi_callback_info info) {
342342
napi_valuetype type;
343343
napi_typeof(env, arg, &type);
344344

345+
if (type == napi_string) {
346+
return arg;
347+
}
348+
345349
if (type != napi_object) {
346350
napi_throw_type_error(env, "TypeError", "Expected an object");
347351
return nullptr;

NativeScript/ffi/NativeScriptException.mm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#include <sstream>
44
#include "js_native_api.h"
55
#include "js_native_api_types.h"
6-
#ifdef ENABLE_JS_ENGINE
6+
#ifdef ENABLE_JS_RUNTIME
77
#include "jsr.h"
88
#endif
99
#include "native_api_util.h"
@@ -50,7 +50,7 @@
5050
}
5151

5252
void NativeScriptException::OnUncaughtError(napi_env env, napi_value error) {
53-
#ifdef ENABLE_JS_ENGINE
53+
#ifdef ENABLE_JS_RUNTIME
5454
NapiScope scope(env);
5555
#endif
5656

NativeScript/ffi/TypeConv.mm

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -910,8 +910,17 @@ void toNative(napi_env env, napi_value value, void* result, bool* shouldFree,
910910
}
911911

912912
case napi_object: {
913-
FunctionReference* ref = FunctionReference::unwrap(env, value);
914-
*res = ref->getFunctionPointer(signatureOffset);
913+
if (Pointer::isInstance(env, value)) {
914+
Pointer* ptr = Pointer::unwrap(env, value);
915+
*res = ptr->data;
916+
} else if (Reference::isInstance(env, value)) {
917+
Reference* ref = Reference::unwrap(env, value);
918+
*res = ref->data;
919+
} else {
920+
FunctionReference* ref = FunctionReference::unwrap(env, value);
921+
*res = ref->getFunctionPointer(signatureOffset);
922+
}
923+
return;
915924
}
916925

917926
case napi_function: {

NativeScript/napi/v8/jsr.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ JSR::JSR(): isolate(nullptr) {
8585

8686
if (!JSR::s_mainThreadInitialized) {
8787
JSR::platform = v8::platform::NewDefaultPlatform().release();
88+
v8::V8::SetFlagsFromString("--expose_gc");
8889
v8::V8::InitializePlatform(JSR::platform);
8990
v8::V8::Initialize();
9091
JSR::s_mainThreadInitialized = true;

NativeScript/runtime/Runtime.cpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ Runtime::~Runtime() {
6060
}
6161
}
6262

63+
napi_value drainMicrotasks(napi_env env, napi_callback_info cbinfo) {
64+
js_execute_pending_jobs(env);
65+
return nullptr;
66+
}
67+
6368
void Runtime::Init(bool isWorker) {
6469
js_set_runtime_flags("");
6570

@@ -118,11 +123,90 @@ void Runtime::Init(bool isWorker) {
118123
napi_create_string_utf8(env_, CompatScript, NAPI_AUTO_LENGTH, &compatScript);
119124
napi_run_script(env_, compatScript, &result);
120125

126+
#ifdef TARGET_ENGINE_V8
127+
const char *PromiseProxyScript = R"(
128+
// Ensure that Promise callbacks are executed on the
129+
// same thread on which they were created
130+
(() => {
131+
global.Promise = new Proxy(global.Promise, {
132+
construct: function(target, args) {
133+
let origFunc = args[0];
134+
let runloop = CFRunLoopGetCurrent();
135+
136+
let promise = new target(function(resolve, reject) {
137+
function isFulfilled() {
138+
return !resolve;
139+
}
140+
function markFulfilled() {
141+
origFunc = null;
142+
resolve = null;
143+
reject = null;
144+
}
145+
origFunc(value => {
146+
if (isFulfilled()) {
147+
return;
148+
}
149+
const resolveCall = resolve.bind(this, value);
150+
if (runloop === CFRunLoopGetCurrent()) {
151+
markFulfilled();
152+
resolveCall();
153+
} else {
154+
CFRunLoopPerformBlock(runloop, kCFRunLoopDefaultMode, resolveCall);
155+
CFRunLoopWakeUp(runloop);
156+
markFulfilled();
157+
}
158+
}, reason => {
159+
if (isFulfilled()) {
160+
return;
161+
}
162+
const rejectCall = reject.bind(this, reason);
163+
if (runloop === CFRunLoopGetCurrent()) {
164+
markFulfilled();
165+
rejectCall();
166+
} else {
167+
CFRunLoopPerformBlock(runloop, kCFRunLoopDefaultMode, rejectCall);
168+
CFRunLoopWakeUp(runloop);
169+
markFulfilled();
170+
}
171+
});
172+
});
173+
174+
return new Proxy(promise, {
175+
get: function(target, name) {
176+
let orig = target[name];
177+
if (name === "then" || name === "catch" || name === "finally") {
178+
return orig.bind(target);
179+
}
180+
return typeof orig === 'function' ? function(x) {
181+
if (runloop === CFRunLoopGetCurrent()) {
182+
orig.bind(target, x)();
183+
return target;
184+
}
185+
CFRunLoopPerformBlock(runloop, kCFRunLoopDefaultMode, orig.bind(target, x));
186+
CFRunLoopWakeUp(runloop);
187+
return target;
188+
} : orig;
189+
}
190+
});
191+
}
192+
});
193+
})();
194+
)";
195+
196+
napi_value promiseProxyScript;
197+
napi_create_string_utf8(env_, PromiseProxyScript, NAPI_AUTO_LENGTH, &promiseProxyScript);
198+
napi_run_script(env_, promiseProxyScript, &result);
199+
#endif // TARGET_ENGINE_V8
200+
121201
if (isWorker) {
122202
napi_property_descriptor prop = napi_util::desc("self", global);
123203
napi_define_properties(env_, global, 1, &prop);
124204
}
125205

206+
napi_property_descriptor prop = napi_util::desc(
207+
"__drainMicrotaskQueue", drainMicrotasks, nullptr);
208+
napi_define_properties(env_, global, 1, &prop);
209+
126210
modules_.Init(env_, global);
127211

128212
const char* metadata_path = std::getenv("NS_METADATA_PATH");

0 commit comments

Comments
 (0)