Skip to content

Commit 7e79633

Browse files
authored
Use fetch rather than XMLHttpRequest for downloading Wasm files (#22015)
This PR switches requests for Wasm files to be loaded with `fetch()` rather than `XMLHttpRequest()`. This fixes running in service workers where `XMLHttpRequest` is not available. Partially fixes #22003, only applies to Wasm files.
1 parent 6c98de6 commit 7e79633

File tree

6 files changed

+133
-15
lines changed

6 files changed

+133
-15
lines changed

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -600,3 +600,4 @@ a license to everyone to use it as detailed in LICENSE.)
600600
* YAMAMOTO Takashi <[email protected]>
601601
* Artur Gatin <[email protected]> (copyright owned by Teladoc Health, Inc.)
602602
* Christian Lloyd <[email protected]> (copyright owned by Teladoc Health, Inc.)
603+
* Sean Morris <[email protected]>

src/polyfill/fetch.js

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Fetch polyfill from https://github.com/developit/unfetch
2+
// License:
3+
//==============================================================================
4+
// Copyright (c) 2017 Jason Miller
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files (the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions:
12+
//
13+
// The above copyright notice and this permission notice shall be included in
14+
// all copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
// THE SOFTWARE.
23+
//==============================================================================
24+
25+
#if !POLYFILL
26+
#error "this file should never be included unless POLYFILL is set"
27+
#endif
28+
29+
if (typeof globalThis.fetch == 'undefined') {
30+
globalThis.fetch = function (url, options) {
31+
options = options || {};
32+
return new Promise((resolve, reject) => {
33+
const request = new XMLHttpRequest();
34+
const keys = [];
35+
const headers = {};
36+
37+
request.responseType = 'arraybuffer';
38+
39+
const response = () => ({
40+
ok: ((request.status / 100) | 0) == 2, // 200-299
41+
statusText: request.statusText,
42+
status: request.status,
43+
url: request.responseURL,
44+
text: () => Promise.resolve(request.responseText),
45+
json: () => Promise.resolve(request.responseText).then(JSON.parse),
46+
blob: () => Promise.resolve(new Blob([request.response])),
47+
arrayBuffer: () => Promise.resolve(request.response),
48+
clone: response,
49+
headers: {
50+
keys: () => keys,
51+
entries: () => keys.map((n) => [n, request.getResponseHeader(n)]),
52+
get: (n) => request.getResponseHeader(n),
53+
has: (n) => request.getResponseHeader(n) != null,
54+
},
55+
});
56+
57+
request.open(options.method || "get", url, true);
58+
59+
request.onload = () => {
60+
request
61+
.getAllResponseHeaders()
62+
.toLowerCase()
63+
.replace(/^(.+?):/gm, (m, key) => {
64+
headers[key] || keys.push((headers[key] = key));
65+
});
66+
resolve(response());
67+
};
68+
69+
request.onerror = reject;
70+
71+
request.withCredentials = options.credentials == "include";
72+
73+
for (const i in options.headers) {
74+
request.setRequestHeader(i, options.headers[i]);
75+
}
76+
77+
request.send(options.body || null);
78+
});
79+
}
80+
}

src/shell.js

+5
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ var Module = typeof {{{ EXPORT_NAME }}} != 'undefined' ? {{{ EXPORT_NAME }}} : {
6161
// See https://caniuse.com/mdn-javascript_builtins_bigint64array
6262
#include "polyfill/bigint64array.js"
6363
#endif
64+
65+
#if MIN_CHROME_VERSION < 40 || MIN_FIREFOX_VERSION < 39 || MIN_SAFARI_VERSION < 103000
66+
// See https://caniuse.com/fetch
67+
#include "polyfill/fetch.js"
68+
#endif
6469
#endif // POLYFILL
6570

6671
#if MODULARIZE

src/web_or_worker_shell_read.js

+8-13
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,12 @@
2222
}
2323

2424
readAsync = (url, onload, onerror) => {
25-
var xhr = new XMLHttpRequest();
26-
xhr.open('GET', url, true);
27-
xhr.responseType = 'arraybuffer';
28-
xhr.onload = () => {
29-
if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) { // file URLs can return 0
30-
onload(xhr.response);
31-
return;
25+
fetch(url)
26+
.then((response) => {
27+
if (response.ok) {
28+
return response.arrayBuffer();
3229
}
33-
onerror();
34-
};
35-
xhr.onerror = onerror;
36-
xhr.send(null);
37-
}
38-
30+
return Promise.reject(new Error(response.statusText + ' : ' + response.url));
31+
})
32+
.then(onload, onerror)
33+
};

