[gjs/ewlsh/interface-resolution: 64/64] gi: Use accessors to dynamically fetch prototype properties
- From: Philip Chimento <pchimento src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/ewlsh/interface-resolution: 64/64] gi: Use accessors to dynamically fetch prototype properties
- Date: Fri, 15 Oct 2021 20:34:51 +0000 (UTC)
commit fe6807a7b04dd5be1081ae6209788f1259e63bed
Author: Evan Welsh <contact evanwelsh com>
Date: Thu Aug 26 21:18:46 2021 -0700
gi: Use accessors to dynamically fetch prototype properties
gi/object.cpp | 132 +++++++++++++++++++++++++----
installed-tests/js/testGObjectInterface.js | 62 ++++++++------
2 files changed, 152 insertions(+), 42 deletions(-)
---
diff --git a/gi/object.cpp b/gi/object.cpp
index da5d632b..64af438d 100644
--- a/gi/object.cpp
+++ b/gi/object.cpp
@@ -645,19 +645,70 @@ bool ObjectPrototype::lazy_define_gobject_property(JSContext* cx,
return true;
}
-static bool copy_interface_method_to_prototype(
- JSContext* cx, GIObjectInfo* iface_info, const char* method_name,
- JS::HandleObject target_prototype, bool* found) {
+enum class InterfaceAccessorPrivateSlot : size_t {
+ // The identifier to get on the interface' prototype.
+ IDENTIFIER = 0,
+ // An object shared by the getter and setter to store the interface'
+ // prototype and
+ // overrides.
+ ACCESSOR = 1,
+};
+
+static bool interface_getter(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JS::RootedValue v_method_id(
+ cx, js::GetFunctionNativeReserved(
+ &args.callee(),
+ static_cast<size_t>(InterfaceAccessorPrivateSlot::IDENTIFIER)));
+ JS::RootedId id(cx);
+ if (!JS_ValueToId(cx, v_method_id, &id))
+ return false;
+ JS::RootedValue v_accessor(
+ cx, js::GetFunctionNativeReserved(
+ &args.callee(),
+ static_cast<size_t>(InterfaceAccessorPrivateSlot::ACCESSOR)));
+ JS::RootedObject accessor(cx, &v_accessor.toObject());
+ JS::RootedValue v_prototype(cx);
+
+ const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
+ bool has_override = false;
+ if (!JS_HasPropertyById(cx, accessor, atoms.overrides(), &has_override))
+ return false;
+ if (has_override)
+ return JS_GetPropertyById(cx, accessor, atoms.overrides(), args.rval());
+ if (!JS_GetPropertyById(cx, accessor, atoms.prototype(), &v_prototype))
+ return false;
+
+ JS::RootedObject prototype(cx, &v_prototype.toObject());
+
+ return JS_GetPropertyById(cx, prototype, id, args.rval());
+}
+
+static bool interface_setter(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JS::RootedValue v_accessor(
+ cx, js::GetFunctionNativeReserved(
+ &args.callee(),
+ static_cast<size_t>(InterfaceAccessorPrivateSlot::ACCESSOR)));
+ JS::RootedObject accessor(cx, &v_accessor.toObject());
+
+ const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
+ return JS_SetPropertyById(cx, accessor, atoms.overrides(), args[0]);
+}
+
+static bool resolve_on_interface_prototype(JSContext* cx,
+ GIObjectInfo* iface_info,
+ JS::HandleId identifier,
+ JS::HandleObject target_prototype,
+ bool* found) {
GType gtype = g_base_info_get_type(iface_info);
JS::RootedObject prototype(
cx, gjs_lookup_object_prototype_from_info(cx, iface_info, gtype));
if (!prototype)
return false;
- JS::RootedId method_name_id(cx, gjs_intern_string_to_id(cx, method_name));
-
bool target_has_property;
- if (!JS_HasPropertyById(cx, target_prototype, method_name_id,
+ if (!JS_HasPropertyById(cx, target_prototype, identifier,
&target_has_property))
return false;
@@ -668,17 +719,62 @@ static bool copy_interface_method_to_prototype(
}
JS::Rooted<JS::PropertyDescriptor> desc(cx);
- if (!JS_GetPropertyDescriptorById(cx, prototype, method_name_id, &desc))
+ if (!JS_GetPropertyDescriptorById(cx, prototype, identifier, &desc))
return false;
+ // Check if the property exists on the prototype...
if (!desc.object()) {
*found = false;
return true;
}
JS::Rooted<JS::PropertyDescriptor> target_desc(cx, desc);
- if (!JS_DefinePropertyById(cx, target_prototype, method_name_id,
- target_desc))
+ JS::RootedObject getter(
+ cx, JS_GetFunctionObject(js::NewFunctionWithReserved(
+ cx, interface_getter, 0, 0, "interface_prototype_getter")));
+ if (!getter)
+ return false;
+
+ JS::RootedObject setter(
+ cx, JS_GetFunctionObject(js::NewFunctionWithReserved(
+ cx, interface_setter, 1, 0, "interface_prototype_setter")));
+ if (!setter)
+ return false;
+
+ JS::RootedValue v_id(cx);
+ if (!JS_IdToValue(cx, identifier, &v_id))
+ return false;
+
+ // Store the property identifier...
+ js::SetFunctionNativeReserved(
+ setter, static_cast<size_t>(InterfaceAccessorPrivateSlot::IDENTIFIER),
+ v_id);
+ js::SetFunctionNativeReserved(
+ getter, static_cast<size_t>(InterfaceAccessorPrivateSlot::IDENTIFIER),
+ v_id);
+
+ JS::RootedObject accessor(cx, JS_NewPlainObject(cx));
+ if (!accessor)
+ return false;
+
+ js::SetFunctionNativeReserved(
+ setter, static_cast<size_t>(InterfaceAccessorPrivateSlot::ACCESSOR),
+ JS::ObjectValue(*accessor));
+ js::SetFunctionNativeReserved(
+ getter, static_cast<size_t>(InterfaceAccessorPrivateSlot::ACCESSOR),
+ JS::ObjectValue(*accessor));
+
+ const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
+ JS::RootedValue v_prototype(cx, JS::ObjectValue(*prototype));
+ if (!JS_SetPropertyById(cx, accessor, atoms.prototype(), v_prototype))
+ return false;
+
+ target_desc.setValue(JS::UndefinedHandleValue);
+ target_desc.setAttributes(JSPROP_SETTER | JSPROP_GETTER);
+ target_desc.setGetterObject(getter);
+ target_desc.setSetterObject(setter);
+
+ if (!JS_DefinePropertyById(cx, target_prototype, identifier, target_desc))
return false;
*found = true;
@@ -733,8 +829,8 @@ bool ObjectPrototype::resolve_no_info(JSContext* cx, JS::HandleObject obj,
if (method_info) {
if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) {
bool found = false;
- if (!copy_interface_method_to_prototype(
- cx, iface_info, method_info.name(), obj, &found))
+ if (!resolve_on_interface_prototype(cx, iface_info, id, obj,
+ &found))
return false;
// Fallback to defining the function from type info...
@@ -747,13 +843,12 @@ bool ObjectPrototype::resolve_no_info(JSContext* cx, JS::HandleObject obj,
}
}
- if (!canonical_name)
- continue;
/* If the name refers to a GObject property, lazily define the property
* in JS as we do below in the real resolve hook. We ignore fields here
* because I don't think interfaces can have fields */
- if (is_ginterface_property_name(iface_info, canonical_name)) {
+ if (canonical_name &&
+ is_ginterface_property_name(iface_info, canonical_name)) {
GjsAutoTypeClass<GObjectClass> oclass(m_gtype);
// unowned
GParamSpec* pspec = g_object_class_find_property(
@@ -763,6 +858,9 @@ bool ObjectPrototype::resolve_no_info(JSContext* cx, JS::HandleObject obj,
name);
}
}
+
+ return resolve_on_interface_prototype(cx, iface_info, id, obj,
+ resolved);
}
*resolved = false;
@@ -951,8 +1049,8 @@ bool ObjectPrototype::uncached_resolve(JSContext* context, JS::HandleObject obj,
method_info.name(), type_name(), ns(), this->name());
if (GI_IS_INTERFACE_INFO(implementor_info)) {
bool found = false;
- if (!copy_interface_method_to_prototype(
- context, implementor_info, method_info.name(), obj, &found))
+ if (!resolve_on_interface_prototype(context, implementor_info, id,
+ obj, &found))
return false;
// If the method was not found fallback to defining the function
@@ -966,8 +1064,6 @@ bool ObjectPrototype::uncached_resolve(JSContext* context, JS::HandleObject obj,
}
*resolved = true; /* we defined the prop in obj */
- } else {
- *resolved = false;
}
return true;
diff --git a/installed-tests/js/testGObjectInterface.js b/installed-tests/js/testGObjectInterface.js
index 34236c53..f6f61e7a 100644
--- a/installed-tests/js/testGObjectInterface.js
+++ b/installed-tests/js/testGObjectInterface.js
@@ -302,6 +302,44 @@ describe('GObject interface', function () {
expect(new GObjectImplementingGObjectInterface().toString()).toMatch(
/\[object instance wrapper GType:Gjs_GObjectImplementingGObjectInterface jsobj@0x[a-f0-9]+
native@0x[a-f0-9]+\]/);
});
+
+ describe('prototype', function () {
+ let originalDup;
+
+ beforeAll(function () {
+ originalDup = Gio.File.prototype.dup;
+ });
+
+ it('overrides are inherited by implementing classes', function () {
+ let dupSpy = jasmine.createSpy();
+ Gio.File.prototype.dup = dupSpy;
+
+ const file = Gio.File.new_for_path('/');
+
+ expect(file).toBeInstanceOf(Gio.File);
+ expect(file).toBeInstanceOf(Gio._LocalFilePrototype.constructor);
+
+ file.dup();
+
+ expect(dupSpy).toHaveBeenCalledOnceWith();
+
+ Gio.File.prototype.dup = originalDup;
+
+ expect(file.dup).toBe(originalDup);
+ });
+
+ it('unknown properties are inherited by implementing classes', function () {
+ const file = Gio.File.new_for_path('/');
+
+ Gio.File.prototype._originalDup = originalDup;
+
+ expect(file._originalDup).toBe(originalDup);
+
+ Gio.File.prototype._originalDup = 5;
+
+ expect(file._originalDup).toBe(5);
+ });
+ });
});
describe('Specific class and interface checks', function () {
@@ -313,27 +351,3 @@ describe('Specific class and interface checks', function () {
})).toThrow();
});
});
-
-describe('Interface prototypes', function () {
- let dupSpy = jasmine.createSpy();
-
- beforeAll(function () {
- // Use .dup because we are unlikely to
- // use it in this test.
- //
- // Unfortunately, overriding interface
- // prototypes is not easily reversed.
- Gio.File.prototype.dup = dupSpy;
- });
-
- it('overrides are inherited by implementing classes', function () {
- const file = Gio.File.new_for_path('/');
-
- expect(file).toBeInstanceOf(Gio.File);
- expect(file).toBeInstanceOf(Gio._LocalFilePrototype.constructor);
-
- file.dup();
-
- expect(dupSpy).toHaveBeenCalledOnceWith();
- });
-});
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]