Skip to content

Use fetch() for loading packages & shared libraries #22002

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 39 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
87743d1
Adding USE_FETCH setting.
seanmorris May 25, 2024
3d38841
Tweaks
seanmorris May 25, 2024
ffcaa3b
Adding tests.
seanmorris May 26, 2024
de79ead
Tweak.
seanmorris May 26, 2024
139bcd4
Tweak.
seanmorris May 26, 2024
893f361
Merge branch 'main' into sm-worker-fetch
seanmorris May 26, 2024
0407112
Tweak.
seanmorris May 26, 2024
24761b6
Merge branch 'sm-worker-fetch' of github.com:seanmorris/emscripten in…
seanmorris May 26, 2024
3ead8de
Rebasing generated sizes.
seanmorris May 26, 2024
35b593f
Reverting extraneous change.
seanmorris May 26, 2024
5c2a7bb
Readability.
seanmorris May 26, 2024
5631928
Testing
seanmorris May 26, 2024
ff2d142
Removing debug.
seanmorris May 26, 2024
9e06ef2
Typo.
seanmorris May 26, 2024
5899d3f
Parameterizing test.
seanmorris May 26, 2024
c09f2c5
Spacing.
seanmorris May 26, 2024
2f43c20
Deduping default info in docs.
seanmorris May 26, 2024
c5ecce5
Deduping if statement.
seanmorris May 26, 2024
7e86b5b
Correcting indentation of JS output.
seanmorris May 27, 2024
6848055
Removing use_fetch option and XmlHttpRequest.
seanmorris May 29, 2024
0bf9b71
Reverting extraneous changes.
seanmorris May 29, 2024
3f765bc
Revert "Reverting extraneous changes."
seanmorris May 29, 2024
316aa8b
Actually reverting extraneous change.
seanmorris May 29, 2024
366b942
Merge branch 'sm-worker-fetch' into updates
seanmorris May 29, 2024
58ab5c6
Merging main
seanmorris May 29, 2024
5899e42
Throw error on bad fetch in runMetaWithFS
seanmorris May 29, 2024
92ffd13
Tweaks.
seanmorris May 29, 2024
df4e1c3
Tweaks.
seanmorris May 29, 2024
bb9a69e
Use rejecting promises instead of throw.
seanmorris May 29, 2024
c8e9db9
Merge branch 'main' into sm-worker-fetch
seanmorris May 29, 2024
272f093
Tweaks.
seanmorris May 29, 2024
6432c5a
Adding polyfill + test.
seanmorris May 29, 2024
e870667
Adding polyfill + test.
seanmorris May 29, 2024
ce4089e
Adding polyfill + test.
seanmorris May 29, 2024
4581d90
Adding fetch polyfill + test
seanmorris May 29, 2024
457ef2c
Adding fetch polyfill + test
seanmorris May 29, 2024
aeac94e
Polyfill tests for browser.
seanmorris May 30, 2024
275388a
Tweaks.
seanmorris May 30, 2024
d4a6162
Tweaks.
seanmorris May 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions src/polyfill/fetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Fetch polyfill from https://github.com/developit/unfetch
// License:
//==============================================================================
// Copyright (c) 2017 Jason Miller
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//==============================================================================

#if !POLYFILL
#error "this file should never be included unless POLYFILL is set"
#endif

