Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/src/lib/libbrowser.js
4150 views
1
/**
2
* @license
3
* Copyright 2011 The Emscripten Authors
4
* SPDX-License-Identifier: MIT
5
*/
6
7
// Utilities for browser environments
8
var LibraryBrowser = {
9
$Browser__deps: [
10
'$callUserCallback',
11
'$getFullscreenElement',
12
'$safeSetTimeout',
13
'$warnOnce',
14
#if FILESYSTEM
15
'$preloadPlugins',
16
#if MAIN_MODULE
17
'$preloadedWasm',
18
#endif
19
#endif
20
],
21
22
$Browser: {
23
useWebGL: false,
24
isFullscreen: false,
25
pointerLock: false,
26
moduleContextCreatedCallbacks: [],
27
workers: [],
28
preloadedImages: {},
29
preloadedAudios: {},
30
31
getCanvas: () => Module['canvas'],
32
33
init() {
34
if (Browser.initted) return;
35
Browser.initted = true;
36
37
#if FILESYSTEM
38
// Support for plugins that can process preloaded files. You can add more of these to
39
// your app by creating and appending to preloadPlugins.
40
//
41
// Each plugin is asked if it can handle a file based on the file's name. If it can,
42
// it is given the file's raw data. When it is done, it calls a callback with the file's
43
// (possibly modified) data. For example, a plugin might decompress a file, or it
44
// might create some side data structure for use later (like an Image element, etc.).
45
46
var imagePlugin = {};
47
imagePlugin['canHandle'] = function imagePlugin_canHandle(name) {
48
return !Module['noImageDecoding'] && /\.(jpg|jpeg|png|bmp|webp)$/i.test(name);
49
};
50
imagePlugin['handle'] = async function imagePlugin_handle(byteArray, name) {
51
var b = new Blob([byteArray], { type: Browser.getMimetype(name) });
52
if (b.size !== byteArray.length) { // Safari bug #118630
53
// Safari's Blob can only take an ArrayBuffer
54
b = new Blob([(new Uint8Array(byteArray)).buffer], { type: Browser.getMimetype(name) });
55
}
56
var url = URL.createObjectURL(b);
57
return new Promise((resolve, reject) => {
58
var img = new Image();
59
img.onload = () => {
60
#if ASSERTIONS
61
assert(img.complete, `Image ${name} could not be decoded`);
62
#endif
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
});
78
};
79
preloadPlugins.push(imagePlugin);
80
81
var audioPlugin = {};
82
audioPlugin['canHandle'] = function audioPlugin_canHandle(name) {
83
return !Module['noAudioDecoding'] && name.slice(-4) in { '.ogg': 1, '.wav': 1, '.mp3': 1 };
84
};
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
}
115
}
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;
124
}
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
});
134
};
135
preloadPlugins.push(audioPlugin);
136
#endif
137
138
// Canvas event setup
139
140
function pointerLockChange() {
141
var canvas = Browser.getCanvas();
142
Browser.pointerLock = document.pointerLockElement === canvas;
143
}
144
var canvas = Browser.getCanvas();
145
if (canvas) {
146
// forced aspect ratio can be enabled by defining 'forcedAspectRatio' on Module
147
// Module['forcedAspectRatio'] = 4 / 3;
148
149
document.addEventListener('pointerlockchange', pointerLockChange, false);
150
151
if (Module['elementPointerLock']) {
152
canvas.addEventListener("click", (ev) => {
153
if (!Browser.pointerLock && Browser.getCanvas().requestPointerLock) {
154
Browser.getCanvas().requestPointerLock();
155
ev.preventDefault();
156
}
157
}, false);
158
}
159
}
160
},
161
162
createContext(/** @type {HTMLCanvasElement} */ canvas, useWebGL, setInModule, webGLContextAttributes) {
163
if (useWebGL && Module['ctx'] && canvas == Browser.getCanvas()) return Module['ctx']; // no need to recreate GL context if it's already been created for this canvas.
164
165
var ctx;
166
var contextHandle;
167
if (useWebGL) {
168
// For GLES2/desktop GL compatibility, adjust a few defaults to be different to WebGL defaults, so that they align better with the desktop defaults.
169
var contextAttributes = {
170
antialias: false,
171
alpha: false,
172
#if MIN_WEBGL_VERSION >= 2
173
majorVersion: 2,
174
#elif MAX_WEBGL_VERSION >= 2 // libbrowser.js defaults: use the WebGL version chosen at compile time (unless overridden below)
175
majorVersion: (typeof WebGL2RenderingContext != 'undefined') ? 2 : 1,
176
#else
177
majorVersion: 1,
178
#endif
179
};
180
181
if (webGLContextAttributes) {
182
for (var attribute in webGLContextAttributes) {
183
contextAttributes[attribute] = webGLContextAttributes[attribute];
184
}
185
}
186
187
// This check of existence of GL is here to satisfy Closure compiler, which yells if variable GL is referenced below but GL object is not
188
// actually compiled in because application is not doing any GL operations. TODO: Ideally if GL is not being used, this function
189
// Browser.createContext() should not even be emitted.
190
if (typeof GL != 'undefined') {
191
contextHandle = GL.createContext(canvas, contextAttributes);
192
if (contextHandle) {
193
ctx = GL.getContext(contextHandle).GLctx;
194
}
195
}
196
} else {
197
ctx = canvas.getContext('2d');
198
}
199
200
if (!ctx) return null;
201
202
if (setInModule) {
203
#if ASSERTIONS
204
if (!useWebGL) assert(typeof GLctx == 'undefined', 'cannot set in module if GLctx is used, but we are a non-GL context that would replace it');
205
#endif
206
Module['ctx'] = ctx;
207
if (useWebGL) GL.makeContextCurrent(contextHandle);
208
Browser.useWebGL = useWebGL;
209
Browser.moduleContextCreatedCallbacks.forEach((callback) => callback());
210
Browser.init();
211
}
212
return ctx;
213
},
214
215
fullscreenHandlersInstalled: false,
216
lockPointer: undefined,
217
resizeCanvas: undefined,
218
requestFullscreen(lockPointer, resizeCanvas) {
219
Browser.lockPointer = lockPointer;
220
Browser.resizeCanvas = resizeCanvas;
221
if (typeof Browser.lockPointer == 'undefined') Browser.lockPointer = true;
222
if (typeof Browser.resizeCanvas == 'undefined') Browser.resizeCanvas = false;
223
224
var canvas = Browser.getCanvas();
225
function fullscreenChange() {
226
Browser.isFullscreen = false;
227
var canvasContainer = canvas.parentNode;
228
if (getFullscreenElement() === canvasContainer) {
229
canvas.exitFullscreen = Browser.exitFullscreen;
230
if (Browser.lockPointer) canvas.requestPointerLock();
231
Browser.isFullscreen = true;
232
if (Browser.resizeCanvas) {
233
Browser.setFullscreenCanvasSize();
234
} else {
235
Browser.updateCanvasDimensions(canvas);
236
}
237
} else {
238
// remove the full screen specific parent of the canvas again to restore the HTML structure from before going full screen
239
canvasContainer.parentNode.insertBefore(canvas, canvasContainer);
240
canvasContainer.parentNode.removeChild(canvasContainer);
241
242
if (Browser.resizeCanvas) {
243
Browser.setWindowedCanvasSize();
244
} else {
245
Browser.updateCanvasDimensions(canvas);
246
}
247
}
248
Module['onFullScreen']?.(Browser.isFullscreen);
249
Module['onFullscreen']?.(Browser.isFullscreen);
250
}
251
252
if (!Browser.fullscreenHandlersInstalled) {
253
Browser.fullscreenHandlersInstalled = true;
254
document.addEventListener('fullscreenchange', fullscreenChange, false);
255
document.addEventListener('mozfullscreenchange', fullscreenChange, false);
256
document.addEventListener('webkitfullscreenchange', fullscreenChange, false);
257
document.addEventListener('MSFullscreenChange', fullscreenChange, false);
258
}
259
260
// create a new parent to ensure the canvas has no siblings. this allows browsers to optimize full screen performance when its parent is the full screen root
261
var canvasContainer = document.createElement("div");
262
canvas.parentNode.insertBefore(canvasContainer, canvas);
263
canvasContainer.appendChild(canvas);
264
265
// use parent of canvas as full screen root to allow aspect ratio correction (Firefox stretches the root to screen size)
266
canvasContainer.requestFullscreen = canvasContainer['requestFullscreen'] ||
267
canvasContainer['mozRequestFullScreen'] ||
268
canvasContainer['msRequestFullscreen'] ||
269
(canvasContainer['webkitRequestFullscreen'] ? () => canvasContainer['webkitRequestFullscreen'](Element['ALLOW_KEYBOARD_INPUT']) : null) ||
270
(canvasContainer['webkitRequestFullScreen'] ? () => canvasContainer['webkitRequestFullScreen'](Element['ALLOW_KEYBOARD_INPUT']) : null);
271
272
canvasContainer.requestFullscreen();
273
},
274
275
#if ASSERTIONS
276
requestFullScreen() {
277
abort('Module.requestFullScreen has been replaced by Module.requestFullscreen (without a capital S)');
278
},
279
#endif
280
281
exitFullscreen() {
282
// This is workaround for chrome. Trying to exit from fullscreen
283
// not in fullscreen state will cause "TypeError: Document not active"
284
// in chrome. See https://github.com/emscripten-core/emscripten/pull/8236
285
if (!Browser.isFullscreen) {
286
return false;
287
}
288
289
var CFS = document['exitFullscreen'] ||
290
document['cancelFullScreen'] ||
291
document['mozCancelFullScreen'] ||
292
document['msExitFullscreen'] ||
293
document['webkitCancelFullScreen'] ||
294
(() => {});
295
CFS.apply(document, []);
296
return true;
297
},
298
299
// abort and pause-aware versions TODO: build main loop on top of this?
300
301
safeSetTimeout(func, timeout) {
302
// Legacy function, this is used by the SDL2 port so we need to keep it
303
// around at least until that is updated.
304
// See https://github.com/libsdl-org/SDL/pull/6304
305
return safeSetTimeout(func, timeout);
306
},
307
308
getMimetype(name) {
309
return {
310
'jpg': 'image/jpeg',
311
'jpeg': 'image/jpeg',
312
'png': 'image/png',
313
'bmp': 'image/bmp',
314
'ogg': 'audio/ogg',
315
'wav': 'audio/wav',
316
'mp3': 'audio/mpeg'
317
}[name.slice(name.lastIndexOf('.')+1)];
318
},
319
320
getUserMedia(func) {
321
window.getUserMedia ||= navigator['getUserMedia'] ||
322
navigator['mozGetUserMedia'];
323
window.getUserMedia(func);
324
},
325
326
327
getMovementX(event) {
328
return event['movementX'] ||
329
event['mozMovementX'] ||
330
event['webkitMovementX'] ||
331
0;
332
},
333
334
getMovementY(event) {
335
return event['movementY'] ||
336
event['mozMovementY'] ||
337
event['webkitMovementY'] ||
338
0;
339
},
340
341
// Browsers specify wheel direction according to the page CSS pixel Y direction:
342
// Scrolling mouse wheel down (==towards user/away from screen) on Windows/Linux (and macOS without 'natural scroll' enabled)
343
// is the positive wheel direction. Scrolling mouse wheel up (towards the screen) is the negative wheel direction.
344
// This function returns the wheel direction in the browser page coordinate system (+: down, -: up). Note that this is often the
345
// opposite of native code: In native APIs the positive scroll direction is to scroll up (away from the user).
346
// NOTE: The mouse wheel delta is a decimal number, and can be a fractional value within -1 and 1. If you need to represent
347
// this as an integer, don't simply cast to int, or you may receive scroll events for wheel delta == 0.
348
// NOTE: We convert all units returned by events into steps, i.e. individual wheel notches.
349
// These conversions are only approximations. Changing browsers, operating systems, or even settings can change the values.
350
getMouseWheelDelta(event) {
351
var delta = 0;
352
switch (event.type) {
353
case 'DOMMouseScroll':
354
// 3 lines make up a step
355
delta = event.detail / 3;
356
break;
357
case 'mousewheel':
358
// 120 units make up a step
359
delta = event.wheelDelta / 120;
360
break;
361
case 'wheel':
362
delta = event.deltaY
363
switch (event.deltaMode) {
364
case 0:
365
// DOM_DELTA_PIXEL: 100 pixels make up a step
366
delta /= 100;
367
break;
368
case 1:
369
// DOM_DELTA_LINE: 3 lines make up a step
370
delta /= 3;
371
break;
372
case 2:
373
// DOM_DELTA_PAGE: A page makes up 80 steps
374
delta *= 80;
375
break;
376
default:
377
throw 'unrecognized mouse wheel delta mode: ' + event.deltaMode;
378
}
379
break;
380
default:
381
throw 'unrecognized mouse wheel event: ' + event.type;
382
}
383
return delta;
384
},
385
386
mouseX: 0,
387
mouseY: 0,
388
mouseMovementX: 0,
389
mouseMovementY: 0,
390
touches: {},
391
lastTouches: {},
392
393
// Return the mouse coordinates relative to the top, left of the canvas, corrected for scroll offset.
394
calculateMouseCoords(pageX, pageY) {
395
// Calculate the movement based on the changes
396
// in the coordinates.
397
var canvas = Browser.getCanvas();
398
var rect = canvas.getBoundingClientRect();
399
400
// Neither .scrollX or .pageXOffset are defined in a spec, but
401
// we prefer .scrollX because it is currently in a spec draft.
402
// (see: http://www.w3.org/TR/2013/WD-cssom-view-20131217/)
403
var scrollX = ((typeof window.scrollX != 'undefined') ? window.scrollX : window.pageXOffset);
404
var scrollY = ((typeof window.scrollY != 'undefined') ? window.scrollY : window.pageYOffset);
405
#if ASSERTIONS
406
// If this assert lands, it's likely because the browser doesn't support scrollX or pageXOffset
407
// and we have no viable fallback.
408
assert((typeof scrollX != 'undefined') && (typeof scrollY != 'undefined'), 'Unable to retrieve scroll position, mouse positions likely broken.');
409
#endif
410
var adjustedX = pageX - (scrollX + rect.left);
411
var adjustedY = pageY - (scrollY + rect.top);
412
413
// the canvas might be CSS-scaled compared to its backbuffer;
414
// SDL-using content will want mouse coordinates in terms
415
// of backbuffer units.
416
adjustedX = adjustedX * (canvas.width / rect.width);
417
adjustedY = adjustedY * (canvas.height / rect.height);
418
419
return { x: adjustedX, y: adjustedY };
420
},
421
422
// Directly set the Browser state with new mouse coordinates calculated using calculateMouseCoords.
423
setMouseCoords(pageX, pageY) {
424
const {x, y} = Browser.calculateMouseCoords(pageX, pageY);
425
Browser.mouseMovementX = x - Browser.mouseX;
426
Browser.mouseMovementY = y - Browser.mouseY;
427
Browser.mouseX = x;
428
Browser.mouseY = y;
429
},
430
431
// Unpack a "mouse" event, handling SDL touch paths and pointerlock compatibility stuff.
432
calculateMouseEvent(event) { // event should be mousemove, mousedown or mouseup
433
if (Browser.pointerLock) {
434
// When the pointer is locked, calculate the coordinates
435
// based on the movement of the mouse.
436
// Workaround for Firefox bug 764498
437
if (event.type != 'mousemove' &&
438
('mozMovementX' in event)) {
439
Browser.mouseMovementX = Browser.mouseMovementY = 0;
440
} else {
441
Browser.mouseMovementX = Browser.getMovementX(event);
442
Browser.mouseMovementY = Browser.getMovementY(event);
443
}
444
445
// add the mouse delta to the current absolute mouse position
446
Browser.mouseX += Browser.mouseMovementX;
447
Browser.mouseY += Browser.mouseMovementY;
448
} else {
449
if (event.type === 'touchstart' || event.type === 'touchend' || event.type === 'touchmove') {
450
var touch = event.touch;
451
if (touch === undefined) {
452
return; // the "touch" property is only defined in SDL
453
454
}
455
var coords = Browser.calculateMouseCoords(touch.pageX, touch.pageY);
456
457
if (event.type === 'touchstart') {
458
Browser.lastTouches[touch.identifier] = coords;
459
Browser.touches[touch.identifier] = coords;
460
} else if (event.type === 'touchend' || event.type === 'touchmove') {
461
var last = Browser.touches[touch.identifier];
462
last ||= coords;
463
Browser.lastTouches[touch.identifier] = last;
464
Browser.touches[touch.identifier] = coords;
465
}
466
return;
467
}
468
469
Browser.setMouseCoords(event.pageX, event.pageY);
470
}
471
},
472
473
resizeListeners: [],
474
475
updateResizeListeners() {
476
var canvas = Browser.getCanvas();
477
Browser.resizeListeners.forEach((listener) => listener(canvas.width, canvas.height));
478
},
479
480
setCanvasSize(width, height, noUpdates) {
481
var canvas = Browser.getCanvas();
482
Browser.updateCanvasDimensions(canvas, width, height);
483
if (!noUpdates) Browser.updateResizeListeners();
484
},
485
486
windowedWidth: 0,
487
windowedHeight: 0,
488
setFullscreenCanvasSize() {
489
// check if SDL is available
490
if (typeof SDL != "undefined") {
491
var flags = {{{ makeGetValue('SDL.screen', '0', 'u32') }}};
492
flags = flags | 0x00800000; // set SDL_FULLSCREEN flag
493
{{{ makeSetValue('SDL.screen', '0', 'flags', 'i32') }}};
494
}
495
Browser.updateCanvasDimensions(Browser.getCanvas());
496
Browser.updateResizeListeners();
497
},
498
499
setWindowedCanvasSize() {
500
// check if SDL is available
501
if (typeof SDL != "undefined") {
502
var flags = {{{ makeGetValue('SDL.screen', '0', 'u32') }}};
503
flags = flags & ~0x00800000; // clear SDL_FULLSCREEN flag
504
{{{ makeSetValue('SDL.screen', '0', 'flags', 'i32') }}};
505
}
506
Browser.updateCanvasDimensions(Browser.getCanvas());
507
Browser.updateResizeListeners();
508
},
509
510
updateCanvasDimensions(canvas, wNative, hNative) {
511
if (wNative && hNative) {
512
canvas.widthNative = wNative;
513
canvas.heightNative = hNative;
514
} else {
515
wNative = canvas.widthNative;
516
hNative = canvas.heightNative;
517
}
518
var w = wNative;
519
var h = hNative;
520
if (Module['forcedAspectRatio'] > 0) {
521
if (w/h < Module['forcedAspectRatio']) {
522
w = Math.round(h * Module['forcedAspectRatio']);
523
} else {
524
h = Math.round(w / Module['forcedAspectRatio']);
525
}
526
}
527
if ((getFullscreenElement() === canvas.parentNode) && (typeof screen != 'undefined')) {
528
var factor = Math.min(screen.width / w, screen.height / h);
529
w = Math.round(w * factor);
530
h = Math.round(h * factor);
531
}
532
if (Browser.resizeCanvas) {
533
if (canvas.width != w) canvas.width = w;
534
if (canvas.height != h) canvas.height = h;
535
if (typeof canvas.style != 'undefined') {
536
canvas.style.removeProperty( "width");
537
canvas.style.removeProperty("height");
538
}
539
} else {
540
if (canvas.width != wNative) canvas.width = wNative;
541
if (canvas.height != hNative) canvas.height = hNative;
542
if (typeof canvas.style != 'undefined') {
543
if (w != wNative || h != hNative) {
544
canvas.style.setProperty( "width", w + "px", "important");
545
canvas.style.setProperty("height", h + "px", "important");
546
} else {
547
canvas.style.removeProperty( "width");
548
canvas.style.removeProperty("height");
549
}
550
}
551
}
552
},
553
},
554
555
$requestFullscreen: 'Browser.requestFullscreen',
556
#if ASSERTIONS
557
$requestFullScreen: 'Browser.requestFullScreen',
558
#endif
559
$setCanvasSize: 'Browser.setCanvasSize',
560
$getUserMedia: 'Browser.getUserMedia',
561
$createContext: 'Browser.createContext',
562
563
emscripten_run_preload_plugins__deps: ['$PATH'],
564
emscripten_run_preload_plugins__proxy: 'sync',
565
emscripten_run_preload_plugins: (file, onload, onerror) => {
566
{{{ runtimeKeepalivePush() }}}
567
568
var _file = UTF8ToString(file);
569
var data = FS.analyzePath(_file);
570
if (!data.exists) return -1;
571
FS.createPreloadedFile(
572
PATH.dirname(_file),
573
PATH.basename(_file),
574
// TODO: This copy is not needed if the contents are already a Uint8Array,
575
// which they often are (and always are in WasmFS).
576
new Uint8Array(data.object.contents), true, true,
577
() => {
578
{{{ runtimeKeepalivePop() }}}
579
if (onload) {{{ makeDynCall('vp', 'onload') }}}(file);
580
},
581
() => {
582
{{{ runtimeKeepalivePop() }}}
583
if (onerror) {{{ makeDynCall('vp', 'onerror') }}}(file);
584
},
585
true // don'tCreateFile - it's already there
586
);
587
return 0;
588
},
589
590
$Browser_asyncPrepareDataCounter: 0,
591
592
emscripten_run_preload_plugins_data__proxy: 'sync',
593
emscripten_run_preload_plugins_data__deps: ['$stringToNewUTF8', '$Browser_asyncPrepareDataCounter'],
594
emscripten_run_preload_plugins_data: (data, size, suffix, arg, onload, onerror) => {
595
{{{ runtimeKeepalivePush() }}}
596
597
var _suffix = UTF8ToString(suffix);
598
var name = 'prepare_data_' + (Browser_asyncPrepareDataCounter++) + '.' + _suffix;
599
var cname = stringToNewUTF8(name);
600
FS.createPreloadedFile(
601
'/',
602
name,
603
{{{ makeHEAPView('U8', 'data', 'data + size') }}},
604
true, true,
605
() => {
606
{{{ runtimeKeepalivePop() }}}
607
if (onload) {{{ makeDynCall('vpp', 'onload') }}}(arg, cname);
608
},
609
() => {
610
{{{ runtimeKeepalivePop() }}}
611
if (onerror) {{{ makeDynCall('vp', 'onerror') }}}(arg);
612
},
613
true // don'tCreateFile - it's already there
614
);
615
},
616
617
// Callable from pthread, executes in pthread context.
618
emscripten_async_run_script__deps: ['emscripten_run_script', '$safeSetTimeout'],
619
emscripten_async_run_script: (script, millis) => {
620
// TODO: cache these to avoid generating garbage
621
safeSetTimeout(() => _emscripten_run_script(script), millis);
622
},
623
624
// TODO: currently not callable from a pthread, but immediately calls onerror() if not on main thread.
625
emscripten_async_load_script__deps: ['$UTF8ToString'],
626
emscripten_async_load_script: async (url, onload, onerror) => {
627
url = UTF8ToString(url);
628
#if PTHREADS
629
if (ENVIRONMENT_IS_PTHREAD) {
630
err(`emscripten_async_load_script("${url}") failed, emscripten_async_load_script is currently not available in pthreads!`);
631
onerror && {{{ makeDynCall('v', 'onerror') }}}();
632
return;
633
}
634
#endif
635
#if ASSERTIONS
636
assert(runDependencies === 0, 'async_load_script must be run when no other dependencies are active');
637
#endif
638
{{{ runtimeKeepalivePush() }}}
639
640
var loadDone = () => {
641
{{{ runtimeKeepalivePop() }}}
642
if (onload) {
643
var onloadCallback = () => callUserCallback({{{ makeDynCall('v', 'onload') }}});
644
if (runDependencies > 0) {
645
dependenciesFulfilled = onloadCallback;
646
} else {
647
onloadCallback();
648
}
649
}
650
}
651
652
var loadError = () => {
653
{{{ runtimeKeepalivePop() }}}
654
if (onerror) {
655
callUserCallback({{{ makeDynCall('v', 'onerror') }}});
656
}
657
};
658
659
#if ENVIRONMENT_MAY_BE_NODE && DYNAMIC_EXECUTION
660
if (ENVIRONMENT_IS_NODE) {
661
try {
662
var data = await readAsync(url, false);
663
eval(data);
664
loadDone();
665
} catch (e) {
666
err(e);
667
loadError();
668
}
669
return;
670
}
671
#endif
672
673
var script = document.createElement('script');
674
script.onload = loadDone;
675
script.onerror = loadError;
676
script.src = url;
677
document.body.appendChild(script);
678
},
679
680
emscripten_get_window_title__proxy: 'sync',
681
emscripten_get_window_title: () => {
682
var buflen = 256;
683
684
if (!_emscripten_get_window_title.buffer) {
685
_emscripten_get_window_title.buffer = _malloc(buflen);
686
}
687
688
stringToUTF8(document.title, _emscripten_get_window_title.buffer, buflen);
689
690
return _emscripten_get_window_title.buffer;
691
},
692
693
emscripten_set_window_title__proxy: 'sync',
694
emscripten_set_window_title: (title) => document.title = UTF8ToString(title),
695
696
emscripten_get_screen_size__proxy: 'sync',
697
emscripten_get_screen_size: (width, height) => {
698
{{{ makeSetValue('width', '0', 'screen.width', 'i32') }}};
699
{{{ makeSetValue('height', '0', 'screen.height', 'i32') }}};
700
},
701
702
emscripten_hide_mouse__proxy: 'sync',
703
emscripten_hide_mouse: () => {
704
var styleSheet = document.styleSheets[0];
705
var rules = styleSheet.cssRules;
706
for (var i = 0; i < rules.length; i++) {
707
if (rules[i].cssText.startsWith('canvas')) {
708
styleSheet.deleteRule(i);
709
i--;
710
}
711
}
712
styleSheet.insertRule('canvas.emscripten { border: 1px solid black; cursor: none; }', 0);
713
},
714
715
emscripten_set_canvas_size__proxy: 'sync',
716
emscripten_set_canvas_size: (width, height) => Browser.setCanvasSize(width, height),
717
718
emscripten_get_canvas_size__proxy: 'sync',
719
emscripten_get_canvas_size: (width, height, isFullscreen) => {
720
var canvas = Browser.getCanvas();
721
{{{ makeSetValue('width', '0', 'canvas.width', 'i32') }}};
722
{{{ makeSetValue('height', '0', 'canvas.height', 'i32') }}};
723
{{{ makeSetValue('isFullscreen', '0', 'Browser.isFullscreen ? 1 : 0', 'i32') }}};
724
},
725
726
// To avoid creating worker parent->child chains, always proxies to execute on the main thread.
727
emscripten_create_worker__proxy: 'sync',
728
emscripten_create_worker__deps: ['$UTF8ToString', 'realloc'],
729
emscripten_create_worker: (url) => {
730
url = UTF8ToString(url);
731
var id = Browser.workers.length;
732
var info = {
733
worker: new Worker(url),
734
callbacks: [],
735
awaited: 0,
736
buffer: 0,
737
};
738
info.worker.onmessage = function info_worker_onmessage(msg) {
739
if (ABORT) return;
740
var info = Browser.workers[id];
741
if (!info) return; // worker was destroyed meanwhile
742
var callbackId = msg.data['callbackId'];
743
var callbackInfo = info.callbacks[callbackId];
744
if (!callbackInfo) return; // no callback or callback removed meanwhile
745
// Don't trash our callback state if we expect additional calls.
746
if (msg.data['finalResponse']) {
747
info.awaited--;
748
info.callbacks[callbackId] = null; // TODO: reuse callbackIds, compress this
749
{{{ runtimeKeepalivePop() }}}
750
}
751
var data = msg.data['data'];
752
if (data) {
753
if (!data.byteLength) data = new Uint8Array(data);
754
info.buffer = _realloc(info.buffer, data.length);
755
HEAPU8.set(data, info.buffer);
756
callbackInfo.func(info.buffer, data.length, callbackInfo.arg);
757
} else {
758
callbackInfo.func(0, 0, callbackInfo.arg);
759
}
760
};
761
Browser.workers.push(info);
762
return id;
763
},
764
765
emscripten_destroy_worker__deps: ['free'],
766
emscripten_destroy_worker__proxy: 'sync',
767
emscripten_destroy_worker: (id) => {
768
var info = Browser.workers[id];
769
info.worker.terminate();
770
_free(info.buffer);
771
Browser.workers[id] = null;
772
},
773
774
emscripten_call_worker__proxy: 'sync',
775
emscripten_call_worker: (id, funcName, data, size, callback, arg) => {
776
funcName = UTF8ToString(funcName);
777
var info = Browser.workers[id];
778
var callbackId = -1;
779
if (callback) {
780
// If we are waiting for a response from the worker we need to keep
781
// the runtime alive at least long enough to receive it.
782
// The corresponding runtimeKeepalivePop is in the `finalResponse`
783
// handler above.
784
{{{ runtimeKeepalivePush() }}}
785
callbackId = info.callbacks.length;
786
info.callbacks.push({
787
func: {{{ makeDynCall('vpip', 'callback') }}},
788
arg
789
});
790
info.awaited++;
791
}
792
var transferObject = {
793
'funcName': funcName,
794
'callbackId': callbackId,
795
'data': data ? new Uint8Array({{{ makeHEAPView('U8', 'data', 'data + size') }}}) : 0
796
};
797
if (data) {
798
info.worker.postMessage(transferObject, [transferObject.data.buffer]);
799
} else {
800
info.worker.postMessage(transferObject);
801
}
802
},
803
804
#if BUILD_AS_WORKER
805
emscripten_worker_respond_provisionally__proxy: 'sync',
806
emscripten_worker_respond_provisionally: (data, size) => {
807
if (workerResponded) throw 'already responded with final response!';
808
var transferObject = {
809
'callbackId': workerCallbackId,
810
'finalResponse': false,
811
'data': data ? new Uint8Array({{{ makeHEAPView('U8', 'data', 'data + size') }}}) : 0
812
};
813
if (data) {
814
postMessage(transferObject, [transferObject.data.buffer]);
815
} else {
816
postMessage(transferObject);
817
}
818
},
819
820
emscripten_worker_respond__proxy: 'sync',
821
emscripten_worker_respond: (data, size) => {
822
if (workerResponded) throw 'already responded with final response!';
823
workerResponded = true;
824
var transferObject = {
825
'callbackId': workerCallbackId,
826
'finalResponse': true,
827
'data': data ? new Uint8Array({{{ makeHEAPView('U8', 'data', 'data + size') }}}) : 0
828
};
829
if (data) {
830
postMessage(transferObject, [transferObject.data.buffer]);
831
} else {
832
postMessage(transferObject);
833
}
834
},
835
#endif
836
837
emscripten_get_worker_queue_size__proxy: 'sync',
838
emscripten_get_worker_queue_size: (id) => {
839
var info = Browser.workers[id];
840
if (!info) return -1;
841
return info.awaited;
842
},
843
844
emscripten_get_preloaded_image_data__deps: ['$getPreloadedImageData', '$UTF8ToString'],
845
emscripten_get_preloaded_image_data__proxy: 'sync',
846
emscripten_get_preloaded_image_data: (path, w, h) => getPreloadedImageData(UTF8ToString(path), w, h),
847
848
$getPreloadedImageData__internal: true,
849
$getPreloadedImageData__data: ['$PATH_FS', 'malloc'],
850
$getPreloadedImageData: (path, w, h) => {
851
path = PATH_FS.resolve(path);
852
853
var canvas = /** @type {HTMLCanvasElement} */(Browser.preloadedImages[path]);
854
if (!canvas) return 0;
855
856
var ctx = canvas.getContext("2d");
857
var image = ctx.getImageData(0, 0, canvas.width, canvas.height);
858
var buf = _malloc(canvas.width * canvas.height * 4);
859
860
HEAPU8.set(image.data, buf);
861
862
{{{ makeSetValue('w', '0', 'canvas.width', 'i32') }}};
863
{{{ makeSetValue('h', '0', 'canvas.height', 'i32') }}};
864
return buf;
865
},
866
867
#if !WASMFS // WasmFS implements this in wasm
868
emscripten_get_preloaded_image_data_from_FILE__deps: ['$getPreloadedImageData', 'fileno'],
869
emscripten_get_preloaded_image_data_from_FILE__proxy: 'sync',
870
emscripten_get_preloaded_image_data_from_FILE: (file, w, h) => {
871
var fd = _fileno(file);
872
var stream = FS.getStream(fd);
873
if (stream) {
874
return getPreloadedImageData(stream.path, w, h);
875
}
876
877
return 0;
878
}
879
#endif
880
};
881
882
autoAddDeps(LibraryBrowser, '$Browser');
883
884
addToLibrary(LibraryBrowser);
885
886