diff --git a/NativeScript/runtime/ArgConverter.mm b/NativeScript/runtime/ArgConverter.mm index d5464db8..ed32c9d7 100644 --- a/NativeScript/runtime/ArgConverter.mm +++ b/NativeScript/runtime/ArgConverter.mm @@ -928,19 +928,31 @@ tns::Assert(false, isolate); } } else { - Class knownClass = objc_getClass(meta->name()); - KnownUnknownClassPair pair(knownClass, klass); - Local ctorFuncTemplate = cache->ObjectCtorInitializer( - context, static_cast(meta), pair, additionalProtocols); - Local ctorFunc; - bool success = ctorFuncTemplate->GetFunction(context).ToLocal(&ctorFunc); - tns::Assert(success, isolate); - - Local prototypeValue; - success = - ctorFunc->Get(context, tns::ToV8String(isolate, "prototype")).ToLocal(&prototypeValue); - tns::Assert(success, isolate); - Local prototype = prototypeValue.As(); + auto cached = additionalProtocols.empty() ? cache->MetaInstancePrototypes.find(meta) + : cache->MetaInstancePrototypes.end(); + Local prototype; + bool success; + if (cached != cache->MetaInstancePrototypes.end()) { + prototype = cached->second->Get(isolate); + } else { + Class knownClass = objc_getClass(meta->name()); + KnownUnknownClassPair pair(knownClass, klass); + Local ctorFuncTemplate = cache->ObjectCtorInitializer( + context, static_cast(meta), pair, additionalProtocols); + Local ctorFunc; + success = ctorFuncTemplate->GetFunction(context).ToLocal(&ctorFunc); + tns::Assert(success, isolate); + + Local prototypeValue; + success = + ctorFunc->Get(context, tns::ToV8String(isolate, "prototype")).ToLocal(&prototypeValue); + tns::Assert(success, isolate); + prototype = prototypeValue.As(); + if (additionalProtocols.empty()) { + cache->MetaInstancePrototypes.emplace( + meta, std::make_unique>(isolate, prototype)); + } + } if (!receiver->SetPrototype(context, prototype).To(&success) || !success) { tns::Assert(false, isolate); diff --git a/NativeScript/runtime/Caches.cpp b/NativeScript/runtime/Caches.cpp index dc1a3344..75e42bcb 100644 --- a/NativeScript/runtime/Caches.cpp +++ b/NativeScript/runtime/Caches.cpp @@ -22,6 +22,7 @@ Caches::~Caches() { this->Prototypes.clear(); this->ClassPrototypes.clear(); + this->MetaInstancePrototypes.clear(); this->CtorFuncTemplates.clear(); this->CtorFuncs.clear(); this->ProtocolCtorFuncs.clear(); diff --git a/NativeScript/runtime/Caches.h b/NativeScript/runtime/Caches.h index 666d7e63..be8281a8 100644 --- a/NativeScript/runtime/Caches.h +++ b/NativeScript/runtime/Caches.h @@ -145,6 +145,9 @@ class Caches { std::unique_ptr>, TransparentStringHash, TransparentStringEqual> ClassPrototypes; + robin_hood::unordered_map>> + MetaInstancePrototypes; robin_hood::unordered_map< const BaseClassMeta*, std::unique_ptr>> diff --git a/NativeScript/runtime/MetadataBuilder.mm b/NativeScript/runtime/MetadataBuilder.mm index d45d980c..c42c83f8 100644 --- a/NativeScript/runtime/MetadataBuilder.mm +++ b/NativeScript/runtime/MetadataBuilder.mm @@ -546,6 +546,8 @@ throw NativeScriptException( Local ext = External::New(isolate, item, v8::kExternalPointerTypeTagDefault); ctorFuncTemplate = FunctionTemplate::New(isolate, ClassConstructorCallback, ext); + // MetaInstancePrototypes caches this prototype. + ctorFuncTemplate->ReadOnlyPrototype(); ctorFuncTemplate->InstanceTemplate()->SetInternalFieldCount(1); ctorFuncTemplate->SetClassName(tns::ToV8String(isolate, meta->name())); diff --git a/TestRunner/app/tests/ApiTests.js b/TestRunner/app/tests/ApiTests.js index ddd27494..ae50257c 100644 --- a/TestRunner/app/tests/ApiTests.js +++ b/TestRunner/app/tests/ApiTests.js @@ -150,6 +150,21 @@ describe(module.id, function () { expect(array instanceof NSObject).toBe(true); }); + it("keeps a native class prototype read-only", function () { + var original = NSMutableArray.prototype; + var descriptor = Object.getOwnPropertyDescriptor(NSMutableArray, "prototype"); + expect(descriptor.writable).toBe(false); + try { + expect(function () { "use strict"; NSMutableArray.prototype = {}; }).toThrow(); + } finally { + // Keep later array tests working if the prototype became writable again. + NSMutableArray.prototype = original; + } + var array = NSMutableArray.arrayWithCapacity(1); + expect(Object.getPrototypeOf(array)).toBe(original); + expect(array instanceof NSMutableArray).toBe(true); + }); + it("instanceOfDerivedClass", function () { var JSObject = TNSDerivedInterface.extend({}); var object = JSObject.alloc().init();