test/common.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2272,7 +2272,7 @@ def compile_btest(self, filename, args, reporting=Reporting.FULL):
22722272
# also include report_result.c and force-include report_result.h
22732273
self.run_process([EMCC, '-c', '-I' + TEST_ROOT,
22742274
'-DEMTEST_PORT_NUMBER=%d' % self.port,
2275-
test_file('report_result.c')] + self.get_emcc_args(compile_only=True))
2275+
test_file('report_result.c')] + self.get_emcc_args(compile_only=True) + (['-fPIC'] if '-fPIC' in args else []))
22762276
args += ['report_result.o', '-include', test_file('report_result.h')]
22772277
if EMTEST_BROWSER == 'node':
22782278
args.append('-DEMTEST_NODE')

test/test_browser.py

+38-1
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,9 @@ def setup(assetLocalization):
700700
<center><canvas id='canvas' width='256' height='256'></canvas></center>
701701
<hr><div id='output'></div><hr>
702702
<script type='text/javascript'>
703-
window.onerror = (error) => {
703+
const handler = (event) => {
704+
event.stopImmediatePropagation();
705+
const error = String(event instanceof ErrorEvent ? event.message : (event.reason || event));
704706
window.disableErrorReporting = true;
705707
window.onerror = null;
706708
var result = error.includes("test.data") ? 1 : 0;
@@ -709,6 +711,8 @@ def setup(assetLocalization):
709711
xhr.send();
710712
setTimeout(function() { window.close() }, 1000);
711713
}
714+
window.addEventListener('error', handler);
715+
window.addEventListener('unhandledrejection', handler);
712716
var Module = {
713717
locateFile: function (path, prefix) {if (path.endsWith(".wasm")) {return prefix + path;} else {return "''' + assetLocalization + r'''" + path;}},
714718
print: (function() {
@@ -5545,6 +5549,39 @@ def test_webpack(self):
55455549
shutil.copyfile('webpack/src/hello.wasm', 'webpack/dist/hello.wasm')
55465550
self.run_browser('webpack/dist/index.html', '/report_result?exit:0')
55475551

5552+
def test_fetch_polyfill_shared_lib(self):
5553+
create_file('library.c', r'''
5554+
#include <stdio.h>
5555+
int library_func() {
5556+
return 42;
5557+
}
5558+
''')
5559+
create_file('main.c', r'''
5560+
#include <dlfcn.h>
5561+
#include <stdio.h>
5562+
int main() {
5563+
void *lib_handle = dlopen("/library.so", RTLD_NOW);
5564+
typedef int (*voidfunc)();
5565+
voidfunc x = (voidfunc)dlsym(lib_handle, "library_func");
5566+
return x();
5567+
}
5568+
''')
5569+
5570+
self.run_process([EMCC, 'library.c', '-sSIDE_MODULE', '-O2', '-o', 'library.so'])
5571+
5572+
def test(args, expect_fail):
5573+
self.compile_btest('main.c', ['-fPIC', 'library.so', '-sMAIN_MODULE=2', '-sEXIT_RUNTIME', '-o', 'a.out.html'] + args)
5574+
if expect_fail:
5575+
js = read_file('a.out.js')
5576+
create_file('a.out.js', 'fetch = undefined;\n' + js)
5577+
return self.run_browser('a.out.html', '/report_result?abort:TypeError')
5578+
else:
5579+
return self.run_browser('a.out.html', '/report_result?exit:42')
5580+
5581+
test([], expect_fail=True)
5582+
test(['-sLEGACY_VM_SUPPORT'], expect_fail=False)
5583+
test(['-sLEGACY_VM_SUPPORT', '-sNO_POLYFILL'], expect_fail=True)
5584+
55485585

55495586
class emrun(RunnerCore):
55505587
def test_emrun_info(self):

0 commit comments

Comments
 (0)