[FIX] bus: shared-worker failing to start on incognito

Before this PR, the bus service's `SharedWorker` sometimes failed
 to initialized with data url. This error only occurred on some devices,
 on chrome and in incognito mode.

 While the specifications specify that `SharedWorker` constructor should
 handle data url properly [1], there is no way to tell whether or not the
 user browser supports it.

 This commit solves this issue by introducing a fallback on simple `Worker`s
 since they seem to handle data url correctly in this case [2].

[1] https://html.spec.whatwg.org/multipage/workers.html#dom-worker
[2] https://chromium.googlesource.com/chromium/src.git/+/9801daec162285a1f2c6695682ace5d508ea9b7f

opw-3146671

closes odoo/odoo#112921

X-original-commit: 99ac9538d6ba49161be37318fca541c7e51603f9
Signed-off-by: Stockbauer Matthieu (tsm) <tsm@odoo.com>
This commit is contained in:
tsm-odoo
2023-02-17 08:12:26 +01:00
parent d91070936d
commit d6589641cf
2 changed files with 77 additions and 24 deletions
+45 -24
View File
@@ -25,25 +25,11 @@ export const busService = {
async start(env, { multi_tab: multiTab, "bus.parameters": params }) {
const bus = new EventBus();
let worker;
let isActive = false;
let workerURL = `${params.serverURL}/bus/websocket_worker_bundle?v=${WORKER_VERSION}`;
if (params.serverURL !== window.origin) {
// Bus service is loaded from a different origin than the bundle
// URL. The Worker expects an URL from this origin, give it a base64
// URL that will then load the bundle via "importScripts" which
// allows cross origin.
const source = `importScripts("${workerURL}");`;
workerURL = 'data:application/javascript;base64,' + window.btoa(source);
}
const workerClass = 'SharedWorker' in window && !isIosApp() ? browser.SharedWorker : browser.Worker;
const worker = new workerClass(workerURL, {
name: 'SharedWorker' in window && !isIosApp() ? 'odoo:websocket_shared_worker' : 'odoo:websocket_worker',
});
let isInitialized = false;
let isUsingSharedWorker = 'SharedWorker' in browser && !isIosApp();
const connectionInitializedDeferred = new Deferred();
worker.addEventListener("error", (e) => {
connectionInitializedDeferred.resolve();
console.warn("Error while loading 'bus_service' SharedWorker");
});
/**
* Send a message to the worker.
@@ -55,7 +41,7 @@ export const busService = {
*/
function send(action, data) {
const message = { action, data };
if ('SharedWorker' in window && !isIosApp()) {
if (isUsingSharedWorker) {
worker.port.postMessage(message);
} else {
worker.postMessage(message);
@@ -76,6 +62,7 @@ export const busService = {
multiTab.setSharedValue('last_notification_id', data[data.length - 1].id);
data = data.map(notification => notification.message);
} else if (type === 'initialized') {
isInitialized = true;
connectionInitializedDeferred.resolve();
return;
}
@@ -108,13 +95,46 @@ export const busService = {
});
}
if ('SharedWorker' in window && !isIosApp()) {
worker.port.start();
worker.port.addEventListener('message', handleMessage);
} else {
worker.addEventListener('message', handleMessage);
/**
* Start the "bus_service" worker.
*/
function startWorker() {
let workerURL = `${params.serverURL}/bus/websocket_worker_bundle?v=${WORKER_VERSION}`;
if (params.serverURL !== window.origin) {
// Bus service is loaded from a different origin than the bundle
// URL. The Worker expects an URL from this origin, give it a base64
// URL that will then load the bundle via "importScripts" which
// allows cross origin.
const source = `importScripts("${workerURL}");`;
workerURL = 'data:application/javascript;base64,' + window.btoa(source);
}
const workerClass = isUsingSharedWorker ? browser.SharedWorker : browser.Worker;
worker = new workerClass(workerURL, {
name: isUsingSharedWorker
? 'odoo:websocket_shared_worker'
: 'odoo:websocket_worker',
});
worker.addEventListener("error", (e) => {
if (!isInitialized && workerClass === browser.SharedWorker) {
console.warn(
'Error while loading "bus_service" SharedWorker, fallback on Worker.'
);
isUsingSharedWorker = false;
startWorker();
} else if (!isInitialized) {
isInitialized = true;
connectionInitializedDeferred.resolve();
console.warn('Bus service failed to initialized.');
}
});
if (isUsingSharedWorker) {
worker.port.start();
worker.port.addEventListener('message', handleMessage);
} else {
worker.addEventListener('message', handleMessage);
}
initializeWorkerConnection();
}
initializeWorkerConnection();
browser.addEventListener('pagehide', ({ persisted }) => {
if (!persisted) {
// Page is gonna be unloaded, disconnect this client
@@ -128,6 +148,7 @@ export const busService = {
}
});
browser.addEventListener('offline', () => send('stop'));
startWorker();
await connectionInitializedDeferred;
return {
+32
View File
@@ -547,6 +547,38 @@ QUnit.module('Bus', {
"reconnect",
]);
});
QUnit.test(
"Fallback on simple worker when shared worker failed to initialize",
async function (assert) {
const originalSharedWorker = browser.SharedWorker;
const originalWorker = browser.Worker;
patchWithCleanup(browser, {
SharedWorker: function (url, options) {
assert.step("shared-worker creation");
const sw = new originalSharedWorker(url, options);
// Simulate error during shared worker creation.
setTimeout(() => sw.dispatchEvent(new Event("error")));
return sw;
},
Worker: function (url, options) {
assert.step("worker creation");
return new originalWorker(url, options);
},
}, { pure: true });
patchWithCleanup(window.console, {
warn(message) {
assert.step(message);
},
})
await makeTestEnv();
assert.verifySteps([
"shared-worker creation",
"Error while loading \"bus_service\" SharedWorker, fallback on Worker.",
"worker creation",
]);
}
);
});
});