Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions packages/gems/js/lib/js.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion packages/gems/js/lib/js/array.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 2 additions & 1 deletion packages/gems/js/lib/js/hash.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down
22 changes: 22 additions & 0 deletions packages/npm-packages/ruby-wasm-wasi/test/no_eval.test.js
Original file line number Diff line number Diff line change
@@ -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]');
});
});