diff --git a/components/devtools/actors/source.rs b/components/devtools/actors/source.rs index 7f26f5e979d..856f14ab8ad 100644 --- a/components/devtools/actors/source.rs +++ b/components/devtools/actors/source.rs @@ -56,6 +56,7 @@ pub struct SourceActor { pub content: Option, pub content_type: Option, + pub spidermonkey_id: u32, /// `introductionType` in SpiderMonkey `CompileOptionsWrapper`. pub introduction_type: String, } @@ -96,6 +97,7 @@ impl SourceActor { url: ServoUrl, content: Option, content_type: Option, + spidermonkey_id: u32, introduction_type: String, ) -> SourceActor { SourceActor { @@ -104,6 +106,7 @@ impl SourceActor { content, content_type, is_black_boxed: false, + spidermonkey_id, introduction_type, } } @@ -114,6 +117,7 @@ impl SourceActor { url: ServoUrl, content: Option, content_type: Option, + spidermonkey_id: u32, introduction_type: String, ) -> &SourceActor { let source_actor_name = actors.new_name("source"); @@ -123,6 +127,7 @@ impl SourceActor { url, content, content_type, + spidermonkey_id, introduction_type, ); actors.register(Box::new(source_actor)); diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs index 93bb7b18d22..695c98dba4a 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.spidermonkey_id, source_info.introduction_type, ); let source_actor_name = source_actor.name.clone(); diff --git a/components/script/dom/debuggerglobalscope.rs b/components/script/dom/debuggerglobalscope.rs index 1fcf20c7cd8..56d2b76a7f8 100644 --- a/components/script/dom/debuggerglobalscope.rs +++ b/components/script/dom/debuggerglobalscope.rs @@ -2,9 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use base::id::PipelineId; +use base::id::{Index, PipelineId, PipelineNamespaceId}; use constellation_traits::ScriptToConstellationChan; -use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId}; +use devtools_traits::{ScriptToDevtoolsControlMsg, SourceInfo, WorkerId}; use dom_struct::dom_struct; use embedder_traits::resources::{self, Resource}; use ipc_channel::ipc::IpcSender; @@ -13,6 +13,9 @@ use js::rust::Runtime; use js::rust::wrappers::JS_DefineDebuggerObject; use net_traits::ResourceThreads; use profile_traits::{mem, time}; +use script_bindings::codegen::GenericBindings::DebuggerGlobalScopeBinding::{ + DebuggerGlobalScopeMethods, NotifyNewSource, +}; use script_bindings::realms::InRealm; use script_bindings::reflector::DomObject; use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl}; @@ -104,6 +107,10 @@ impl DebuggerGlobalScope { GlobalScope::get_cx() } + pub(crate) fn as_global_scope(&self) -> &GlobalScope { + self.upcast::() + } + fn evaluate_js(&self, script: &str, can_gc: CanGc) -> bool { rooted!(in (*Self::get_cx()) let mut rval = UndefinedValue()); self.global_scope.evaluate_js_on_global_with_result( @@ -145,3 +152,89 @@ impl DebuggerGlobalScope { ); } } + +impl DebuggerGlobalScopeMethods for DebuggerGlobalScope { + // check-tidy: no specs after this line + fn NotifyNewSource(&self, args: &NotifyNewSource) { + info!( + "NotifyNewSource: ({},{}) {} {} {}", + args.pipelineId.namespaceId, + args.pipelineId.index, + args.spidermonkeyId, + args.url, + args.text + ); + if let Some(devtools_chan) = self.as_global_scope().devtools_chan() { + let pipeline_id = PipelineId { + namespace_id: PipelineNamespaceId(args.pipelineId.namespaceId), + index: Index::new(args.pipelineId.index) + .expect("`pipelineId.index` must not be zero"), + }; + + if let Some(introduction_type) = args.introductionType.as_ref() { + // TODO: handle the other cases in + // + // + + // TODO: remove trailing details that may have been appended by SpiderMonkey (currently buggy) + // + let url_original = args.url.str(); + + let url_original = ServoUrl::parse(url_original).ok(); + let url_override = args + .urlOverride + .as_ref() + .map(|url| url.str()) + // TODO: do we need to use the page url as base here, say if url_original fails to parse? + .and_then(|url| ServoUrl::parse_with_base(url_original.as_ref(), url).ok()); + + // + if [ + "injectedScript", + "eval", + "debugger eval", + "Function", + "javascriptURL", + "eventHandler", + "domTimer", + ] + .contains(&introduction_type.str()) && + url_override.is_none() + { + debug!( + "Not creating debuggee: `introductionType` is `{introduction_type}` but no valid url" + ); + return; + } + + // TODO: handle the other cases in + // + let inline = introduction_type.str() == "inlineScript" && url_override.is_none(); + let Some(url) = url_override.or(url_original) else { + debug!("Not creating debuggee: no valid url"); + return; + }; + + let worker_id = args.workerId.as_ref().map(|id| dbg!(id).parse().unwrap()); + + let source_info = SourceInfo { + url, + introduction_type: introduction_type.str().to_owned(), + inline, + worker_id, + content: (!inline).then(|| args.text.to_string()), + content_type: None, // TODO + spidermonkey_id: args.spidermonkeyId, + }; + devtools_chan + .send(ScriptToDevtoolsControlMsg::CreateSourceActor( + pipeline_id, + source_info, + )) + .expect("Failed to send to devtools server"); + } else { + debug!("Not creating debuggee for script with no `introductionType`"); + } + } + } +} diff --git a/components/script/dom/dedicatedworkerglobalscope.rs b/components/script/dom/dedicatedworkerglobalscope.rs index aceff6e03ea..dffdb942e67 100644 --- a/components/script/dom/dedicatedworkerglobalscope.rs +++ b/components/script/dom/dedicatedworkerglobalscope.rs @@ -9,7 +9,7 @@ use std::thread::{self, JoinHandle}; use base::id::{BrowsingContextId, PipelineId, WebViewId}; use constellation_traits::{WorkerGlobalScopeInit, WorkerScriptLoadOrigin}; use crossbeam_channel::{Receiver, Sender, unbounded}; -use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, SourceInfo}; +use devtools_traits::DevtoolScriptControlMsg; use dom_struct::dom_struct; use headers::{HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader}; use ipc_channel::ipc::IpcReceiver; @@ -536,24 +536,6 @@ impl DedicatedWorkerGlobalScope { )); global_scope.set_https_state(metadata.https_state); let source = String::from_utf8_lossy(&bytes); - if let Some(chan) = global_scope.devtools_chan() { - 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()), - content_type: metadata.content_type.map(|c_type| c_type.0.to_string()), - }; - let _ = chan.send(ScriptToDevtoolsControlMsg::CreateSourceActor( - pipeline_id, - source_info, - )); - } unsafe { // Handle interrupt requests diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index 40cc26d48cc..1a03618d39e 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -1024,59 +1024,6 @@ impl HTMLScriptElement { Ok(script) => script, }; - if let Some(chan) = self.global().devtools_chan() { - let pipeline_id = self.global().pipeline_id(); - - 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", - 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). - // If we can’t fetch it from cache, we should probably give up, because with a real - // fetch, the server could return a different response. - - // TODO: handle cases where Content-Type is not text/html. - ( - 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, - content_type: Some(content_type.to_string()), - }; - let _ = chan.send(ScriptToDevtoolsControlMsg::CreateSourceActor( - pipeline_id, - source_info, - )); - } - if script.type_ == ScriptType::Classic { unminify_js(&mut script); self.substitute_with_local_script(&mut script); diff --git a/components/script_bindings/webidls/DebuggerGlobalScope.webidl b/components/script_bindings/webidls/DebuggerGlobalScope.webidl index 4f85602a157..7237e89869d 100644 --- a/components/script_bindings/webidls/DebuggerGlobalScope.webidl +++ b/components/script_bindings/webidls/DebuggerGlobalScope.webidl @@ -6,4 +6,27 @@ // web pages. [Global=DebuggerGlobalScope, Exposed=DebuggerGlobalScope] interface DebuggerGlobalScope: GlobalScope { + undefined notifyNewSource(NotifyNewSource args); +}; + +// http://dev.w3.org/csswg/cssom-view/#extensions-to-the-window-interface +dictionary NotifyNewSource { + required PipelineIdInit pipelineId; + required DOMString? workerId; + required unsigned long spidermonkeyId; + required DOMString url; + required DOMString? urlOverride; + required DOMString text; + required DOMString? introductionType; + + // FIXME: error[E0599]: the method `trace` exists for reference `&Option>`, but + // its trait bounds were not satisfied + // Uint8Array binary; + + // TODO: contentType +}; + +dictionary PipelineIdInit { + required unsigned long namespaceId; + required unsigned long index; }; diff --git a/components/shared/devtools/lib.rs b/components/shared/devtools/lib.rs index 3a044a8f4ae..8ad872af0eb 100644 --- a/components/shared/devtools/lib.rs +++ b/components/shared/devtools/lib.rs @@ -598,8 +598,9 @@ impl fmt::Display for ShadowRootMode { pub struct SourceInfo { pub url: ServoUrl, pub introduction_type: String, - pub external: bool, + pub inline: bool, pub worker_id: Option, pub content: Option, pub content_type: Option, + pub spidermonkey_id: u32, } diff --git a/python/servo/devtools_tests.py b/python/servo/devtools_tests.py index 1078b7a10b0..4426e99ce6e 100644 --- a/python/servo/devtools_tests.py +++ b/python/servo/devtools_tests.py @@ -71,8 +71,9 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase): [ Source("srcScript", f"{self.base_urls[0]}/classic.js"), Source("inlineScript", f"{self.base_urls[0]}/test.html"), - Source("srcScript", f"{self.base_urls[1]}/classic.js"), Source("inlineScript", f"{self.base_urls[0]}/test.html"), + Source("srcScript", f"{self.base_urls[1]}/classic.js"), + Source("importedModule", f"{self.base_urls[0]}/module.js"), ] ), tuple([Source("Worker", f"{self.base_urls[0]}/classic_worker.js")]), @@ -116,7 +117,6 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase): # 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") @@ -135,7 +135,6 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase): ), ) - @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") @@ -196,6 +195,302 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase): ), ) + # Sources list for `introductionType` set to values that require `displayURL` (`//# sourceURL`) + + def test_sources_list_with_injected_script_write_and_display_url(self): + self.run_servoshell( + url='data:text/html,' + ) + self.assert_sources_list( + set( + [ + tuple( + [ + Source( + "inlineScript", + 'data:text/html,', + ), + Source("injectedScript", "http://test/"), + ] + ) + ] + ) + ) + + def test_sources_list_with_injected_script_write_but_no_display_url(self): + self.run_servoshell(url='data:text/html,') + self.assert_sources_list( + set( + [ + tuple( + [ + Source( + "inlineScript", + 'data:text/html,', + ), + ] + ) + ] + ) + ) + + def test_sources_list_with_injected_script_append_and_display_url(self): + script = 's=document.createElement("script");s.append("//%23 sourceURL=http://test");document.body.append(s)' + self.run_servoshell(url=f"data:text/html,") + self.assert_sources_list( + set( + [ + tuple( + [ + Source( + "inlineScript", + f"data:text/html,", + ), + Source("injectedScript", "http://test/"), + ] + ) + ] + ) + ) + + def test_sources_list_with_injected_script_append_but_no_display_url(self): + script = 's=document.createElement("script");s.append("1");document.body.append(s)' + self.run_servoshell(url=f"data:text/html,") + self.assert_sources_list( + set( + [ + tuple( + [ + Source( + "inlineScript", + f"data:text/html,", + ), + ] + ) + ] + ) + ) + + def test_sources_list_with_eval_and_display_url(self): + self.run_servoshell(url='data:text/html,') + self.assert_sources_list( + set( + [ + tuple( + [ + Source( + "inlineScript", 'data:text/html,' + ), + Source("eval", "http://test/"), + ] + ) + ] + ) + ) + + def test_sources_list_with_eval_but_no_display_url(self): + self.run_servoshell(url='data:text/html,') + self.assert_sources_list(set([tuple([Source("inlineScript", 'data:text/html,')])])) + + def test_sources_list_with_debugger_eval_and_display_url(self): + pass + + def test_sources_list_with_debugger_eval_but_no_display_url(self): + pass + + def test_sources_list_with_function_and_display_url(self): + self.run_servoshell(url='data:text/html,') + self.assert_sources_list( + set( + [ + tuple( + [ + Source( + "inlineScript", + 'data:text/html,', + ), + Source("Function", "http://test/"), + ] + ) + ] + ) + ) + + def test_sources_list_with_function_but_no_display_url(self): + self.run_servoshell(url='data:text/html,') + self.assert_sources_list( + set( + [ + tuple( + [ + Source("inlineScript", 'data:text/html,'), + ] + ) + ] + ) + ) + + def test_sources_list_with_javascript_url_and_display_url(self): + # “1” prefix is a workaround for + self.run_servoshell( + url='data:text/html,' + ) + self.assert_sources_list( + set( + [ + tuple( + [ + Source( + "inlineScript", + 'data:text/html,', + ), + Source("javascriptURL", "http://test/"), + ] + ) + ] + ) + ) + + def test_sources_list_with_javascript_url_but_no_display_url(self): + self.run_servoshell(url='data:text/html,') + self.assert_sources_list(set([tuple([])])) + + @unittest.expectedFailure + def test_sources_list_with_event_handler_and_display_url(self): + self.run_servoshell(url='data:text/html,') + self.assert_sources_list( + set( + [ + tuple( + [ + Source("eventHandler", "http://test/"), + ] + ) + ] + ) + ) + + def test_sources_list_with_event_handler_but_no_display_url(self): + self.run_servoshell(url='data:text/html,') + self.assert_sources_list(set([tuple([])])) + + @unittest.expectedFailure + def test_sources_list_with_dom_timer_and_display_url(self): + self.run_servoshell(url='data:text/html,') + self.assert_sources_list( + set( + [ + tuple( + [ + Source("domTimer", "http://test/"), + ] + ) + ] + ) + ) + + @unittest.expectedFailure + def test_sources_list_with_dom_timer_but_no_display_url(self): + self.run_servoshell(url='data:text/html,') + self.assert_sources_list(set([tuple([])])) + + # Sources list for scripts with `displayURL` (`//# sourceURL`), despite not being required by `introductionType` + + def test_sources_list_with_inline_script_and_display_url(self): + self.run_servoshell(url="data:text/html,") + self.assert_sources_list( + set( + [ + tuple( + [ + Source("inlineScript", "http://test/"), + ] + ) + ] + ) + ) + + # Extra test case for situation where `//# sourceURL` can’t be parsed with page url as base. + def test_sources_list_with_inline_script_but_invalid_display_url(self): + self.run_servoshell(url="data:text/html,") + self.assert_sources_list( + set( + [ + tuple( + [ + Source("inlineScript", "data:text/html,"), + ] + ) + ] + ) + ) + + def test_sources_list_with_inline_script_but_no_display_url(self): + self.run_servoshell(url="data:text/html,") + self.assert_sources_list( + set( + [ + tuple( + [ + Source("inlineScript", "data:text/html,"), + ] + ) + ] + ) + ) + + # Sources list for inline scripts in `