gnome-shell/js/extensions/extension.js
Florian Müllner a332771562 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>
2023-08-05 12:35:30 +02:00

111 lines
3.3 KiB
JavaScript

const Gi = imports._gi;
import {ExtensionBase, GettextWrapper} from './sharedInternals.js';
const {extensionManager} = imports.ui.main;
export class Extension extends ExtensionBase {
static lookupByUUID(uuid) {
return extensionManager.lookup(uuid)?.stateObj ?? null;
}
static defineTranslationFunctions(url) {
const wrapper = new GettextWrapper(this, url);
return wrapper.defineTranslationFunctions();
}
enable() {
}
disable() {
}
/**
* Open the extension's preferences window
*/
openPreferences() {
extensionManager.openExtensionPrefs(this.uuid, '', {});
}
}
export const {
gettext, ngettext, pgettext,
} = Extension.defineTranslationFunctions();
export class InjectionManager {
#savedMethods = new Map();
/**
* @callback CreateOverrideFunc
* @param {Function?} originalMethod - the original method if it exists
* @returns {Function} - a function to be used as override
*/
/**
* Modify, replace or inject a method
*
* @param {object} prototype - the object (or prototype) that is modified
* @param {string} methodName - the name of the overwritten method
* @param {CreateOverrideFunc} createOverrideFunc - function to call to create the override
*/
overrideMethod(prototype, methodName, createOverrideFunc) {
const originalMethod = this._saveMethod(prototype, methodName);
this._installMethod(prototype, methodName, createOverrideFunc(originalMethod));
}
/**
* Restore the original method
*
* @param {object} prototype - the object (or prototype) that is modified
* @param {string} methodName - the name of the method to restore
*/
restoreMethod(prototype, methodName) {
const savedProtoMethods = this.#savedMethods.get(prototype);
if (!savedProtoMethods)
return;
const originalMethod = savedProtoMethods.get(methodName);
if (originalMethod === undefined)
delete prototype[methodName];
else
this._installMethod(prototype, methodName, originalMethod);
savedProtoMethods.delete(methodName);
if (savedProtoMethods.size === 0)
this.#savedMethods.delete(prototype);
}
/**
* Restore all original methods and clear overrides
*/
clear() {
for (const [proto, map] of this.#savedMethods) {
map.forEach(
(_, methodName) => this.restoreMethod(proto, methodName));
}
console.assert(this.#savedMethods.size === 0,
`${this.#savedMethods.size} overrides left after clear()`);
}
_saveMethod(prototype, methodName) {
let savedProtoMethods = this.#savedMethods.get(prototype);
if (!savedProtoMethods) {
savedProtoMethods = new Map();
this.#savedMethods.set(prototype, savedProtoMethods);
}
const originalMethod = prototype[methodName];
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;
}
}
}