gnome-shell/js/extensions/extension.js
Florian Müllner f70a75a905 extensionUtils: Add InjectionManager
It is fairly common for extensions to monkey-patch existing
classes. Add a small helper class that makes this a tad bit
more convenient.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2809>
2023-08-05 12:34:41 +02:00

100 lines
3.0 KiB
JavaScript

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