diff --git a/packages/gems/js/lib/js.rb b/packages/gems/js/lib/js.rb index 3564065f8..116ed69fd 100644 --- a/packages/gems/js/lib/js.rb +++ b/packages/gems/js/lib/js.rb @@ -43,8 +43,10 @@ # end # module JS - Undefined = JS.eval("return undefined") - Null = JS.eval("return null") + # These constants avoid JS.eval so that `require "js"` works under a + # Content-Security-Policy without 'unsafe-eval'. + Undefined = JS.global[:undefined] + Null = JS.global[:JSON].call(:parse, "null") # A boolean value in JavaScript is always a JS::Object instance from Ruby's point of view. # If we use the boolean value returned by a JavaScript function as the condition for an if expression in Ruby, @@ -67,8 +69,8 @@ module JS # if searchParams.has('phrase') == JS::True # ... # end - True = JS.eval("return true;") - False = JS.eval("return false;") + True = true.to_js + False = false.to_js class PromiseScheduler def initialize(loop) diff --git a/packages/gems/js/lib/js/array.rb b/packages/gems/js/lib/js/array.rb index 214bb3cd0..c16c8872c 100644 --- a/packages/gems/js/lib/js/array.rb +++ b/packages/gems/js/lib/js/array.rb @@ -1,7 +1,9 @@ class Array # Convert Ruby array to JavaScript array def to_js - new_array = JS.eval("return []") + # Array() instead of JS.eval, which a CSP without 'unsafe-eval' blocks. + # Not JS.global[:Array].new: JS::Object#new calls Array#to_js. + new_array = JS.global.call(:Array) # NOTE: This method call implicitly convert element to JS object by to_js new_array.push *self new_array diff --git a/packages/gems/js/lib/js/hash.rb b/packages/gems/js/lib/js/hash.rb index 3a0d061bd..5d827a898 100644 --- a/packages/gems/js/lib/js/hash.rb +++ b/packages/gems/js/lib/js/hash.rb @@ -1,7 +1,8 @@ class Hash # Convert a hash to a JavaScript object def to_js - new_object = JS.eval("return {}") + # Object() instead of JS.eval, which a CSP without 'unsafe-eval' blocks. + new_object = JS.global.call(:Object) self.each { |key, value| new_object[key] = value } new_object end diff --git a/packages/npm-packages/ruby-wasm-wasi/test/no_eval.test.js b/packages/npm-packages/ruby-wasm-wasi/test/no_eval.test.js new file mode 100644 index 000000000..88f63d40b --- /dev/null +++ b/packages/npm-packages/ruby-wasm-wasi/test/no_eval.test.js @@ -0,0 +1,22 @@ +import { initRubyVM } from "./init"; +import { describe, test, expect, vi, afterEach } from "vitest" + +// A page whose Content-Security-Policy lacks 'unsafe-eval' blocks Function(), +// which JS.eval uses. Loading the js gem and converting values must not need it. +describe("without Function()", () => { + afterEach(() => vi.unstubAllGlobals()); + + test("require 'js' and to_js work", async () => { + vi.stubGlobal("Function", () => { + throw new EvalError("Function() is blocked"); + }); + const vm = await initRubyVM(); + const result = vm.eval(` + require "js" + blocked = begin; JS.eval("return 1"); false; rescue JS::Error; true; end + [blocked, JS::Undefined.typeof, JS::Null.inspect, JS::True.to_s, JS::False.to_s, + [1, 2].to_js[:length].to_i, { a: 1 }.to_js[:a].to_i].inspect + `); + expect(result.toString()).toBe('[true, "undefined", "null", "true", "false", 2, 1]'); + }); +});