diff --git a/lib/collection-methods.js b/lib/collection-methods.js index 28fad893ac..cfe7af72ad 100644 --- a/lib/collection-methods.js +++ b/lib/collection-methods.js @@ -49,7 +49,7 @@ Object.defineProperty(iteratorPrototype, Symbol.iterator, { ].forEach(function(methodName) { var method = arrayPrototype[methodName]; if (method) { - exports[methodName] = {value: method, configurable: true, writable: true}; + exports[methodName] = {value(...args) { return [...this][methodName](...args)}, configurable: true, writable: true}; } }); diff --git a/lib/extensions.js b/lib/extensions.js index 10a3d14bb7..e7a41b5d0a 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -76,7 +76,7 @@ module.exports = function(realmConstructor, environment) { } Object.defineProperty(realmConstructor.Object.prototype, "toJSON", { - value: function (_, cache = new Map()) { + value: function toJSON(_, cache = new Map()) { // Construct a reference-id of table-name & primaryKey if it exists, or fall back to objectId. const id = getInternalCacheId(this); diff --git a/src/hermes/hermes_class.hpp b/src/hermes/hermes_class.hpp index f15510fe1b..173f454a69 100644 --- a/src/hermes/hermes_class.hpp +++ b/src/hermes/hermes_class.hpp @@ -162,7 +162,7 @@ inline T& unwrap(JsiEnv env, const jsi::Object& wrapper) { template inline T& unwrap(JsiEnv env, const jsi::Value& wrapper) { - return unwrap(env, wrapper.getObject(env)); + return unwrap(env, wrapper.asObject(env)); } template @@ -228,10 +228,12 @@ class ObjectWrap { util::format(R"( return function %1(...args) { //"use strict"; - if (!nativeFunc) + if (!nativeFunc && false) // XXX only disable for Realm.Object throw TypeError("%1() cannot be constructed directly from javascript"); - if (!new.target) + if (!new.target && false) { // XXX find another way to detect this correctly throw TypeError("%1() must be called as a constructor"); + } + if (nativeFunc) nativeFunc(this, ...args); if ('_proxyWrapper' in %1) @@ -278,6 +280,19 @@ class ObjectWrap { defineProperty(env, proto, name, desc); } + if constexpr (!std::is_void_v) { + REALM_ASSERT_RELEASE(ObjectWrap::s_ctor); + JsiFunc parentCtor = *ObjectWrap::s_ctor; + + auto parentProto = parentCtor->getProperty(env, "prototype"); + if (parentProto.isUndefined()) { + throw std::runtime_error("undefined 'prototype' on parent constructor"); + } + + ObjectSetPrototypeOf(env, jsi::Value(env, proto), jsi::Value(std::move(parentProto))); + ObjectSetPrototypeOf(env, jsi::Value(env, *s_ctor), jsi::Value(std::move(parentCtor.get()))); + } + if (s_type.index_accessor) { // Code below assumes getter is present, and it doesn't make sense to have setter without one. REALM_ASSERT_RELEASE(s_type.index_accessor.getter); @@ -291,17 +306,35 @@ class ObjectWrap { globalType(env, "Function").call(env, "getter", "setter", R"( const isNumber = /^[-+]?\d+$/; const handler = { - get(target, property, receiver) { - if (typeof(property) != 'string' || !isNumber.test(property)) - return Reflect.get(target, property, receiver); - return getter(target, Number(property)) + ownKeys(target) { + const out = Reflect.ownKeys(target) + const end = target.length + for (let i = 0; i < end; i++) { + out.push(String(i)); + } + return out; }, - set(target, property, receiver, val) { - if (typeof(property) != 'string' || !isNumber.test(property)) - return Reflect.set(target, property, receiver, val); + getOwnPropertyDescriptor(target, prop) { + if (typeof(prop) != 'string' || !isNumber.test(prop)) + return Reflect.getOwnPropertyDescriptor(target, prop) + const index = Number(prop); + if (index >= 0 && index < target.length) + return { + configurable: true, + enumerable: true, + }; + }, + get(target, prop, receiver) { + if (typeof(prop) != 'string' || !isNumber.test(prop)) + return Reflect.get(target, prop, receiver); + return getter(target, Number(prop)) + }, + set(target, prop, receiver, val) { + if (typeof(prop) != 'string' || !isNumber.test(prop)) + return Reflect.set(target, prop, receiver, val); if (!setter) return false; - return setter(target, Number(property), val) + return setter(target, Number(prop), val) } } return (obj) => new Proxy(obj, handler); @@ -346,17 +379,29 @@ class ObjectWrap { } static Internal* get_internal(JsiEnv env, const JsiObj& object) { - return unwrapUnique(env, object->getProperty(env, g_internal_field)); + if (!JsiObj(object)->instanceOf(env, *s_ctor)) { + throw jsi::JSError(env, "calling method on wrong type of object"); + } + auto internal = object->getProperty(env, g_internal_field); + if (internal.isUndefined()) { + if constexpr (std::is_same_v>) // XXX comment why + return nullptr; + throw jsi::JSError(env, "no internal field"); + } + return unwrapUnique(env, std::move(internal)); } static void set_internal(JsiEnv env, const JsiObj& object, Internal* data) { - env(object)->setProperty(env, g_internal_field, wrapUnique(env, data)); + auto desc = jsi::Object(env); + desc.setProperty(env, "value", wrapUnique(env, data)); + desc.setProperty(env, "configurable", true); + defineProperty(env, object, g_internal_field, desc); } private: static jsi::Value funcVal(JsiEnv env, const std::string& name, size_t args, jsi::HostFunctionType&& func) { if (!func) return jsi::Value(); - return jsi::Value(jsi::Function::createFromHostFunction(env, propName(env, name), args, std::move(func))); + return jsi::Value(jsi::Function::createFromHostFunction(env, propName(env, name), uint32_t(args), std::move(func))); }; static void defineSchemaProperties(JsiEnv env, const jsi::Object& constructorPrototype, const realm::ObjectSchema& schema, bool redefine) { @@ -364,8 +409,9 @@ class ObjectWrap { auto loopBody = [&] (const Property& property) { const auto& name = property.public_name.empty() ? property.name : property.public_name; // TODO should this use hasOwnProperty? - if (!redefine && !constructorPrototype.hasProperty(env, str(env, name))) + if (!redefine && constructorPrototype.hasProperty(env, str(env, name))) { return; + } auto desc = jsi::Object(env); desc.setProperty(env, "enumerable", true); @@ -422,6 +468,7 @@ class ObjectWrap { //2.Create the constructor //create the RealmObject function by name + // XXX May need to escape/sanitize schema.name to avoid code injection auto schemaObjectConstructor = globalType(env, "Function") .callAsConstructor(env, "return function " + schema.name + "() {}") diff --git a/src/hermes/hermes_init.cpp b/src/hermes/hermes_init.cpp index 7c29756663..31940ba8a8 100644 --- a/src/hermes/hermes_init.cpp +++ b/src/hermes/hermes_init.cpp @@ -32,9 +32,6 @@ extern "C" void realm_hermes_init(jsi::Runtime& rt, jsi::Object& exports) { jsi::Function realm_constructor = js::RealmClass::create_constructor(env); auto name = realm_constructor.getProperty(env, "name").asString(env); exports.setProperty(env, std::move(name), std::move(realm_constructor)); - - // Only calling to populate static cache. Eventually this should be stored somewhere non-static. - (void)js::ObjectWrap>::create_constructor(env); } } diff --git a/src/hermes/hermes_types.hpp b/src/hermes/hermes_types.hpp index a77d5a6a79..b903861648 100644 --- a/src/hermes/hermes_types.hpp +++ b/src/hermes/hermes_types.hpp @@ -113,7 +113,7 @@ class JsiWrap { } friend bool operator==(const JsiWrap& a, const JsiWrap& b) { - REALM_ASSERT_RELEASE(&a.env() == &b.env()); + REALM_ASSERT_RELEASE(&a.env().get() == &b.env().get()); return T::strictEquals(a.env(), a.get(), b.get()); } diff --git a/src/js_realm.hpp b/src/js_realm.hpp index c2529e7c28..f3d19d9763 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -476,6 +476,9 @@ class RealmClass : public ClassDefinition> { template inline typename T::Function RealmClass::create_constructor(ContextType ctx) { + // Only calling to populate static cache. Eventually this should be stored somewhere non-static. + (void)ObjectWrap>::create_constructor(ctx); + FunctionType realm_constructor = ObjectWrap>::create_constructor(ctx); FunctionType collection_constructor = ObjectWrap>::create_constructor(ctx); FunctionType list_constructor = ObjectWrap>::create_constructor(ctx); diff --git a/src/js_schema.hpp b/src/js_schema.hpp index ec402f0cea..703bd2adba 100644 --- a/src/js_schema.hpp +++ b/src/js_schema.hpp @@ -363,6 +363,7 @@ realm::Schema Schema::parse_schema(ContextType ctx, ObjectType schema_object, template typename T::Object Schema::object_for_schema(ContextType ctx, const realm::Schema &schema) { ObjectType object = Object::create_array(ctx); + Object::set_property(ctx, object, "length", Value::from_number(ctx, double(schema.size()))); uint32_t count = 0; for (auto& object_schema : schema) { Object::set_property(ctx, object, count++, object_for_object_schema(ctx, object_schema));