Skip to content

Commit 43baa09

Browse files
authored
Convert FS_handledByPreloadPlugin and plugin handle callback to async. (#24914)
The `FS_handledByPreloadPlugin` is completely internal so changing this should be completely non-functional. If external projects are implementing custom preload plugins they would need to update their `handle` methods. I've added an assertion in debug builds to detect plugins that are not async. This change will allow a followup which is to convert that whole file preloading mechanism to async.
1 parent df64a3d commit 43baa09

File tree

7 files changed

+115
-102
lines changed

7 files changed

+115
-102
lines changed

ChangeLog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ See docs/process.md for more on how version tagging works.
2020

2121
4.0.13 (in development)
2222
-----------------------
23+
- The `handle` callback on the `preloadPlugins` used by `--use-preload-plugins`
24+
(and `FS_createPreloadedFile` API`) was converted from callbacks to async.
25+
Any externally managed plugins would need to be updated accordingly. An
26+
assertion will detect any such non-async plugins in the wild. (#24914)
2327
- SDL2 updated from 2.32.0 to 2.32.8. (#24912/)
2428
- `sdl-config` and `sdl2-config` scripts were simplified to avoid using python
2529
and the `.bat` file versions were removed, matching upstream SDL. (#24907)

src/lib/libbrowser.js

Lines changed: 67 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -47,86 +47,90 @@ var LibraryBrowser = {
4747
imagePlugin['canHandle'] = function imagePlugin_canHandle(name) {
4848
return !Module['noImageDecoding'] && /\.(jpg|jpeg|png|bmp|webp)$/i.test(name);
4949
};
50-
imagePlugin['handle'] = function imagePlugin_handle(byteArray, name, onload, onerror) {
50+
imagePlugin['handle'] = async function imagePlugin_handle(byteArray, name) {
5151
var b = new Blob([byteArray], { type: Browser.getMimetype(name) });
5252
if (b.size !== byteArray.length) { // Safari bug #118630
5353
// Safari's Blob can only take an ArrayBuffer
5454
b = new Blob([(new Uint8Array(byteArray)).buffer], { type: Browser.getMimetype(name) });
5555
}
5656
var url = URL.createObjectURL(b);
57-
var img = new Image();
58-
img.onload = () => {
57+
return new Promise((resolve, reject) => {
58+
var img = new Image();
59+
img.onload = () => {
5960
#if ASSERTIONS
60-
assert(img.complete, `Image ${name} could not be decoded`);
61+
assert(img.complete, `Image ${name} could not be decoded`);
6162
#endif
62-
var canvas = /** @type {!HTMLCanvasElement} */ (document.createElement('canvas'));
63-
canvas.width = img.width;
64-
canvas.height = img.height;
65-
var ctx = canvas.getContext('2d');
66-
ctx.drawImage(img, 0, 0);
67-
Browser.preloadedImages[name] = canvas;
68-
URL.revokeObjectURL(url);
69-
onload?.(byteArray);
70-
};
71-
img.onerror = (event) => {
72-
err(`Image ${url} could not be decoded`);
73-
onerror?.();
74-
};
75-
img.src = url;
63+
var canvas = /** @type {!HTMLCanvasElement} */ (document.createElement('canvas'));
64+
canvas.width = img.width;
65+
canvas.height = img.height;
66+
var ctx = canvas.getContext('2d');
67+
ctx.drawImage(img, 0, 0);
68+
Browser.preloadedImages[name] = canvas;
69+
URL.revokeObjectURL(url);
70+
resolve(byteArray);
71+
};
72+
img.onerror = (event) => {
73+
err(`Image ${url} could not be decoded`);
74+
reject();
75+
};
76+
img.src = url;
77+
});
7678
};
7779
preloadPlugins.push(imagePlugin);
7880

7981
var audioPlugin = {};
8082
audioPlugin['canHandle'] = function audioPlugin_canHandle(name) {
8183
return !Module['noAudioDecoding'] && name.slice(-4) in { '.ogg': 1, '.wav': 1, '.mp3': 1 };
8284
};
83-
audioPlugin['handle'] = function audioPlugin_handle(byteArray, name, onload, onerror) {
84-
var done = false;
85-
function finish(audio) {
86-
if (done) return;
87-
done = true;
88-
Browser.preloadedAudios[name] = audio;
89-
onload?.(byteArray);
90-
}
91-
var b = new Blob([byteArray], { type: Browser.getMimetype(name) });
92-
var url = URL.createObjectURL(b); // XXX we never revoke this!
93-
var audio = new Audio();
94-
audio.addEventListener('canplaythrough', () => finish(audio), false); // use addEventListener due to chromium bug 124926
95-
audio.onerror = function audio_onerror(event) {
96-
if (done) return;
97-
err(`warning: browser could not fully decode audio ${name}, trying slower base64 approach`);
98-
function encode64(data) {
99-
var BASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
100-
var PAD = '=';
101-
var ret = '';
102-
var leftchar = 0;
103-
var leftbits = 0;
104-
for (var i = 0; i < data.length; i++) {
105-
leftchar = (leftchar << 8) | data[i];
106-
leftbits += 8;
107-
while (leftbits >= 6) {
108-
var curr = (leftchar >> (leftbits-6)) & 0x3f;
109-
leftbits -= 6;
110-
ret += BASE[curr];
85+
audioPlugin['handle'] = async function audioPlugin_handle(byteArray, name) {
86+
return new Promise((resolve, reject) => {
87+
var done = false;
88+
function finish(audio) {
89+
if (done) return;
90+
done = true;
91+
Browser.preloadedAudios[name] = audio;
92+
resolve(byteArray);
93+
}
94+
var b = new Blob([byteArray], { type: Browser.getMimetype(name) });
95+
var url = URL.createObjectURL(b); // XXX we never revoke this!
96+
var audio = new Audio();
97+
audio.addEventListener('canplaythrough', () => finish(audio), false); // use addEventListener due to chromium bug 124926
98+
audio.onerror = function audio_onerror(event) {
99+
if (done) return;
100+
err(`warning: browser could not fully decode audio ${name}, trying slower base64 approach`);
101+
function encode64(data) {
102+
var BASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
103+
var PAD = '=';
104+
var ret = '';
105+
var leftchar = 0;
106+
var leftbits = 0;
107+
for (var i = 0; i < data.length; i++) {
108+
leftchar = (leftchar << 8) | data[i];
109+
leftbits += 8;
110+
while (leftbits >= 6) {
111+
var curr = (leftchar >> (leftbits-6)) & 0x3f;
112+
leftbits -= 6;
113+
ret += BASE[curr];
114+
}
111115
}
116+
if (leftbits == 2) {
117+
ret += BASE[(leftchar&3) << 4];
118+
ret += PAD + PAD;
119+
} else if (leftbits == 4) {
120+
ret += BASE[(leftchar&0xf) << 2];
121+
ret += PAD;
122+
}
123+
return ret;
112124
}
113-
if (leftbits == 2) {
114-
ret += BASE[(leftchar&3) << 4];
115-
ret += PAD + PAD;
116-
} else if (leftbits == 4) {
117-
ret += BASE[(leftchar&0xf) << 2];
118-
ret += PAD;
119-
}
120-
return ret;
121-
}
122-
audio.src = 'data:audio/x-' + name.slice(-3) + ';base64,' + encode64(byteArray);
123-
finish(audio); // we don't wait for confirmation this worked - but it's worth trying
124-
};
125-
audio.src = url;
126-
// workaround for chrome bug 124926 - we do not always get oncanplaythrough or onerror
127-
safeSetTimeout(() => {
128-
finish(audio); // try to use it even though it is not necessarily ready to play
129-
}, 10000);
125+
audio.src = 'data:audio/x-' + name.slice(-3) + ';base64,' + encode64(byteArray);
126+
finish(audio); // we don't wait for confirmation this worked - but it's worth trying
127+
};
128+
audio.src = url;
129+
// workaround for chrome bug 124926 - we do not always get oncanplaythrough or onerror
130+
safeSetTimeout(() => {
131+
finish(audio); // try to use it even though it is not necessarily ready to play
132+
}, 10000);
133+
});
130134
};
131135
preloadPlugins.push(audioPlugin);
132136
#endif

src/lib/libdylink.js

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,22 @@ var LibraryDylink = {
2121
'canHandle': (name) => {
2222
return !Module['noWasmDecoding'] && name.endsWith('.so')
2323
},
24-
'handle': (byteArray, name, onload, onerror) => {
24+
'handle': async (byteArray, name) =>
2525
// loadWebAssemblyModule can not load modules out-of-order, so rather
2626
// than just running the promises in parallel, this makes a chain of
2727
// promises to run in series.
28-
wasmPlugin.promiseChainEnd = wasmPlugin.promiseChainEnd.then(
29-
() => loadWebAssemblyModule(byteArray, {loadAsync: true, nodelete: true}, name, {})).then(
30-
(exports) => {
28+
wasmPlugin.promiseChainEnd = wasmPlugin.promiseChainEnd.then(async () => {
29+
try {
30+
var exports = await loadWebAssemblyModule(byteArray, {loadAsync: true, nodelete: true}, name, {});
31+
} catch (error) {
32+
throw new Error(`failed to instantiate wasm: ${name}: ${error}`);
33+
}
3134
#if DYLINK_DEBUG
32-
dbg('registering preloadedWasm:', name);
33-
#endif
34-
preloadedWasm[name] = exports;
35-
onload(byteArray);
36-
},
37-
(error) => {
38-
err(`failed to instantiate wasm: ${name}: ${error}`);
39-
onerror();
40-
});
41-
}
35+
dbg('registering preloadedWasm:', name);
36+
#endif
37+
preloadedWasm[name] = exports;
38+
return byteArray;
39+
})
4240
};
4341
preloadPlugins.push(wasmPlugin);
4442
},

src/lib/libfs_shared.js

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,23 @@ addToLibrary({
1313
// it was handled.
1414
$FS_handledByPreloadPlugin__internal: true,
1515
$FS_handledByPreloadPlugin__deps: ['$preloadPlugins'],
16-
$FS_handledByPreloadPlugin: (byteArray, fullname, finish, onerror) => {
16+
$FS_handledByPreloadPlugin: async (byteArray, fullname) => {
1717
#if LibraryManager.has('libbrowser.js')
1818
// Ensure plugins are ready.
1919
if (typeof Browser != 'undefined') Browser.init();
2020
#endif
2121

2222
for (var plugin of preloadPlugins) {
2323
if (plugin['canHandle'](fullname)) {
24-
plugin['handle'](byteArray, fullname, finish, onerror);
25-
return true;
24+
#if ASSERTIONS
25+
assert(plugin['handle'].constructor.name === 'AsyncFunction', 'Filesystem plugin handlers must be async functions (See #24914)')
26+
#endif
27+
return plugin['handle'](byteArray, fullname);
2628
}
2729
}
28-
return false;
30+
// In no plugin handled this file then return the original/unmodified
31+
// byteArray.
32+
return byteArray;
2933
},
3034

3135
// Preloads a file asynchronously. You can call this before run, for example in
@@ -53,21 +57,21 @@ addToLibrary({
5357
var fullname = name ? PATH_FS.resolve(PATH.join2(parent, name)) : parent;
5458
var dep = getUniqueRunDependency(`cp ${fullname}`); // might have several active requests for the same fullname
5559
function processData(byteArray) {
56-
function finish(byteArray) {
57-
preFinish?.();
58-
if (!dontCreateFile) {
59-
FS_createDataFile(parent, name, byteArray, canRead, canWrite, canOwn);
60-
}
61-
onload?.();
62-
removeRunDependency(dep);
63-
}
64-
if (!FS_handledByPreloadPlugin(byteArray, fullname, finish, () => {
65-
onerror?.();
66-
removeRunDependency(dep);
67-
})) {
68-
finish(byteArray);
69-
}
60+
FS_handledByPreloadPlugin(byteArray, fullname)
61+
.then((byteArray) => {
62+
preFinish?.();
63+
if (!dontCreateFile) {
64+
FS_createDataFile(parent, name, byteArray, canRead, canWrite, canOwn);
65+
}
66+
onload?.();
67+
removeRunDependency(dep);
68+
})
69+
.catch(() => {
70+
onerror?.();
71+
removeRunDependency(dep);
72+
});
7073
}
74+
7175
addRunDependency(dep);
7276
if (typeof url == 'string') {
7377
asyncLoad(url).then(processData, onerror);

src/lib/liblz4.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ addToLibrary({
5656
addRunDependency(dep);
5757
var finish = () => removeRunDependency(dep);
5858
var byteArray = FS.readFile(fullname);
59-
plugin['handle'](byteArray, fullname, finish, finish);
59+
#if ASSERTIONS
60+
assert(plugin['handle'].constructor.name === 'AsyncFunction', 'Filesystem plugin handlers must be async functions (See #24914)')
61+
#endif
62+
plugin['handle'](byteArray, fullname).then(finish).catch(finish);
6063
break;
6164
}
6265
}

test/code_size/test_codesize_hello_dylink.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
2-
"a.out.js": 26982,
3-
"a.out.js.gz": 11461,
2+
"a.out.js": 27008,
3+
"a.out.js.gz": 11463,
44
"a.out.nodebug.wasm": 18561,
55
"a.out.nodebug.wasm.gz": 9167,
6-
"total": 45543,
7-
"total_gz": 20628,
6+
"total": 45569,
7+
"total_gz": 20630,
88
"sent": [
99
"__heap_base",
1010
"__indirect_function_table",

test/code_size/test_codesize_hello_dylink_all.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
2-
"a.out.js": 246799,
2+
"a.out.js": 246873,
33
"a.out.nodebug.wasm": 597826,
4-
"total": 844625,
4+
"total": 844699,
55
"sent": [
66
"IMG_Init",
77
"IMG_Load",

0 commit comments

Comments
 (0)