Merge pull request #474 from ben-polinsky/csp-no-dynamic-execution

Add opt-in WebGL builds that work under a strict CSP (no `unsafe-eval`)
This commit is contained in:
Rich Geldreich
2026-10-23 14:48:47 -04:00
committed by GitHub
6 changed files with 88 additions and 13 deletions
+2 -1
View File
@@ -11,6 +11,7 @@
# ./build.sh --clean # make clean + wipe CMake cache first (full rebuild)
# ./build.sh --slow # serial build (plain make, one file at a time)
# ./build.sh encoder -DKTX2_ZSTANDARD=OFF -DCMAKE_BUILD_TYPE=Debug
# ./build.sh -DNO_DYNAMIC_EXECUTION=ON # no eval/new Function (strict CSP)
#
# Any -D... argument is forwarded to cmake. Runnable from any directory.
#
@@ -35,7 +36,7 @@ for a in "$@"; do
--slow) SLOW=1 ;;
transcoder|encoder) TARGETS+=("$a") ;;
-D*) CMAKE_ARGS+=("$a") ;;
-h|--help) sed -n '3,15p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
-h|--help) sed -n '3,16p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) die "unknown argument: '$a' (try --help)" ;;
esac
done
+17
View File
@@ -5,8 +5,18 @@ project(basisu_encoder_js)
option(KTX2_ZSTANDARD "Enable KTX2 Zstandard support" TRUE)
option(SUPPORT_ASTCENC "Enable astcenc support" FALSE)
# Build JavaScript glue that never calls eval() or new Function(), so the module
# loads under a Content-Security-Policy without 'unsafe-eval' (OFF by default).
option(NO_DYNAMIC_EXECUTION "Disallow eval()/new Function() in the JS glue" FALSE)
# With NO_DYNAMIC_EXECUTION, generate the embind call wrappers at build time (fast)
# instead of using the slower generic wrappers. No effect unless NO_DYNAMIC_EXECUTION is ON.
option(EMBIND_AOT "Generate embind wrappers at build time" TRUE)
message("KTX2_ZSTANDARD=${KTX2_ZSTANDARD}")
message("SUPPORT_ASTCENC=${SUPPORT_ASTCENC}")
message("NO_DYNAMIC_EXECUTION=${NO_DYNAMIC_EXECUTION}")
message("EMBIND_AOT=${EMBIND_AOT}")
# Only for Emscripten builds.
if(EMSCRIPTEN)
@@ -132,6 +142,13 @@ if(EMSCRIPTEN)
# Base link flags
set(LINK_BASE "--bind -s ALLOW_MEMORY_GROWTH=1 -s INITIAL_MEMORY=134217728 -s STACK_SIZE=2097152 -s MODULARIZE=1 -s EXPORT_NAME=BASIS -s EXPORTED_RUNTIME_METHODS=['HEAP8']")
if(NO_DYNAMIC_EXECUTION)
set(LINK_BASE "${LINK_BASE} -s DYNAMIC_EXECUTION=0")
if(EMBIND_AOT)
set(LINK_BASE "${LINK_BASE} -s EMBIND_AOT=1")
endif()
endif()
set(LINK_THREADS "-s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=32 -s ENVIRONMENT=web,worker")
set(LINK_WASM64 "-s MEMORY64=1 -sWASM_BIGINT=1 --profiling-funcs")
+19
View File
@@ -5,3 +5,22 @@ cd webgl/encoder/build/
emcmake cmake ../
make
```
### Strict Content-Security-Policy (no `unsafe-eval`)
By default, the generated JavaScript glue uses `new Function()` to build its
embind bindings at runtime. A Content-Security-Policy without `'unsafe-eval'`
blocks this, and module startup fails with an `EvalError`. To build glue that
never calls `eval()` or `new Function()`, add `-DNO_DYNAMIC_EXECUTION=ON`:
```shell
emcmake cmake ../ -DNO_DYNAMIC_EXECUTION=ON
make
```
This adds `-s DYNAMIC_EXECUTION=0 -s EMBIND_AOT=1` to the link flags. The
`.wasm` file does not change. `EMBIND_AOT` generates the bindings at build
time, so JavaScript-to-WASM calls stay as fast as in the default build. The
page still needs `'wasm-unsafe-eval'` in `script-src` to compile the
WebAssembly module. Use `-DEMBIND_AOT=OFF` to get the smaller, generic
bindings, which are slower per call.
+20 -1
View File
@@ -10,8 +10,18 @@ option(KTX2 "KTX2" TRUE)
# If you know you'll never be transcoding KTX2 files using this feature you can set KTX2_ZSTANDARD to 0.
option(KTX2_ZSTANDARD "KTX2_ZSTANDARD" TRUE)
# Use "cmake -DNO_DYNAMIC_EXECUTION=TRUE" to build JavaScript glue that never calls eval() or new Function().
# This lets the module load under a Content-Security-Policy without 'unsafe-eval'.
option(NO_DYNAMIC_EXECUTION "NO_DYNAMIC_EXECUTION" FALSE)
# With NO_DYNAMIC_EXECUTION, generate the embind call wrappers at build time (fast) instead of
# using the slower generic wrappers. Only has an effect when NO_DYNAMIC_EXECUTION is TRUE.
option(EMBIND_AOT "EMBIND_AOT" TRUE)
message("KTX2=${KTX2}")
message("KTX2_ZSTANDARD=${KTX2_ZSTANDARD}")
message("NO_DYNAMIC_EXECUTION=${NO_DYNAMIC_EXECUTION}")
message("EMBIND_AOT=${EMBIND_AOT}")
if (EMSCRIPTEN)
set(CMAKE_CXX_STANDARD 17)
@@ -61,8 +71,17 @@ if (EMSCRIPTEN)
target_include_directories(basis_transcoder.js PRIVATE ../../transcoder)
set(TRANSCODER_LINK_FLAGS "--bind -s ALLOW_MEMORY_GROWTH=1 -O3 -s MALLOC=emmalloc -s MODULARIZE=1 -s EXPORT_NAME=BASIS -s ASSERTIONS=0 -s EXPORTED_RUNTIME_METHODS=['HEAP8']")
if (NO_DYNAMIC_EXECUTION)
set(TRANSCODER_LINK_FLAGS "${TRANSCODER_LINK_FLAGS} -s DYNAMIC_EXECUTION=0")
if (EMBIND_AOT)
set(TRANSCODER_LINK_FLAGS "${TRANSCODER_LINK_FLAGS} -s EMBIND_AOT=1")
endif()
endif()
set_target_properties(basis_transcoder.js PROPERTIES
OUTPUT_NAME "basis_transcoder"
SUFFIX ".js"
LINK_FLAGS "--bind -s ALLOW_MEMORY_GROWTH=1 -O3 -s MALLOC=emmalloc -s MODULARIZE=1 -s EXPORT_NAME=BASIS -s ASSERTIONS=0 -s EXPORTED_RUNTIME_METHODS=['HEAP8']")
LINK_FLAGS "${TRANSCODER_LINK_FLAGS}")
endif()
+19
View File
@@ -7,3 +7,22 @@ cd webgl/transcoder/build/
emcmake cmake ../
make
```
### Strict Content-Security-Policy (no `unsafe-eval`)
By default, the generated JavaScript glue uses `new Function()` to build its
embind bindings at runtime. A Content-Security-Policy without `'unsafe-eval'`
blocks this, and module startup fails with an `EvalError`. To build glue that
never calls `eval()` or `new Function()`, add `-DNO_DYNAMIC_EXECUTION=ON`:
```shell
emcmake cmake ../ -DNO_DYNAMIC_EXECUTION=ON
make
```
This adds `-s DYNAMIC_EXECUTION=0 -s EMBIND_AOT=1` to the link flags. The
`.wasm` file does not change. `EMBIND_AOT` generates the bindings at build
time, so JavaScript-to-WASM calls stay as fast as in the default build. The
page still needs `'wasm-unsafe-eval'` in `script-src` to compile the
WebAssembly module. Use `-DEMBIND_AOT=OFF` to get the smaller, generic
bindings, which are slower per call.
+11 -11
View File
@@ -3347,17 +3347,17 @@ EMSCRIPTEN_BINDINGS(basis_codec) {
constant("ASTC_HDR_MAX_VAL", basist::ASTC_HDR_MAX_VAL);
// UASTC LDR/HDR flags/options
constant("cPackUASTCLevelFastest", cPackUASTCLevelFastest);
constant("cPackUASTCLevelFaster", cPackUASTCLevelFaster);
constant("cPackUASTCLevelDefault", cPackUASTCLevelDefault);
constant("cPackUASTCLevelSlower", cPackUASTCLevelSlower);
constant("cPackUASTCLevelVerySlow", cPackUASTCLevelVerySlow);
constant("cPackUASTCLevelMask", cPackUASTCLevelMask);
constant("cPackUASTCFavorUASTCError", cPackUASTCFavorUASTCError);
constant("cPackUASTCFavorBC7Error", cPackUASTCFavorBC7Error);
constant("cPackUASTCETC1FasterHints", cPackUASTCETC1FasterHints);
constant("cPackUASTCETC1FastestHints", cPackUASTCETC1FastestHints);
constant("cPackUASTCETC1DisableFlipAndIndividual", cPackUASTCETC1DisableFlipAndIndividual);
constant("cPackUASTCLevelFastest", static_cast<uint32_t>(cPackUASTCLevelFastest));
constant("cPackUASTCLevelFaster", static_cast<uint32_t>(cPackUASTCLevelFaster));
constant("cPackUASTCLevelDefault", static_cast<uint32_t>(cPackUASTCLevelDefault));
constant("cPackUASTCLevelSlower", static_cast<uint32_t>(cPackUASTCLevelSlower));
constant("cPackUASTCLevelVerySlow", static_cast<uint32_t>(cPackUASTCLevelVerySlow));
constant("cPackUASTCLevelMask", static_cast<uint32_t>(cPackUASTCLevelMask));
constant("cPackUASTCFavorUASTCError", static_cast<uint32_t>(cPackUASTCFavorUASTCError));
constant("cPackUASTCFavorBC7Error", static_cast<uint32_t>(cPackUASTCFavorBC7Error));
constant("cPackUASTCETC1FasterHints", static_cast<uint32_t>(cPackUASTCETC1FasterHints));
constant("cPackUASTCETC1FastestHints", static_cast<uint32_t>(cPackUASTCETC1FastestHints));
constant("cPackUASTCETC1DisableFlipAndIndividual", static_cast<uint32_t>(cPackUASTCETC1DisableFlipAndIndividual));
constant("UASTC_RDO_DEFAULT_MAX_ALLOWED_RMS_INCREASE_RATIO", UASTC_RDO_DEFAULT_MAX_ALLOWED_RMS_INCREASE_RATIO);
constant("UASTC_RDO_DEFAULT_SKIP_BLOCK_RMS_THRESH", UASTC_RDO_DEFAULT_SKIP_BLOCK_RMS_THRESH);