if (typeof globalThis.fetch == 'undefined') {
globalThis.fetch = function (url, options) {
options = options || {};
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
const keys = [];
const headers = {};

request.responseType = 'arraybuffer';

const response = () => ({
ok: ((request.status / 100) | 0) == 2, // 200-299
statusText: request.statusText,
status: request.status,
url: request.responseURL,
text: () => Promise.resolve(request.responseText),
json: () => Promise.resolve(request.responseText).then(JSON.parse),
blob: () => Promise.resolve(new Blob([request.response])),
arrayBuffer: () => Promise.resolve(request.response),
clone: response,
headers: {
keys: () => keys,
entries: () => keys.map((n) => [n, request.getResponseHeader(n)]),
get: (n) => request.getResponseHeader(n),
has: (n) => request.getResponseHeader(n) != null,
},
});

request.open(options.method || "get", url, true);

request.onload = () => {
request
.getAllResponseHeaders()
.toLowerCase()
.replace(/^(.+?):/gm, (m, key) => {
headers[key] || keys.push((headers[key] = key));
});
resolve(response());
};

request.onerror = reject;

request.withCredentials = options.credentials == "include";

for (const i in options.headers) {
request.setRequestHeader(i, options.headers[i]);
}

request.send(options.body || null);
});
}
}
5 changes: 5 additions & 0 deletions src/shell.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ var Module = typeof {{{ EXPORT_NAME }}} != 'undefined' ? {{{ EXPORT_NAME }}} : {
// See https://caniuse.com/mdn-javascript_builtins_bigint64array
#include "polyfill/bigint64array.js"
#endif

#if MIN_CHROME_VERSION < 40 || MIN_FIREFOX_VERSION < 39 || MIN_SAFARI_VERSION < 103000
// See https://caniuse.com/fetch
#include "polyfill/fetch.js"
#endif
#endif // POLYFILL

#if MODULARIZE
Expand Down
21 changes: 8 additions & 13 deletions src/web_or_worker_shell_read.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,12 @@
}

