Skip to content

Commit af52a16

Browse files
committed
fix(runtime): Expose base class missing properties in inheritors
Fix issue with missing `imageInsets`, `landscapeImagePhone`, `title` properties of UITabBarItem. These properties are declared in `UIBarItem` but it actually doesn't implement them. Some of its successors like `UITabBarItem` however do, and we need to expose them there. This regression has been introduced with #1086.
1 parent 4942060 commit af52a16

File tree

4 files changed

+95
-24
lines changed

4 files changed

+95
-24
lines changed

src/NativeScript/GlobalObject.mm

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,10 +353,12 @@ static void runLoopBeforeWaitingPerformWork(CFRunLoopObserverRef observer, CFRun
353353

354354
auto protocol = createProtocolWrapper(globalObject, static_cast<const ProtocolMeta*>(symbolMeta), aProtocol);
355355
strongSymbolWrapper = protocol;
356+
// Protocols that are not implemented or referred with @protocol at compile time do not have corresponding
357+
// protocol objects at runtime. `objc_getProtocol` returns `nullptr for them!
358+
// See Protocol Objects section at https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProtocols.html#//apple_ref/doc/uid/TP30001163-CH15
356359
if (aProtocol) {
357360
globalObject->_objCProtocolWrappers.insert({ aProtocol, protocol });
358361
}
359-
360362
break;
361363
}
362364
case Union: {

src/NativeScript/ObjC/ObjCPrototype.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313
#include <wtf/RetainPtr.h>
1414

1515
namespace Metadata {
16+
1617
struct BaseClassMeta;
17-
}
18+
struct PropertyMeta;
19+
20+
} // namespace Metadata
1821

