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>
This commit is contained in:
@ -29,3 +29,71 @@ export class Extension extends ExtensionBase {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user