chiark / gitweb /
wasm/js/emscripten: Fix page loading race for-sgt-race
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Thu, 22 Apr 2021 18:20:32 +0000 (19:20 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Thu, 22 Apr 2021 20:23:47 +0000 (21:23 +0100)
Using a stunt webserver which artificially introduces a 3s delay just
before the last line of the HTML output, I have reproduced a
uwer-reported loading/startup race bug:

Previously the wasm loading was started by the <script> element,
synchronously. If the wasm loading is fast, and finishes before the
HTML loading, the onRuntimeInitialized event may occur before
initPuzzles.  But initPuzzles sets up the event handler.

Fix this bug, and introduce a new comment containing an argument for
the correctness of the new approach.

Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
cmake/platforms/emscripten.cmake
emccpost.js [new file with mode: 0644]
emccpre.js
html/jspage.pl

index bbb0897aaa2c84fbd24c25c881fda7bc869e7ed3..c7e55fe89d47fde208f230198491514e42d16fbe 100644 (file)
@@ -43,6 +43,7 @@ endfunction()
 function(set_platform_puzzle_target_properties NAME TARGET)
   em_link_pre_js(${TARGET} ${CMAKE_SOURCE_DIR}/emccpre.js)
   em_link_js_library(${TARGET} ${CMAKE_SOURCE_DIR}/emcclib.js)
+  em_link_post_js(${TARGET} ${CMAKE_SOURCE_DIR}/emccpost.js)
 endfunction()
 
 function(build_platform_extras)
diff --git a/emccpost.js b/emccpost.js
new file mode 100644 (file)
index 0000000..b12b0af
--- /dev/null
@@ -0,0 +1 @@
+initPuzzle();
index 1579ead4209652b8ab6c7d75ae12619800070d2f..310c465b967b3d1cbc49ea384eb7108bc2422114 100644 (file)
@@ -35,6 +35,51 @@ var update_xmin, update_xmax, update_ymin, update_ymax;
 // our own init stuff first), and that when main() returns nothing
 // will get cleaned up so we remain able to call the puzzle's various
 // callbacks.
+//
+//
+// Page loading order:
+//
+// 1. The browser starts reading *.html (which comes from jspage.pl)
+// 2. It finds the <script> tag.  This is marked defer, so the
+//    browser will start fetching and parsing it, but not execute it
+//    until the page has loaded.
+//
+// Now the browser is loading *.html and *.js in parallel.  The
+// html is rendered as we go, and the js is deferred.
+//
+// 3. The HTML finishes loading.  The browser is about to fire the
+//    `DOMContentLoaded` event (ie `onload`) but before that, it
+//    actually runs the deferred JS.  THis consists of
+//
+//    (i) emccpre.js (this file).  This sets up various JS variables
+//      including the emscripten Module object.
+//
+//    (ii) emscripten's JS.  This starts the WASM loading.
+//
+//    (iii) emccpost.js.  This calls initPuzzle, which is defined here
+//      in this file.  initPuzzle:
+//
+//      (a) finds various DOM elements and bind them to variables,
+//      which depend on the HTML having loaded (it has).
+//
+//      (b) makes various `cwrap` calls into the emscripten module to
+//      set up hooks; this depends on the emscripten JS having been
+//      loaded (it has).
+//
+//      (c) Makes the call to emscripten's
+//      Module.onRuntimeInitialized, which sets the callback for when
+//      the WASM has finished loading and initialising.  This has to
+//      come before the WASM finishes loading, or we'll miss the
+//      callback.  We are executing synchronously here in the same JS
+//      file as started the WASM loading, so that is guaranteed.
+//
+// When this JS execution is complete, the browser fires the `onload`
+// event.  This is ignored.  It continues loading the WASM.
+//
+// 4. The WASM loading and initialisation completes.  The
+//    onRuntimeInitialised callback calls into emscripten-generated
+//    WASM to call the C `main`, to actually start the puzzle.
+
 var Module = {
     'noInitialRun': true,
     'noExitRuntime': true
index 2101030560bfb57814b0935f3f35159815344c23..70d7209110cedf84f17ac8d483c3f2530d93d4b2 100755 (executable)
@@ -73,7 +73,7 @@ EOF
 <head>
 <meta http-equiv="Content-Type" content="text/html; charset=ASCII" />
 <title>${puzzlename}, ${unfinishedtitlefragment}from Simon Tatham's Portable Puzzle Collection</title>
-<script type="text/javascript" src="${jspath}${filename}.js"></script>
+<script defer type="text/javascript" src="${jspath}${filename}.js"></script>
 <style class="text/css">
 /* Margins and centring on the top-level div for the game menu */
 #gamemenu { margin-top: 0; margin-bottom: 0.5em; text-align: center }
@@ -217,7 +217,7 @@ EOF
 }
 </style>
 </head>
-<body onLoad="initPuzzle();">
+<body>
 <h1 align=center>${puzzlename}</h1>
 ${unfinishedheading}
 <h2 align=center>from Simon Tatham's Portable Puzzle Collection</h2>