Merge pull request #65975 from Faless/web/4.x_features_detection
[Web] Add feature detection helpers to JS Engine class.
This commit is contained in:
commit
04082597f9
|
@ -259,31 +259,20 @@
|
|||
>Web editor documentation</a> for usage instructions and limitations.
|
||||
</p>
|
||||
</div>
|
||||
<div id="welcome-modal-description-no-cross-origin-isolation" style="display: none">
|
||||
<div id="welcome-modal-missing-description" style="display: none">
|
||||
<p>
|
||||
The web server does not support cross-origin isolation,
|
||||
which is required for the Godot Web Editor to function.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Reasons for cross-origin isolation being disabled:</strong>
|
||||
<ul>
|
||||
<li id="welcome-modal-reason-not-secure">
|
||||
This page is not served from a secure context (HTTPS <i>or</i> localhost).
|
||||
</li>
|
||||
<li>
|
||||
This page may not be served with cross-origin isolation headers
|
||||
(check with the developer tools' Network tab).
|
||||
</li>
|
||||
<strong>The following features required by the Godot Web Editor are missing:</strong>
|
||||
<ul id="welcome-modal-missing-list">
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
If you are self-hosting the web editor,
|
||||
refer to
|
||||
<a
|
||||
href="https://docs.godotengine.org/en/latest/tutorials/export/exporting_for_web.html#threads"
|
||||
href="https://docs.godotengine.org/en/latest/tutorials/export/exporting_for_web.html"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>Exporting for the Web - Threads</a> for more information.
|
||||
>Exporting for the Web</a> for more information.
|
||||
</p>
|
||||
</div>
|
||||
<div style="text-align: center">
|
||||
|
@ -394,16 +383,22 @@
|
|||
});
|
||||
}
|
||||
|
||||
if (!crossOriginIsolated) {
|
||||
const missing = Engine.getMissingFeatures();
|
||||
if (missing.length) {
|
||||
// Display error dialog as threading support is required for the editor.
|
||||
setButtonEnabled('startButton', false);
|
||||
document.getElementById("welcome-modal-description").style.display = "none";
|
||||
document.getElementById("welcome-modal-description-no-cross-origin-isolation").style.display = "block";
|
||||
document.getElementById("welcome-modal-missing-description").style.display = "block";
|
||||
document.getElementById("welcome-modal-dismiss").style.display = "none";
|
||||
document.getElementById("welcome-modal-reason-not-secure").style.display = window.isSecureContext ? "none" : "list-item";
|
||||
const list = document.getElementById("welcome-modal-missing-list");
|
||||
for (let i = 0; i < missing.length; i++) {
|
||||
const node = document.createElement("li");
|
||||
node.innerText = missing[i];
|
||||
list.appendChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
if (!crossOriginIsolated || localStorage.getItem("welcomeModalDismissed") !== 'true') {
|
||||
if (missing.length || localStorage.getItem("welcomeModalDismissed") !== 'true') {
|
||||
document.getElementById("welcome-modal").style.display = "block";
|
||||
document.getElementById("welcome-modal-dismiss").focus();
|
||||
}
|
||||
|
|
|
@ -215,8 +215,10 @@ $GODOT_HEAD_INCLUDE
|
|||
initializing = false;
|
||||
};
|
||||
|
||||
if (!Engine.isWebGLAvailable()) {
|
||||
displayFailureNotice('WebGL not available');
|
||||
const missing = Engine.getMissingFeatures();
|
||||
if (missing.length !== 0) {
|
||||
const missingMsg = 'Warning!\nThe following features required to run Godot projects on the Web are missing:\n';
|
||||
displayFailureNotice(missingMsg + missing.join("\n"));
|
||||
} else {
|
||||
setStatusMode('indeterminate');
|
||||
engine.startGame({
|
||||
|
|
|
@ -5,6 +5,7 @@ module.exports = {
|
|||
"globals": {
|
||||
"InternalConfig": true,
|
||||
"Godot": true,
|
||||
"Features": true,
|
||||
"Preloader": true,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -66,6 +66,7 @@ sys_env.Depends(build[0], sys_env["JS_PRE"])
|
|||
sys_env.Depends(build[0], sys_env["JS_EXTERNS"])
|
||||
|
||||
engine = [
|
||||
"js/engine/features.js",
|
||||
"js/engine/preloader.js",
|
||||
"js/engine/config.js",
|
||||
"js/engine/engine.js",
|
||||
|
|
|
@ -60,20 +60,6 @@ const Engine = (function () {
|
|||
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.
|
||||
* @ignore
|
||||
|
@ -265,14 +251,21 @@ const Engine = (function () {
|
|||
// Also expose static methods as instance methods
|
||||
Engine.prototype['load'] = Engine.load;
|
||||
Engine.prototype['unload'] = Engine.unload;
|
||||
Engine.prototype['isWebGLAvailable'] = Engine.isWebGLAvailable;
|
||||
return new Engine(initConfig);
|
||||
}
|
||||
|
||||
// Closure compiler exported static methods.
|
||||
SafeEngine['load'] = Engine.load;
|
||||
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;
|
||||
}());
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
};
|
|
@ -4,7 +4,7 @@
|
|||
"version": "1.0.0",
|
||||
"description": "Development and linting setup for Godot's Web platform code",
|
||||
"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:engine": "eslint \"js/engine/*.js\" --no-eslintrc -c .eslintrc.engine.js",
|
||||
"lint:libs": "eslint \"js/libs/*.js\" --no-eslintrc -c .eslintrc.libs.js",
|
||||
|
|
Loading…
Reference in New Issue