From b3ff8ec17fffc7646799040b222b2de1a80c29dc Mon Sep 17 00:00:00 2001 From: Chi Tsai Date: Wed, 27 Nov 2024 10:31:51 -0800 Subject: [PATCH] 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] Differential Revision: D66485209 --- .../ReactCommon/jsi/jsi/decorator.h | 9 ++++++ .../react-native/ReactCommon/jsi/jsi/jsi.cpp | 10 +++++++ .../react-native/ReactCommon/jsi/jsi/jsi.h | 8 ++++++ .../ReactCommon/jsi/jsi/test/testlib.cpp | 28 +++++++++++++++++++ 4 files changed, 55 insertions(+) diff --git a/packages/react-native/ReactCommon/jsi/jsi/decorator.h b/packages/react-native/ReactCommon/jsi/jsi/decorator.h index 5e57b44a87dfeb..ff860b3a4d17e8 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/decorator.h +++ b/packages/react-native/ReactCommon/jsi/jsi/decorator.h @@ -232,6 +232,10 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { return plain_.utf16(sym); } + Object createObjectWithPrototype(const Value& prototype) override { + return plain_.createObjectWithPrototype(prototype); + } + Object createObject() override { return plain_.createObject(); }; @@ -695,6 +699,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 e11ef30d77ed65..208f64f75e091c 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp +++ b/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp @@ -258,6 +258,16 @@ std::u16string Runtime::utf16(const String& str) { return convertUTF8ToUTF16(utf8Str); } +Object Runtime::createObjectWithPrototype(const Value& prototype) { + if (!prototype.isObject() && !prototype.isNull()) { + throw JSError(*this, "Object prototype argument must be an Object or null"); + } + 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 be48bb82474352..e85c7eb152747d 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( @@ -688,6 +691,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 57d5afbcfc41c3..f435f37781e237 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp +++ b/packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp @@ -1635,6 +1635,34 @@ TEST_P(JSITest, UTF16Test) { EXPECT_EQ(str.utf16(rd), u"\uFFFD\u007A"); } +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.getProperty(rd, "__proto__").isUndefined()); + + // Throw when prototype is neither an Object nor null + EXPECT_THROW(Object::create(rd, Value(1)), JSError); +} + INSTANTIATE_TEST_CASE_P( Runtimes, JSITest,