Skip to content

Commit

Permalink
v1.1.2.0: Add basic shim static dispatch.
Browse files Browse the repository at this point in the history
Now supports inherited and instance wrapping.
  • Loading branch information
ruipin committed Jan 3, 2021
1 parent 6696e60 commit 76040fc
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 12 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# 1.1.2.0 (2021-01-03)

* Prevent wrapping of libWrapper internals
* Update shim. Now supports very basic inherited method wrapping using static dispatch.

# 1.1.1.0 (2021-01-03)

* Fix parameters when instance-specific wrappers chain to class-specific wrappers.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ To unregister all wrapper functions belonging to a given module, you should call
The [shim.js](shim/shim.js) file in this repository can be used to avoid a hard dependency on libWrapper.
The shim exports a 'libWrapper' symbol which will at the 'init' hook become a reference to the real libWrapper library if present, or to a fallback implementation otherwise. This symbol will be `undefined` until the 'init' hook fires. A fallback implementation is included for the `register` function only (see documentation above). This fallback implementation does not have any of the "fancy" features of the libWrapper library - most importantly, it does not check for module conflicts or enforce call order between the different wrapper types, and it does not support instance-specific nor inherited-method wrapping. *Due to these differences in behaviour, it is extremely important to test your code both with the shim and with the full library.*
The shim exports a 'libWrapper' symbol which will at the 'init' hook become a reference to the real libWrapper library if present, or to a fallback implementation otherwise. This symbol will be `undefined` until the 'init' hook fires. A fallback implementation is included for the `register` function only (see documentation above). This fallback implementation does not have any of the "fancy" features of the libWrapper library - most importantly, it does not check for module conflicts or enforce call order between the different wrapper types, and it does not do dynamic dispatch. *Due to these differences in behaviour, it is extremely important to test your code both with the shim and with the full library.*
To programmatically detect whether the fallback implementation is active, you can check `libWrapper.is_fallback == true`.
Expand Down
2 changes: 1 addition & 1 deletion module.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "lib-wrapper",
"title": "libWrapper",
"description": "Library for wrapping core Foundry VTT methods, meant to improve compatibility between modules that wrap the same methods.",
"version": "1.1.1.0",
"version": "1.1.2.0",
"author": "Rui Pinheiro",
"esmodules": ["src/index.js"],
"styles": ["dist/lib-wrapper.css"],
Expand Down
27 changes: 18 additions & 9 deletions shim/shim.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,34 @@ Hooks.once('init', () => {
const _eval = eval; // The browser doesn't expose all global variables (e.g. 'Game') inside globalThis, but it does to an eval. We copy it to a variable to have it run in global scope.
const obj = split.reduce((x,y)=>x[y], globalThis[root_nm] ?? _eval(root_nm));

const descriptor = Object.getOwnPropertyDescriptor(obj, fn_name);
if(!descriptor) throw `libWrapper Shim: "${target}" does not exist or could not be found.`;
let iObj = obj;
let descriptor = null;
while(iObj) {
descriptor = Object.getOwnPropertyDescriptor(iObj, fn_name);
if(descriptor) break;
iObj = Object.getPrototypeOf(iObj);
}
if(!descriptor) throw `libWrapper Shim: '${target}' does not exist or could not be found.`;

let original = null;
const wrapper = (type == 'OVERRIDE') ? function() { return fn.call(this, ...arguments); } : function() { return fn.call(this, original.bind(this), ...arguments); }
if(descriptor.value) {
original = obj[fn_name];
obj[fn_name] = wrapper;
return;
}

if(!is_setter) {
original = descriptor.get;
descriptor.get = wrapper;
if(descriptor.value) {
original = descriptor.value;
descriptor.value = wrapper;
}
else {
original = descriptor.get;
descriptor.get = wrapper;
}
}
else {
if(!descriptor.set) throw `libWrapper Shim: '${target}' does not have a setter`;
original = descriptor.set;
descriptor.set = wrapper;
}

descriptor.configurable = true;
Object.defineProperty(obj, fn_name, descriptor);
}
Expand Down
59 changes: 58 additions & 1 deletion tests/test_shim.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function setup() {
}


test('Shim: Basic functionality', async function (t) {
test('Shim: Basic functionality', function (t) {
setup();

// Definitions
Expand Down Expand Up @@ -85,5 +85,62 @@ test('Shim: Basic functionality', async function (t) {
libWrapper.unregister('module1', 'A.prototype.x');


// Test invalid getter
t.throws(() => { libWrapperShim.register('module1', 'A.prototype.xyz', ()=>{}); }, undefined, "Wrap invalid getter");

// Test invalid setter
t.throws(() => { libWrapperShim.register('module1', 'A.prototype.x#set', ()=>{}); }, undefined, "Wrap invalid setter");


// Inherited method wrapping
class B {
x() {
return 1;
}
}
globalThis.B = B;

class C extends B {
}
globalThis.C = C;

let bxValue = 1;
let c = new C();
t.equal(c.x(), bxValue, 'Original');

// Register wrapper for inherited class
libWrapperShim.register('module1', 'C.prototype.x', function(original) {
t.equal(original(), bxValue, 'xWrapper 1');
return 10;
});
t.equal(c.x(), 10, "Wrapped with 10");


// Setter
let __dx = 1;
class D {
get x() {
return __dx;
}

set x(value) {
__dx = value;
}
}
globalThis.D = D;
let d = new D();
t.equal(d.x, 1, 'Original');

// Register wrapper for setter
libWrapperShim.register('module1', 'D.prototype.x#set', function(wrapped, value) {
return wrapped.call(this, value + 1);
});
t.equal(d.x, 1, 'Wrapped Setter #1');

d.x = 3;
t.equal(d.x, 4, 'Set 3 (+1)');


// Done
t.end();
});

0 comments on commit 76040fc

Please sign in to comment.