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: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2809>
This commit is contained in:
Florian Müllner 2023-07-22 18:44:08 +02:00
parent f70a75a905
commit a332771562
2 changed files with 57 additions and 2 deletions

View File

@ -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;
}
}
}

View File

@ -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);