This commit fixes two things: - The owl compatibility layer adds an `on` and `off` method on owl's EventBus so that it can keep being used as it used to in owl 1. To do this, it needs to keep track of the the callbacks that are attached to it and by whom. To do this it keeps a Map where the "owners" are keys. When calling `off` to remove event listeners, we remove the callbacks but did not remove the owner from the Map, causing the EventBus to always hold a strong reference to any object that was given as the owner to the `on` method, preventing the item from being garbage collected. Objects that do this are typically components, and components hold a reference to their owl application that contains the entire tree of components. This was particularly problematic in tests where components are created and destroyed at a rapid pace. - The mock websocket creates a MessageChannel so that it can mock the server side of the websocket, but it never closes that MessageChannel's MessagePorts, which can cause garbage collection to be slow to happen [1] (in practice it looks like these are basically never garbage collected unless closed). This commit registers a cleanup to close the message ports after the execution of the current test. [1]: https://html.spec.whatwg.org/dev/web-messaging.html#ports-and-garbage-collection closes odoo/odoo#108309 X-original-commit: 4facc396a4e8036592a473002253d5a5daf41a19 Signed-off-by: Aaron Bohy (aab) <aab@odoo.com> Co-authored-by: Aaron Bohy <aab@odoo.com>
92 lines
2.8 KiB
JavaScript
92 lines
2.8 KiB
JavaScript
/** @odoo-module **/
|
|
|
|
import { WebsocketWorker } from "@bus/workers/websocket_worker";
|
|
import { browser } from "@web/core/browser/browser";
|
|
import { patchWithCleanup } from "@web/../tests/helpers/utils";
|
|
import { registerCleanup } from "@web/../tests/helpers/cleanup";
|
|
|
|
class WebSocketMock extends EventTarget {
|
|
constructor(url) {
|
|
super();
|
|
this.readyState = 0;
|
|
this.url = url;
|
|
|
|
queueMicrotask(() => {
|
|
this.readyState = 1;
|
|
const openEv = new Event('open');
|
|
this.onopen(openEv);
|
|
this.dispatchEvent(openEv);
|
|
});
|
|
}
|
|
|
|
close(code, reason) {
|
|
this.readyState = 3;
|
|
const closeEv = new CloseEvent('close', {
|
|
code,
|
|
reason,
|
|
wasClean: code === 1000,
|
|
});
|
|
this.onclose(closeEv);
|
|
this.dispatchEvent(closeEv);
|
|
}
|
|
|
|
onclose(closeEv) {}
|
|
onerror(errorEv) {}
|
|
onopen(openEv) {}
|
|
|
|
send(data) {
|
|
if (this.readyState !== 1) {
|
|
const errorEv = new Event('error');
|
|
this.onerror(errorEv);
|
|
this.dispatchEvent(errorEv);
|
|
throw new DOMException("Failed to execute 'send' on 'WebSocket': State is not OPEN");
|
|
}
|
|
}
|
|
}
|
|
|
|
class SharedWorkerMock {
|
|
constructor(websocketWorker) {
|
|
this._websocketWorker = websocketWorker;
|
|
this._messageChannel = new MessageChannel();
|
|
this.port = this._messageChannel.port1;
|
|
// port 1 should be started by the service itself.
|
|
this._messageChannel.port2.start();
|
|
this._websocketWorker.registerClient(this._messageChannel.port2);
|
|
}
|
|
}
|
|
|
|
|
|
let websocketWorker;
|
|
/**
|
|
* @param {*} params Parameters used to patch the websocket worker.
|
|
* @returns {WebsocketWorker} Instance of the worker which will run during the
|
|
* test. Usefull to interact with the worker in order to test the
|
|
* websocket behavior.
|
|
*/
|
|
export function patchWebsocketWorkerWithCleanup(params = {}) {
|
|
patchWithCleanup(window, {
|
|
WebSocket: function () {
|
|
return new WebSocketMock();
|
|
},
|
|
}, { pure: true });
|
|
patchWithCleanup(websocketWorker || WebsocketWorker.prototype, params);
|
|
websocketWorker = websocketWorker || new WebsocketWorker('wss://odoo.com/websocket');
|
|
patchWithCleanup(browser, {
|
|
SharedWorker: function () {
|
|
const sharedWorker = new SharedWorkerMock(websocketWorker);
|
|
registerCleanup(() => {
|
|
sharedWorker._messageChannel.port1.close();
|
|
sharedWorker._messageChannel.port2.close();
|
|
});
|
|
return sharedWorker;
|
|
},
|
|
}, { pure: true });
|
|
registerCleanup(() => {
|
|
if (websocketWorker) {
|
|
clearTimeout(websocketWorker.connectTimeout);
|
|
websocketWorker = null;
|
|
}
|
|
});
|
|
return websocketWorker;
|
|
}
|