1922
namespace NativeScript {
2023
class ObjCPrototype : public JSC::JSDestructibleObject {
@@ -39,6 +42,8 @@ class ObjCPrototype : public JSC::JSDestructibleObject {
3942

4043
void materializeProperties(JSC::VM& vm, GlobalObject* globalObject);
4144

45+
void defineNativeProperty(JSC::VM& vm, GlobalObject* globalObject, const Metadata::PropertyMeta* propertyMeta);
46+
4247
const Metadata::BaseClassMeta* metadata() const {
4348
return this->_metadata;
4449
}

src/NativeScript/ObjC/ObjCPrototype.mm

Lines changed: 76 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -171,32 +171,86 @@ static EncodedJSValue JSC_HOST_CALL getIterator(ExecState* execState) {
171171
Base::getOwnPropertyNames(object, execState, propertyNames, enumerationMode);
172172
}
173173

174+
void ObjCPrototype::defineNativeProperty(VM& vm, GlobalObject* globalObject, const PropertyMeta* propertyMeta) {
175+
SymbolLoader::instance().ensureModule(propertyMeta->topLevelModule());
176+
177+
const MethodMeta* getter = (propertyMeta->hasGetter() && propertyMeta->getter()->isAvailable()) ? propertyMeta->getter() : nullptr;
178+
const MethodMeta* setter = (propertyMeta->hasSetter() && propertyMeta->setter()->isAvailable()) ? propertyMeta->setter() : nullptr;
179+
180+
PropertyDescriptor descriptor;
181+
descriptor.setConfigurable(true);
182+
Strong<ObjCMethodWrapper> strongGetter;
183+
Strong<ObjCMethodWrapper> strongSetter;
184+
if (getter) {
185+
MembersCollection getters = { getter };
186+
strongGetter = ObjCMethodWrapper::create(vm, globalObject, globalObject->objCMethodWrapperStructure(), getters);
187+
descriptor.setGetter(strongGetter.get());
188+
}
189+
190+
if (setter) {
191+
MembersCollection setters = { setter };
192+
strongSetter = ObjCMethodWrapper::create(vm, globalObject, globalObject->objCMethodWrapperStructure(), setters);
193+
descriptor.setSetter(strongSetter.get());
194+
}
195+
196+
Base::defineOwnProperty(this, globalObject->globalExec(), Identifier::fromString(globalObject->globalExec(), propertyMeta->jsName()), descriptor, false);
197+
}
198+
174199
void ObjCPrototype::materializeProperties(VM& vm, GlobalObject* globalObject) {
175-
std::vector<const PropertyMeta*> properties = this->_metadata->instancePropertiesWithProtocols(this->klass());
176-
177-
for (const PropertyMeta* propertyMeta : properties) {
178-
SymbolLoader::instance().ensureModule(propertyMeta->topLevelModule());
179-
180-
const MethodMeta* getter = (propertyMeta->getter() != nullptr && propertyMeta->getter()->isAvailable()) ? propertyMeta->getter() : nullptr;
181-
const MethodMeta* setter = (propertyMeta->setter() != nullptr && propertyMeta->setter()->isAvailable()) ? propertyMeta->setter() : nullptr;
182-
183-
PropertyDescriptor descriptor;
184-
descriptor.setConfigurable(true);
185-
Strong<ObjCMethodWrapper> strongGetter;
186-
Strong<ObjCMethodWrapper> strongSetter;
187-
if (getter) {
188-
MembersCollection getters = { getter };
189-
strongGetter = ObjCMethodWrapper::create(vm, globalObject, globalObject->objCMethodWrapperStructure(), getters);
190-
descriptor.setGetter(strongGetter.get());
191-
}
200+
// The cycle here works around an issue with incorrect public headers of some iOS system frameworks.
201+
// In particular:
202+
// * UIBarItem doesn't define 6 of its declared properties (enabled, image, imageInsets,
203+
// landscapeImagePhone, landscapeImagePhoneInsets and title) but its inheritors UIBarButtonItem and
204+
// UITabBarItem do
205+
// * MTLRenderPassAttachmentDescriptor doesn't define 11 of its properties but it's inheritors
206+
// MTLRenderPassDepthAttachmentDescriptor, MTLRenderPassColorAttachmentDescriptor and
207+
// MTLRenderPassStencilAttachmentDescriptor do.
208+
// As a result we were not providing their implementation in JS before we started looking for missing
209+
// properties in the base class. This additional overhead increased the time spent in materializeProperties
210+
// from ~5.3 sec to ~7.5 sec (~40%) when running TestRunner with ApiIterator test enabled in RelWithDebInfo configuration
211+
// on an iPhone 6s device and from 3.0-3.2 to 4.4 sec (~40%) on an iPhone X
212+
213+
// std::chrono::time_point<std::chrono::system_clock> startTime = std::chrono::system_clock::now();
214+
// int addedProps = 0;
215+
216+
const BaseClassMeta* meta = this->metadata();
217+
Class klass = this->klass();
218+
while (meta) {
219+
std::vector<const PropertyMeta*> properties = meta->instancePropertiesWithProtocols(nullptr);
220+
221+
for (const PropertyMeta* propertyMeta : properties) {
222+
bool shouldDefine = false;
223+
224+
if (klass == this->klass()) {
225+
// Property is coming from this class, define it if available
226+
shouldDefine = propertyMeta->isAvailableInClass(klass, false);
227+
} else {
228+
// Property is coming from a base class, define it as our property if isn't available there, but we've got it
229+
shouldDefine = !propertyMeta->isAvailableInClass(klass, false) && propertyMeta->isAvailableInClass(this->klass(), false);
230+
// addedProps += shouldDefine ? 1 : 0;
231+
}
192232

193-
if (setter) {
194-
MembersCollection setters = { setter };
195-
strongSetter = ObjCMethodWrapper::create(vm, globalObject, globalObject->objCMethodWrapperStructure(), setters);
196-
descriptor.setSetter(strongSetter.get());
233+
if (shouldDefine) {
234+
this->defineNativeProperty(vm, globalObject, propertyMeta);
235+
}
197236
}
198237

199-
Base::defineOwnProperty(this, globalObject->globalExec(), Identifier::fromString(globalObject->globalExec(), propertyMeta->jsName()), descriptor, false);
238+
if (klass == this->klass() && meta->type() == Interface) {
239+
meta = static_cast<const InterfaceMeta*>(meta)->baseMeta();
240+
klass = meta ? objc_getClass(meta->name()) : nullptr;
241+
} else {
242+
// Check only properties from the direct base class and then stop.
243+
// All the cases that we need to fix are like that so we don't have
244+
// to pay the additional overhead of looking intothe whole inheritance chain.
245+
meta = nullptr;
246+
}
200247
}
248+
// std::chrono::time_point<std::chrono::system_clock> endTime = std::chrono::system_clock::now();
249+
// double duration = std::chrono::duration_cast<std::chrono::microseconds>(endTime - startTime).count() / 1000.;
250+
// static double totalDuration = 0;
251+
// totalDuration += duration;
252+
//
253+
// std::cout << "**** materializeProperties " << this->metadata()->jsName() << ": " << duration << "(Total: " << totalDuration << ") added " << addedProps << std::endl;
201254
}
255+
202256
}

tests/TestRunner/app/ApiTests.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,16 @@ describe(module.id, function () {
687687
expect(result).toBe('method called');
688688
});
689689

690+
it("Unimplemented properties from UIBarItem class should be provided by the inheritors", function () {
691+
var classConstructors = [ UIBarButtonItem, UITabBarItem ];
692+
var props = ["enabled", "image", "imageInsets", "landscapeImagePhone", "landscapeImagePhoneInsets", "title"];
693+
for (var klass of classConstructors) {
694+
for (var prop of props) {
695+
expect(klass.prototype[prop]).toBeDefined(`${prop} must be defined in the prototype of ${klass}`);
696+
}
697+
}
698+
});
699+
690700
if (TNSIsConfigurationDebug) {
691701
it("ApiIterator", function () {
692702
var counter = 0;

0 commit comments

Comments
 (0)