From 646945c2f2e1fe226d3b093cc0e75102eadbdfa1 Mon Sep 17 00:00:00 2001 From: Chi Tsai Date: Thu, 21 Aug 2025 17:35:55 -0700 Subject: [PATCH] Add deleteProperty API (#52911) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/52911 Add a new `deleteProperty` API to JSI. As the name implies, allows users to delete properties from Objects through JSI. The default implementation uses `Reflect.deleteProperty.` For the `PropNameID` overload, convert the propNameID to a String and pass into the `deleteProperty` function. Changelog: [Internal] Reviewed By: dannysu Differential Revision: D79120814 fbshipit-source-id: e30f383247d94bb5971e4909f004c75e8165adda --- .../ReactCommon/jsi/jsi/decorator.h | 27 +++++++++ .../ReactCommon/jsi/jsi/jsi-inl.h | 17 ++++++ .../react-native/ReactCommon/jsi/jsi/jsi.cpp | 31 ++++++++++ .../react-native/ReactCommon/jsi/jsi/jsi.h | 20 +++++++ .../ReactCommon/jsi/jsi/test/testlib.cpp | 59 +++++++++++++++++++ 5 files changed, 154 insertions(+) diff --git a/packages/react-native/ReactCommon/jsi/jsi/decorator.h b/packages/react-native/ReactCommon/jsi/jsi/decorator.h index 6410257b250..d736aeaaf61 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/decorator.h +++ b/packages/react-native/ReactCommon/jsi/jsi/decorator.h @@ -328,6 +328,18 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { plain_.setPropertyValue(o, name, value); }; + void deleteProperty(const Object& object, const PropNameID& name) override { + plain_.deleteProperty(object, name); + } + + void deleteProperty(const Object& object, const String& name) override { + plain_.deleteProperty(object, name); + } + + void deleteProperty(const Object& object, const Value& name) override { + plain_.deleteProperty(object, name); + } + bool isArray(const Object& o) const override { return plain_.isArray(o); }; @@ -852,6 +864,21 @@ class WithRuntimeDecorator : public RuntimeDecorator { RD::setPropertyValue(o, name, value); }; + void deleteProperty(const Object& object, const PropNameID& name) override { + Around around{with_}; + RD::deleteProperty(object, name); + } + + void deleteProperty(const Object& object, const String& name) override { + Around around{with_}; + RD::deleteProperty(object, name); + } + + void deleteProperty(const Object& object, const Value& name) override { + Around around{with_}; + RD::deleteProperty(object, name); + } + bool isArray(const Object& o) const override { Around around{with_}; return RD::isArray(o); diff --git a/packages/react-native/ReactCommon/jsi/jsi/jsi-inl.h b/packages/react-native/ReactCommon/jsi/jsi/jsi-inl.h index f859db9d786..ed4b6fcd8df 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/jsi-inl.h +++ b/packages/react-native/ReactCommon/jsi/jsi/jsi-inl.h @@ -148,6 +148,23 @@ void Object::setProperty(Runtime& runtime, const PropNameID& name, T&& value) runtime, name, detail::toValue(runtime, std::forward(value))); } +inline void Object::deleteProperty(Runtime& runtime, const char* name) const { + deleteProperty(runtime, String::createFromAscii(runtime, name)); +} + +inline void Object::deleteProperty(Runtime& runtime, const String& name) const { + runtime.deleteProperty(*this, name); +} + +inline void Object::deleteProperty(Runtime& runtime, const PropNameID& name) + const { + runtime.deleteProperty(*this, name); +} + +inline void Object::deleteProperty(Runtime& runtime, const Value& name) const { + runtime.deleteProperty(*this, name); +} + inline Array Object::getArray(Runtime& runtime) const& { assert(runtime.isArray(*this)); (void)runtime; // when assert is disabled we need to mark this as used diff --git a/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp b/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp index 65c57dd3737..5e2b24cf67b 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp +++ b/packages/react-native/ReactCommon/jsi/jsi/jsi.cpp @@ -416,6 +416,37 @@ Object Runtime::createObjectWithPrototype(const Value& prototype) { return createFn.call(*this, prototype).asObject(*this); } +void Runtime::deleteProperty(const Object& object, const PropNameID& name) { + auto nameStr = String::createFromUtf16(*this, name.utf16(*this)); + auto deleteFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "deleteProperty"); + auto res = deleteFn.call(*this, object, nameStr).getBool(); + if (!res) { + throw JSError(*this, "Failed to delete property"); + } +} + +void Runtime::deleteProperty(const Object& object, const String& name) { + auto deleteFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "deleteProperty"); + auto res = deleteFn.call(*this, object, name).getBool(); + if (!res) { + throw JSError(*this, "Failed to delete property"); + } +} + +void Runtime::deleteProperty(const Object& object, const Value& name) { + auto deleteFn = global() + .getPropertyAsObject(*this, "Reflect") + .getPropertyAsFunction(*this, "deleteProperty"); + auto res = deleteFn.call(*this, object, name).getBool(); + if (!res) { + throw JSError(*this, "Failed to delete property"); + } +} + void Runtime::setRuntimeDataImpl( const UUID& uuid, const void* data, diff --git a/packages/react-native/ReactCommon/jsi/jsi/jsi.h b/packages/react-native/ReactCommon/jsi/jsi/jsi.h index ed8d37f265b..938ee28ab22 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/jsi.h +++ b/packages/react-native/ReactCommon/jsi/jsi/jsi.h @@ -486,6 +486,10 @@ class JSI_EXPORT Runtime : public ICast { virtual void setPropertyValue(const Object&, const String& name, const Value& value) = 0; + virtual void deleteProperty(const Object&, const PropNameID& name); + virtual void deleteProperty(const Object&, const String& name); + virtual void deleteProperty(const Object&, const Value& name); + virtual bool isArray(const Object&) const = 0; virtual bool isArrayBuffer(const Object&) const = 0; virtual bool isFunction(const Object&) const = 0; @@ -984,6 +988,22 @@ class JSI_EXPORT Object : public Pointer { template void setProperty(Runtime& runtime, const PropNameID& name, T&& value) const; + /// Delete the property with the given ascii name. Throws if the deletion + /// failed. + void deleteProperty(Runtime& runtime, const char* name) const; + + /// Delete the property with the given String name. Throws if the deletion + /// failed. + void deleteProperty(Runtime& runtime, const String& name) const; + + /// Delete the property with the given PropNameID name. Throws if the deletion + /// failed. + void deleteProperty(Runtime& runtime, const PropNameID& name) const; + + /// Delete the property with the given Value name. Throws if the deletion + /// failed. + void deleteProperty(Runtime& runtime, const Value& name) const; + /// \return true iff JS \c Array.isArray() would return \c true. If /// so, then \c getArray() will succeed. bool isArray(Runtime& runtime) const { diff --git a/packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp b/packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp index 4b106621f5a..195ac28ec4f 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp +++ b/packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp @@ -1882,6 +1882,65 @@ TEST_P(JSITest, CastInterface) { EXPECT_TRUE(ptr == nullptr); } +TEST_P(JSITest, DeleteProperty) { + // This Runtime Decorator is used to test the default implementation of + // Runtime::deleteProperty + class RD : public RuntimeDecorator { + public: + explicit RD(Runtime& rt) : RuntimeDecorator(rt) {} + + void deleteProperty(const Object& object, const PropNameID& name) override { + Runtime::deleteProperty(object, name); + } + void deleteProperty(const Object& object, const String& name) override { + Runtime::deleteProperty(object, name); + } + void deleteProperty(const Object& object, const Value& name) override { + Runtime::deleteProperty(object, name); + } + }; + RD rd = RD(rt); + auto obj = eval("obj = {1:2, foo: 'bar', 3: 4, salt:'pepper'}").getObject(rd); + + auto prop = PropNameID::forAscii(rd, "1"); + auto hasRes = obj.hasProperty(rd, prop); + EXPECT_TRUE(hasRes); + obj.deleteProperty(rd, prop); + hasRes = obj.hasProperty(rd, prop); + EXPECT_FALSE(hasRes); + + auto str = String::createFromAscii(rd, "foo"); + hasRes = obj.hasProperty(rd, str); + EXPECT_TRUE(hasRes); + obj.deleteProperty(rd, str); + hasRes = obj.hasProperty(rd, str); + EXPECT_FALSE(hasRes); + + auto valProp = Value(3); + hasRes = obj.hasProperty(rd, "3"); + EXPECT_TRUE(hasRes); + obj.deleteProperty(rd, valProp); + auto getRes = obj.getProperty(rd, "3"); + EXPECT_TRUE(getRes.isUndefined()); + + hasRes = obj.hasProperty(rd, "salt"); + EXPECT_TRUE(hasRes); + obj.deleteProperty(rd, "salt"); + hasRes = obj.hasProperty(rd, "salt"); + EXPECT_FALSE(hasRes); + + obj = eval( + "const obj = {};" + "Object.defineProperty(obj, 'prop', {" + " value: 10," + " configurable: false,});" + "obj;") + .getObject(rd); + EXPECT_THROW(obj.deleteProperty(rd, "prop"), JSError); + hasRes = obj.hasProperty(rd, "prop"); + EXPECT_TRUE(hasRes); +} + INSTANTIATE_TEST_CASE_P( Runtimes, JSITest,