From a3327715623476519ed38c0abd0c1b49e44c0c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Sat, 22 Jul 2023 18:44:08 +0200 Subject: [PATCH] injectionManager: Support overriding vfuncs Overriding vfuncs can be useful, in particular when we convert to ES modules, and exported symbols cannot easily be swapped out. Adapt overrideMethod() to work correctly in that case as well. Part-of: --- js/extensions/extension.js | 15 ++++++++++-- tests/unit/injectionManager.js | 44 ++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/js/extensions/extension.js b/js/extensions/extension.js index a0d3f049d..c6a05f08b 100644 --- a/js/extensions/extension.js +++ b/js/extensions/extension.js @@ -1,3 +1,5 @@ +const Gi = imports._gi; + import {ExtensionBase, GettextWrapper} from './sharedInternals.js'; const {extensionManager} = imports.ui.main; @@ -48,7 +50,7 @@ export class InjectionManager { */ overrideMethod(prototype, methodName, createOverrideFunc) { const originalMethod = this._saveMethod(prototype, methodName); - prototype[methodName] = createOverrideFunc(originalMethod); + this._installMethod(prototype, methodName, createOverrideFunc(originalMethod)); } /** @@ -66,7 +68,7 @@ export class InjectionManager { if (originalMethod === undefined) delete prototype[methodName]; else - prototype[methodName] = originalMethod; + this._installMethod(prototype, methodName, originalMethod); savedProtoMethods.delete(methodName); if (savedProtoMethods.size === 0) @@ -96,4 +98,13 @@ export class InjectionManager { savedProtoMethods.set(methodName, originalMethod); return originalMethod; } + + _installMethod(prototype, methodName, method) { + if (methodName.startsWith('vfunc_')) { + const giPrototype = prototype[Gi.gobject_prototype_symbol]; + giPrototype[Gi.hook_up_vfunc_symbol](methodName.slice(6), method); + } else { + prototype[methodName] = method; + } + } } diff --git a/tests/unit/injectionManager.js b/tests/unit/injectionManager.js index 6de509441..e727f35e9 100644 --- a/tests/unit/injectionManager.js +++ b/tests/unit/injectionManager.js @@ -1,5 +1,7 @@ const JsUnit = imports.jsUnit; +import GObject from 'gi://GObject'; + import '../../js/ui/environment.js'; import {InjectionManager} from '../../js/extensions/extension.js'; @@ -16,6 +18,23 @@ class Object1 { } } +class GObject1 extends GObject.Object { + static [GObject.properties] = { + 'plonked': GObject.ParamSpec.boolean( + 'plonked', '', '', + GObject.ParamFlags.READWRITE, + false), + }; + + static { + GObject.registerClass(this); + } + + plonk() { + this.set_property('plonked', true); + } +} + /** * @param {object} object to modify */ @@ -91,3 +110,28 @@ JsUnit.assertEquals(obj.getNumber(), 42); JsUnit.assertEquals(obj.getCount(), obj.count); JsUnit.assert(obj.count > 0); JsUnit.assertRaises(() => obj.getOtherNumber()); + +// GObject injections +const gobj = new GObject1(); +let vfuncCalled; + +injectionManager.overrideMethod( + GObject1.prototype, 'vfunc_set_property', originalMethod => { + return function (...args) { + // eslint-disable-next-line no-invalid-this + originalMethod.apply(this, args); + vfuncCalled = true; + }; + }); + +// gobj is now modified +vfuncCalled = false; +gobj.plonk(); +JsUnit.assertTrue(vfuncCalled); + +injectionManager.clear(); + +// gobj is unmodified again +vfuncCalled = false; +gobj.plonk(); +JsUnit.assertFalse(vfuncCalled);