dmloader.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. /* ********************************************************************* */
  2. /* Load and combine data that is split into archives */
  3. /* ********************************************************************* */
  4. var Combine = {
  5. _targets: [],
  6. _targetIndex: 0,
  7. // target: build target
  8. // name: intended filepath of built object
  9. // size: expected size of built object.
  10. // data: combined data
  11. // downloaded: total amount of data downloaded
  12. // pieces: array of name, offset and data objects
  13. // numExpectedFiles: total number of files expected in description
  14. // lastRequestedPiece: index of last data file requested (strictly ascending)
  15. // totalLoadedPieces: counts the number of data files received
  16. //MAX_CONCURRENT_XHR: 6, // remove comment if throttling of XHR is desired.
  17. isCompleted: false, // status of process
  18. _onCombineCompleted: [], // signature: name, data.
  19. _onAllTargetsBuilt:[], // signature: void
  20. _onDownloadProgress: [], // signature: downloaded, total
  21. _totalDownloadBytes: 0,
  22. _archiveLocationFilter: function(path) { return "split" + path; },
  23. addProgressListener: function(callback) {
  24. if (typeof callback !== 'function') {
  25. throw "Invalid callback registration";
  26. }
  27. this._onDownloadProgress.push(callback);
  28. },
  29. addCombineCompletedListener: function(callback) {
  30. if (typeof callback !== 'function') {
  31. throw "Invalid callback registration";
  32. }
  33. this._onCombineCompleted.push(callback);
  34. },
  35. addAllTargetsBuiltListener: function(callback) {
  36. if (typeof callback !== 'function') {
  37. throw "Invalid callback registration";
  38. }
  39. this._onAllTargetsBuilt.push(callback);
  40. },
  41. // descriptUrl: location of text file describing files to be preloaded
  42. process: function(descriptUrl) {
  43. var xhr = new XMLHttpRequest();
  44. xhr.open('GET', descriptUrl);
  45. xhr.responseType = 'text';
  46. xhr.onload = function(evt) {
  47. Combine.onReceiveDescription(xhr);
  48. };
  49. xhr.send(null);
  50. },
  51. cleanUp: function() {
  52. this._targets = [];
  53. this._targetIndex = 0;
  54. this.isCompleted = false;
  55. this._onCombineCompleted = [];
  56. this._onAllTargetsBuilt = [];
  57. this._onDownloadProgress = [];
  58. this._totalDownloadBytes = 0;
  59. },
  60. onReceiveDescription: function(xhr) {
  61. var json = JSON.parse(xhr.responseText);
  62. this._targets = json.content;
  63. this._totalDownloadBytes = 0;
  64. var targets = this._targets;
  65. for(var i=0; i<targets.length; ++i) {
  66. this._totalDownloadBytes += targets[i].size;
  67. }
  68. this.requestContent();
  69. },
  70. requestContent: function() {
  71. var target = this._targets[this._targetIndex];
  72. if (1 < target.pieces.length) {
  73. target.data = new Uint8Array(target.size);
  74. }
  75. var limit = target.pieces.length;
  76. if (typeof this.MAX_CONCURRENT_XHR !== 'undefined') {
  77. limit = Math.min(limit, this.MAX_CONCURRENT_XHR);
  78. }
  79. for (var i=0; i<limit; ++i) {
  80. this.requestPiece(target, i);
  81. }
  82. },
  83. requestPiece: function(target, index) {
  84. if (index < target.lastRequestedPiece) {
  85. throw "Request out of order";
  86. }
  87. target.lastRequestedPiece = index;
  88. target.progress = {};
  89. var item = target.pieces[index];
  90. var xhr = new XMLHttpRequest();
  91. xhr.open('GET', this._archiveLocationFilter('/' + item.name), true);
  92. xhr.responseType = 'arraybuffer';
  93. xhr.onprogress = function(evt) {
  94. target.progress[item.name] = {total: 0, downloaded: 0};
  95. if (evt.total && evt.lengthComputable) {
  96. target.progress[item.name].total = evt.total;
  97. }
  98. if (evt.loaded && evt.lengthComputable) {
  99. target.progress[item.name].downloaded = evt.loaded;
  100. Combine.updateProgress(target);
  101. }
  102. };
  103. xhr.onload = function(evt) {
  104. item.data = new Uint8Array(xhr.response);
  105. item.dataLength = item.data.length;
  106. target.progress[item.name].total = item.dataLength;
  107. target.progress[item.name].downloaded = item.dataLength;
  108. Combine.copyData(target, item);
  109. Combine.onPieceLoaded(target, item);
  110. Combine.updateProgress(target);
  111. item.data = undefined;
  112. };
  113. xhr.send(null);
  114. },
  115. updateProgress: function(target) {
  116. var total_downloaded = 0;
  117. for (var p in target.progress) {
  118. total_downloaded += target.progress[p].downloaded;
  119. }
  120. for(i = 0; i<this._onDownloadProgress.length; ++i) {
  121. this._onDownloadProgress[i](total_downloaded, this._totalDownloadBytes);
  122. }
  123. },
  124. copyData: function(target, item) {
  125. if (1 == target.pieces.length) {
  126. target.data = item.data;
  127. } else {
  128. var start = item.offset;
  129. var end = start + item.data.length;
  130. if (0 > start) {
  131. throw "Buffer underflow";
  132. }
  133. if (end > target.data.length) {
  134. throw "Buffer overflow";
  135. }
  136. target.data.set(item.data, item.offset);
  137. }
  138. },
  139. onPieceLoaded: function(target, item) {
  140. if (typeof target.totalLoadedPieces === 'undefined') {
  141. target.totalLoadedPieces = 0;
  142. }
  143. ++target.totalLoadedPieces;
  144. if (target.totalLoadedPieces == target.pieces.length) {
  145. this.finalizeTarget(target);
  146. ++this._targetIndex;
  147. for (var i=0; i<this._onCombineCompleted.length; ++i) {
  148. this._onCombineCompleted[i](target.name, target.data);
  149. }
  150. if (this._targetIndex < this._targets.length) {
  151. this.requestContent();
  152. } else {
  153. this.isCompleted = true;
  154. for (i=0; i<this._onAllTargetsBuilt.length; ++i) {
  155. this._onAllTargetsBuilt[i]();
  156. }
  157. }
  158. } else {
  159. var next = target.lastRequestedPiece + 1;
  160. if (next < target.pieces.length) {
  161. this.requestPiece(target, next);
  162. }
  163. }
  164. },
  165. finalizeTarget: function(target) {
  166. var actualSize = 0;
  167. for (var i=0;i<target.pieces.length; ++i) {
  168. actualSize += target.pieces[i].dataLength;
  169. }
  170. if (actualSize != target.size) {
  171. throw "Unexpected data size";
  172. }
  173. if (1 < target.pieces.length) {
  174. var output = target.data;
  175. var pieces = target.pieces;
  176. for (i=0; i<pieces.length; ++i) {
  177. var item = pieces[i];
  178. // Bounds check
  179. var start = item.offset;
  180. var end = start + item.dataLength;
  181. if (0 < i) {
  182. var previous = pieces[i - 1];
  183. if (previous.offset + previous.dataLength > start) {
  184. throw "Segment underflow";
  185. }
  186. }
  187. if (pieces.length - 2 > i) {
  188. var next = pieces[i + 1];
  189. if (end > next.offset) {
  190. throw "Segment overflow";
  191. }
  192. }
  193. }
  194. }
  195. }
  196. };
  197. /* ********************************************************************* */
  198. /* Default splash and progress visualisation */
  199. /* ********************************************************************* */
  200. var DefaultProgress = {
  201. progress_id: "_defold-progress",
  202. bar_id: "_defold-progress-bar",
  203. status_id: "_defold-status",
  204. start : function (canvas) {
  205. /* Insert default progress bar and status */
  206. var div = '<div id="' + DefaultProgress.progress_id + '">' +
  207. '<div id="' + DefaultProgress.bar_id + '">' +
  208. '</div></div>' +
  209. '<div id="' + DefaultProgress.status_id + '"></div>';
  210. canvas.insertAdjacentHTML('afterend', div);
  211. var status = document.getElementById(DefaultProgress.status_id);
  212. var bar = document.getElementById(DefaultProgress.bar_id);
  213. var progress = document.getElementById(DefaultProgress.progress_id);
  214. status.style.display = 'none';
  215. status.style.position = 'absolute';
  216. status.style.textAlign = 'center';
  217. status.style.fontWeight = '600';
  218. status.style.bottom = '0';
  219. status.style.left = '50%';
  220. status.style.transform = 'translateX(-50%)';
  221. status.style.backgroundColor = 'hsla(0, 0%, 100%, .75)';
  222. status.style.boxShadow = '-0.035px 2px 5px 0px rgba(46, 37, 22, 0.25)';
  223. status.style.padding = '6px 12px';
  224. progress.style.width = '100%';
  225. progress.style.position = 'absolute';
  226. progress.style.bottom = '0';
  227. bar.style.width = '50%';
  228. bar.style.height = '4px';
  229. bar.style.backgroundColor = '#3F7BBB';
  230. DefaultProgress.status = status;
  231. DefaultProgress.bar = bar;
  232. DefaultProgress.progress = progress;
  233. },
  234. progress: function (bytes_loaded, bytes_total, status) {
  235. var percentage = bytes_loaded / bytes_total * 100;
  236. DefaultProgress.bar.style.width = percentage + "%";
  237. if(status) {
  238. DefaultProgress.status.innerText = status;
  239. DefaultProgress.status.style.display = 'block';
  240. } else {
  241. DefaultProgress.status.innerText = '';
  242. DefaultProgress.status.style.display = 'none';
  243. }
  244. },
  245. done: function () {
  246. DefaultProgress.status.innerText = "Starting...";
  247. DefaultProgress.status.style.display = 'block';
  248. },
  249. end: function () {
  250. DefaultProgress.progress.parentElement.removeChild(DefaultProgress.progress);
  251. DefaultProgress.status.parentElement.removeChild(DefaultProgress.status);
  252. }
  253. };
  254. /* ********************************************************************* */
  255. /* Default input override */
  256. /* ********************************************************************* */
  257. var CanvasInput = {
  258. arrowKeysHandler : function(e) {
  259. switch(e.keyCode) {
  260. case 37: case 38: case 39: case 40: // Arrow keys
  261. case 32: e.preventDefault(); e.stopPropagation(); // Space
  262. default: break; // do not block other keys
  263. }
  264. },
  265. onFocusIn : function(e) {
  266. window.addEventListener("keydown", CanvasInput.arrowKeysHandler, false);
  267. },
  268. onFocusOut: function(e) {
  269. window.removeEventListener("keydown", CanvasInput.arrowKeysHandler, false);
  270. },
  271. addToCanvas : function(canvas) {
  272. canvas.addEventListener("focus", CanvasInput.onFocusIn, false);
  273. canvas.addEventListener("blur", CanvasInput.onFocusOut, false);
  274. canvas.focus();
  275. CanvasInput.onFocusIn();
  276. }
  277. };
  278. /* ********************************************************************* */
  279. /* Module is Emscripten namespace */
  280. /* ********************************************************************* */
  281. var Module = {
  282. noInitialRun: true,
  283. _filesToPreload: [],
  284. _archiveLoaded: false,
  285. _preLoadDone: false,
  286. _waitingForArchive: false,
  287. // Persistent storage
  288. persistentStorage: true,
  289. _syncInProgress: false,
  290. _syncNeeded: false,
  291. _syncInitial: false,
  292. _syncMaxTries: 3,
  293. _syncTries: 0,
  294. print: function(text) { console.log(text); },
  295. printErr: function(text) { console.error(text); },
  296. setStatus: function(text) { console.log(text); },
  297. prepareErrorObject: function (err, url, line, column, errObj) {
  298. line = typeof line == "undefined" ? 0 : line;
  299. column = typeof column == "undefined" ? 0 : column;
  300. url = typeof url == "undefined" ? "" : url;
  301. var errorLine = url + ":" + line + ":" + column;
  302. var error = errObj || (typeof window.event != "undefined" ? window.event.error : "" ) || err || "Undefined Error";
  303. var message = "";
  304. var stack = "";
  305. var backtrace = "";
  306. if (typeof error == "object" && typeof error.stack != "undefined" && typeof error.message != "undefined") {
  307. stack = String(error.stack);
  308. message = String(error.message);
  309. } else {
  310. stack = String(error).split("\n");
  311. message = stack.shift();
  312. stack = stack.join("\n");
  313. }
  314. stack = stack || errorLine;
  315. var callLine = /at (\S+:\d*$)/.exec(message);
  316. if (callLine) {
  317. message = message.replace(/(at \S+:\d*$)/, "");
  318. stack = callLine[1] + "\n" + stack;
  319. }
  320. message = message.replace(/(abort\(.+\)) at .+/, "$1");
  321. stack = stack.replace(/\?{1}\S+(:\d+:\d+)/g, "$1");
  322. stack = stack.replace(/ *at (\S+)$/gm, "@$1");
  323. stack = stack.replace(/ *at (\S+)(?: \[as \S+\])? +\((.+)\)/g, "$1@$2");
  324. stack = stack.replace(/^((?:Object|Array)\.)/gm, "");
  325. stack = stack.split("\n");
  326. return { stack:stack, message:message };
  327. },
  328. hasWebGLSupport: function() {
  329. var webgl_support = false;
  330. try {
  331. var canvas = document.createElement("canvas");
  332. var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
  333. if (gl && gl instanceof WebGLRenderingContext) {
  334. webgl_support = true;
  335. }
  336. } catch (error) {
  337. console.log("An error occurred while detecting WebGL support: " + error);
  338. webgl_support = false;
  339. }
  340. return webgl_support;
  341. },
  342. /**
  343. * Module.runApp - Starts the application given a canvas element id
  344. *
  345. * 'extra_params' is an optional object that can have the following fields:
  346. *
  347. * 'archive_location_filter':
  348. * Filter function that will run for each archive path.
  349. *
  350. * 'unsupported_webgl_callback':
  351. * Function that is called if WebGL is not supported.
  352. *
  353. * 'engine_arguments':
  354. * List of arguments (strings) that will be passed to the engine.
  355. *
  356. * 'persistent_storage':
  357. * Boolean toggling the usage of persistent storage.
  358. *
  359. * 'custom_heap_size':
  360. * Number of bytes specifying the memory heap size.
  361. *
  362. * 'load_start':
  363. * Function to call when download starts.
  364. * function(canvas) { }
  365. *
  366. * 'load_progress':
  367. * Function to call on download progress.
  368. * function(bytes_downloaded, bytes_total, status)
  369. *
  370. * 'load_done':
  371. * Function to call when download is done.
  372. * function()
  373. *
  374. * 'load_end':
  375. * Function to call when game is loaded and ready to start.
  376. * function()
  377. *
  378. * 'game_start':
  379. * Function to call right before game starts.
  380. * function()
  381. *
  382. **/
  383. setParams: function(extra_params) {
  384. var params = {
  385. splash_image: undefined,
  386. archive_location_filter: function(path) { return 'split' + path; },
  387. unsupported_webgl_callback: undefined,
  388. engine_arguments: [],
  389. persistent_storage: true,
  390. custom_heap_size: undefined,
  391. load_start: DefaultProgress.start,
  392. load_progress: DefaultProgress.progress,
  393. load_done: DefaultProgress.done,
  394. load_end: DefaultProgress.end,
  395. game_start: function() {},
  396. };
  397. for (var k in extra_params) {
  398. if (extra_params.hasOwnProperty(k)) {
  399. params[k] = extra_params[k];
  400. }
  401. }
  402. Module.archive_location_filter = params["archive_location_filter"];
  403. Module.arguments = params["engine_arguments"];
  404. Module.persistentStorage = params["persistent_storage"];
  405. Module["TOTAL_MEMORY"] = params["custom_heap_size"];
  406. Module.load_start = params["load_start"];
  407. Module.load_progress = params["load_progress"];
  408. Module.load_done = params["load_done"];
  409. Module.load_end = params["load_end"];
  410. Module.game_start = params["game_start"];
  411. },
  412. runApp: function(app_canvas_id, extra_params) {
  413. app_canvas_id = (typeof app_canvas_id === 'undefined') ? 'canvas' : app_canvas_id;
  414. Module.canvas = document.getElementById(app_canvas_id);
  415. Module.setParams(extra_params);
  416. if (Module.hasWebGLSupport()) {
  417. // Override game keys
  418. CanvasInput.addToCanvas(Module.canvas);
  419. // Load Facebook API
  420. var fb = document.createElement('script');
  421. fb.type = 'text/javascript';
  422. fb.src = '//connect.facebook.net/en_US/sdk.js';
  423. document.head.appendChild(fb);
  424. // Add progress
  425. Module.load_start(Module.canvas);
  426. // Load and assemble archive
  427. Combine.addCombineCompletedListener(Module.onArchiveFileLoaded);
  428. Combine.addAllTargetsBuiltListener(Module.onArchiveLoaded);
  429. Combine.addProgressListener(Module.onArchiveLoadProgress);
  430. Combine._archiveLocationFilter = Module.archive_location_filter;
  431. Combine.process(Combine._archiveLocationFilter('/archive_files.json'));
  432. } else {
  433. Module.load_start(Module.canvas);
  434. Module.load_progress(0, 0, "Unable to start game, WebGL not supported");
  435. Module.setStatus = function(text) {
  436. if (text) Module.printErr('[missing WebGL] ' + text);
  437. };
  438. if (typeof params["unsupported_webgl_callback"] === "function") {
  439. params["unsupported_webgl_callback"]();
  440. }
  441. }
  442. },
  443. /* Simulate app loading etc for frontend testing */
  444. testApp: function(app_canvas_id, extra_params) {
  445. app_canvas_id = (typeof app_canvas_id === 'undefined') ? 'canvas' : app_canvas_id;
  446. Module.canvas = document.getElementById(app_canvas_id);
  447. Module.setParams(extra_params);
  448. // Test progress
  449. Module._test_inc = 1;
  450. Module._test_bytes = 0;
  451. Module._test_total_bytes = 1000;
  452. Module.load_start(Module.canvas);
  453. Module._testintervall = setInterval(Module.testUpdate, 10);
  454. },
  455. testUpdate: function() {
  456. Module._test_bytes += Module._test_inc;
  457. var msg = undefined;
  458. var rat = Module._test_bytes / Module._test_total_bytes;
  459. if (rat > 0.3 && rat < 0.9) {
  460. msg = "Looking good so far";
  461. if (rat > 0.5) {
  462. msg = "If something bad happens, this is how it's presented.";
  463. }
  464. }
  465. if (Module._test_bytes < Module._test_total_bytes) {
  466. Module.load_progress(Module._test_bytes, Module._test_total_bytes, msg);
  467. } else {
  468. Module.load_done();
  469. clearInterval(Module._testintervall);
  470. setTimeout(Module.load_end, 2000);
  471. setTimeout(Module.game_start, 3000);
  472. }
  473. },
  474. onArchiveLoadProgress: function(downloaded, total) {
  475. Module.load_progress(downloaded, total);
  476. },
  477. onArchiveFileLoaded: function(name, data) {
  478. Module._filesToPreload.push({path: name, data: data});
  479. },
  480. onArchiveLoaded: function() {
  481. Combine.cleanUp();
  482. Module._archiveLoaded = true;
  483. Module.load_done();
  484. if (Module._waitingForArchive) {
  485. Module._preloadAndCallMain();
  486. }
  487. },
  488. toggleFullscreen: function() {
  489. if (GLFW.isFullscreen) {
  490. GLFW.cancelFullScreen();
  491. } else {
  492. GLFW.requestFullScreen();
  493. }
  494. },
  495. preSync: function(done) {
  496. // Initial persistent sync before main is called
  497. FS.syncfs(true, function(err) {
  498. if(err) {
  499. Module._syncTries += 1;
  500. console.error("FS syncfs error: " + err);
  501. if (Module._syncMaxTries > Module._syncTries) {
  502. Module.preSync(done);
  503. } else {
  504. Module._syncInitial = true;
  505. done();
  506. }
  507. } else {
  508. Module._syncInitial = true;
  509. if (done !== undefined) {
  510. done();
  511. }
  512. }
  513. });
  514. },
  515. preloadAll: function() {
  516. if (Module._preLoadDone) {
  517. return;
  518. }
  519. for (var i = 0; i < Module._filesToPreload.length; ++i) {
  520. var item = Module._filesToPreload[i];
  521. FS.createPreloadedFile("", item.path, item.data, true, true);
  522. }
  523. Module._preLoadDone = true;
  524. },
  525. // Tries to do a MEM->IDB sync
  526. // It will flag that another one is needed if there is already one sync running.
  527. persistentSync: function() {
  528. // Need to wait for the initial sync to finish since it
  529. // will call close on all its file streams which will trigger
  530. // new persistentSync for each.
  531. if (Module._syncInitial) {
  532. if (Module._syncInProgress) {
  533. Module._syncNeeded = true;
  534. } else {
  535. Module._startSyncFS();
  536. }
  537. }
  538. },
  539. preInit: [function() {
  540. /* Mount filesystem on preinit */
  541. var dir = DMSYS.GetUserPersistentDataRoot();
  542. FS.mkdir(dir);
  543. // If IndexedDB is supported we mount the persistent data root as IDBFS,
  544. // then try to do a IDB->MEM sync before we start the engine to get
  545. // previously saved data before boot.
  546. window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
  547. if (Module.persistentStorage && window.indexedDB) {
  548. FS.mount(IDBFS, {}, dir);
  549. // Patch FS.close so it will try to sync MEM->IDB
  550. var _close = FS.close; FS.close = function(stream) { var r = _close(stream); Module.persistentSync(); return r; }
  551. // Sync IDB->MEM before calling main()
  552. Module.preSync(function() {
  553. Module._preloadAndCallMain();
  554. });
  555. } else {
  556. Module._preloadAndCallMain();
  557. }
  558. }],
  559. preRun: [function() {
  560. /* If archive is loaded, preload all its files */
  561. if(Module._archiveLoaded) {
  562. Module.preloadAll();
  563. }
  564. }],
  565. _preloadAndCallMain: function() {
  566. // If the archive isn't loaded,
  567. // we will have to wait with calling main.
  568. if (!Module._archiveLoaded) {
  569. Module._waitingForArchive = true;
  570. } else {
  571. // Need to set heap size before calling main
  572. TOTAL_MEMORY = Module["TOTAL_MEMORY"] || TOTAL_MEMORY;
  573. Module.preloadAll();
  574. Module.load_end();
  575. Module.game_start();
  576. Module.callMain(Module.arguments);
  577. }
  578. },
  579. // Wrap IDBFS syncfs call with logic to avoid multiple syncs
  580. // running at the same time.
  581. _startSyncFS: function() {
  582. Module._syncInProgress = true;
  583. if (Module._syncMaxTries > Module._syncTries) {
  584. FS.syncfs(false, function(err) {
  585. Module._syncInProgress = false;
  586. if (err) {
  587. console.error("Module._startSyncFS error: " + err);
  588. Module._syncTries += 1;
  589. }
  590. if (Module._syncNeeded) {
  591. Module._syncNeeded = false;
  592. Module._startSyncFS();
  593. }
  594. });
  595. }
  596. },
  597. };
  598. window.onerror = function(err, url, line, column, errObj) {
  599. var errorObject = Module.prepareErrorObject(err, url, line, column, errObj);
  600. Module.ccall('JSWriteDump', 'null', ['string'], [JSON.stringify(errorObject.stack)]);
  601. Module.setStatus('Exception thrown, see JavaScript console');
  602. Module.setStatus = function(text) {
  603. if (text) Module.printErr('[post-exception status] ' + text);
  604. };
  605. };