readAsync = (url, onload, onerror) => {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'arraybuffer';
xhr.onload = () => {
if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) { // file URLs can return 0
onload(xhr.response);
return;
fetch(url)
.then(response => {
if(response.ok) {
return response.arrayBuffer();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not call onload here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

response.arrayBuffer(); returns a promise that resolves to a buffer, not a literal buffer.

}
onerror();
};
xhr.onerror = onerror;
xhr.send(null);
}

return Promise.reject(new Error(response.statusText + ' : ' + response.url));
})
.then(onload, onerror)
};
2 changes: 1 addition & 1 deletion test/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2270,7 +2270,7 @@ def compile_btest(self, filename, args, reporting=Reporting.FULL):
if reporting == Reporting.FULL:
# If C reporting (i.e. the REPORT_RESULT macro) is required we
# also include report_result.c and force-include report_result.h
self.run_process([EMCC, '-c', '-I' + TEST_ROOT,
self.run_process([EMCC, '-c', '-fPIC', '-I' + TEST_ROOT,
'-DEMTEST_PORT_NUMBER=%d' % self.port,
test_file('report_result.c')] + self.get_emcc_args(compile_only=True))
args += ['report_result.o', '-include', test_file('report_result.h')]
Expand Down
2 changes: 1 addition & 1 deletion test/other/test_unoptimized_code_size_no_asserts.js.size
Original file line number Diff line number Diff line change
@@ -1 +1 @@
31420
31418
132 changes: 130 additions & 2 deletions test/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -699,15 +699,16 @@ def setup(assetLocalization):
<center><canvas id='canvas' width='256' height='256'></canvas></center>
<hr><div id='output'></div><hr>
<script type='text/javascript'>
window.onerror = (error) => {
window.addEventListener('unhandledrejection', event => {
const error = String(event.reason);
window.disableErrorReporting = true;
window.onerror = null;
var result = error.includes("test.data") ? 1 : 0;
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://localhost:8888/report_result?' + result, true);
xhr.send();
setTimeout(function() { window.close() }, 1000);
}
});
var Module = {
locateFile: function (path, prefix) {if (path.endsWith(".wasm")) {return prefix + path;} else {return "''' + assetLocalization + r'''" + path;}},
print: (function() {
Expand Down Expand Up @@ -5549,6 +5550,133 @@ def test_webpack(self):
shutil.copyfile('webpack/src/hello.wasm', 'webpack/dist/hello.wasm')
self.run_browser('webpack/dist/index.html', '/report_result?exit:0')

def test_fetch_polyfill_preload(self):
path = 'hello-world.txt'
create_file(path, 'hello, world!')
create_file('main.cpp', r'''
#include <stdio.h>
#include <string.h>
#include <emscripten.h>
int main() {
FILE *f = fopen("%s", "r");
char buf[100];
fread(buf, 1, 20, f);
buf[20] = 0;
fclose(f);
printf("|%%s|\n", buf);
return 0;
}
''' % path)
create_file('on_window_error_shell.html', r'''
<html>
<center><canvas id='canvas' width='256' height='256'></canvas></center>
<hr><div id='output'></div><hr>
<script type='text/javascript'>
window.addEventListener('error', event => {
const error = String(event.message);
console.log({error});
window.disableErrorReporting = true;
window.onerror = null;
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://localhost:8888/report_result?' + error, true);
xhr.send();
setTimeout(function() { window.close() }, 1000);
});
var Module = {
print: (function() {
var element = document.getElementById('output');
return function(text) {
console.log({text});
if(window.disableErrorReporting) return;
element.innerHTML += text.replace('\n', '<br>', 'g') + '<br>';
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://localhost:8888/report_result?' + (text === '|hello, world!|' ? 1 : 0), true);
xhr.send();
};
})(),
canvas: document.getElementById('canvas')
};
</script>
{{{ SCRIPT }}}
</body>
</html>''')

def test(args, expect_fail):
self.compile_btest('main.cpp', args + ['--preload-file', path, '--shell-file', 'on_window_error_shell.html', '-o', 'a.out.html'])
js = read_file('a.out.js')
if expect_fail:
create_file('a.out.js', 'fetch = undefined;\n' + js)
return self.run_browser('a.out.html', '/report_result?TypeError: fetch is not a function')
else:
return self.run_browser('a.out.html', '/report_result?1')

test([], expect_fail=True)
test(['-sLEGACY_VM_SUPPORT'], expect_fail=False)
test(['-sLEGACY_VM_SUPPORT', '-sNO_POLYFILL'], expect_fail=True)

def test_fetch_polyfill_shared_lib(self):
create_file('library.c', r'''
#include <stdio.h>
int library_func() {
return 42;
}
''')
create_file('main.c', r'''
#include <assert.h>
#include <dlfcn.h>
#include <stdio.h>
#include <emscripten.h>
int main() {
int found = EM_ASM_INT(
return preloadedWasm['/library.so'] !== undefined;
);
void *lib_handle = dlopen("/library.so", RTLD_NOW);
typedef int (*voidfunc)();
voidfunc x = (voidfunc)dlsym(lib_handle, "library_func");
printf("Got val: %d\n", x());
assert(x() == 42);
return 0;
}
''')
create_file('on_window_error_shell.html', r'''
<html>
<center><canvas id='canvas' width='256' height='256'></canvas></center>
<hr><div id='output'></div><hr>
<script type='text/javascript'>
var Module = {
print: (function() {
var element = document.getElementById('output');
return function(text) {
if(window.disableErrorReporting) return;
element.innerHTML += text.replace('\n', '<br>', 'g') + '<br>';
var result = text.includes("42") ? 1 : 0;
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://localhost:8888/report_result?' + result, true);
xhr.send();
};
})(),
canvas: document.getElementById('canvas')
};
</script>
{{{ SCRIPT }}}
</body>
</html>''')

self.run_process([EMCC, 'library.c', '-sSIDE_MODULE', '-O2', '-o', 'library.so'])

def test(args, expect_fail):
self.compile_btest('main.c', ['library.so', '-sMAIN_MODULE', '--shell-file', 'on_window_error_shell.html', '-o', 'a.out.html'])
js = read_file('a.out.js')
if expect_fail:
create_file('a.out.js', 'fetch = undefined;\n' + js)
return self.run_browser('a.out.html', '/report_result?abort:TypeError')
else:
return self.run_browser('a.out.html', '/report_result?1')

test([], expect_fail=True)
test(['-sLEGACY_VM_SUPPORT'], expect_fail=False)
test(['-sLEGACY_VM_SUPPORT', '-sNO_POLYFILL'], expect_fail=True)


class emrun(RunnerCore):
def test_emrun_info(self):
Expand Down
Loading