Skip to content

Commit 3f5a30a

Browse files
committed
Implement support and add a test that pthreads can be used from main HTML files that load the Emscripten application .js file from a Blob.
1 parent 960f5d9 commit 3f5a30a

File tree

4 files changed

+204
-2
lines changed

4 files changed

+204
-2
lines changed

src/library_pthread.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,12 @@ var LibraryPThread = {
321321
// Ask the new worker to load up the Emscripten-compiled page. This is a heavy operation.
322322
worker.postMessage({
323323
cmd: 'load',
324-
url: currentScriptUrl,
324+
// If the application main .js file was loaded from a Blob, then it is not possible
325+
// to access the URL of the current script that could be passed to a Web Worker so that
326+
// it could load up the same file. In that case, developer must either deliver the Blob
327+
// object in Module['mainScriptUrlOrBlob'], or a URL to it, so that pthread Workers can
328+
// independently load up the same main application file.
329+
urlOrBlob: Module['mainScriptUrlOrBlob'] || currentScriptUrl,
325330
buffer: HEAPU8.buffer,
326331
tempDoublePtr: tempDoublePtr,
327332
TOTAL_MEMORY: TOTAL_MEMORY,

src/pthread-main.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,13 @@ this.onmessage = function(e) {
5656
DYNAMICTOP_PTR = e.data.DYNAMICTOP_PTR;
5757

5858
PthreadWorkerInit = e.data.PthreadWorkerInit;
59-
importScripts(e.data.url);
59+
if (typeof e.data.urlOrBlob === 'string') {
60+
importScripts(e.data.urlOrBlob);
61+
} else {
62+
var objectUrl = URL.createObjectURL(e.data.urlOrBlob);
63+
importScripts(objectUrl);
64+
URL.revokeObjectURL(objectUrl);
65+
}
6066
if (typeof FS !== 'undefined') FS.createStandardStreams();
6167
postMessage({ cmd: 'loaded' });
6268
} else if (e.data.cmd === 'objectTransfer') {
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
<!doctype html>
2+
<html lang="en-us">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6+
<title>Emscripten-Generated Code</title>
7+
<style>
8+
.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
9+
textarea.emscripten { font-family: monospace; width: 80%; }
10+
div.emscripten { text-align: center; }
11+
div.emscripten_border { border: 1px solid black; }
12+
/* the canvas *must not* have any border or padding, or mouse coords will be wrong */
13+
canvas.emscripten { border: 0px none; background-color: black; }
14+
15+
.spinner {
16+
height: 50px;
17+
width: 50px;
18+
margin: 0px auto;
19+
-webkit-animation: rotation .8s linear infinite;
20+
-moz-animation: rotation .8s linear infinite;
21+
-o-animation: rotation .8s linear infinite;
22+
animation: rotation 0.8s linear infinite;
23+
border-left: 10px solid rgb(0,150,240);
24+
border-right: 10px solid rgb(0,150,240);
25+
border-bottom: 10px solid rgb(0,150,240);
26+
border-top: 10px solid rgb(100,0,200);
27+
border-radius: 100%;
28+
background-color: rgb(200,100,250);
29+
}
30+
@-webkit-keyframes rotation {
31+
from {-webkit-transform: rotate(0deg);}
32+
to {-webkit-transform: rotate(360deg);}
33+
}
34+
@-moz-keyframes rotation {
35+
from {-moz-transform: rotate(0deg);}
36+
to {-moz-transform: rotate(360deg);}
37+
}
38+
@-o-keyframes rotation {
39+
from {-o-transform: rotate(0deg);}
40+
to {-o-transform: rotate(360deg);}
41+
}
42+
@keyframes rotation {
43+
from {transform: rotate(0deg);}
44+
to {transform: rotate(360deg);}
45+
}
46+
47+
</style>
48+
</head>
49+
<body>
50+
<hr/>
51+
<figure style="overflow:visible;" id="spinner"><div class="spinner"></div><center style="margin-top:0.5em"><strong>emscripten</strong></center></figure>
52+
<div class="emscripten" id="status">Downloading...</div>
53+
<div class="emscripten">
54+
<progress value="0" max="100" id="progress" hidden=1></progress>
55+
</div>
56+
<div class="emscripten_border">
57+
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()"></canvas>
58+
</div>
59+
<hr/>
60+
<div class="emscripten">
61+
<input type="checkbox" id="resize">Resize canvas
62+
<input type="checkbox" id="pointerLock" checked>Lock/hide mouse pointer
63+
&nbsp;&nbsp;&nbsp;
64+
<input type="button" value="Fullscreen" onclick="Module.requestFullscreen(document.getElementById('pointerLock').checked,
65+
document.getElementById('resize').checked)">
66+
</div>
67+
68+
<hr/>
69+
<textarea class="emscripten" id="output" rows="8"></textarea>
70+
<hr>
71+
<script type='text/javascript'>
72+
var statusElement = document.getElementById('status');
73+
var progressElement = document.getElementById('progress');
74+
var spinnerElement = document.getElementById('spinner');
75+
76+
var Module = {
77+
preRun: [],
78+
postRun: [],
79+
print: (function() {
80+
var element = document.getElementById('output');
81+
if (element) element.value = ''; // clear browser cache
82+
return function(text) {
83+
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
84+
// These replacements are necessary if you render to raw HTML
85+
//text = text.replace(/&/g, "&amp;");
86+
//text = text.replace(/</g, "&lt;");
87+
//text = text.replace(/>/g, "&gt;");
88+
//text = text.replace('\n', '<br>', 'g');
89+
console.log(text);
90+
if (element) {
91+
element.value += text + "\n";
92+
element.scrollTop = element.scrollHeight; // focus on bottom
93+
}
94+
};
95+
})(),
96+
printErr: function(text) {
97+
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
98+
if (0) { // XXX disabled for safety typeof dump == 'function') {
99+
dump(text + '\n'); // fast, straight to the real console
100+
} else {
101+
console.error(text);
102+
}
103+
},
104+
canvas: (function() {
105+
var canvas = document.getElementById('canvas');
106+
107+
// As a default initial behavior, pop up an alert when webgl context is lost. To make your
108+
// application robust, you may want to override this behavior before shipping!
109+
// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
110+
canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);
111+
112+
return canvas;
113+
})(),
114+
setStatus: function(text) {
115+
if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
116+
if (text === Module.setStatus.text) return;
117+
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
118+
var now = Date.now();
119+
if (m && now - Date.now() < 30) return; // if this is a progress update, skip it if too soon
120+
if (m) {
121+
text = m[1];
122+
progressElement.value = parseInt(m[2])*100;
123+
progressElement.max = parseInt(m[4])*100;
124+
progressElement.hidden = false;
125+
spinnerElement.hidden = false;
126+
} else {
127+
progressElement.value = null;
128+
progressElement.max = null;
129+
progressElement.hidden = true;
130+
if (!text) spinnerElement.hidden = true;
131+
}
132+
statusElement.innerHTML = text;
133+
},
134+
totalDependencies: 0,
135+
monitorRunDependencies: function(left) {
136+
this.totalDependencies = Math.max(this.totalDependencies, left);
137+
Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
138+
}
139+
};
140+
Module.setStatus('Downloading...');
141+
window.onerror = function() {
142+
Module.setStatus('Exception thrown, see JavaScript console');
143+
spinnerElement.style.display = 'none';
144+
Module.setStatus = function(text) {
145+
if (text) Module.printErr('[post-exception status] ' + text);
146+
};
147+
};
148+
</script>
149+
150+
<script>
151+
152+
function download(url, responseType) {
153+
return new Promise(function(resolve, reject) {
154+
var xhr = new XMLHttpRequest();
155+
xhr.open('GET', url, true);
156+
xhr.responseType = 'blob';
157+
xhr.onload = function() {
158+
resolve(xhr.response);
159+
};
160+
xhr.send(null);
161+
});
162+
}
163+
164+
function addToDom(scriptCodeBlob) {
165+
return new Promise(function(resolve, reject) {
166+
var script = document.createElement('script');
167+
var objectUrl = URL.createObjectURL(scriptCodeBlob);
168+
script.src = objectUrl;
169+
script.onload = function() {
170+
Module['mainScriptUrlOrBlob'] = scriptCodeBlob;
171+
URL.revokeObjectURL(objectUrl);
172+
resolve();
173+
}
174+
document.body.appendChild(script);
175+
});
176+
}
177+
178+
download('hello_thread_with_blob_url.js').then(addToDom);
179+
180+
</script>
181+
</body>
182+
</html>

tests/test_browser.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3624,3 +3624,12 @@ def test_emscripten_set_canvas_element_size(self):
36243624
# Tests the absolute minimum pthread-enabled application.
36253625
def test_hello_thread(self):
36263626
self.btest(path_from_root('tests', 'pthread', 'hello_thread.c'), expected='1', args=['-s', 'USE_PTHREADS=1'])
3627+
3628+
# Tests that it is possible to load the main .js file of the application manually via a Blob URL, and still use pthreads.
3629+
def test_load_js_from_blob_with_pthreads(self):
3630+
src = os.path.join(self.get_dir(), 'src.c')
3631+
open(src, 'w').write(self.with_report_result(open(path_from_root('tests', 'pthread', 'hello_thread.c')).read()))
3632+
3633+
Popen([PYTHON, EMCC, 'src.c', '-s', 'USE_PTHREADS=1', '-o', 'hello_thread_with_blob_url.js']).communicate()
3634+
shutil.copyfile(path_from_root('tests', 'pthread', 'main_js_as_blob_loader.html'), os.path.join(self.get_dir(), 'hello_thread_with_blob_url.html'))
3635+
self.run_browser('hello_thread_with_blob_url.html', 'hello from thread!', '/report_result?1')

0 commit comments

Comments
 (0)