-
Notifications
You must be signed in to change notification settings - Fork 152
Playground: bundle babylonjs-addons + polyfill ES2019/ES2022 gaps on Chakra #1700
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
42f508b
26f0802
1d64457
037196b
57bceac
b27b3a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| set(SOURCES | ||
| "Include/Babylon/Polyfills/ErrorCause.h" | ||
| "Source/ErrorCause.cpp") | ||
|
|
||
| add_library(ErrorCause ${SOURCES}) | ||
| warnings_as_errors(ErrorCause) | ||
|
|
||
| target_include_directories(ErrorCause PUBLIC "Include") | ||
|
|
||
| target_link_libraries(ErrorCause | ||
| PUBLIC napi | ||
| PUBLIC Foundation) | ||
|
|
||
| set_property(TARGET ErrorCause PROPERTY FOLDER Polyfills) | ||
| source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES}) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| #pragma once | ||
|
|
||
| #include <napi/env.h> | ||
| #include <Babylon/Api.h> | ||
|
|
||
| namespace Babylon::Polyfills::ErrorCause | ||
| { | ||
| // ES2022 Error(message, options) polyfill. | ||
| // | ||
| // Chakra's built-in Error constructor predates ES2022 and treats its second | ||
| // argument as part of the message: `new Error("hello", { cause: 42 })` | ||
| // produces an Error whose `message` is `"[object Object]"` and whose `cause` | ||
| // is undefined. That swallows every Babylon.js shader-compile error message | ||
| // that uses the modern `throw new Error("SHADER ERROR\n" + msg, { cause: e })` | ||
| // pattern, leaving only `Error: [object Object]` in stdout, hiding every | ||
| // glslang / SPIRV-Cross / DXC diagnostic. | ||
| // | ||
| // Initialize() replaces the global Error constructor (and its standard | ||
| // subclasses) with a thin wrapper that forwards `message` correctly and | ||
| // attaches `cause` from the options bag. The shim is a no-op on engines | ||
| // that already implement the ES2022 signature (V8, modern JavaScriptCore), | ||
| // so it is safe to call on every backend. | ||
| void BABYLON_API Initialize(Napi::Env env); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| #include <Babylon/Polyfills/ErrorCause.h> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Drop (sorry — flipping my earlier ask). Either run Playground validation against an ES-compliant engine ( |
||
|
|
||
| #include <napi/napi.h> | ||
|
|
||
| // The N-API JSI variant uses a different entry point for script evaluation | ||
| // (`Napi::Eval(env, source, url)` via <napi/env.h>) than the shared N-API | ||
| // variant (`env.RunScript(source, url)`). Pick the right one at compile time | ||
| // via __has_include so the same source builds against both. | ||
| #if defined(__has_include) && __has_include(<napi/env.h>) | ||
| #include <napi/env.h> | ||
| #define BABYLON_POLYFILL_USE_NAPI_JSI_EVAL 1 | ||
| #endif | ||
|
|
||
| namespace | ||
| { | ||
| // The polyfill is expressed as a self-contained JS IIFE so that it can be | ||
| // injected into any host engine via napi_run_script. Implementing the same | ||
| // semantics directly through the N-API C++ surface is possible but would | ||
| // require manual replication of `new.target` preservation and | ||
| // Reflect.construct semantics for each of the seven standard Error | ||
| // subclasses; the JS form is shorter, easier to review, and identical to | ||
| // what V8 implements natively. | ||
| constexpr const char* kPolyfillScript = R"JS((function () { | ||
| var globalObj = | ||
| (typeof globalThis !== "undefined") ? globalThis : | ||
| (typeof self !== "undefined") ? self : | ||
| (typeof global !== "undefined") ? global : | ||
| (new Function("return this;"))(); | ||
|
|
||
| // If the host already implements ES2022 Error options, do nothing. | ||
| try { | ||
| var probe = new Error("probe", { cause: 42 }); | ||
| if (probe.message === "probe" && probe.cause === 42) { | ||
| return; | ||
| } | ||
| } catch (_) { | ||
| // fall through to patching | ||
| } | ||
|
|
||
| function patchCtor(name) { | ||
| var Orig = globalObj[name]; | ||
| if (typeof Orig !== "function") { | ||
| return; | ||
| } | ||
|
|
||
| var hasReflectConstruct = | ||
| (typeof Reflect !== "undefined") && | ||
| (typeof Reflect.construct === "function"); | ||
|
|
||
| function Patched(message, options) { | ||
| var m; | ||
| if (typeof message === "string") { | ||
| m = message; | ||
| } else if (typeof message === "undefined") { | ||
| m = undefined; | ||
| } else { | ||
| m = String(message); | ||
| } | ||
|
|
||
| var args = (m === undefined) ? [] : [m]; | ||
| // new.target preservation: when invoked via `super(...)` from a | ||
| // subclass we want the new instance's prototype chain to point at | ||
| // the subclass, not at Orig. Reflect.construct does that for us. | ||
| var newTarget = (typeof new.target !== "undefined") ? new.target : Patched; | ||
| var err = hasReflectConstruct | ||
| ? Reflect.construct(Orig, args, newTarget) | ||
| : (m === undefined ? new Orig() : new Orig(m)); | ||
|
|
||
| if (options !== null && typeof options === "object" && "cause" in options) { | ||
| Object.defineProperty(err, "cause", { | ||
| value: options.cause, | ||
| writable: true, | ||
| configurable: true, | ||
| enumerable: false | ||
| }); | ||
| } | ||
| return err; | ||
| } | ||
|
|
||
| Patched.prototype = Orig.prototype; | ||
|
bkaradzic-microsoft marked this conversation as resolved.
|
||
| // Repoint the shared prototype's `constructor` at Patched so | ||
| // `(new Error()).constructor === Error` holds after we reassign | ||
| // the global. `instanceof` already works because the prototype | ||
| // chain is unchanged. | ||
| try { | ||
| Object.defineProperty(Orig.prototype, "constructor", { | ||
| value: Patched, | ||
| writable: true, | ||
| configurable: true | ||
| }); | ||
| } catch (_) { /* frozen prototype on some engines, harmless */ } | ||
| // Inherit Orig's statics (captureStackTrace, stackTraceLimit, any | ||
| // future additions) without having to enumerate them. | ||
| try { | ||
| Object.setPrototypeOf(Patched, Orig); | ||
| } catch (_) { /* engines that disallow proto mutation, harmless */ } | ||
| // Preserve the original name so stack traces still show "Error: ...". | ||
| try { | ||
| Object.defineProperty(Patched, "name", { value: name, configurable: true }); | ||
| } catch (_) { /* non-writable on some engines, harmless */ } | ||
|
|
||
| globalObj[name] = Patched; | ||
| } | ||
|
|
||
| var ctorNames = [ | ||
| "Error", | ||
| "TypeError", | ||
| "RangeError", | ||
| "SyntaxError", | ||
| "ReferenceError", | ||
| "URIError", | ||
| "EvalError" | ||
| ]; | ||
| for (var i = 0; i < ctorNames.length; i++) { | ||
| patchCtor(ctorNames[i]); | ||
| } | ||
| })(); | ||
| )JS"; | ||
| } | ||
|
|
||
| namespace Babylon::Polyfills::ErrorCause | ||
| { | ||
| void BABYLON_API Initialize(Napi::Env env) | ||
| { | ||
| Napi::HandleScope scope{env}; | ||
| #if defined(BABYLON_POLYFILL_USE_NAPI_JSI_EVAL) | ||
| Napi::Eval(env, kPolyfillScript, "Babylon.Polyfills.ErrorCause"); | ||
| #else | ||
| env.RunScript(kPolyfillScript, "Babylon.Polyfills.ErrorCause"); | ||
| #endif | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| set(SOURCES | ||
| "Include/Babylon/Polyfills/StringTrim.h" | ||
| "Source/StringTrim.cpp") | ||
|
|
||
| add_library(StringTrim ${SOURCES}) | ||
| warnings_as_errors(StringTrim) | ||
|
|
||
| target_include_directories(StringTrim PUBLIC "Include") | ||
|
|
||
| target_link_libraries(StringTrim | ||
| PUBLIC napi | ||
| PUBLIC Foundation) | ||
|
|
||
| set_property(TARGET StringTrim PROPERTY FOLDER Polyfills) | ||
| source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES}) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| #pragma once | ||
|
|
||
| #include <napi/env.h> | ||
| #include <Babylon/Api.h> | ||
|
|
||
| namespace Babylon::Polyfills::StringTrim | ||
| { | ||
| // ES2019 String.prototype.trimStart / trimEnd polyfill. | ||
| // | ||
| // Chakra predates ES2019 and only implements the older trimLeft / trimRight | ||
| // names. Babylon.js's ShaderProcessor and NodeMaterial code paths use the | ||
| // newer trimEnd name, so on Chakra-based BN backends those paths fail with | ||
| // `TypeError: Object doesn't support property or method 'trimEnd'`. | ||
| // | ||
| // Initialize() aliases trimStart -> trimLeft and trimEnd -> trimRight when | ||
| // the new names are missing. Both old and new names are now standard | ||
| // aliases in the spec, so engines that already provide trimEnd/trimStart | ||
| // (V8, modern JavaScriptCore) are not touched. | ||
| void BABYLON_API Initialize(Napi::Env env); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| #include <Babylon/Polyfills/StringTrim.h> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Drop (sorry — same as ErrorCause). Switching Playground validation off Chakra makes this unnecessary. |
||
|
|
||
| #include <napi/napi.h> | ||
|
|
||
| // See ErrorCause.cpp for the rationale behind this compile-time switch | ||
| // between the JSI and shared N-API entry points for script evaluation. | ||
| #if defined(__has_include) && __has_include(<napi/env.h>) | ||
| #include <napi/env.h> | ||
| #define BABYLON_POLYFILL_USE_NAPI_JSI_EVAL 1 | ||
| #endif | ||
|
|
||
| namespace | ||
| { | ||
| // Implemented as an injected JS IIFE for symmetry with the ErrorCause | ||
| // polyfill and to keep the shim verbatim-identical to what runs in browser | ||
| // ES2019 environments. | ||
| constexpr const char* kPolyfillScript = R"JS((function () { | ||
| var sp = String.prototype; | ||
| if (typeof sp.trimEnd !== "function" && typeof sp.trimRight === "function") { | ||
| Object.defineProperty(sp, "trimEnd", { | ||
| value: sp.trimRight, | ||
| writable: true, | ||
| configurable: true | ||
| }); | ||
| } | ||
| if (typeof sp.trimStart !== "function" && typeof sp.trimLeft === "function") { | ||
| Object.defineProperty(sp, "trimStart", { | ||
| value: sp.trimLeft, | ||
| writable: true, | ||
| configurable: true | ||
| }); | ||
| } | ||
| })(); | ||
| )JS"; | ||
| } | ||
|
|
||
| namespace Babylon::Polyfills::StringTrim | ||
| { | ||
| void BABYLON_API Initialize(Napi::Env env) | ||
| { | ||
| Napi::HandleScope scope{env}; | ||
| #if defined(BABYLON_POLYFILL_USE_NAPI_JSI_EVAL) | ||
| Napi::Eval(env, kPolyfillScript, "Babylon.Polyfills.StringTrim"); | ||
| #else | ||
| env.RunScript(kPolyfillScript, "Babylon.Polyfills.StringTrim"); | ||
| #endif | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.