Browse Source

[Web] Add feature detection helpers to JS Engine class.

Fabio Alessandrelli 2 years ago
parent
commit
6bbde346ab

+ 1 - 0
platform/web/.eslintrc.engine.js

@@ -5,6 +5,7 @@ module.exports = {
 	"globals": {
 	"globals": {
 		"InternalConfig": true,
 		"InternalConfig": true,
 		"Godot": true,
 		"Godot": true,
+		"Features": true,
 		"Preloader": true,
 		"Preloader": true,
 	},
 	},
 };
 };

+ 1 - 0
platform/web/SCsub

@@ -66,6 +66,7 @@ sys_env.Depends(build[0], sys_env["JS_PRE"])
 sys_env.Depends(build[0], sys_env["JS_EXTERNS"])
 sys_env.Depends(build[0], sys_env["JS_EXTERNS"])
 
 
 engine = [
 engine = [
+    "js/engine/features.js",
     "js/engine/preloader.js",
     "js/engine/preloader.js",
     "js/engine/config.js",
     "js/engine/config.js",
     "js/engine/engine.js",
     "js/engine/engine.js",

+ 9 - 16
platform/web/js/engine/engine.js

@@ -60,20 +60,6 @@ const Engine = (function () {
 		loadPromise = null;
 		loadPromise = null;
 	};
 	};
 
 
-	/**
-	 * Check whether WebGL is available. Optionally, specify a particular version of WebGL to check for.
-	 *
-	 * @param {number=} [majorVersion=1] The major WebGL version to check for.
-	 * @returns {boolean} If the given major version of WebGL is available.
-	 * @function Engine.isWebGLAvailable
-	 */
-	Engine.isWebGLAvailable = function (majorVersion = 1) {
-		try {
-			return !!document.createElement('canvas').getContext(['webgl', 'webgl2'][majorVersion - 1]);
-		} catch (e) { /* Not available */ }
-		return false;
-	};
-
 	/**
 	/**
 	 * Safe Engine constructor, creates a new prototype for every new instance to avoid prototype pollution.
 	 * Safe Engine constructor, creates a new prototype for every new instance to avoid prototype pollution.
 	 * @ignore
 	 * @ignore
@@ -265,14 +251,21 @@ const Engine = (function () {
 		// Also expose static methods as instance methods
 		// Also expose static methods as instance methods
 		Engine.prototype['load'] = Engine.load;
 		Engine.prototype['load'] = Engine.load;
 		Engine.prototype['unload'] = Engine.unload;
 		Engine.prototype['unload'] = Engine.unload;
-		Engine.prototype['isWebGLAvailable'] = Engine.isWebGLAvailable;
 		return new Engine(initConfig);
 		return new Engine(initConfig);
 	}
 	}
 
 
 	// Closure compiler exported static methods.
 	// Closure compiler exported static methods.
 	SafeEngine['load'] = Engine.load;
 	SafeEngine['load'] = Engine.load;
 	SafeEngine['unload'] = Engine.unload;
 	SafeEngine['unload'] = Engine.unload;
-	SafeEngine['isWebGLAvailable'] = Engine.isWebGLAvailable;
+
+	// Feature-detection utilities.
+	SafeEngine['isWebGLAvailable'] = Features.isWebGLAvailable;
+	SafeEngine['isFetchAvailable'] = Features.isFetchAvailable;
+	SafeEngine['isSecureContext'] = Features.isSecureContext;
+	SafeEngine['isCrossOriginIsolated'] = Features.isCrossOriginIsolated;
+	SafeEngine['isSharedArrayBufferAvailable'] = Features.isSharedArrayBufferAvailable;
+	SafeEngine['isAudioWorkletAvailable'] = Features.isAudioWorkletAvailable;
+	SafeEngine['getMissingFeatures'] = Features.getMissingFeatures;
 
 
 	return SafeEngine;
 	return SafeEngine;
 }());
 }());

+ 96 - 0
platform/web/js/engine/features.js

@@ -0,0 +1,96 @@
+const Features = { // eslint-disable-line no-unused-vars
+	/**
+	 * Check whether WebGL is available. Optionally, specify a particular version of WebGL to check for.
+	 *
+	 * @param {number=} [majorVersion=1] The major WebGL version to check for.
+	 * @returns {boolean} If the given major version of WebGL is available.
+	 * @function Engine.isWebGLAvailable
+	 */
+	isWebGLAvailable: function (majorVersion = 1) {
+		try {
+			return !!document.createElement('canvas').getContext(['webgl', 'webgl2'][majorVersion - 1]);
+		} catch (e) { /* Not available */ }
+		return false;
+	},
+
+	/**
+	 * Check whether the Fetch API available and supports streaming responses.
+	 *
+	 * @returns {boolean} If the Fetch API is available and supports streaming responses.
+	 * @function Engine.isFetchAvailable
+	 */
+	isFetchAvailable: function () {
+		return 'fetch' in window && 'Response' in window && 'body' in window.Response.prototype;
+	},
+
+	/**
+	 * Check whether the engine is running in a Secure Context.
+	 *
+	 * @returns {boolean} If the engine is running in a Secure Context.
+	 * @function Engine.isSecureContext
+	 */
+	isSecureContext: function () {
+		return window['isSecureContext'] === true;
+	},
+
+	/**
+	 * Check whether the engine is cross origin isolated.
+	 * This value is dependent on Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers sent by the server.
+	 *
+	 * @returns {boolean} If the engine is running in a Secure Context.
+	 * @function Engine.isSecureContext
+	 */
+	isCrossOriginIsolated: function () {
+		return window['crossOriginIsolated'] === true;
+	},
+
+	/**
+	 * Check whether SharedBufferArray is available.
+	 *
+	 * Most browsers require the page to be running in a secure context, and the
+	 * the server to provide specific CORS headers for SharedArrayBuffer to be available.
+	 *
+	 * @returns {boolean} If SharedArrayBuffer is available.
+	 * @function Engine.isSharedArrayBufferAvailable
+	 */
+	isSharedArrayBufferAvailable: function () {
+		return 'SharedArrayBuffer' in window;
+	},
+
+	/**
+	 * Check whether the AudioContext supports AudioWorkletNodes.
+	 *
+	 * @returns {boolean} If AudioWorkletNode is available.
+	 * @function Engine.isAudioWorkletAvailable
+	 */
+	isAudioWorkletAvailable: function () {
+		return 'AudioContext' in window && 'audioWorklet' in AudioContext.prototype;
+	},
+
+	/**
+	 * Return an array of missing required features (as string).
+	 *
+	 * @returns {Array<string>} A list of human-readable missing features.
+	 * @function Engine.getMissingFeatures
+	 */
+	getMissingFeatures: function () {
+		const missing = [];
+		if (!Features.isWebGLAvailable(2)) {
+			missing.push('WebGL2');
+		}
+		if (!Features.isFetchAvailable()) {
+			missing.push('Fetch');
+		}
+		if (!Features.isSecureContext()) {
+			missing.push('Secure Context');
+		}
+		if (!Features.isCrossOriginIsolated()) {
+			missing.push('Cross Origin Isolation');
+		}
+		if (!Features.isSharedArrayBufferAvailable()) {
+			missing.push('SharedArrayBuffer');
+		}
+		// Audio is normally optional since we have a dummy fallback.
+		return missing;
+	},
+};

+ 1 - 1
platform/web/package.json

@@ -4,7 +4,7 @@
   "version": "1.0.0",
   "version": "1.0.0",
   "description": "Development and linting setup for Godot's Web platform code",
   "description": "Development and linting setup for Godot's Web platform code",
   "scripts": {
   "scripts": {
-    "docs": "jsdoc --template js/jsdoc2rst/ js/engine/engine.js js/engine/config.js --destination ''",
+    "docs": "jsdoc --template js/jsdoc2rst/ js/engine/engine.js js/engine/config.js js/engine/features.js --destination ''",
     "lint": "npm run lint:engine && npm run lint:libs && npm run lint:modules && npm run lint:tools",
     "lint": "npm run lint:engine && npm run lint:libs && npm run lint:modules && npm run lint:tools",
     "lint:engine": "eslint \"js/engine/*.js\" --no-eslintrc -c .eslintrc.engine.js",
     "lint:engine": "eslint \"js/engine/*.js\" --no-eslintrc -c .eslintrc.engine.js",
     "lint:libs": "eslint \"js/libs/*.js\" --no-eslintrc -c .eslintrc.libs.js",
     "lint:libs": "eslint \"js/libs/*.js\" --no-eslintrc -c .eslintrc.libs.js",