Skip to content
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

Add SINGLE_FILE option to embed all subresources into emitted JS #5296

Merged
merged 86 commits into from
Oct 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
5ea8765
Add SINGLE_FILE option to embed all subresources into emitted JS
buu700 Jun 13, 2017
3a562fa
misc cleanup as suggested by @kripken (#5296)
buu700 Jun 13, 2017
d2edd4c
replace --memory-init-file 0 logic with SINGLE_FILE-style embedding (…
buu700 Jun 13, 2017
e9b81cc
parse data URIs in JS to resolve cross-environment and CSP issues (#5…
buu700 Jun 14, 2017
cb300b6
minor cleanup (#5296)
buu700 Jun 14, 2017
2e33fb0
handle EMTERPRETIFY_FILE (#5296)
buu700 Jun 14, 2017
32bee1e
HTML worker fix (#5296)
buu700 Jun 15, 2017
a21e1d8
document SINGLE_FILE + HTML output (#5296)
buu700 Jun 15, 2017
e124141
include sodiumutil only when needed (#5296)
buu700 Jun 15, 2017
01b12b5
minor fixes (#5296)
buu700 Jun 15, 2017
0c82c16
binaryen get_subresource_location timing fix (#5296)
buu700 Jun 15, 2017
4fb1605
handle SINGLE_FILE in getBinary (#5296)
buu700 Jun 15, 2017
998f937
follow replacement string convention (#5296)
buu700 Jun 16, 2017
549a9b0
skip unneeded work for non-SINGLE_FILE compilation (#5296)
buu700 Jun 16, 2017
f7e889e
fix merge conflict (#5296)
buu700 Jun 17, 2017
9e5c34a
add myself to AUTHORS (#5296)
buu700 Jun 17, 2017
eeb547a
switch from sodiumutil to first-party base64 and UTF-8 implementations
buu700 Jun 17, 2017
b43f8ea
document encoding functions' behaviour (#5296)
buu700 Jun 18, 2017
c16ca3e
possible minor fix (#5296)
buu700 Jun 18, 2017
bc0074e
minor fix (#5296)
buu700 Jun 19, 2017
a21f18f
fix merge conflict (#5296)
buu700 Jun 19, 2017
19cbb02
getBinary minor cleanup (#5296)
buu700 Jun 19, 2017
7765795
merge bytesToString into intArrayToString (#5296)
buu700 Jun 19, 2017
73d1c91
handle data URI parsing in HTML output (#5296)
buu700 Jun 19, 2017
a6f46aa
move data URI parsing functions to preamble.js and rename for consist…
buu700 Jun 19, 2017
06ed6dc
factor out duplicated functions to arrayUtils.js (#5296)
buu700 Jun 21, 2017
8c74983
embed ASMJS_CODE_FILE only when needed (#5296)
buu700 Jun 26, 2017
0539459
embed mem init only when needed (#5296)
buu700 Jun 27, 2017
ec5cc8d
mem init method 0 fix (#5296)
buu700 Jun 27, 2017
962576f
handle ASSERTIONS in arrayUtils.js (#5296)
buu700 Jun 28, 2017
584b21c
html output condition comment (#5296)
buu700 Jun 29, 2017
362b79e
html output closure (#5296)
buu700 Jun 29, 2017
f0cb8da
do_binaryen SINGLE_FILE string replacement loop cleanup (#5296)
buu700 Jun 29, 2017
63e225a
arrayUtils maybeExport fix (#5296)
buu700 Jun 29, 2017
3711653
SINGLE_FILE regression tests (#5296)
buu700 Jun 29, 2017
dc1b69e
test fixes (#5296)
buu700 Jun 29, 2017
bd11a5e
SINGLE_FILE embed_memfile fix (#5296)
buu700 Jun 29, 2017
2e630bd
test cleanup (#5296)
buu700 Jun 30, 2017
5dece1f
arrayUtils.js embedding fix (#5296)
buu700 Jun 30, 2017
bd28f19
fix merge conflict (#5296)
buu700 Jul 5, 2017
ca3af49
node tryParseAsDataURI fix/cleanup (#5296)
buu700 Jul 5, 2017
638732b
base64 conversion cleanup (#5296)
buu700 Jul 5, 2017
5f93654
fix merge conflict (#5296)
buu700 Jul 6, 2017
200fb16
node intArrayFromBase64 fix (#5296)
buu700 Jul 10, 2017
fd28b2f
-g4 -> -g in SINGLE_FILE test to work around pre-exising bug (#5296)
buu700 Jul 10, 2017
ee64597
EMTERPRETIFY_FILE + SINGLE_FILE fixes (#5296)
buu700 Jul 10, 2017
6fa3a88
SINGLE_FILE + emterpreter test fix (#5296)
buu700 Jul 11, 2017
e3f7566
arrayUtils maybeExport cleanup (#5296)
buu700 Jul 11, 2017
e0f4e6c
SINGLE_FILE + EMTERPRETIFY_FILE test fix (#5296)
buu700 Jul 11, 2017
6b8a3e6
comment clarification (#5296)
buu700 Jul 13, 2017
67e41dc
SINGLE_FILE write in binary mode (#5296)
buu700 Jul 14, 2017
0f72d92
try XHR before tryParseAsDataURI where applicable (#5296)
buu700 Jul 14, 2017
97ae2ed
misc cleanup (#5296)
buu700 Jul 20, 2017
4452cb1
runtime size optimization (#5296)
buu700 Jul 20, 2017
3a2ccc4
INCLUDE_BASE64_UTILS -> SUPPORT_BASE64_EMBEDDING (#5296)
buu700 Jul 21, 2017
fa0ed7a
proxyClient SUPPORT_BASE64_EMBEDDING fix (#5296)
buu700 Jul 21, 2017
84c9e57
use regex to remove preprocessor directives from HTML output (#5296)
buu700 Aug 1, 2017
34d7e03
intArrayFromBase64 fixes for older Node and SpiderMonkey (#5296)
buu700 Aug 1, 2017
ebb8d7c
misc base64Utils fixes (#5296)
buu700 Aug 7, 2017
32f96f2
limited HTML output preprocessor support (#5296)
buu700 Aug 7, 2017
24e4356
run preprocessor on arrayUtils/base64Utils in jsifier (#5296)
buu700 Aug 7, 2017
b422419
misc touch ups (#5296)
buu700 Aug 7, 2017
1104ee0
clean up global var embed_memfile
kripken Aug 21, 2017
8814a69
clean up preprocessing
kripken Aug 21, 2017
bc33edd
use interpret-binary in other.test_single_file, so we don't depend on…
kripken Aug 21, 2017
d190392
minor cleanup (#5296)
buu700 Aug 21, 2017
86c319b
minor fixes (#5296)
buu700 Aug 22, 2017
62d9306
Merge remote-tracking branch 'upstream/incoming' into incoming
buu700 Sep 22, 2017
d55bc45
remove SINGLE_FILE dependency on Python preprocessor method (#5296)
buu700 Sep 26, 2017
a4a864d
minor fixes (#5296)
buu700 Sep 29, 2017
adbb60d
for better run-time efficiency, temporarily duplicate function pendin…
buu700 Sep 29, 2017
2bce73f
minor fix (#5296)
buu700 Sep 30, 2017
7489546
Merge remote-tracking branch 'upstream/incoming' into incoming
buu700 Sep 30, 2017
7e168bb
skip wasm streaming for SINGLE_FILE (#5296)
buu700 Sep 30, 2017
6fae984
s/request.response/response/ (#5296)
buu700 Oct 2, 2017
eb1fd57
Merge remote-tracking branch 'upstream/incoming' into incoming
buu700 Oct 5, 2017
8e4be3e
test update (#5296)
buu700 Oct 5, 2017
f33363b
proxyClient temporary fix pending Python preprocessor support (#5296)
buu700 Oct 8, 2017
50e58bd
SINGLE_FILE + Closure test case (#5296)
buu700 Oct 10, 2017
60fc52b
remove unneeded function (#5296)
buu700 Oct 10, 2017
eae3ed5
print arrayUtils/base64Utils earlier (via @erikdubbelboer) (#5296)
buu700 Oct 10, 2017
e8fd9e9
regression test for atob fallback issue (#5296)
buu700 Oct 10, 2017
8685abf
Merge remote-tracking branch 'upstream/incoming' into incoming
buu700 Oct 11, 2017
f98d511
HTML generation fix (#5296)
buu700 Oct 12, 2017
42992ef
atob function scope fix (#5296)
buu700 Oct 12, 2017
8e45599
asm.js synchronous init fix (#5296)
buu700 Oct 13, 2017
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
165 changes: 117 additions & 48 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,10 @@ def do_minify(self):
self.cleanup_shell = True


def embed_memfile(options):
return shared.Settings.SINGLE_FILE or (shared.Settings.MEM_INIT_METHOD == 0 and (not shared.Settings.MAIN_MODULE and not shared.Settings.SIDE_MODULE and options.debug_level < 4))


#
# Main run() function
#
Expand Down Expand Up @@ -800,7 +804,7 @@ def detect_fixed_language_mode(args):
options.separate_asm = True
logging.warning('forcing separate asm output (--separate-asm), because -s PRECISE_F32=2 or -s USE_PTHREADS=2 was passed.')
if options.separate_asm:
shared.Settings.SEPARATE_ASM = os.path.basename(asm_target)
shared.Settings.SEPARATE_ASM = shared.JS.get_subresource_location(asm_target)

if 'EMCC_STRICT' in os.environ:
shared.Settings.STRICT = os.environ.get('EMCC_STRICT') != '0'
Expand Down Expand Up @@ -917,6 +921,8 @@ def check(input_file):
logging.warning('disabling closure because debug info was requested')
options.use_closure_compiler = False

assert not (shared.Settings.EMTERPRETIFY_FILE and shared.Settings.SINGLE_FILE), 'cannot have both EMTERPRETIFY_FILE and SINGLE_FILE enabled at the same time'
Copy link
Member

Choose a reason for hiding this comment

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

good idea


assert not (shared.Settings.NO_DYNAMIC_EXECUTION and options.use_closure_compiler), 'cannot have both NO_DYNAMIC_EXECUTION and closure compiler enabled at the same time'

if options.use_closure_compiler:
Expand Down Expand Up @@ -1109,10 +1115,16 @@ def check(input_file):
os.environ['EMCC_WASM_BACKEND_BINARYEN'] = '1'

if shared.Settings.BINARYEN:
# set file locations, so that JS glue can find what it needs
shared.Settings.WASM_TEXT_FILE = os.path.basename(wasm_text_target)
shared.Settings.WASM_BINARY_FILE = os.path.basename(wasm_binary_target)
shared.Settings.ASMJS_CODE_FILE = os.path.basename(asm_target)
if shared.Settings.SINGLE_FILE:
# placeholder strings for JS glue, to be replaced with subresource locations in do_binaryen
shared.Settings.WASM_TEXT_FILE = shared.FilenameReplacementStrings.WASM_TEXT_FILE
shared.Settings.WASM_BINARY_FILE = shared.FilenameReplacementStrings.WASM_BINARY_FILE
shared.Settings.ASMJS_CODE_FILE = shared.FilenameReplacementStrings.ASMJS_CODE_FILE
else:
# set file locations, so that JS glue can find what it needs
shared.Settings.WASM_TEXT_FILE = shared.JS.get_subresource_location(wasm_text_target)
shared.Settings.WASM_BINARY_FILE = shared.JS.get_subresource_location(wasm_binary_target)
shared.Settings.ASMJS_CODE_FILE = shared.JS.get_subresource_location(asm_target)

shared.Settings.ASM_JS = 2 # when targeting wasm, we use a wasm Memory, but that is not compatible with asm.js opts
shared.Settings.GLOBAL_BASE = 1024 # leave some room for mapping global vars
Expand Down Expand Up @@ -1537,6 +1549,10 @@ def get_final():
shared.Settings.MEM_INIT_METHOD = 1
else:
assert shared.Settings.MEM_INIT_METHOD != 1

if embed_memfile(options):
shared.Settings.SUPPORT_BASE64_EMBEDDING = 1

final = shared.Building.emscripten(final, append_ext=False, extra_args=extra_args)
if DEBUG: save_intermediate('original')

Expand Down Expand Up @@ -1624,7 +1640,8 @@ def get_final():

with ToolchainProfiler.profile_block('memory initializer'):
memfile = None
if shared.Settings.MEM_INIT_METHOD > 0:

if shared.Settings.MEM_INIT_METHOD > 0 or embed_memfile(options):
memfile = target + '.mem'
shared.try_delete(memfile)
def repl(m):
Expand All @@ -1635,20 +1652,17 @@ def repl(m):
while membytes and membytes[-1] == 0:
membytes.pop()
if not membytes: return ''
if not options.memory_init_file:
if shared.Settings.MEM_INIT_METHOD == 2:
# memory initializer in a string literal
return "memoryInitializer = '%s';" % shared.JS.generate_string_initializer(list(membytes))
open(memfile, 'wb').write(''.join(map(chr, membytes)))
if DEBUG:
# Copy into temp dir as well, so can be run there too
shared.safe_copy(memfile, os.path.join(shared.get_emscripten_temp_dir(), os.path.basename(memfile)))
if not shared.Settings.BINARYEN:
return 'memoryInitializer = "%s";' % os.path.basename(memfile)
if not shared.Settings.BINARYEN or 'asmjs' in shared.Settings.BINARYEN_METHOD or 'interpret-asm2wasm' in shared.Settings.BINARYEN_METHOD:
return 'memoryInitializer = "%s";' % shared.JS.get_subresource_location(memfile, embed_memfile(options))
else:
# with wasm, we may have the mem init file in the wasm binary already
return ('memoryInitializer = Module["wasmJSMethod"].indexOf("asmjs") >= 0 || '
'Module["wasmJSMethod"].indexOf("interpret-asm2wasm") >= 0 ? "%s" : null;'
% os.path.basename(memfile))
return 'memoryInitializer = null;'
src = re.sub(shared.JS.memory_initializer_pattern, repl, open(final).read(), count=1)
open(final + '.mem.js', 'w').write(src)
final += '.mem.js'
Expand All @@ -1660,15 +1674,6 @@ def repl(m):
logging.debug('wrote memory initialization to %s', memfile)
else:
logging.debug('did not see memory initialization')
elif not shared.Settings.MAIN_MODULE and not shared.Settings.SIDE_MODULE and options.debug_level < 4:
# not writing a binary init, but we can at least optimize them by splitting them up
src = open(final).read()
src = shared.JS.optimize_initializer(src)
if src is not None:
logging.debug('optimizing memory initialization')
open(final + '.mem.js', 'w').write(src)
final += '.mem.js'
src = None

if shared.Settings.USE_PTHREADS:
target_dir = os.path.dirname(os.path.abspath(target))
Expand Down Expand Up @@ -1828,6 +1833,9 @@ def get_eliminate():
if options.proxy_to_worker:
generate_worker_js(target, js_target, target_basename)

if embed_memfile(options):
shared.try_delete(memfile)

for f in generated_text_files_with_native_eols:
tools.line_endings.convert_line_endings_in_file(f, os.linesep, options.output_eol)
log_time('final emitting')
Expand Down Expand Up @@ -2351,6 +2359,24 @@ def do_binaryen(final, target, asm_target, options, memfile, wasm_binary_target,
passes.append('minifyWhitespace')
final = shared.Building.js_optimizer_no_asmjs(final, passes)
if DEBUG: save_intermediate('postclean', 'js')
# replace placeholder strings with correct subresource locations
Copy link
Member

Choose a reason for hiding this comment

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

could this entire new code section be behind if SINGLE_FILE to avoid the overhead in the other case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, just took care of that.

if shared.Settings.SINGLE_FILE:
f = open(final, 'rb')
js = f.read()
f.close()
f = open(final, 'wb')
for target, replacement_string, should_embed in [
(wasm_text_target, shared.FilenameReplacementStrings.WASM_TEXT_FILE, True),
(wasm_binary_target, shared.FilenameReplacementStrings.WASM_BINARY_FILE, True),
(asm_target, shared.FilenameReplacementStrings.ASMJS_CODE_FILE, not shared.Building.is_wasm_only())
]:
if should_embed and os.path.isfile(target):
js = js.replace(replacement_string, shared.JS.get_subresource_location(target))
else:
js = js.replace(replacement_string, '')
shared.try_delete(target)
f.write(js)
f.close()
return final


Expand Down Expand Up @@ -2398,11 +2424,17 @@ def generate_html(target, options, js_target, target_basename,
} else {
// note: no support for code mods (PRECISE_F32==2)
console.log('running code on the main thread');
var filename = '%s';
var fileBytes = tryParseAsDataURI(filename);
Copy link
Member

Choose a reason for hiding this comment

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

all the invocations of tryParseAsDataURI could be emitted only when SINGLE_FILE, I think?

Copy link
Contributor Author

@buu700 buu700 Jul 20, 2017

Choose a reason for hiding this comment

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

That's similar to something @juj pointed out here. At the moment we could emit it only when SINGLE_FILE || MEM_INIT_METHOD == 0, which might make more sense to factor out to a dedicated setting rather than copying and pasting everywhere if it's expected to potentially change in the future.

We could also save some space by only including intArrayFromBase64 and tryParseAsDataURI in arrayUtils.js under the same conditions.

var script = document.createElement('script');
script.src = "%s.js";
if (fileBytes) {
script.innerHTML = intArrayToString(fileBytes);
} else {
script.src = filename;
}
document.body.appendChild(script);
}
''' % proxy_worker_filename
''' % shared.JS.get_subresource_location(proxy_worker_filename + '.js')
else:
# Normal code generation path
script.src = base_js_target
Expand All @@ -2416,33 +2448,40 @@ def generate_html(target, options, js_target, target_basename,
# We need to load the emterpreter file before anything else, it has to be synchronously ready
script.un_src()
script.inline = '''
var emterpretURL = '%s';
var emterpretXHR = new XMLHttpRequest();
emterpretXHR.open('GET', '%s', true);
emterpretXHR.open('GET', emterpretURL, true);
emterpretXHR.responseType = 'arraybuffer';
emterpretXHR.onload = function() {
Module.emterpreterFile = emterpretXHR.response;
if (emterpretXHR.status === 200 || emterpretXHR.status === 0) {
Module.emterpreterFile = emterpretXHR.response;
} else {
var emterpretURLBytes = tryParseAsDataURI(emterpretURL);
if (emterpretURLBytes) {
Module.emterpreterFile = emterpretURLBytes.buffer;
}
}
%s
};
emterpretXHR.send(null);
''' % (shared.Settings.EMTERPRETIFY_FILE, script.inline)
''' % (shared.JS.get_subresource_location(shared.Settings.EMTERPRETIFY_FILE), script.inline)
Copy link
Contributor Author

@buu700 buu700 Jun 14, 2017

Choose a reason for hiding this comment

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

I took care of this as suggested by @curiousdannii. However, while doing this, I noticed that this part of emcc.py injects a number of one-off implementations of basically the same logic from Module.read*. Is there a reason that I shouldn't change them to use Module.read* where possible instead of reimplementing the data URI parsing in each one?

Copy link
Member

Choose a reason for hiding this comment

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

I think those places are emitting code for the HTML file, where we normally don't depend on JS contents. However, we do create Module in the HTML, and we define Module.read there - so this might work. But if the JS is required to run to set up things, it might not. The safest thing might be to leave it as it is, or to create a utility function in the HTML to avoid duplication in there.

Copy link
Contributor Author

@buu700 buu700 Jun 15, 2017

Choose a reason for hiding this comment

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

Ah okay, got it. In that case, it sounds like the best solution right now would be to just copy and paste my tryParseAsDataURI function from shell.js into emcc.py to be inserted into the HTML? (Another candidate for future refactoring, but it would be consistent with the Module.read* logic already being duplicated here.)

Edit: Alternatively, we could either:

  1. Not support SINGLE_FILE with HTML output.

  2. Document CSP requirements for combining either SINGLE_FILE or --memory-init-file 0 with HTML output. Slightly longer-term, maybe go back and resolve this requirement after the duplication of the Module.read* logic is factored out.

I like option 2, if that's all right with you.

I'm biased since I don't personally use the HTML output for anything, but my thinking is that:

  • Whereas someone could easily rebuild and publish a JS library without knowing that they'll have just broken some of their users' applications, it's pretty obvious and easy to fix when your own page blows up on load after throwing a CSP error

  • Whereas a JS lib needs to work in every environment (web, node, shell), an HTML page only needs to run in a browser, where we know that loading a data URI through XHR works (CSP issues aside)

Copy link
Member

Choose a reason for hiding this comment

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

I'm confused. If we emit html, then we are on the web and should be able to load the data uri directly? So there is no need for special code in the html to handle parsing it, that's just a problem for shell environments?

Copy link
Contributor Author

@buu700 buu700 Jun 16, 2017

Choose a reason for hiding this comment

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

Yep, requesting a data URI through XHR/fetch works as expected; it's just in the node and shell environments where it wouldn't work since they would try to read it from the filesystem.

The drawback to relying on XHR for this in a web page is the CSP thing — that a Content-Security-Policy header would need to whitelist data: — which I documented as a limitation of combining SINGLE_FILE with HTML output. This would eventually fix itself when/if the HTML generation is refactored to use the same implementations from shell.js, and until then probably wouldn't be a common issue.

As a demonstration, you can compare running (async () => console.log(await (await fetch('data:text/plain;base64,YmFsbHMK')).text()))() in a JS console on GitHub (which has a strict CSP) and almost any other site.

Copy link
Member

Choose a reason for hiding this comment

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

Got it, thanks. Seems fine to document the CSP issue. The one concern I have is does a situation exist where a user needs to use a single file, but can't set the CSP (like maybe they are an embedded game in a game website). For that case, could we detect that the xhr fails and use the parsing code we use for shell environments?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, well, in that case, I can just copy and paste the new functions into the HTML output and update all the one-off Module.read*-like to behave like Module.read* (parse data URIs without relying on XHR), in which case there'd be no reason to document the CSP connect-src concern. This seemed like more of a hassle when there was a whole other library involved, but not as much now.

That said, script-src will be an issue with SINGLE_FILE in that kind of scenario no matter how we handle it. Right now, script-src (if defined at all) must whitelist data:, which I've also documented, but if we parse the data URI to a string then we'll still need either unsafe-inline or unsafe-eval to be able to execute it. I believe script-src is most commonly used to whitelist just 'self' (the current origin) and maybe specific CDNs, which is fine for regular non-SINGLE_FILE usage but would be a problem here.

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, if this has any significant risk at all of not working, then we shouldn't do it in -O0, as that's the first thing people try and we should work our hardest to not break. So falling back to parsing the string seems like the right thing do do. As for other concerns, for -O0 we just use the mem init file, which doesn't require eval or such, so it should be ok (with the string parsing)?

Yeah, if we do that, seems like we need the code in the html too. Might put those methods in src/ and include them in both places.

Copy link
Contributor Author

@buu700 buu700 Jun 19, 2017

Choose a reason for hiding this comment

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

Cool, sounds good; I just pushed another commit to handle all the parsing in the HTML output and update the documentation. The only remaining CSP quirks are:

  • Embedding asm.js requires script-src 'unsafe-inline' (out of data:, 'unsafe-eval', and 'unsafe-inline', that one seemed like the least unusual to require)

  • Using Workers requires child-src blob: (something new that I caught — passing a data URI into the Worker constructor wouldn't have worked in most browsers, so I had to parse it and convert it into a Blob URL)

As far as mem init 0, yep, there's no risk of CSP breaking that now that it's always directly parsed without XHR.


if options.memory_init_file:
# start to load the memory init file in the HTML, in parallel with the JS
script.un_src()
script.inline = ('''
(function() {
var memoryInitializer = '%s';
if (typeof Module['locateFile'] === 'function') {
memoryInitializer = Module['locateFile'](memoryInitializer);
} else if (Module['memoryInitializerPrefixURL']) {
memoryInitializer = Module['memoryInitializerPrefixURL'] + memoryInitializer;
}
var meminitXHR = Module['memoryInitializerRequest'] = new XMLHttpRequest();
meminitXHR.open('GET', memoryInitializer, true);
meminitXHR.responseType = 'arraybuffer';
meminitXHR.send(null);
})();
''' % os.path.basename(memfile)) + script.inline
var memoryInitializer = '%s';
if (typeof Module['locateFile'] === 'function') {
memoryInitializer = Module['locateFile'](memoryInitializer);
} else if (Module['memoryInitializerPrefixURL']) {
memoryInitializer = Module['memoryInitializerPrefixURL'] + memoryInitializer;
}
Module['memoryInitializerRequestURL'] = memoryInitializer;
var meminitXHR = Module['memoryInitializerRequest'] = new XMLHttpRequest();
meminitXHR.open('GET', memoryInitializer, true);
meminitXHR.responseType = 'arraybuffer';
meminitXHR.send(null);
''' % shared.JS.get_subresource_location(memfile)) + script.inline

# Download .asm.js if --separate-asm was passed in an asm.js build, or if 'asmjs' is one
# of the wasm run methods.
Expand All @@ -2453,22 +2492,37 @@ def generate_html(target, options, js_target, target_basename,
if len(asm_mods) == 0:
# just load the asm, then load the rest
script.inline = '''
var filename = '%s';
var fileBytes = tryParseAsDataURI(filename);
var script = document.createElement('script');
script.src = "%s";
if (fileBytes) {
script.innerHTML = intArrayToString(fileBytes);
} else {
script.src = filename;
}
script.onload = function() {
setTimeout(function() {
%s
}, 1); // delaying even 1ms is enough to allow compilation memory to be reclaimed
};
document.body.appendChild(script);
''' % (os.path.basename(asm_target), script.inline)
''' % (shared.JS.get_subresource_location(asm_target), script.inline)
else:
# may need to modify the asm code, load it as text, modify, and load asynchronously
script.inline = '''
var codeURL = '%s';
var codeXHR = new XMLHttpRequest();
codeXHR.open('GET', '%s', true);
codeXHR.open('GET', codeURL, true);
codeXHR.onload = function() {
var code = codeXHR.responseText;
var code;
if (codeXHR.status === 200 || codeXHR.status === 0) {
code = codeXHR.responseText;
} else {
var codeURLBytes = tryParseAsDataURI(codeURL);
if (codeURLBytes) {
code = intArrayToString(codeURLBytes);
}
}
%s
var blob = new Blob([code], { type: 'text/javascript' });
codeXHR = null;
Expand All @@ -2484,21 +2538,36 @@ def generate_html(target, options, js_target, target_basename,
document.body.appendChild(script);
};
codeXHR.send(null);
''' % (os.path.basename(asm_target), '\n'.join(asm_mods), script.inline)
''' % (shared.JS.get_subresource_location(asm_target), '\n'.join(asm_mods), script.inline)

if shared.Settings.BINARYEN and not shared.Settings.BINARYEN_ASYNC_COMPILATION:
# We need to load the wasm file before anything else, it has to be synchronously ready TODO: optimize
script.un_src()
script.inline = '''
var wasmURL = '%s';
var wasmXHR = new XMLHttpRequest();
wasmXHR.open('GET', '%s', true);
wasmXHR.open('GET', wasmURL, true);
wasmXHR.responseType = 'arraybuffer';
wasmXHR.onload = function() {
Module.wasmBinary = wasmXHR.response;
if (wasmXHR.status === 200 || wasmXHR.status === 0) {
Module.wasmBinary = wasmXHR.response;
} else {
var wasmURLBytes = tryParseAsDataURI(wasmURL);
if (wasmURLBytes) {
Module.wasmBinary = wasmURLBytes.buffer;
}
}
%s
};
wasmXHR.send(null);
''' % (os.path.basename(wasm_binary_target), script.inline)
''' % (shared.JS.get_subresource_location(wasm_binary_target), script.inline)

# when script.inline isn't empty, add required helper functions such as tryParseAsDataURI
if script.inline:
for file in ['src/arrayUtils.js', 'src/base64Utils.js']:
f = open(shared.path_from_root(file), 'r')
script.inline = f.read() + script.inline
f.close()

html = open(target, 'wb')
html_contents = shell.replace('{{{ SCRIPT }}}', script.replacement())
Expand Down
38 changes: 38 additions & 0 deletions src/arrayUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// All functions here should be maybeExported from jsifier.js

/** @type {function(string, boolean=, number=)} */
function intArrayFromString(stringy, dontAddNull, length) {
var len = length > 0 ? length : lengthBytesUTF8(stringy)+1;
var u8array = new Array(len);
var numBytesWritten = stringToUTF8Array(stringy, u8array, 0, u8array.length);
if (dontAddNull) u8array.length = numBytesWritten;
return u8array;
}

// Temporarily duplicating function pending Python preprocessor support
var ASSERTIONS;
var intArrayToString = ASSERTIONS ?
function (array) {
var ret = [];
for (var i = 0; i < array.length; i++) {
var chr = array[i];
if (chr > 0xFF) {
assert(false, 'Character code ' + chr + ' (' + String.fromCharCode(chr) + ') at offset ' + i + ' not in 0x00-0xFF.');
chr &= 0xFF;
}
ret.push(String.fromCharCode(chr));
}
return ret.join('');
} :
function (array) {
var ret = [];
for (var i = 0; i < array.length; i++) {
var chr = array[i];
if (chr > 0xFF) {
chr &= 0xFF;
}
ret.push(String.fromCharCode(chr));
}
return ret.join('');
}
;
Loading