Skip to content

Commit

Permalink
Fix hermes implementation.
Browse files Browse the repository at this point in the history
Now passing integration tests on iOS.
  • Loading branch information
kraenhansen committed Jun 15, 2021
1 parent 53061e8 commit e41992a
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 21 deletions.
2 changes: 1 addition & 1 deletion lib/collection-methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -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};
}
});

Expand Down
2 changes: 1 addition & 1 deletion lib/extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
77 changes: 62 additions & 15 deletions src/hermes/hermes_class.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ inline T& unwrap(JsiEnv env, const jsi::Object& wrapper) {

template <typename T>
inline T& unwrap(JsiEnv env, const jsi::Value& wrapper) {
return unwrap<T>(env, wrapper.getObject(env));
return unwrap<T>(env, wrapper.asObject(env));
}

template <typename T>
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -278,6 +280,19 @@ class ObjectWrap {
defineProperty(env, proto, name, desc);
}

if constexpr (!std::is_void_v<ParentClassType>) {
REALM_ASSERT_RELEASE(ObjectWrap<ParentClassType>::s_ctor);
JsiFunc parentCtor = *ObjectWrap<ParentClassType>::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);
Expand All @@ -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);
Expand Down Expand Up @@ -346,26 +379,39 @@ class ObjectWrap {
}

static Internal* get_internal(JsiEnv env, const JsiObj& object) {
return unwrapUnique<Internal>(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<T, RealmObjectClass<hermes::Types>>) // XXX comment why
return nullptr;
throw jsi::JSError(env, "no internal field");
}
return unwrapUnique<Internal>(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) {
// Do the same thing for all computed and persisted properties
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);
Expand Down Expand Up @@ -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 + "() {}")
Expand Down
3 changes: 0 additions & 3 deletions src/hermes/hermes_init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@ extern "C" void realm_hermes_init(jsi::Runtime& rt, jsi::Object& exports) {
jsi::Function realm_constructor = js::RealmClass<Types>::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<Types, js::RealmObjectClass<Types>>::create_constructor(env);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/hermes/hermes_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

Expand Down
3 changes: 3 additions & 0 deletions src/js_realm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,9 @@ class RealmClass : public ClassDefinition<T, SharedRealm, ObservableClass<T>> {

template<typename T>
inline typename T::Function RealmClass<T>::create_constructor(ContextType ctx) {
// Only calling to populate static cache. Eventually this should be stored somewhere non-static.
(void)ObjectWrap<T, ObservableClass<T>>::create_constructor(ctx);

FunctionType realm_constructor = ObjectWrap<T, RealmClass<T>>::create_constructor(ctx);
FunctionType collection_constructor = ObjectWrap<T, CollectionClass<T>>::create_constructor(ctx);
FunctionType list_constructor = ObjectWrap<T, ListClass<T>>::create_constructor(ctx);
Expand Down
1 change: 1 addition & 0 deletions src/js_schema.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ realm::Schema Schema<T>::parse_schema(ContextType ctx, ObjectType schema_object,
template<typename T>
typename T::Object Schema<T>::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));
Expand Down

0 comments on commit e41992a

Please sign in to comment.