Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion NativeScript/runtime/ClassBuilder.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#ifndef ClassBuilder_h
#define ClassBuilder_h

#include <string>
#include <unordered_set>

#include "Common.h"
#include "Metadata.h"

Expand Down Expand Up @@ -28,14 +31,25 @@ class ClassBuilder {
static void RegisterBaseTypeScriptExtendsFunction(v8::Local<v8::Context> context);
static void RegisterNativeTypeScriptExtendsFunction(v8::Local<v8::Context> context);
static std::string GetTypeEncoding(const TypeEncoding* typeEncoding, int argsCount);

// Lazily registers an Objective-C subclass for a plain ES `class X extends NativeBase {}`
// constructor function. Returns the registered class, or nil when ctorFunc is not part of a
// native inheritance chain (or the chain goes through a legacy `.extend()`-created class).
// Idempotent: subsequent calls return the cached class from the ctor's ObjCClassWrapper.
static Class EnsureExtendedClass(v8::Local<v8::Context> context, v8::Local<v8::Function> ctorFunc);

// Resolves the Objective-C class that should be instantiated for a construct call, honoring
// `new.target` so that plain ES subclasses of native classes get their own registered class.
static Class ResolveConstructedClass(v8::Local<v8::Context> context, v8::Local<v8::Value> newTarget, Class fallback);
private:
static std::atomic<unsigned long long> classNameCounter_;

static void ExtendCallback(const v8::FunctionCallbackInfo<v8::Value>& info);
static void SuperAccessorGetterCallback(v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info);
static void ExtendedClassConstructorCallback(const v8::FunctionCallbackInfo<v8::Value>& info);

static void ExposeDynamicMethods(v8::Local<v8::Context> context, Class extendedClass, v8::Local<v8::Value> exposedMethods, v8::Local<v8::Value> exposedProtocols, v8::Local<v8::Object> implementationObject);
static void SwizzleRetainRelease(v8::Isolate* isolate, Class extendedClass);
static void ExposeDynamicMethods(v8::Local<v8::Context> context, Class extendedClass, v8::Local<v8::Value> exposedMethods, v8::Local<v8::Value> exposedProtocols, v8::Local<v8::Object> implementationObject, std::unordered_set<std::string>* visitedNames = nullptr);
static void ExposeDynamicMembers(v8::Local<v8::Context> context, Class extendedClass, v8::Local<v8::Object> implementationObject, v8::Local<v8::Object> nativeSignature);
static void VisitMethods(Class extendedClass, std::string methodName, const BaseClassMeta* meta, std::vector<const MethodMeta*>& methodMetas, std::vector<const ProtocolMeta*> exposedProtocols);
static void VisitProperties(std::string propertyName, const BaseClassMeta* meta, std::vector<const PropertyMeta*>& propertyMetas, std::vector<const ProtocolMeta*> exposedProtocols);
Expand Down
374 changes: 272 additions & 102 deletions NativeScript/runtime/ClassBuilder.mm

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions NativeScript/runtime/DataWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -368,18 +368,23 @@ class ObjCDataWrapper : public BaseDataWrapper {

class ObjCClassWrapper : public BaseDataWrapper {
public:
ObjCClassWrapper(Class klazz, bool extendedClass = false)
: klass_(klazz), extendedClass_(extendedClass) {}
ObjCClassWrapper(Class klazz, bool extendedClass = false, bool esDerivedClass = false)
: klass_(klazz), extendedClass_(extendedClass), esDerivedClass_(esDerivedClass) {}

const WrapperType Type() { return WrapperType::ObjCClass; }

Class Klass() { return this->klass_; }

bool ExtendedClass() { return this->extendedClass_; }

// true when the class was registered lazily from a plain ES `class X extends NativeBase {}`
// constructor (see ClassBuilder::EnsureExtendedClass)
bool ESDerivedClass() { return this->esDerivedClass_; }

private:
Class klass_;
bool extendedClass_;
bool esDerivedClass_;
};

class ObjCProtocolWrapper : public BaseDataWrapper {
Expand Down
13 changes: 13 additions & 0 deletions NativeScript/runtime/InlineFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ void InlineFunctions::Init(Local<Context> context) {
" }"
" }"
" },"
" NativeClass(arg) {"
" if (typeof arg === 'function') {"
" return arg;"
" }"
" var options = arg || {};"
" return function (target) {"
" if (options.protocols && options.protocols.length > 0) {"
" target.ObjCProtocols = (target.ObjCProtocols && target.ObjCProtocols instanceof Array ? target.ObjCProtocols.concat(options.protocols) : options.protocols.slice());"
" }"
" return target;"
" }"
" },"
" ObjCMethod() {"
" var name = arguments[0];"
" var hasName = (name !== undefined && typeof name === \"string\");"
Expand Down Expand Up @@ -134,6 +146,7 @@ bool InlineFunctions::IsGlobalFunction(std::string name) {
name == "NSMakeRange" ||
name == "__decorate" ||
name == "__param" ||
name == "NativeClass" ||
name == "ObjCClass" ||
name == "ObjCMethod" ||
name == "ObjC" ||
Expand Down
18 changes: 18 additions & 0 deletions NativeScript/runtime/Interop.mm
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "ArgConverter.h"
#include "ArrayAdapter.h"
#include "Caches.h"
#include "ClassBuilder.h"
#include "Constants.h"
#include "DictionaryAdapter.h"
#include "ExtVector.h"
Expand Down Expand Up @@ -576,6 +577,15 @@ inline bool isBool() {
} else if (argHelper.isObject() && typeEncoding->type == BinaryTypeEncodingType::ClassEncoding) {
Local<Object> obj = arg.As<Object>();
BaseDataWrapper* wrapper = tns::GetValue(isolate, obj);
if (wrapper == nullptr && obj->IsFunction()) {
// A plain ES subclass of a native type passed where a Class is expected
// (e.g. `tableView.registerClassForCellReuseIdentifier(JSClass, ...)`) - lazily
// register its derived class first.
Class ensured = ClassBuilder::EnsureExtendedClass(context, obj.As<v8::Function>());
if (ensured != nil) {
wrapper = tns::GetValue(isolate, obj);
}
}
tns::Assert(wrapper != nullptr && wrapper->Type() == WrapperType::ObjCClass, isolate);
ObjCClassWrapper* classWrapper = static_cast<ObjCClassWrapper*>(wrapper);
Class clazz = classWrapper->Klass();
Expand Down Expand Up @@ -728,6 +738,14 @@ inline bool isBool() {
}
} else {
Local<Object> obj = arg.As<Object>();
if (obj->IsFunction()) {
// A plain ES subclass of a native type passed where an `id` is expected - lazily
// register its derived class and marshal the Objective-C Class object.
Class ensured = ClassBuilder::EnsureExtendedClass(context, obj.As<v8::Function>());
if (ensured != nil) {
return ensured;
}
}
DictionaryAdapter* adapter = [[DictionaryAdapter alloc] initWithJSObject:obj isolate:isolate];
// CFAutorelease(adapter);
return adapter;
Expand Down
61 changes: 53 additions & 8 deletions NativeScript/runtime/MetadataBuilder.mm
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,10 @@
CacheItem<BaseClassMeta>* item = static_cast<CacheItem<BaseClassMeta>*>(info.Data().As<External>()->Value());
Class klass = objc_getClass(item->meta_->name());

// Plain ES `class X extends NativeType {}` subclasses reach this callback through
// super(); use new.target to lazily register (and construct) the derived class.
klass = ClassBuilder::ResolveConstructedClass(context, info.NewTarget(), klass);

const InterfaceMeta* interfaceMeta = static_cast<const InterfaceMeta*>(item->meta_);
ArgConverter::ConstructObject(context, info, klass, interfaceMeta);
} catch (NativeScriptException& ex) {
Expand All @@ -633,19 +637,24 @@

try {
Local<Object> thiz = info.This();
Class klass;
Local<Context> context = isolate->GetCurrentContext();
Class klass = nil;

BaseDataWrapper* wrapper = tns::GetValue(isolate, thiz);
if (wrapper != nullptr && wrapper->Type() == WrapperType::ObjCClass) {
ObjCClassWrapper* classWrapper = static_cast<ObjCClassWrapper*>(wrapper);
klass = classWrapper->Klass();
} else {
} else if (wrapper == nullptr && thiz->IsFunction()) {
// `JSClass.alloc()` where JSClass is a plain ES subclass of a native type that has
// not been constructed yet - lazily register its derived class first.
klass = ClassBuilder::EnsureExtendedClass(context, thiz.As<v8::Function>());
}

if (klass == nil) {
CacheItem<InterfaceMeta>* item = static_cast<CacheItem<InterfaceMeta>*>(info.Data().As<External>()->Value());
const InterfaceMeta* meta = item->meta_;
klass = objc_getClass(meta->name());
}

Local<Context> context = isolate->GetCurrentContext();
ObjCAllocDataWrapper* allocWrapper = new ObjCAllocDataWrapper(klass);
Local<Value> result = ArgConverter::CreateJsWrapper(context, allocWrapper, Local<Object>());
info.GetReturnValue().Set(result);
Expand All @@ -663,15 +672,24 @@

std::string className = item->className_;

Local<Context> context = isolate->GetCurrentContext();
Local<Object> thiz = info.This();
if (thiz->IsFunction()) {
if (BaseDataWrapper* wrapper = tns::GetValue(isolate, thiz)) {
BaseDataWrapper* wrapper = tns::GetValue(isolate, thiz);
if (wrapper != nullptr) {
ObjCClassWrapper* classWrapper = static_cast<ObjCClassWrapper*>(wrapper);
className = class_getName(classWrapper->Klass());
} else {
// Static call through a plain ES subclass of a native type (inherited static),
// e.g. `JSClass.new()` - lazily register the derived class so the invocation
// dispatches to it.
Class ensured = ClassBuilder::EnsureExtendedClass(context, thiz.As<v8::Function>());
if (ensured != nil) {
className = class_getName(ensured);
}
}
}

Local<Context> context = isolate->GetCurrentContext();
Local<Value> result = instanceMethod
? MetadataBuilder::InvokeMethod(context, item->meta_, info.This(), args, className, true)
: MetadataBuilder::InvokeMethod(context, item->meta_, Local<Object>(), args, className, true);
Expand Down Expand Up @@ -720,6 +738,31 @@
MetadataBuilder::InvokeMethod(context, item->meta_->setter(), receiver, args, item->className_, true);
}

// Resolves the class name a static member access should dispatch to, honoring plain ES
// subclasses of native types used as receivers (e.g. `JSClass.someStaticProperty`).
static std::string ResolveStaticReceiverClassName(Local<Context> context, Local<Object> receiver,
const std::string& fallback) {
if (receiver.IsEmpty() || !receiver->IsFunction()) {
return fallback;
}

Isolate* isolate = context->GetIsolate();
BaseDataWrapper* wrapper = tns::GetValue(isolate, receiver);
if (wrapper != nullptr) {
if (wrapper->Type() == WrapperType::ObjCClass) {
return class_getName(static_cast<ObjCClassWrapper*>(wrapper)->Klass());
}
return fallback;
}

Class ensured = ClassBuilder::EnsureExtendedClass(context, receiver.As<v8::Function>());
if (ensured != nil) {
return class_getName(ensured);
}

return fallback;
}

void MetadataBuilder::PropertyNameGetterCallback(Local<v8::Name> name, const PropertyCallbackInfo<Value> &info) {
Isolate* isolate = info.GetIsolate();
CacheItem<PropertyMeta>* item = static_cast<CacheItem<PropertyMeta>*>(info.Data().As<External>()->Value());
Expand All @@ -731,7 +774,8 @@

V8EmptyValueArgs args;
Local<Context> context = isolate->GetCurrentContext();
Local<Value> result = MetadataBuilder::InvokeMethod(context, item->meta_->getter(), Local<Object>(), args, item->className_, false);
std::string className = ResolveStaticReceiverClassName(context, info.This(), item->className_);
Local<Value> result = MetadataBuilder::InvokeMethod(context, item->meta_->getter(), Local<Object>(), args, className, false);
if (!result.IsEmpty()) {
info.GetReturnValue().Set(result);
}
Expand All @@ -748,7 +792,8 @@

V8SimpleValueArgs args(value);
Local<Context> context = isolate->GetCurrentContext();
MetadataBuilder::InvokeMethod(context, item->meta_->setter(), Local<Object>(), args, item->className_, false);
std::string className = ResolveStaticReceiverClassName(context, info.This(), item->className_);
MetadataBuilder::InvokeMethod(context, item->meta_->setter(), Local<Object>(), args, className, false);
}

void MetadataBuilder::StructPropertyGetterCallback(Local<v8::Name> property, const PropertyCallbackInfo<Value>& info) {
Expand Down
Loading
Loading