From 49b92c1e7602c2de900ede0e1b59cb97003899b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 4 Jul 2024 23:13:11 +0100 Subject: [PATCH] feat: Add Source Maps APIs (#1514) This commit adds bindings for: - v8::UnboundScript::get_source_mapping_url - v8::UnboundScript::get_source_url - v8::UnboundModuleScript::get_source_mapping_url - v8::UnboundModuleScript::get_source_url --- src/binding.cc | 21 ++++ src/unbound_module_script.rs | 33 ++++++ src/unbound_script.rs | 36 +++++- tests/test_api.rs | 205 ++++++++++++++++++++++++++++++++++- 4 files changed, 293 insertions(+), 2 deletions(-) diff --git a/src/binding.cc b/src/binding.cc index 273bc3ca..f4c2fd2b 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -2521,12 +2521,33 @@ v8::ScriptCompiler::CachedData* v8__UnboundScript__CreateCodeCache( return v8::ScriptCompiler::CreateCodeCache(ptr_to_local(&unbound_script)); } +v8::Value* v8__UnboundScript__GetSourceMappingURL( + const v8::UnboundScript& unbound_script) { + return local_to_ptr(ptr_to_local(&unbound_script)->GetSourceMappingURL()); +} + +v8::Value* v8__UnboundScript__GetSourceURL( + const v8::UnboundScript& unbound_script) { + return local_to_ptr(ptr_to_local(&unbound_script)->GetSourceURL()); +} + v8::ScriptCompiler::CachedData* v8__UnboundModuleScript__CreateCodeCache( const v8::UnboundModuleScript& unbound_module_script) { return v8::ScriptCompiler::CreateCodeCache( ptr_to_local(&unbound_module_script)); } +v8::Value* v8__UnboundModuleScript__GetSourceMappingURL( + const v8::UnboundModuleScript& unbound_module_script) { + return local_to_ptr( + ptr_to_local(&unbound_module_script)->GetSourceMappingURL()); +} + +v8::Value* v8__UnboundModuleScript__GetSourceURL( + const v8::UnboundModuleScript& unbound_module_script) { + return local_to_ptr(ptr_to_local(&unbound_module_script)->GetSourceURL()); +} + v8::ScriptCompiler::CachedData* v8__Function__CreateCodeCache( const v8::Function& self) { return v8::ScriptCompiler::CreateCodeCacheForFunction(ptr_to_local(&self)); diff --git a/src/unbound_module_script.rs b/src/unbound_module_script.rs index f1055cec..e52d4554 100644 --- a/src/unbound_module_script.rs +++ b/src/unbound_module_script.rs @@ -1,11 +1,22 @@ use crate::CachedData; +use crate::HandleScope; +use crate::Local; use crate::UnboundModuleScript; use crate::UniqueRef; +use crate::Value; extern "C" { fn v8__UnboundModuleScript__CreateCodeCache( script: *const UnboundModuleScript, ) -> *mut CachedData<'static>; + + fn v8__UnboundModuleScript__GetSourceMappingURL( + script: *const UnboundModuleScript, + ) -> *const Value; + + fn v8__UnboundModuleScript__GetSourceURL( + script: *const UnboundModuleScript, + ) -> *const Value; } impl UnboundModuleScript { @@ -25,4 +36,26 @@ impl UnboundModuleScript { } code_cache } + + pub fn get_source_mapping_url<'s>( + &self, + scope: &mut HandleScope<'s>, + ) -> Local<'s, Value> { + unsafe { + scope + .cast_local(|_| v8__UnboundModuleScript__GetSourceMappingURL(self)) + .unwrap() + } + } + + pub fn get_source_url<'s>( + &self, + scope: &mut HandleScope<'s>, + ) -> Local<'s, Value> { + unsafe { + scope + .cast_local(|_| v8__UnboundModuleScript__GetSourceURL(self)) + .unwrap() + } + } } diff --git a/src/unbound_script.rs b/src/unbound_script.rs index 3211abe4..be4b4c2f 100644 --- a/src/unbound_script.rs +++ b/src/unbound_script.rs @@ -1,8 +1,10 @@ use crate::CachedData; +use crate::HandleScope; use crate::Local; use crate::Script; use crate::UnboundScript; -use crate::{HandleScope, UniqueRef}; +use crate::UniqueRef; +use crate::Value; extern "C" { fn v8__UnboundScript__BindToCurrentContext( @@ -11,6 +13,14 @@ extern "C" { fn v8__UnboundScript__CreateCodeCache( script: *const UnboundScript, ) -> *mut CachedData<'static>; + + fn v8__UnboundScript__GetSourceMappingURL( + script: *const UnboundScript, + ) -> *const Value; + + fn v8__UnboundScript__GetSourceURL( + script: *const UnboundScript, + ) -> *const Value; } impl UnboundScript { @@ -42,4 +52,28 @@ impl UnboundScript { } code_cache } + + #[inline(always)] + pub fn get_source_mapping_url<'s>( + &self, + scope: &mut HandleScope<'s>, + ) -> Local<'s, Value> { + unsafe { + scope + .cast_local(|_| v8__UnboundScript__GetSourceMappingURL(self)) + .unwrap() + } + } + + #[inline(always)] + pub fn get_source_url<'s>( + &self, + scope: &mut HandleScope<'s>, + ) -> Local<'s, Value> { + unsafe { + scope + .cast_local(|_| v8__UnboundScript__GetSourceURL(self)) + .unwrap() + } + } } diff --git a/tests/test_api.rs b/tests/test_api.rs index 1079c25e..837cee1f 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -8595,7 +8595,11 @@ fn unbound_script_conversion() { let unbound_script = { let context = v8::Context::new(scope); let scope = &mut v8::ContextScope::new(scope, context); - let source = v8::String::new(scope, "'Hello ' + value").unwrap(); + let source = v8::String::new( + scope, + "'Hello ' + value\n//# sourceMappingURL=foo.js.map", + ) + .unwrap(); let script = v8::Script::compile(scope, source, None).unwrap(); script.get_unbound_script(scope) }; @@ -8615,6 +8619,205 @@ fn unbound_script_conversion() { } } +#[test] +fn get_source_mapping_from_comment() { + let _setup_guard = setup::parallel_test(); + let isolate = &mut v8::Isolate::new(Default::default()); + let scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + + // TODO(bartlomieju): add a shorter version of `v8::ScriptOrigin` + let resource_name = + v8::String::new(scope, "http://www.foo.com/foo.js").unwrap(); + let resource_line_offset = 0; + let resource_column_offset = 0; + let resource_is_shared_cross_origin = false; + let script_id = -1; + let source_map_url = v8::undefined(scope); + let resource_is_opaque = false; + let is_wasm = false; + let is_module = false; + + let script_origin = v8::ScriptOrigin::new( + scope, + resource_name.into(), + resource_line_offset, + resource_column_offset, + resource_is_shared_cross_origin, + script_id, + source_map_url.into(), + resource_is_opaque, + is_wasm, + is_module, + ); + let code = + v8::String::new(scope, "var foo;\n//# sourceMappingURL=foo.js.map") + .unwrap(); + let mut source = v8::script_compiler::Source::new(code, Some(&script_origin)); + let script = v8::script_compiler::compile( + scope, + &mut source, + v8::script_compiler::CompileOptions::EagerCompile, + v8::script_compiler::NoCacheReason::NoReason, + ) + .unwrap(); + let source_mapping_url = script + .get_unbound_script(scope) + .get_source_mapping_url(scope) + .to_rust_string_lossy(scope); + assert_eq!("foo.js.map", source_mapping_url) +} + +#[test] +fn origin_source_map_overrides_source_mapping_url_comment() { + let _setup_guard = setup::parallel_test(); + let isolate = &mut v8::Isolate::new(Default::default()); + let scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + + let expected_source_map_url = "http://override/foo.js.map"; + // TODO(bartlomieju): add a shorter version of `v8::ScriptOrigin` + let resource_name = + v8::String::new(scope, "http://www.foo.com/foo.js").unwrap(); + let resource_line_offset = 13; + let resource_column_offset = 0; + let resource_is_shared_cross_origin = false; + let script_id = -1; + let source_map_url = v8::String::new(scope, expected_source_map_url).unwrap(); + let resource_is_opaque = false; + let is_wasm = false; + let is_module = false; + + let script_origin = v8::ScriptOrigin::new( + scope, + resource_name.into(), + resource_line_offset, + resource_column_offset, + resource_is_shared_cross_origin, + script_id, + source_map_url.into(), + resource_is_opaque, + is_wasm, + is_module, + ); + let code = + v8::String::new(scope, "var foo;\n//# sourceMappingURL=foo.js.map") + .unwrap(); + let mut source = v8::script_compiler::Source::new(code, Some(&script_origin)); + let script = v8::script_compiler::compile( + scope, + &mut source, + v8::script_compiler::CompileOptions::EagerCompile, + v8::script_compiler::NoCacheReason::NoReason, + ) + .unwrap(); + let source_mapping_url = script + .get_unbound_script(scope) + .get_source_mapping_url(scope) + .to_rust_string_lossy(scope); + assert_eq!(expected_source_map_url, source_mapping_url) +} + +#[test] +fn ignore_origin_source_map_empty_string() { + let _setup_guard = setup::parallel_test(); + let isolate = &mut v8::Isolate::new(Default::default()); + let scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + + // TODO(bartlomieju): add a shorter version of `v8::ScriptOrigin` + let resource_name = + v8::String::new(scope, "http://www.foo.com/foo.js").unwrap(); + let resource_line_offset = 0; + let resource_column_offset = 0; + let resource_is_shared_cross_origin = false; + let script_id = -1; + let source_map_url = v8::String::new(scope, "").unwrap(); + let resource_is_opaque = false; + let is_wasm = false; + let is_module = false; + + let script_origin = v8::ScriptOrigin::new( + scope, + resource_name.into(), + resource_line_offset, + resource_column_offset, + resource_is_shared_cross_origin, + script_id, + source_map_url.into(), + resource_is_opaque, + is_wasm, + is_module, + ); + let code = + v8::String::new(scope, "var foo;\n//# sourceMappingURL=foo.js.map") + .unwrap(); + let mut source = v8::script_compiler::Source::new(code, Some(&script_origin)); + let script = v8::script_compiler::compile( + scope, + &mut source, + v8::script_compiler::CompileOptions::EagerCompile, + v8::script_compiler::NoCacheReason::NoReason, + ) + .unwrap(); + let source_mapping_url = script + .get_unbound_script(scope) + .get_source_mapping_url(scope) + .to_rust_string_lossy(scope); + assert_eq!("foo.js.map", source_mapping_url) +} + +#[test] +fn no_source_map_comment() { + let _setup_guard = setup::parallel_test(); + let isolate = &mut v8::Isolate::new(Default::default()); + let scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + + // TODO(bartlomieju): add a shorter version of `v8::ScriptOrigin` + let resource_name = + v8::String::new(scope, "http://www.foo.com/foo.js").unwrap(); + let resource_line_offset = 0; + let resource_column_offset = 0; + let resource_is_shared_cross_origin = false; + let script_id = -1; + let source_map_url = v8::undefined(scope); + let resource_is_opaque = false; + let is_wasm = false; + let is_module = false; + + let script_origin = v8::ScriptOrigin::new( + scope, + resource_name.into(), + resource_line_offset, + resource_column_offset, + resource_is_shared_cross_origin, + script_id, + source_map_url.into(), + resource_is_opaque, + is_wasm, + is_module, + ); + let code = v8::String::new(scope, "var foo;\n").unwrap(); + let mut source = v8::script_compiler::Source::new(code, Some(&script_origin)); + let script = v8::script_compiler::compile( + scope, + &mut source, + v8::script_compiler::CompileOptions::EagerCompile, + v8::script_compiler::NoCacheReason::NoReason, + ) + .unwrap(); + let source_mapping_url = script + .get_unbound_script(scope) + .get_source_mapping_url(scope) + .to_rust_string_lossy(scope); + assert_eq!("undefined", source_mapping_url) +} + #[test] fn ept_torture_test() { let _setup_guard = setup::parallel_test();