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
This commit is contained in:
Chi Tsai
2025-08-21 17:35:55 -07:00
committed by Facebook GitHub Bot
parent da9136f587
commit 646945c2f2
5 changed files with 154 additions and 0 deletions
@@ -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<Plain, Base> {
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);
@@ -148,6 +148,23 @@ void Object::setProperty(Runtime& runtime, const PropNameID& name, T&& value)
runtime, name, detail::toValue(runtime, std::forward<T>(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
@@ -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,
@@ -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 <typename T>
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 {
@@ -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<Runtime, Runtime> {
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,