diff --git a/components/devtools/actors/source.rs b/components/devtools/actors/source.rs index 487f34cef3d..7f26f5e979d 100644 --- a/components/devtools/actors/source.rs +++ b/components/devtools/actors/source.rs @@ -27,6 +27,8 @@ pub(crate) struct SourceForm { /// URL of the script, or URL of the page for inline scripts. pub url: String, pub is_black_boxed: bool, + /// `introductionType` in SpiderMonkey `CompileOptionsWrapper`. + pub introduction_type: String, } #[derive(Serialize)] @@ -53,6 +55,9 @@ pub struct SourceActor { pub content: Option, pub content_type: Option, + + /// `introductionType` in SpiderMonkey `CompileOptionsWrapper`. + pub introduction_type: String, } #[derive(Serialize)] @@ -91,6 +96,7 @@ impl SourceActor { url: ServoUrl, content: Option, content_type: Option, + introduction_type: String, ) -> SourceActor { SourceActor { name, @@ -98,6 +104,7 @@ impl SourceActor { content, content_type, is_black_boxed: false, + introduction_type, } } @@ -107,10 +114,17 @@ impl SourceActor { url: ServoUrl, content: Option, content_type: Option, + introduction_type: String, ) -> &SourceActor { let source_actor_name = actors.new_name("source"); - let source_actor = SourceActor::new(source_actor_name.clone(), url, content, content_type); + let source_actor = SourceActor::new( + source_actor_name.clone(), + url, + content, + content_type, + introduction_type, + ); actors.register(Box::new(source_actor)); actors.register_source_actor(pipeline_id, &source_actor_name); @@ -122,6 +136,7 @@ impl SourceActor { actor: self.name.clone(), url: self.url.to_string(), is_black_boxed: self.is_black_boxed, + introduction_type: self.introduction_type.clone(), } } } diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs index 03cba49a516..93bb7b18d22 100644 --- a/components/devtools/lib.rs +++ b/components/devtools/lib.rs @@ -552,6 +552,7 @@ impl DevtoolsInstance { source_info.url, source_content, source_info.content_type, + source_info.introduction_type, ); let source_actor_name = source_actor.name.clone(); let source_form = source_actor.source_form(); diff --git a/components/script/dom/dedicatedworkerglobalscope.rs b/components/script/dom/dedicatedworkerglobalscope.rs index 30404d23757..b96e3174e46 100644 --- a/components/script/dom/dedicatedworkerglobalscope.rs +++ b/components/script/dom/dedicatedworkerglobalscope.rs @@ -59,7 +59,9 @@ use crate::fetch::{CspViolationsProcessor, load_whole_resource}; use crate::messaging::{CommonScriptMsg, ScriptEventLoopReceiver, ScriptEventLoopSender}; use crate::realms::{AlreadyInRealm, InRealm, enter_realm}; use crate::script_runtime::ScriptThreadEventCategory::WorkerEvent; -use crate::script_runtime::{CanGc, JSContext as SafeJSContext, Runtime, ThreadSafeJSContext}; +use crate::script_runtime::{ + CanGc, IntroductionType, JSContext as SafeJSContext, Runtime, ThreadSafeJSContext, +}; use crate::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue}; use crate::task_source::{SendableTaskSource, TaskSourceName}; @@ -517,6 +519,10 @@ impl DedicatedWorkerGlobalScope { let pipeline_id = global_scope.pipeline_id(); let source_info = SourceInfo { url: metadata.final_url, + introduction_type: IntroductionType::WORKER + .to_str() + .expect("Guaranteed by definition") + .to_owned(), external: true, // Worker scripts are always external. worker_id: Some(global.upcast::().get_worker_id()), content: Some(source.to_string()), diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index 752434e1987..17b255b492c 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -1095,14 +1095,23 @@ impl HTMLScriptElement { if let Some(chan) = self.global().devtools_chan() { let pipeline_id = self.global().pipeline_id(); - let (url, content, content_type, is_external) = if script.external { + let (url, content, content_type, introduction_type, is_external) = if script.external { let content = match &script.code { SourceCode::Text(text) => text.to_string(), SourceCode::Compiled(compiled) => compiled.original_text.to_string(), }; // content_type: https://html.spec.whatwg.org/multipage/#scriptingLanguages - (script.url.clone(), Some(content), "text/javascript", true) + ( + script.url.clone(), + Some(content), + "text/javascript", + IntroductionType::SRC_SCRIPT + .to_str() + .expect("Guaranteed by definition") + .to_owned(), + true, + ) } else { // TODO: if needed, fetch the page again, in the same way as in the original request. // Fetch it from cache, even if the original request was non-idempotent (e.g. POST). @@ -1110,11 +1119,21 @@ impl HTMLScriptElement { // fetch, the server could return a different response. // TODO: handle cases where Content-Type is not text/html. - (doc.url(), None, "text/html", false) + ( + doc.url(), + None, + "text/html", + IntroductionType::INLINE_SCRIPT + .to_str() + .expect("Guaranteed by definition") + .to_owned(), + false, + ) }; let source_info = SourceInfo { url, + introduction_type: introduction_type.to_owned(), external: is_external, worker_id: None, content, diff --git a/components/script/dom/workerglobalscope.rs b/components/script/dom/workerglobalscope.rs index f338439bcf6..dd1e17cbd83 100644 --- a/components/script/dom/workerglobalscope.rs +++ b/components/script/dom/workerglobalscope.rs @@ -72,7 +72,7 @@ use crate::dom::workernavigator::WorkerNavigator; use crate::fetch::{CspViolationsProcessor, Fetch, load_whole_resource}; use crate::messaging::{CommonScriptMsg, ScriptEventLoopReceiver, ScriptEventLoopSender}; use crate::realms::{InRealm, enter_realm}; -use crate::script_runtime::{CanGc, JSContext, JSContextHelper, Runtime}; +use crate::script_runtime::{CanGc, IntroductionType, JSContext, JSContextHelper, Runtime}; use crate::task::TaskCanceller; use crate::timers::{IsInterval, TimerCallback}; @@ -644,12 +644,13 @@ impl WorkerGlobalScope { let _aes = AutoEntryScript::new(self.upcast()); let cx = self.runtime.borrow().as_ref().unwrap().cx(); rooted!(in(cx) let mut rval = UndefinedValue()); - let options = self + let mut options = self .runtime .borrow() .as_ref() .unwrap() .new_compile_options(self.worker_url.borrow().as_str(), 1); + options.set_introduction_type(IntroductionType::WORKER); match self.runtime.borrow().as_ref().unwrap().evaluate_script( self.reflector().get_jsobject(), &source, diff --git a/components/script/script_runtime.rs b/components/script/script_runtime.rs index 395dd24e3ea..4f5699e335c 100644 --- a/components/script/script_runtime.rs +++ b/components/script/script_runtime.rs @@ -1249,7 +1249,21 @@ impl Runnable { pub(crate) use script_bindings::script_runtime::CanGc; /// `introductionType` values in SpiderMonkey TransitiveCompileOptions. +/// +/// Value definitions are based on the SpiderMonkey Debugger API docs: +/// +// TODO: squish `scriptElement` pub(crate) struct IntroductionType; impl IntroductionType { - pub const INLINE_SCRIPT: &'static CStr = c"inlineScript"; + /// `introductionType` for code belonging to `` elements. + /// This includes `") - self.assert_sources_list(1, set([tuple()])) + self.assert_sources_list(set([tuple()])) def test_sources_list_with_data_inline_classic_script(self): self.run_servoshell(url="data:text/html,") - self.assert_sources_list(1, set([tuple(["data:text/html,"])])) + self.assert_sources_list(set([tuple([Source("inlineScript", "data:text/html,")])])) def test_sources_list_with_data_external_classic_script(self): self.start_web_server(test_dir=os.path.join(DevtoolsTests.script_path, "devtools_tests/sources")) self.run_servoshell(url=f'data:text/html,') - self.assert_sources_list(1, set([tuple([f"{self.base_urls[0]}/classic.js"])])) + self.assert_sources_list(set([tuple([Source("srcScript", f"{self.base_urls[0]}/classic.js")])])) def test_sources_list_with_data_empty_inline_module_script(self): self.run_servoshell(url="data:text/html,") - self.assert_sources_list(1, set([tuple()])) + self.assert_sources_list(set([tuple()])) def test_sources_list_with_data_inline_module_script(self): self.run_servoshell(url="data:text/html,") - self.assert_sources_list(1, set([tuple(["data:text/html,"])])) + self.assert_sources_list( + set([tuple([Source("inlineScript", "data:text/html,")])]) + ) + + def test_sources_list_with_data_external_module_script(self): + self.start_web_server(test_dir=os.path.join(DevtoolsTests.script_path, "devtools_tests/sources")) + self.run_servoshell(url=f"{self.base_urls[0]}/test_sources_list_with_data_external_module_script.html") + self.assert_sources_list(set([tuple([Source("srcScript", f"{self.base_urls[0]}/module.js")])])) + + # Sources list for `introductionType` = `importedModule` + + @unittest.expectedFailure + def test_sources_list_with_static_import_module(self): + self.start_web_server(test_dir=os.path.join(DevtoolsTests.script_path, "devtools_tests/sources")) + self.run_servoshell(url=f"{self.base_urls[0]}/test_sources_list_with_static_import_module.html") + self.assert_sources_list( + set( + [ + tuple( + [ + Source( + "inlineScript", f"{self.base_urls[0]}/test_sources_list_with_static_import_module.html" + ), + Source("importedModule", f"{self.base_urls[0]}/module.js"), + ] + ) + ] + ), + ) + + @unittest.expectedFailure + def test_sources_list_with_dynamic_import_module(self): + self.start_web_server(test_dir=os.path.join(DevtoolsTests.script_path, "devtools_tests/sources")) + self.run_servoshell(url=f"{self.base_urls[0]}/test_sources_list_with_dynamic_import_module.html") + self.assert_sources_list( + set( + [ + tuple( + [ + Source( + "inlineScript", f"{self.base_urls[0]}/test_sources_list_with_dynamic_import_module.html" + ), + Source("importedModule", f"{self.base_urls[0]}/module.js"), + ] + ) + ] + ), + ) + + # Sources list for `introductionType` = `Worker` + + def test_sources_list_with_classic_worker(self): + self.start_web_server(test_dir=os.path.join(DevtoolsTests.script_path, "devtools_tests/sources")) + self.run_servoshell(url=f"{self.base_urls[0]}/test_sources_list_with_classic_worker.html") + self.assert_sources_list( + set( + [ + tuple( + [ + Source("inlineScript", f"{self.base_urls[0]}/test_sources_list_with_classic_worker.html"), + ] + ), + tuple( + [ + Source("Worker", f"{self.base_urls[0]}/classic_worker.js"), + ] + ), + ] + ), + ) + + def test_sources_list_with_module_worker(self): + self.start_web_server(test_dir=os.path.join(DevtoolsTests.script_path, "devtools_tests/sources")) + self.run_servoshell(url=f"{self.base_urls[0]}/test_sources_list_with_module_worker.html") + self.assert_sources_list( + set( + [ + tuple( + [ + Source("inlineScript", f"{self.base_urls[0]}/test_sources_list_with_module_worker.html"), + ] + ), + tuple( + [ + Source("Worker", f"{self.base_urls[0]}/module_worker.js"), + ] + ), + ] + ), + ) + + # Source contents def test_source_content_inline_script(self): script_tag = "" self.run_servoshell(url=f"data:text/html,{script_tag}") - self.assert_source_content(f"data:text/html,{script_tag}", script_tag) + self.assert_source_content(Source("inlineScript", f"data:text/html,{script_tag}"), script_tag) def test_source_content_external_script(self): self.start_web_server(test_dir=os.path.join(DevtoolsTests.script_path, "devtools_tests/sources")) self.run_servoshell(url=f'data:text/html,') expected_content = 'console.log("external classic");\n' - self.assert_source_content(f"{self.base_urls[0]}/classic.js", expected_content) + self.assert_source_content(Source("srcScript", f"{self.base_urls[0]}/classic.js"), expected_content) def test_source_content_html_file(self): self.start_web_server(test_dir=self.get_test_path("sources")) self.run_servoshell() expected_content = open(self.get_test_path("sources/test.html")).read() - self.assert_source_content(f"{self.base_urls[0]}/test.html", expected_content) + self.assert_source_content(Source("inlineScript", f"{self.base_urls[0]}/test.html"), expected_content) def test_source_content_with_inline_module_import_external(self): self.start_web_server(test_dir=self.get_test_path("sources_content_with_inline_module_import_external")) @@ -118,28 +221,28 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase): expected_content = open( self.get_test_path("sources_content_with_inline_module_import_external/test.html") ).read() - self.assert_source_content(f"{self.base_urls[0]}/test.html", expected_content) + self.assert_source_content(Source("inlineScript", f"{self.base_urls[0]}/test.html"), expected_content) # Test case that uses innerHTML and would actually need the HTML parser # (innerHTML has a fast path for values that don’t contain b'&' | b'\0' | b'<' | b'\r') def test_source_content_inline_script_with_inner_html(self): script_tag = '
' self.run_servoshell(url=f"data:text/html,{script_tag}") - self.assert_source_content(f"data:text/html,{script_tag}", script_tag) + self.assert_source_content(Source("inlineScript", f"data:text/html,{script_tag}"), script_tag) # Test case that uses outerHTML and would actually need the HTML parser # (innerHTML has a fast path for values that don’t contain b'&' | b'\0' | b'<' | b'\r') def test_source_content_inline_script_with_outer_html(self): script_tag = '
' self.run_servoshell(url=f"data:text/html,{script_tag}") - self.assert_source_content(f"data:text/html,{script_tag}", script_tag) + self.assert_source_content(Source("inlineScript", f"data:text/html,{script_tag}"), script_tag) # Test case that uses DOMParser and would actually need the HTML parser # (innerHTML has a fast path for values that don’t contain b'&' | b'\0' | b'<' | b'\r') def test_source_content_inline_script_with_domparser(self): script_tag = '' self.run_servoshell(url=f"data:text/html,{script_tag}") - self.assert_source_content(f"data:text/html,{script_tag}", script_tag) + self.assert_source_content(Source("inlineScript", f"data:text/html,{script_tag}"), script_tag) # Test case that uses XMLHttpRequest#responseXML and would actually need the HTML parser # (innerHTML has a fast path for values that don’t contain b'&' | b'\0' | b'<' | b'\r') @@ -147,7 +250,7 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase): self.start_web_server(test_dir=self.get_test_path("sources_content_with_responsexml")) self.run_servoshell() expected_content = open(self.get_test_path("sources_content_with_responsexml/test.html")).read() - self.assert_source_content(f"{self.base_urls[0]}/test.html", expected_content) + self.assert_source_content(Source("inlineScript", f"{self.base_urls[0]}/test.html"), expected_content) # Sets `base_url` and `web_server` and `web_server_thread`. def start_web_server(self, *, test_dir=None, num_servers=2): @@ -254,21 +357,22 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase): return client, watcher, targets - def assert_sources_list(self, expected_targets: int, expected_urls_by_target: set[tuple[str]]): + def assert_sources_list(self, expected_sources_by_target: set[tuple[Source]]): + expected_targets = len(expected_sources_by_target) client, watcher, targets = self._setup_devtools_client(expected_targets) done = Future() # NOTE: breaks if two targets have the same list of source urls. # This should really be a multiset, but Python does not have multisets. - actual_urls_by_target: set[tuple[str]] = set() + actual_sources_by_target: set[tuple[Source]] = set() def on_source_resource(data): for [resource_type, sources] in data["array"]: try: self.assertEqual(resource_type, "source") - source_urls = tuple([source["url"] for source in sources]) - self.assertFalse(source_urls in sources) # See NOTE above - actual_urls_by_target.add(source_urls) - if len(actual_urls_by_target) == expected_targets: + source_urls = tuple([Source(source["introductionType"], source["url"]) for source in sources]) + self.assertFalse(source_urls in actual_sources_by_target) # See NOTE above + actual_sources_by_target.add(source_urls) + if len(actual_sources_by_target) == expected_targets: done.set_result(None) except Exception as e: # Raising here does nothing, for some reason. @@ -286,10 +390,10 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase): result: Optional[Exception] = done.result(1) if result: raise result - self.assertEqual(actual_urls_by_target, expected_urls_by_target) + self.assertEqual(actual_sources_by_target, expected_sources_by_target) client.disconnect() - def assert_source_content(self, source_url: str, expected_content: str): + def assert_source_content(self, expected_source: Source, expected_content: str): client, watcher, targets = self._setup_devtools_client() done = Future() @@ -300,8 +404,8 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase): try: self.assertEqual(resource_type, "source") for source in sources: - if source["url"] == source_url: - source_actors[source_url] = source["actor"] + if Source(source["introductionType"], source["url"]) == expected_source: + source_actors[expected_source] = source["actor"] done.set_result(None) except Exception as e: done.set_result(e) @@ -319,8 +423,8 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase): raise result # We found at least one source with the given url. - self.assertIn(source_url, source_actors) - source_actor = source_actors[source_url] + self.assertIn(expected_source, source_actors) + source_actor = source_actors[expected_source] response = client.send_receive({"to": source_actor, "type": "source"}) diff --git a/python/servo/devtools_tests/sources/classic_worker.js b/python/servo/devtools_tests/sources/classic_worker.js new file mode 100644 index 00000000000..321a4b16c1b --- /dev/null +++ b/python/servo/devtools_tests/sources/classic_worker.js @@ -0,0 +1,4 @@ +console.log("external classic worker"); + +// Prevent worker exiting before devtools client can query sources +setInterval(() => {}, 0); diff --git a/python/servo/devtools_tests/sources/module_worker.js b/python/servo/devtools_tests/sources/module_worker.js new file mode 100644 index 00000000000..b1758223dbb --- /dev/null +++ b/python/servo/devtools_tests/sources/module_worker.js @@ -0,0 +1,4 @@ +console.log("external module worker"); + +// Prevent worker exiting before devtools client can query sources +setInterval(() => {}, 0); diff --git a/python/servo/devtools_tests/sources/test.html b/python/servo/devtools_tests/sources/test.html index 18ea6e65d97..b43d1667aa4 100644 --- a/python/servo/devtools_tests/sources/test.html +++ b/python/servo/devtools_tests/sources/test.html @@ -2,7 +2,7 @@ diff --git a/python/servo/devtools_tests/sources/test_sources_list_with_data_external_module_script.html b/python/servo/devtools_tests/sources/test_sources_list_with_data_external_module_script.html new file mode 100644 index 00000000000..e5bebd463ba --- /dev/null +++ b/python/servo/devtools_tests/sources/test_sources_list_with_data_external_module_script.html @@ -0,0 +1,2 @@ + + diff --git a/python/servo/devtools_tests/sources/test_sources_list_with_dynamic_import_module.html b/python/servo/devtools_tests/sources/test_sources_list_with_dynamic_import_module.html new file mode 100644 index 00000000000..cb1c0717987 --- /dev/null +++ b/python/servo/devtools_tests/sources/test_sources_list_with_dynamic_import_module.html @@ -0,0 +1,5 @@ + + diff --git a/python/servo/devtools_tests/sources/test_sources_list_with_module_worker.html b/python/servo/devtools_tests/sources/test_sources_list_with_module_worker.html new file mode 100644 index 00000000000..7d791c21979 --- /dev/null +++ b/python/servo/devtools_tests/sources/test_sources_list_with_module_worker.html @@ -0,0 +1,5 @@ + + diff --git a/python/servo/devtools_tests/sources/test_sources_list_with_static_import_module.html b/python/servo/devtools_tests/sources/test_sources_list_with_static_import_module.html new file mode 100644 index 00000000000..a5e0bf4e2fe --- /dev/null +++ b/python/servo/devtools_tests/sources/test_sources_list_with_static_import_module.html @@ -0,0 +1,5 @@ + + diff --git a/python/servo/devtools_tests/sources/worker.js b/python/servo/devtools_tests/sources/worker.js deleted file mode 100644 index a7993a8b5fb..00000000000 --- a/python/servo/devtools_tests/sources/worker.js +++ /dev/null @@ -1 +0,0 @@ -console.log("external classic worker");