From 0e882f272f18a7282212e7b61bcd51e31c7330e2 Mon Sep 17 00:00:00 2001 From: Chi Tsai Date: Wed, 11 Dec 2024 16:44:17 -0800 Subject: [PATCH 1/2] [skip ci] Add default implementation for Object.getPrototypeOf and Object.setPrototypeOf (#47996) Summary: Getting and setting an Object's prototype is convoluted. Users have to call into the global object to get the method, then call it. This diff adds a JSI API for Object.getPrototype and Object.setPrototype to make it easy for users. Changelog: [Internal] Reviewed By: fbmal7 Differential Revision: D66562549 --- .../ReactCommon/jsi/jsi/decorator.h | 18 ++++++++++ .../ReactCommon/jsi/jsi/jsi-inl.h | 4 +++ .../react-native/ReactCommon/jsi/jsi/jsi.cpp | 14 ++++++++ .../react-native/ReactCommon/jsi/jsi/jsi.h | 13 +++++++ .../ReactCommon/jsi/jsi/test/testlib.cpp | 34 +++++++++++++++++++ 5 files changed, 83 insertions(+) diff --git a/packages/react-native/ReactCommon/jsi/jsi/decorator.h b/packages/react-native/ReactCommon/jsi/jsi/decorator.h index d952ccbee0f6cc..7136a27430d9e7 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/decorator.h +++ b/packages/react-native/ReactCommon/jsi/jsi/decorator.h @@ -282,6 +282,14 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { plain_.setExternalMemoryPressure(obj, amt); } + void setPrototypeOf(const Object& object, const Value& prototype) override { + plain_.setPrototypeOf(object, prototype); + } + + Value getPrototypeOf(const Object& object) override { + return plain_.getPrototypeOf(object); + } + Value getProperty(const Object& o, const PropNameID& name) override { return plain_.getProperty(o, name); }; @@ -760,6 +768,16 @@ class WithRuntimeDecorator : public RuntimeDecorator { RD::setNativeState(o, state); }; + void setPrototypeOf(const Object& object, const Value& prototype) override { + Around around{with_}; + RD::setPrototypeOf(object, prototype); + } + + Value getPrototypeOf(const Object& object) override { + Around around{with_}; + return RD::getPrototypeOf(object); + } + Value getProperty(const Object& o, const PropNameID& name) override { Around around{with_}; return RD::getProperty(o, name); diff --git a/packages/react-native/ReactCommon/jsi/jsi/jsi-inl.h b/packages/react-native/ReactCommon/jsi/jsi/jsi-inl.h index 111a470288168a..6076c4955be2fc 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/jsi-inl.h +++ b/packages/react-native/ReactCommon/jsi/jsi/jsi-inl.h @@ -84,6 +84,10 @@ inline const Runtime::PointerValue* Runtime::getPointerValue( return value.data_.pointer.ptr_; } +Value Object::getPrototype(Runtime& runtime) const { + return runtime.getPrototypeOf(*this); +} + inline Value Object::getProperty(Runtime& runtime, const char* name) const { return getProperty(runtime, String::createFromAscii(runtime, name)); } diff --git a/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp b/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp index bce1e27f0ec55e..b386565906fe1e 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp +++ b/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp @@ -274,6 +274,20 @@ void Runtime::getPropNameIdData( cb(ctx, false, utf16Str.data(), utf16Str.size()); } +void Runtime::setPrototypeOf(const Object& object, const Value& prototype) { + auto setPrototypeOfFn = global() + .getPropertyAsObject(*this, "Object") + .getPropertyAsFunction(*this, "setPrototypeOf"); + setPrototypeOfFn.call(*this, object, prototype).asObject(*this); +} + +Value Runtime::getPrototypeOf(const Object& object) { + auto setPrototypeOfFn = global() + .getPropertyAsObject(*this, "Object") + .getPropertyAsFunction(*this, "getPrototypeOf"); + return setPrototypeOfFn.call(*this, object); +} + Pointer& Pointer::operator=(Pointer&& other) noexcept { if (ptr_) { ptr_->invalidate(); diff --git a/packages/react-native/ReactCommon/jsi/jsi/jsi.h b/packages/react-native/ReactCommon/jsi/jsi/jsi.h index 4e997aa7fd6c4d..9196510c64b7e0 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/jsi.h +++ b/packages/react-native/ReactCommon/jsi/jsi/jsi.h @@ -339,6 +339,9 @@ class JSI_EXPORT Runtime { const jsi::Object&, std::shared_ptr state) = 0; + virtual void setPrototypeOf(const Object& object, const Value& prototype); + virtual Value getPrototypeOf(const Object& object); + virtual Value getProperty(const Object&, const PropNameID& name) = 0; virtual Value getProperty(const Object&, const String& name) = 0; virtual bool hasProperty(const Object&, const PropNameID& name) = 0; @@ -758,6 +761,16 @@ class JSI_EXPORT Object : public Pointer { return rt.instanceOf(*this, ctor); } + /// Sets \p prototype as the prototype of the object. The prototype must be + /// either an Object or null. If the prototype was not set successfully, this + /// method will throw. + void setPrototype(Runtime& runtime, const Value& prototype) const { + return runtime.setPrototypeOf(*this, prototype); + } + + /// \return the prototype of the object + inline Value getPrototype(Runtime& runtime) const; + /// \return the property of the object with the given ascii name. /// If the name isn't a property on the object, returns the /// undefined value. diff --git a/packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp b/packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp index 5f779427d0f0a3..3f1613b382b128 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp +++ b/packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp @@ -1664,6 +1664,40 @@ TEST_P(JSITest, GetStringDataTest) { EXPECT_EQ(buf, str.utf16(rd)); } +TEST_P(JSITest, ObjectSetPrototype) { + // This Runtime Decorator is used to test the default implementation of + // Object.setPrototypeOf + class RD : public RuntimeDecorator { + public: + explicit RD(Runtime& rt) : RuntimeDecorator(rt) {} + + void setPrototypeOf(const Object& object, const Value& prototype) override { + return Runtime::setPrototypeOf(object, prototype); + } + + Value getPrototypeOf(const Object& object) override { + return Runtime::getPrototypeOf(object); + } + }; + + RD rd = RD(rt); + Object child(rd); + + // Tests null value as prototype + child.setPrototype(rd, Value::null()); + EXPECT_TRUE(child.getPrototype(rd).isNull()); + + Object prototypeObj(rd); + prototypeObj.setProperty(rd, "someProperty", 123); + Value prototype(rd, prototypeObj); + + child.setPrototype(rd, prototype); + EXPECT_EQ(child.getProperty(rd, "someProperty").getNumber(), 123); + + auto getPrototypeRes = child.getPrototype(rd).asObject(rd); + EXPECT_EQ(getPrototypeRes.getProperty(rd, "someProperty").getNumber(), 123); +} + INSTANTIATE_TEST_CASE_P( Runtimes, JSITest, From 32fc0c735151672a00baf4b856653cdf40a7e30f Mon Sep 17 00:00:00 2001 From: Chi Tsai Date: Wed, 11 Dec 2024 16:44:17 -0800 Subject: [PATCH 2/2] Add default implementation for Object.create(prototype) (#47946) Summary: Object creation with custom prototype can currently be done, but it is unnecessarily convoluted. Users have to call into the global object to get the `Object.create` function, then call it with the custom prototype. This diff adds a JSI API for Object.create(prototype) to make it easy for users. Changelog: [Internal] Reviewed By: avp Differential Revision: D66485209 --- .../ReactCommon/jsi/jsi/decorator.h | 9 +++++++ .../react-native/ReactCommon/jsi/jsi/jsi.cpp | 7 ++++++ .../react-native/ReactCommon/jsi/jsi/jsi.h | 8 ++++++ .../ReactCommon/jsi/jsi/test/testlib.cpp | 25 +++++++++++++++++++ 4 files changed, 49 insertions(+) diff --git a/packages/react-native/ReactCommon/jsi/jsi/decorator.h b/packages/react-native/ReactCommon/jsi/jsi/decorator.h index 7136a27430d9e7..b16ecc21831642 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/decorator.h +++ b/packages/react-native/ReactCommon/jsi/jsi/decorator.h @@ -248,6 +248,10 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { plain_.getPropNameIdData(sym, ctx, cb); } + Object createObjectWithPrototype(const Value& prototype) override { + return plain_.createObjectWithPrototype(prototype); + } + Object createObject() override { return plain_.createObject(); }; @@ -737,6 +741,11 @@ class WithRuntimeDecorator : public RuntimeDecorator { return RD::createValueFromJsonUtf8(json, length); }; + Object createObjectWithPrototype(const Value& prototype) override { + Around around{with_}; + return RD::createObjectWithPrototype(prototype); + } + Object createObject() override { Around around{with_}; return RD::createObject(); diff --git a/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp b/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp index b386565906fe1e..e2e4a6faad45a3 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp +++ b/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp @@ -288,6 +288,13 @@ Value Runtime::getPrototypeOf(const Object& object) { return setPrototypeOfFn.call(*this, object); } +Object Runtime::createObjectWithPrototype(const Value& prototype) { + auto createFn = global() + .getPropertyAsObject(*this, "Object") + .getPropertyAsFunction(*this, "create"); + return createFn.call(*this, prototype).asObject(*this); +} + Pointer& Pointer::operator=(Pointer&& other) noexcept { if (ptr_) { ptr_->invalidate(); diff --git a/packages/react-native/ReactCommon/jsi/jsi/jsi.h b/packages/react-native/ReactCommon/jsi/jsi/jsi.h index 9196510c64b7e0..6b59a8945257cc 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/jsi.h +++ b/packages/react-native/ReactCommon/jsi/jsi/jsi.h @@ -333,6 +333,9 @@ class JSI_EXPORT Runtime { virtual std::shared_ptr getHostObject(const jsi::Object&) = 0; virtual HostFunctionType& getHostFunction(const jsi::Function&) = 0; + // Creates a new Object with the custom prototype + virtual Object createObjectWithPrototype(const Value& prototype); + virtual bool hasNativeState(const jsi::Object&) = 0; virtual std::shared_ptr getNativeState(const jsi::Object&) = 0; virtual void setNativeState( @@ -751,6 +754,11 @@ class JSI_EXPORT Object : public Pointer { return runtime.createObject(ho); } + /// Creates a new Object with the custom prototype + static Object create(Runtime& runtime, const Value& prototype) { + return runtime.createObjectWithPrototype(prototype); + } + /// \return whether this and \c obj are the same JSObject or not. static bool strictEquals(Runtime& runtime, const Object& a, const Object& b) { return runtime.strictEquals(a, b); diff --git a/packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp b/packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp index 3f1613b382b128..f6d81f1021b084 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp +++ b/packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp @@ -1698,6 +1698,31 @@ TEST_P(JSITest, ObjectSetPrototype) { EXPECT_EQ(getPrototypeRes.getProperty(rd, "someProperty").getNumber(), 123); } +TEST_P(JSITest, ObjectCreateWithPrototype) { + // This Runtime Decorator is used to test the default implementation of + // Object.create(prototype) + class RD : public RuntimeDecorator { + public: + RD(Runtime& rt) : RuntimeDecorator(rt) {} + + Object createObjectWithPrototype(const Value& prototype) override { + return Runtime::createObjectWithPrototype(prototype); + } + }; + + RD rd = RD(rt); + Object prototypeObj(rd); + prototypeObj.setProperty(rd, "someProperty", 123); + Value prototype(rd, prototypeObj); + + Object child = Object::create(rd, prototype); + EXPECT_EQ(child.getProperty(rd, "someProperty").getNumber(), 123); + + // Tests null value as prototype + child = Object::create(rd, Value::null()); + EXPECT_TRUE(child.getPrototype(rd).isNull()); +} + INSTANTIATE_TEST_CASE_P( Runtimes, JSITest,