From e40da9a02466cf640bf0fb0c2ca8916dbf3c65d0 Mon Sep 17 00:00:00 2001 From: Jonathan Olson Date: Thu, 27 Jun 2019 17:50:51 -0600 Subject: [PATCH] Adding a delayed sim startup to wait for suitable conditions, see https://github.com/phetsims/chipper/issues/764 and https://github.com/phetsims/tasks/issues/1002 --- js/grunt/buildRunnable.js | 20 +++++--- js/grunt/buildStandalone.js | 2 +- js/grunt/requireBuild.js | 7 ++- templates/chipper-startup.js | 89 ++++++++++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 templates/chipper-startup.js diff --git a/js/grunt/buildRunnable.js b/js/grunt/buildRunnable.js index 2eefc679c..ce25b3d08 100644 --- a/js/grunt/buildRunnable.js +++ b/js/grunt/buildRunnable.js @@ -73,7 +73,8 @@ module.exports = async function( repo, minifyOptions, instrument, allHTML, brand const requireJS = await requireBuild( repo, `../${repo}/js/${repo}-config.js`, { insertRequire: repo + '-main', instrument: instrument, - brand: brand + brand: brand, + wrapPath: 'phet.chipper.runRequireJS' } ); // Debug version is independent of passed in minifyOptions. PhET-iO brand is minified, but leaves assertions & logging. @@ -141,7 +142,14 @@ module.exports = async function( repo, minifyOptions, instrument, allHTML, brand 'University of Colorado. Contact phethelp@colorado.edu regarding licensing.'; } - const chipperStringsScript = grunt.file.read( '../chipper/templates/chipper-strings.js' ); + const stringsJS = grunt.file.read( '../chipper/templates/chipper-strings.js' ); + const productionStringsJS = minify( stringsJS, minifyOptions ); + const debugStringsJS = minify( stringsJS, debugMinifyOptions ); + + const startupJS = grunt.file.read( '../chipper/templates/chipper-startup.js' ); + const productionStartupJS = minify( startupJS, minifyOptions ); + const debugStartupJS = minify( startupJS, debugMinifyOptions ); + const splashScript = `window.PHET_SPLASH_DATA_URI="${loadFileAsDataURI( `../brand/${brand}/images/splash.svg` )}";`; grunt.log.ok( `Minification for ${brand} complete` ); @@ -176,7 +184,7 @@ module.exports = async function( repo, minifyOptions, instrument, allHTML, brand stringMap: stringMap, htmlHeader: htmlHeader, locale: locale, - scripts: [ initializationScript, splashScript, mipmapsJavaScript, ...productionPreloads, chipperStringsScript, productionJS ] + scripts: [ initializationScript, splashScript, mipmapsJavaScript, ...productionPreloads, productionStringsJS, productionJS, productionStartupJS ] } ) ); } } @@ -195,7 +203,7 @@ module.exports = async function( repo, minifyOptions, instrument, allHTML, brand stringMap: stringMap, htmlHeader: htmlHeader, locale: ChipperConstants.FALLBACK_LOCALE, - scripts: [ initializationScript, splashScript, mipmapsJavaScript, ...productionPreloads, chipperStringsScript, productionJS ] + scripts: [ initializationScript, splashScript, mipmapsJavaScript, ...productionPreloads, productionStringsJS, productionJS, productionStartupJS ] } ); grunt.file.write( allHTMLFilename, allHTMLContents ); @@ -214,7 +222,7 @@ module.exports = async function( repo, minifyOptions, instrument, allHTML, brand stringMap: stringMap, htmlHeader: htmlHeader, locale: ChipperConstants.FALLBACK_LOCALE, - scripts: [ debugInitializationScript, splashScript, mipmapsJavaScript, ...debugPreloads, chipperStringsScript, debugJS ] + scripts: [ debugInitializationScript, splashScript, mipmapsJavaScript, ...debugPreloads, debugStringsJS, debugJS, debugStartupJS ] } ) ); // XHTML build (ePub compatibility, etc.) @@ -230,7 +238,7 @@ module.exports = async function( repo, minifyOptions, instrument, allHTML, brand brand: brand, stringMap: stringMap, htmlHeader: htmlHeader, - scripts: [ xhtmlInitializationScript, splashScript, mipmapsJavaScript, ...productionPreloads, chipperStringsScript, productionJS ] + scripts: [ xhtmlInitializationScript, splashScript, mipmapsJavaScript, ...productionPreloads, productionStringsJS, productionJS, productionStartupJS ] } ); // dependencies.json diff --git a/js/grunt/buildStandalone.js b/js/grunt/buildStandalone.js index 154a30541..866216590 100644 --- a/js/grunt/buildStandalone.js +++ b/js/grunt/buildStandalone.js @@ -29,7 +29,7 @@ module.exports = async function( repo, minifyOptions ) { const packageObject = grunt.file.readJSON( `../${repo}/package.json` ); - const requireJS = await requireBuild( repo, `../${repo}/js/${repo}-config.js`, { wrap: false } ); + const requireJS = await requireBuild( repo, `../${repo}/js/${repo}-config.js` ); const includedSources = [ '../assert/js/assert.js', diff --git a/js/grunt/requireBuild.js b/js/grunt/requireBuild.js index d04e5111e..f454f625c 100644 --- a/js/grunt/requireBuild.js +++ b/js/grunt/requireBuild.js @@ -26,7 +26,7 @@ const requirejs = require( 'requirejs' ); module.exports = function( repo, mainConfigFile, options ) { const { - wrap = true, + wrapPath = null, insertRequire = false, instrument = false, brand = 'phet' @@ -53,7 +53,10 @@ module.exports = function( repo, mainConfigFile, options ) { optimize: 'none', - wrap: wrap, + wrap: wrapPath ? { + start: 'phet.chipper.runRequireJS = function() {', + end: '};' + } : false, // Avoid optimization names that are outside the baseUrl, see http://requirejs.org/docs/optimization.html#pitfalls paths: { diff --git a/templates/chipper-startup.js b/templates/chipper-startup.js new file mode 100644 index 000000000..3c94072dc --- /dev/null +++ b/templates/chipper-startup.js @@ -0,0 +1,89 @@ +// Copyright 2019, University of Colorado Boulder + +/* + * Delayed simulation/runnable startup so that we can ensure we are in an environment suitable for the sim to start up. + * See https://github.com/phetsims/chipper/issues/764 for more information. + * + * The require.js part is wrapped in a phet.chipper.runRequireJS() method. + */ + +(function() { + 'use strict'; + + // constants + const svgns = 'http://www.w3.org/2000/svg'; + + // Whether phet.chipper.runRequireJS has been called yet. + let started = false; + + function isReady() { + // NOTE: We do NOT care about window.innerWidth/innerHeight. We can start up with those equal to 0, as long as our + // SVG bounds detection works. See https://github.com/phetsims/tasks/issues/1002. + if ( !document.body ) { + return false; + } + + // Initialize the container and element, very similar in form to TextBounds.initializeTextBounds() + + const container = document.createElementNS( svgns, 'svg' ); + container.setAttribute( 'width', '2' ); + container.setAttribute( 'height', '2' ); + container.setAttribute( 'style', 'visibility: hidden; pointer-events: none; position: absolute; left: -65535px; right: -65535px;' ); + + const element = document.createElementNS( svgns, 'text' ); + element.appendChild( document.createTextNode( '' ) ); + element.setAttribute( 'dominant-baseline', 'alphabetic' ); // to match Canvas right now + element.setAttribute( 'text-rendering', 'geometricPrecision' ); + element.setAttributeNS( 'http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve' ); + container.appendChild( element ); + + document.body.appendChild( container ); + + // Equivalent setup to TextBounds.approximateSVGBounds + element.setAttribute( 'direction', 'ltr' ); + element.setAttribute( 'font-family', 'Arial' ); + element.setAttribute( 'font-size', 16 ); + element.setAttribute( 'font-style', 'normal' ); + element.setAttribute( 'font-weight', 'normal' ); + element.setAttribute( 'font-stretch', 'normal' ); + element.lastChild.nodeValue = 'm'; + + // Extract the measurement, and see if we have zero bounds. + const rect = element.getBBox(); + const result = rect.width !== 0 || rect.height !== 0; + + // Wait to remove the element until we've measured everything. + document.body.removeChild( container ); + + return result; + } + + // We can call this anytime to attempt to start things (if they haven't been started already). + function attemptStart() { + if ( !started && isReady() ) { + started = true; + + phet.chipper.runRequireJS(); + } + } + + // Attempt to start based on timeouts (in case other methods don't work). This will call attemptStart() at least once. + (function timeoutListener() { + attemptStart(); + + if ( !started ) { + setTimeout( timeoutListener, 1000 ); + } + })(); + + if ( !started ) { + // Attempt to start on window resizes + window.addEventListener( 'resize', function resizeListener() { + attemptStart(); + + if ( started ) { + window.removeEventListener( 'resize', resizeListener ); + } + } ); + } +})();