mirror of
https://github.com/servo/servo.git
synced 2025-08-12 00:45:33 +01:00
devtools: Create source actors from Debugger API notifications (#38334)
currently our devtools impl creates source actors in script, when executing scripts in HTMLScriptElement or DedicatedWorkerGlobalScope. this approach is cumbersome, and it means that many pathways to running scripts are missed, such as imported ES modules. with the [SpiderMonkey Debugger API](https://firefox-source-docs.mozilla.org/js/Debugger/), we can pick up all of the scripts and all of their sources without any extra code, as long as we tell it about every global we create (#38333, #38551). this patch adds a [Debugger#onNewScript() hook](https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.html#onnewscript-script-global) to the debugger script, which calls DebuggerGlobalScope#notifyNewSource() to notify our script system when a new script runs. if the source is relevant to the file tree in the Sources tab, script tells devtools to create a source actor. Testing: adds several new automated devtools tests Fixes: part of #36027 Signed-off-by: Delan Azabani <dazabani@igalia.com> Co-authored-by: atbrakhi <atbrakhi@igalia.com>
This commit is contained in:
parent
de73d4a25c
commit
4784668fa9
10 changed files with 522 additions and 118 deletions
|
@ -56,6 +56,9 @@ pub struct SourceActor {
|
||||||
pub content: Option<String>,
|
pub content: Option<String>,
|
||||||
pub content_type: Option<String>,
|
pub content_type: Option<String>,
|
||||||
|
|
||||||
|
// TODO: use it in #37667, then remove this allow
|
||||||
|
#[allow(unused)]
|
||||||
|
pub spidermonkey_id: u32,
|
||||||
/// `introductionType` in SpiderMonkey `CompileOptionsWrapper`.
|
/// `introductionType` in SpiderMonkey `CompileOptionsWrapper`.
|
||||||
pub introduction_type: String,
|
pub introduction_type: String,
|
||||||
}
|
}
|
||||||
|
@ -96,6 +99,7 @@ impl SourceActor {
|
||||||
url: ServoUrl,
|
url: ServoUrl,
|
||||||
content: Option<String>,
|
content: Option<String>,
|
||||||
content_type: Option<String>,
|
content_type: Option<String>,
|
||||||
|
spidermonkey_id: u32,
|
||||||
introduction_type: String,
|
introduction_type: String,
|
||||||
) -> SourceActor {
|
) -> SourceActor {
|
||||||
SourceActor {
|
SourceActor {
|
||||||
|
@ -104,6 +108,7 @@ impl SourceActor {
|
||||||
content,
|
content,
|
||||||
content_type,
|
content_type,
|
||||||
is_black_boxed: false,
|
is_black_boxed: false,
|
||||||
|
spidermonkey_id,
|
||||||
introduction_type,
|
introduction_type,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,6 +119,7 @@ impl SourceActor {
|
||||||
url: ServoUrl,
|
url: ServoUrl,
|
||||||
content: Option<String>,
|
content: Option<String>,
|
||||||
content_type: Option<String>,
|
content_type: Option<String>,
|
||||||
|
spidermonkey_id: u32,
|
||||||
introduction_type: String,
|
introduction_type: String,
|
||||||
) -> &SourceActor {
|
) -> &SourceActor {
|
||||||
let source_actor_name = actors.new_name("source");
|
let source_actor_name = actors.new_name("source");
|
||||||
|
@ -123,6 +129,7 @@ impl SourceActor {
|
||||||
url,
|
url,
|
||||||
content,
|
content,
|
||||||
content_type,
|
content_type,
|
||||||
|
spidermonkey_id,
|
||||||
introduction_type,
|
introduction_type,
|
||||||
);
|
);
|
||||||
actors.register(Box::new(source_actor));
|
actors.register(Box::new(source_actor));
|
||||||
|
@ -160,6 +167,10 @@ impl Actor for SourceActor {
|
||||||
let reply = SourceContentReply {
|
let reply = SourceContentReply {
|
||||||
from: self.name(),
|
from: self.name(),
|
||||||
content_type: self.content_type.clone(),
|
content_type: self.content_type.clone(),
|
||||||
|
// 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: do we want to wait instead of giving up immediately, in cases where the content could
|
// TODO: do we want to wait instead of giving up immediately, in cases where the content could
|
||||||
// become available later (e.g. after a fetch)?
|
// become available later (e.g. after a fetch)?
|
||||||
source: self
|
source: self
|
||||||
|
|
|
@ -552,6 +552,7 @@ impl DevtoolsInstance {
|
||||||
source_info.url,
|
source_info.url,
|
||||||
source_content,
|
source_content,
|
||||||
source_info.content_type,
|
source_info.content_type,
|
||||||
|
source_info.spidermonkey_id,
|
||||||
source_info.introduction_type,
|
source_info.introduction_type,
|
||||||
);
|
);
|
||||||
let source_actor_name = source_actor.name.clone();
|
let source_actor_name = source_actor.name.clone();
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* 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 constellation_traits::ScriptToConstellationChan;
|
||||||
use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId};
|
use devtools_traits::{ScriptToDevtoolsControlMsg, SourceInfo, WorkerId};
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use embedder_traits::JavaScriptEvaluationError;
|
use embedder_traits::JavaScriptEvaluationError;
|
||||||
use embedder_traits::resources::{self, Resource};
|
use embedder_traits::resources::{self, Resource};
|
||||||
|
@ -14,6 +14,9 @@ use js::rust::Runtime;
|
||||||
use js::rust::wrappers::JS_DefineDebuggerObject;
|
use js::rust::wrappers::JS_DefineDebuggerObject;
|
||||||
use net_traits::ResourceThreads;
|
use net_traits::ResourceThreads;
|
||||||
use profile_traits::{mem, time};
|
use profile_traits::{mem, time};
|
||||||
|
use script_bindings::codegen::GenericBindings::DebuggerGlobalScopeBinding::{
|
||||||
|
DebuggerGlobalScopeMethods, NotifyNewSource,
|
||||||
|
};
|
||||||
use script_bindings::realms::InRealm;
|
use script_bindings::realms::InRealm;
|
||||||
use script_bindings::reflector::DomObject;
|
use script_bindings::reflector::DomObject;
|
||||||
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
|
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
|
||||||
|
@ -30,7 +33,7 @@ use crate::dom::types::{DebuggerAddDebuggeeEvent, Event};
|
||||||
use crate::dom::webgpu::identityhub::IdentityHub;
|
use crate::dom::webgpu::identityhub::IdentityHub;
|
||||||
use crate::realms::enter_realm;
|
use crate::realms::enter_realm;
|
||||||
use crate::script_module::ScriptFetchOptions;
|
use crate::script_module::ScriptFetchOptions;
|
||||||
use crate::script_runtime::{CanGc, JSContext};
|
use crate::script_runtime::{CanGc, IntroductionType, JSContext};
|
||||||
|
|
||||||
#[dom_struct]
|
#[dom_struct]
|
||||||
/// Global scope for interacting with the devtools Debugger API.
|
/// Global scope for interacting with the devtools Debugger API.
|
||||||
|
@ -105,6 +108,10 @@ impl DebuggerGlobalScope {
|
||||||
GlobalScope::get_cx()
|
GlobalScope::get_cx()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn as_global_scope(&self) -> &GlobalScope {
|
||||||
|
self.upcast::<GlobalScope>()
|
||||||
|
}
|
||||||
|
|
||||||
fn evaluate_js(&self, script: &str, can_gc: CanGc) -> Result<(), JavaScriptEvaluationError> {
|
fn evaluate_js(&self, script: &str, can_gc: CanGc) -> Result<(), JavaScriptEvaluationError> {
|
||||||
rooted!(in (*Self::get_cx()) let mut rval = UndefinedValue());
|
rooted!(in (*Self::get_cx()) let mut rval = UndefinedValue());
|
||||||
self.global_scope.evaluate_js_on_global_with_result(
|
self.global_scope.evaluate_js_on_global_with_result(
|
||||||
|
@ -149,3 +156,99 @@ impl DebuggerGlobalScope {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DebuggerGlobalScopeMethods<crate::DomTypeHolder> for DebuggerGlobalScope {
|
||||||
|
// check-tidy: no specs after this line
|
||||||
|
fn NotifyNewSource(&self, args: &NotifyNewSource) {
|
||||||
|
let Some(devtools_chan) = self.as_global_scope().devtools_chan() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
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() {
|
||||||
|
// Check the `introductionType` and `url`, decide whether or not to create a source actor, and if so,
|
||||||
|
// tell the devtools server to create a source actor. Based on the Firefox impl in:
|
||||||
|
// - getDebuggerSourceURL() <https://searchfox.org/mozilla-central/rev/85667ab51e4b2a3352f7077a9ee43513049ed2d6/devtools/server/actors/utils/source-url.js#7-42>
|
||||||
|
// - getSourceURL() <https://searchfox.org/mozilla-central/rev/85667ab51e4b2a3352f7077a9ee43513049ed2d6/devtools/server/actors/source.js#67-109>
|
||||||
|
// - resolveSourceURL() <https://searchfox.org/mozilla-central/rev/85667ab51e4b2a3352f7077a9ee43513049ed2d6/devtools/server/actors/source.js#48-66>
|
||||||
|
// - SourceActor#_isInlineSource <https://searchfox.org/mozilla-central/rev/85667ab51e4b2a3352f7077a9ee43513049ed2d6/devtools/server/actors/source.js#130-143>
|
||||||
|
// - SourceActor#url <https://searchfox.org/mozilla-central/rev/85667ab51e4b2a3352f7077a9ee43513049ed2d6/devtools/server/actors/source.js#157-162>
|
||||||
|
|
||||||
|
// Firefox impl: getDebuggerSourceURL(), getSourceURL()
|
||||||
|
// TODO: handle `about:srcdoc` case (see Firefox getDebuggerSourceURL())
|
||||||
|
// TODO: remove trailing details that may have been appended by SpiderMonkey
|
||||||
|
// (currently impossible to do robustly due to <https://bugzilla.mozilla.org/show_bug.cgi?id=1982001>)
|
||||||
|
let url_original = args.url.str();
|
||||||
|
// FIXME: use page/worker url as base here
|
||||||
|
let url_original = ServoUrl::parse(url_original).ok();
|
||||||
|
|
||||||
|
// If the source has a `urlOverride` (aka `displayURL` aka `//# sourceURL`), it should be a valid url,
|
||||||
|
// possibly relative to the page/worker url, and we should treat the source as coming from that url for
|
||||||
|
// devtools purposes, including the file tree in the Sources tab.
|
||||||
|
// Firefox impl: getSourceURL()
|
||||||
|
let url_override = args
|
||||||
|
.urlOverride
|
||||||
|
.as_ref()
|
||||||
|
.map(|url| url.str())
|
||||||
|
// FIXME: use page/worker url as base here, not `url_original`
|
||||||
|
.and_then(|url| ServoUrl::parse_with_base(url_original.as_ref(), url).ok());
|
||||||
|
|
||||||
|
// If the `introductionType` is “eval or eval-like”, the `url` won’t be meaningful, so ignore these
|
||||||
|
// sources unless we have a `urlOverride` (aka `displayURL` aka `//# sourceURL`).
|
||||||
|
// Firefox impl: getDebuggerSourceURL(), getSourceURL()
|
||||||
|
if [
|
||||||
|
IntroductionType::INJECTED_SCRIPT_STR,
|
||||||
|
IntroductionType::EVAL_STR,
|
||||||
|
IntroductionType::DEBUGGER_EVAL_STR,
|
||||||
|
IntroductionType::FUNCTION_STR,
|
||||||
|
IntroductionType::JAVASCRIPT_URL_STR,
|
||||||
|
IntroductionType::EVENT_HANDLER_STR,
|
||||||
|
IntroductionType::DOM_TIMER_STR,
|
||||||
|
]
|
||||||
|
.contains(&introduction_type.str()) &&
|
||||||
|
url_override.is_none()
|
||||||
|
{
|
||||||
|
debug!(
|
||||||
|
"Not creating debuggee: `introductionType` is `{introduction_type}` but no valid url"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sources with an `introductionType` of `inlineScript` are generally inline, meaning their contents
|
||||||
|
// are a substring of the page markup (hence not known to SpiderMonkey, requiring plumbing in Servo).
|
||||||
|
// But sources with a `urlOverride` are not inline, since they get their own place in the Sources tree.
|
||||||
|
// nor are sources created for `<iframe srcdoc>`, since they are not necessarily a substring of the
|
||||||
|
// page markup as originally sent by the server.
|
||||||
|
// Firefox impl: SourceActor#_isInlineSource
|
||||||
|
// TODO: handle `about:srcdoc` case (see Firefox SourceActor#_isInlineSource)
|
||||||
|
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| 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,
|
||||||
|
};
|
||||||
|
if let Err(error) = devtools_chan.send(ScriptToDevtoolsControlMsg::CreateSourceActor(
|
||||||
|
pipeline_id,
|
||||||
|
source_info,
|
||||||
|
)) {
|
||||||
|
warn!("Failed to send to devtools server: {error:?}");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug!("Not creating debuggee for script with no `introductionType`");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use std::thread::{self, JoinHandle};
|
||||||
use base::id::{BrowsingContextId, PipelineId, WebViewId};
|
use base::id::{BrowsingContextId, PipelineId, WebViewId};
|
||||||
use constellation_traits::{WorkerGlobalScopeInit, WorkerScriptLoadOrigin};
|
use constellation_traits::{WorkerGlobalScopeInit, WorkerScriptLoadOrigin};
|
||||||
use crossbeam_channel::{Receiver, Sender, unbounded};
|
use crossbeam_channel::{Receiver, Sender, unbounded};
|
||||||
use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, SourceInfo};
|
use devtools_traits::DevtoolScriptControlMsg;
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use headers::{HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader};
|
use headers::{HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader};
|
||||||
use ipc_channel::ipc::IpcReceiver;
|
use ipc_channel::ipc::IpcReceiver;
|
||||||
|
@ -60,9 +60,7 @@ use crate::fetch::{CspViolationsProcessor, load_whole_resource};
|
||||||
use crate::messaging::{CommonScriptMsg, ScriptEventLoopReceiver, ScriptEventLoopSender};
|
use crate::messaging::{CommonScriptMsg, ScriptEventLoopReceiver, ScriptEventLoopSender};
|
||||||
use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
|
use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
|
||||||
use crate::script_runtime::ScriptThreadEventCategory::WorkerEvent;
|
use crate::script_runtime::ScriptThreadEventCategory::WorkerEvent;
|
||||||
use crate::script_runtime::{
|
use crate::script_runtime::{CanGc, JSContext as SafeJSContext, Runtime, ThreadSafeJSContext};
|
||||||
CanGc, IntroductionType, JSContext as SafeJSContext, Runtime, ThreadSafeJSContext,
|
|
||||||
};
|
|
||||||
use crate::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue};
|
use crate::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue};
|
||||||
use crate::task_source::{SendableTaskSource, TaskSourceName};
|
use crate::task_source::{SendableTaskSource, TaskSourceName};
|
||||||
|
|
||||||
|
@ -536,24 +534,6 @@ impl DedicatedWorkerGlobalScope {
|
||||||
));
|
));
|
||||||
global_scope.set_https_state(metadata.https_state);
|
global_scope.set_https_state(metadata.https_state);
|
||||||
let source = String::from_utf8_lossy(&bytes);
|
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::<WorkerGlobalScope>().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 {
|
unsafe {
|
||||||
// Handle interrupt requests
|
// Handle interrupt requests
|
||||||
|
|
|
@ -8,7 +8,6 @@ use std::path::PathBuf;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use base::id::{PipelineId, WebViewId};
|
use base::id::{PipelineId, WebViewId};
|
||||||
use devtools_traits::{ScriptToDevtoolsControlMsg, SourceInfo};
|
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use encoding_rs::Encoding;
|
use encoding_rs::Encoding;
|
||||||
use html5ever::{LocalName, Prefix, local_name, ns};
|
use html5ever::{LocalName, Prefix, local_name, ns};
|
||||||
|
@ -1024,59 +1023,6 @@ impl HTMLScriptElement {
|
||||||
Ok(script) => script,
|
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 {
|
if script.type_ == ScriptType::Classic {
|
||||||
unminify_js(&mut script);
|
unminify_js(&mut script);
|
||||||
self.substitute_with_local_script(&mut script);
|
self.substitute_with_local_script(&mut script);
|
||||||
|
|
|
@ -1254,20 +1254,36 @@ pub(crate) use script_bindings::script_runtime::CanGc;
|
||||||
// TODO: squish `scriptElement` <https://searchfox.org/mozilla-central/rev/202069c4c5113a1a9052d84fa4679d4c1b22113e/devtools/server/actors/source.js#199-201>
|
// TODO: squish `scriptElement` <https://searchfox.org/mozilla-central/rev/202069c4c5113a1a9052d84fa4679d4c1b22113e/devtools/server/actors/source.js#199-201>
|
||||||
pub(crate) struct IntroductionType;
|
pub(crate) struct IntroductionType;
|
||||||
impl IntroductionType {
|
impl IntroductionType {
|
||||||
|
/// `introductionType` for code passed to `eval`.
|
||||||
|
pub const EVAL: &CStr = c"eval";
|
||||||
|
pub const EVAL_STR: &str = "eval";
|
||||||
|
|
||||||
/// `introductionType` for code evaluated by debugger.
|
/// `introductionType` for code evaluated by debugger.
|
||||||
/// This includes code run via the devtools repl, even if the thread is not paused.
|
/// This includes code run via the devtools repl, even if the thread is not paused.
|
||||||
pub const DEBUGGER_EVAL: &CStr = c"debugger eval";
|
pub const DEBUGGER_EVAL: &CStr = c"debugger eval";
|
||||||
|
pub const DEBUGGER_EVAL_STR: &str = "debugger eval";
|
||||||
|
|
||||||
|
/// `introductionType` for code passed to the `Function` constructor.
|
||||||
|
pub const FUNCTION: &CStr = c"Function";
|
||||||
|
pub const FUNCTION_STR: &str = "Function";
|
||||||
|
|
||||||
/// `introductionType` for code loaded by worklet.
|
/// `introductionType` for code loaded by worklet.
|
||||||
pub const WORKLET: &CStr = c"Worklet";
|
pub const WORKLET: &CStr = c"Worklet";
|
||||||
|
pub const WORKLET_STR: &str = "Worklet";
|
||||||
|
|
||||||
|
/// `introductionType` for code assigned to DOM elements’ event handler IDL attributes as a string.
|
||||||
|
pub const EVENT_HANDLER: &CStr = c"eventHandler";
|
||||||
|
pub const EVENT_HANDLER_STR: &str = "eventHandler";
|
||||||
|
|
||||||
/// `introductionType` for code belonging to `<script src="file.js">` elements.
|
/// `introductionType` for code belonging to `<script src="file.js">` elements.
|
||||||
/// This includes `<script type="module" src="...">`.
|
/// This includes `<script type="module" src="...">`.
|
||||||
pub const SRC_SCRIPT: &CStr = c"srcScript";
|
pub const SRC_SCRIPT: &CStr = c"srcScript";
|
||||||
|
pub const SRC_SCRIPT_STR: &str = "srcScript";
|
||||||
|
|
||||||
/// `introductionType` for code belonging to `<script>code;</script>` elements.
|
/// `introductionType` for code belonging to `<script>code;</script>` elements.
|
||||||
/// This includes `<script type="module" src="...">`.
|
/// This includes `<script type="module" src="...">`.
|
||||||
pub const INLINE_SCRIPT: &CStr = c"inlineScript";
|
pub const INLINE_SCRIPT: &CStr = c"inlineScript";
|
||||||
|
pub const INLINE_SCRIPT_STR: &str = "inlineScript";
|
||||||
|
|
||||||
/// `introductionType` for code belonging to scripts that *would* be `"inlineScript"` except that they were not
|
/// `introductionType` for code belonging to scripts that *would* be `"inlineScript"` except that they were not
|
||||||
/// part of the initial file itself.
|
/// part of the initial file itself.
|
||||||
|
@ -1275,18 +1291,24 @@ impl IntroductionType {
|
||||||
/// - `document.write("<script>code;</script>")`
|
/// - `document.write("<script>code;</script>")`
|
||||||
/// - `var s = document.createElement("script"); s.text = "code";`
|
/// - `var s = document.createElement("script"); s.text = "code";`
|
||||||
pub const INJECTED_SCRIPT: &CStr = c"injectedScript";
|
pub const INJECTED_SCRIPT: &CStr = c"injectedScript";
|
||||||
|
pub const INJECTED_SCRIPT_STR: &str = "injectedScript";
|
||||||
|
|
||||||
/// `introductionType` for code that was loaded indirectly by being imported by another script
|
/// `introductionType` for code that was loaded indirectly by being imported by another script
|
||||||
/// using ESM static or dynamic imports.
|
/// using ESM static or dynamic imports.
|
||||||
pub const IMPORTED_MODULE: &CStr = c"importedModule";
|
pub const IMPORTED_MODULE: &CStr = c"importedModule";
|
||||||
|
pub const IMPORTED_MODULE_STR: &str = "importedModule";
|
||||||
|
|
||||||
/// `introductionType` for code presented in `javascript:` URLs.
|
/// `introductionType` for code presented in `javascript:` URLs.
|
||||||
pub const JAVASCRIPT_URL: &CStr = c"javascriptURL";
|
pub const JAVASCRIPT_URL: &CStr = c"javascriptURL";
|
||||||
|
pub const JAVASCRIPT_URL_STR: &str = "javascriptURL";
|
||||||
|
|
||||||
/// `introductionType` for code passed to `setTimeout`/`setInterval` as a string.
|
/// `introductionType` for code passed to `setTimeout`/`setInterval` as a string.
|
||||||
pub const DOM_TIMER: &CStr = c"domTimer";
|
pub const DOM_TIMER: &CStr = c"domTimer";
|
||||||
|
pub const DOM_TIMER_STR: &str = "domTimer";
|
||||||
|
|
||||||
/// `introductionType` for web workers.
|
/// `introductionType` for web workers.
|
||||||
/// <https://searchfox.org/mozilla-central/rev/202069c4c5113a1a9052d84fa4679d4c1b22113e/devtools/docs/user/debugger-api/debugger.source/index.rst#96>
|
/// FIXME: only documented in older(?) devtools user docs
|
||||||
|
/// <https://firefox-source-docs.mozilla.org/devtools-user/debugger-api/debugger.source/index.html>
|
||||||
pub const WORKER: &CStr = c"Worker";
|
pub const WORKER: &CStr = c"Worker";
|
||||||
|
pub const WORKER_STR: &str = "Worker";
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,4 +6,20 @@
|
||||||
// web pages.
|
// web pages.
|
||||||
[Global=DebuggerGlobalScope, Exposed=DebuggerGlobalScope]
|
[Global=DebuggerGlobalScope, Exposed=DebuggerGlobalScope]
|
||||||
interface DebuggerGlobalScope: GlobalScope {
|
interface DebuggerGlobalScope: GlobalScope {
|
||||||
|
undefined notifyNewSource(NotifyNewSource args);
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary NotifyNewSource {
|
||||||
|
required PipelineIdInit pipelineId;
|
||||||
|
required DOMString? workerId;
|
||||||
|
required unsigned long spidermonkeyId;
|
||||||
|
required DOMString url;
|
||||||
|
required DOMString? urlOverride;
|
||||||
|
required DOMString text;
|
||||||
|
required DOMString? introductionType;
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary PipelineIdInit {
|
||||||
|
required unsigned long namespaceId;
|
||||||
|
required unsigned long index;
|
||||||
};
|
};
|
||||||
|
|
|
@ -602,8 +602,9 @@ impl fmt::Display for ShadowRootMode {
|
||||||
pub struct SourceInfo {
|
pub struct SourceInfo {
|
||||||
pub url: ServoUrl,
|
pub url: ServoUrl,
|
||||||
pub introduction_type: String,
|
pub introduction_type: String,
|
||||||
pub external: bool,
|
pub inline: bool,
|
||||||
pub worker_id: Option<WorkerId>,
|
pub worker_id: Option<WorkerId>,
|
||||||
pub content: Option<String>,
|
pub content: Option<String>,
|
||||||
pub content_type: Option<String>,
|
pub content_type: Option<String>,
|
||||||
|
pub spidermonkey_id: u32,
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import logging
|
||||||
from geckordp.actors.root import RootActor
|
from geckordp.actors.root import RootActor
|
||||||
from geckordp.actors.descriptors.tab import TabActor
|
from geckordp.actors.descriptors.tab import TabActor
|
||||||
from geckordp.actors.watcher import WatcherActor
|
from geckordp.actors.watcher import WatcherActor
|
||||||
|
from geckordp.actors.web_console import WebConsoleActor
|
||||||
from geckordp.actors.resources import Resources
|
from geckordp.actors.resources import Resources
|
||||||
from geckordp.actors.events import Events
|
from geckordp.actors.events import Events
|
||||||
from geckordp.rdp_client import RDPClient
|
from geckordp.rdp_client import RDPClient
|
||||||
|
@ -37,6 +38,13 @@ class Source:
|
||||||
url: str
|
url: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Devtools:
|
||||||
|
client: RDPClient
|
||||||
|
watcher: WatcherActor
|
||||||
|
targets: list
|
||||||
|
|
||||||
|
|
||||||
class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
||||||
# /path/to/servo/python/servo
|
# /path/to/servo/python/servo
|
||||||
script_path = None
|
script_path = None
|
||||||
|
@ -71,8 +79,9 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
||||||
[
|
[
|
||||||
Source("srcScript", f"{self.base_urls[0]}/classic.js"),
|
Source("srcScript", f"{self.base_urls[0]}/classic.js"),
|
||||||
Source("inlineScript", f"{self.base_urls[0]}/test.html"),
|
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("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")]),
|
tuple([Source("Worker", f"{self.base_urls[0]}/classic_worker.js")]),
|
||||||
|
@ -116,7 +125,6 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
||||||
|
|
||||||
# Sources list for `introductionType` = `importedModule`
|
# Sources list for `introductionType` = `importedModule`
|
||||||
|
|
||||||
@unittest.expectedFailure
|
|
||||||
def test_sources_list_with_static_import_module(self):
|
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.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.run_servoshell(url=f"{self.base_urls[0]}/test_sources_list_with_static_import_module.html")
|
||||||
|
@ -135,7 +143,6 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@unittest.expectedFailure
|
|
||||||
def test_sources_list_with_dynamic_import_module(self):
|
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.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.run_servoshell(url=f"{self.base_urls[0]}/test_sources_list_with_dynamic_import_module.html")
|
||||||
|
@ -196,6 +203,324 @@ 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,<script>document.write("<script>//%23 sourceURL=http://test</scr"+"ipt>")</script>'
|
||||||
|
)
|
||||||
|
self.assert_sources_list(
|
||||||
|
set(
|
||||||
|
[
|
||||||
|
tuple(
|
||||||
|
[
|
||||||
|
Source(
|
||||||
|
"inlineScript",
|
||||||
|
'data:text/html,<script>document.write("<script>//%23 sourceURL=http://test</scr"+"ipt>")</script>',
|
||||||
|
),
|
||||||
|
Source("injectedScript", "http://test/"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_sources_list_with_injected_script_write_but_no_display_url(self):
|
||||||
|
self.run_servoshell(url='data:text/html,<script>document.write("<script>1</scr"+"ipt>")</script>')
|
||||||
|
self.assert_sources_list(
|
||||||
|
set(
|
||||||
|
[
|
||||||
|
tuple(
|
||||||
|
[
|
||||||
|
Source(
|
||||||
|
"inlineScript",
|
||||||
|
'data:text/html,<script>document.write("<script>1</scr"+"ipt>")</script>',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
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,<body><script>{script}</script>")
|
||||||
|
self.assert_sources_list(
|
||||||
|
set(
|
||||||
|
[
|
||||||
|
tuple(
|
||||||
|
[
|
||||||
|
Source(
|
||||||
|
"inlineScript",
|
||||||
|
f"data:text/html,<body><script>{script}</script>",
|
||||||
|
),
|
||||||
|
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,<body><script>{script}</script>")
|
||||||
|
self.assert_sources_list(
|
||||||
|
set(
|
||||||
|
[
|
||||||
|
tuple(
|
||||||
|
[
|
||||||
|
Source(
|
||||||
|
"inlineScript",
|
||||||
|
f"data:text/html,<body><script>{script}</script>",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_sources_list_with_eval_and_display_url(self):
|
||||||
|
self.run_servoshell(url='data:text/html,<script>eval("//%23 sourceURL=http://test")</script>')
|
||||||
|
self.assert_sources_list(
|
||||||
|
set(
|
||||||
|
[
|
||||||
|
tuple(
|
||||||
|
[
|
||||||
|
Source(
|
||||||
|
"inlineScript", 'data:text/html,<script>eval("//%23 sourceURL=http://test")</script>'
|
||||||
|
),
|
||||||
|
Source("eval", "http://test/"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_sources_list_with_eval_but_no_display_url(self):
|
||||||
|
self.run_servoshell(url='data:text/html,<script>eval("1")</script>')
|
||||||
|
self.assert_sources_list(set([tuple([Source("inlineScript", 'data:text/html,<script>eval("1")</script>')])]))
|
||||||
|
|
||||||
|
def test_sources_list_with_debugger_eval_and_display_url(self):
|
||||||
|
self.run_servoshell(url="data:text/html,")
|
||||||
|
devtools = self._setup_devtools_client()
|
||||||
|
console = WebConsoleActor(devtools.client, devtools.targets[0]["consoleActor"])
|
||||||
|
evaluation_result = Future()
|
||||||
|
|
||||||
|
async def on_evaluation_result(data: dict):
|
||||||
|
evaluation_result.set_result(data)
|
||||||
|
|
||||||
|
devtools.client.add_event_listener(console.actor_id, Events.WebConsole.EVALUATION_RESULT, on_evaluation_result)
|
||||||
|
console.evaluate_js_async("//# sourceURL=http://test")
|
||||||
|
evaluation_result.result(1)
|
||||||
|
self.assert_sources_list(set([tuple([Source("debugger eval", "http://test/")])]))
|
||||||
|
|
||||||
|
def test_sources_list_with_debugger_eval_but_no_display_url(self):
|
||||||
|
self.run_servoshell(url="data:text/html,")
|
||||||
|
devtools = self._setup_devtools_client()
|
||||||
|
console = WebConsoleActor(devtools.client, devtools.targets[0]["consoleActor"])
|
||||||
|
evaluation_result = Future()
|
||||||
|
|
||||||
|
async def on_evaluation_result(data: dict):
|
||||||
|
evaluation_result.set_result(data)
|
||||||
|
|
||||||
|
devtools.client.add_event_listener(console.actor_id, Events.WebConsole.EVALUATION_RESULT, on_evaluation_result)
|
||||||
|
console.evaluate_js_async("1")
|
||||||
|
evaluation_result.result(1)
|
||||||
|
self.assert_sources_list(set([tuple([])]))
|
||||||
|
|
||||||
|
def test_sources_list_with_function_and_display_url(self):
|
||||||
|
self.run_servoshell(url='data:text/html,<script>new Function("//%23 sourceURL=http://test")</script>')
|
||||||
|
self.assert_sources_list(
|
||||||
|
set(
|
||||||
|
[
|
||||||
|
tuple(
|
||||||
|
[
|
||||||
|
Source(
|
||||||
|
"inlineScript",
|
||||||
|
'data:text/html,<script>new Function("//%23 sourceURL=http://test")</script>',
|
||||||
|
),
|
||||||
|
Source("Function", "http://test/"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_sources_list_with_function_but_no_display_url(self):
|
||||||
|
self.run_servoshell(url='data:text/html,<script>new Function("1")</script>')
|
||||||
|
self.assert_sources_list(
|
||||||
|
set(
|
||||||
|
[
|
||||||
|
tuple(
|
||||||
|
[
|
||||||
|
Source("inlineScript", 'data:text/html,<script>new Function("1")</script>'),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_sources_list_with_javascript_url_and_display_url(self):
|
||||||
|
# “1” prefix is a workaround for <https://github.com/servo/servo/issues/38547>
|
||||||
|
self.run_servoshell(
|
||||||
|
url='data:text/html,<a href="javascript:1//%23 sourceURL=http://test"></a><script>document.querySelector("a").click()</script>'
|
||||||
|
)
|
||||||
|
self.assert_sources_list(
|
||||||
|
set(
|
||||||
|
[
|
||||||
|
tuple(
|
||||||
|
[
|
||||||
|
Source(
|
||||||
|
"inlineScript",
|
||||||
|
'data:text/html,<a href="javascript:1//%23 sourceURL=http://test"></a><script>document.querySelector("a").click()</script>',
|
||||||
|
),
|
||||||
|
Source("javascriptURL", "http://test/"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_sources_list_with_javascript_url_but_no_display_url(self):
|
||||||
|
self.run_servoshell(url='data:text/html,<a href="javascript:1"></a>')
|
||||||
|
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,<a onclick="//%23 sourceURL=http://test"></a>')
|
||||||
|
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,<a onclick="1"></a>')
|
||||||
|
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,<script>setTimeout("//%23 sourceURL=http://test",0)</script>')
|
||||||
|
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,<script>setTimeout("1",0)</script>')
|
||||||
|
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,<script>//%23 sourceURL=http://test</script>")
|
||||||
|
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,<script>//%23 sourceURL=test</script>")
|
||||||
|
self.assert_sources_list(
|
||||||
|
set(
|
||||||
|
[
|
||||||
|
tuple(
|
||||||
|
[
|
||||||
|
Source("inlineScript", "data:text/html,<script>//%23 sourceURL=test</script>"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_sources_list_with_inline_script_but_no_display_url(self):
|
||||||
|
self.run_servoshell(url="data:text/html,<script>1</script>")
|
||||||
|
self.assert_sources_list(
|
||||||
|
set(
|
||||||
|
[
|
||||||
|
tuple(
|
||||||
|
[
|
||||||
|
Source("inlineScript", "data:text/html,<script>1</script>"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sources list for inline scripts in `<iframe srcdoc>`
|
||||||
|
|
||||||
|
@unittest.expectedFailure
|
||||||
|
def test_sources_list_with_iframe_srcdoc_and_display_url(self):
|
||||||
|
self.run_servoshell(url='data:text/html,<iframe srcdoc="<script>//%23 sourceURL=http://test</script>">')
|
||||||
|
self.assert_sources_list(
|
||||||
|
set(
|
||||||
|
[
|
||||||
|
tuple(
|
||||||
|
[
|
||||||
|
Source("inlineScript", "http://test/"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@unittest.expectedFailure
|
||||||
|
def test_sources_list_with_iframe_srcdoc_but_no_display_url(self):
|
||||||
|
self.run_servoshell(url='data:text/html,<iframe srcdoc="<script>1</script>">')
|
||||||
|
self.assert_sources_list(
|
||||||
|
set(
|
||||||
|
[
|
||||||
|
tuple(
|
||||||
|
[
|
||||||
|
# FIXME: it’s not really gonna be 0
|
||||||
|
Source("inlineScript", "about:srcdoc#0"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@unittest.expectedFailure
|
||||||
|
def test_sources_list_with_iframe_srcdoc_multiple_inline_scripts(self):
|
||||||
|
self.run_servoshell(
|
||||||
|
url='data:text/html,<iframe srcdoc="<script>//%23 sourceURL=http://test</script><script>2</script>">'
|
||||||
|
)
|
||||||
|
self.assert_sources_list(
|
||||||
|
set(
|
||||||
|
[
|
||||||
|
tuple(
|
||||||
|
[
|
||||||
|
Source("inlineScript", "http://test/"),
|
||||||
|
# FIXME: it’s not really gonna be 0
|
||||||
|
Source("inlineScript", "about:srcdoc#0"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Source contents
|
# Source contents
|
||||||
|
|
||||||
def test_source_content_inline_script(self):
|
def test_source_content_inline_script(self):
|
||||||
|
@ -320,7 +645,7 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
||||||
if self.base_urls is not None:
|
if self.base_urls is not None:
|
||||||
self.base_urls = None
|
self.base_urls = None
|
||||||
|
|
||||||
def _setup_devtools_client(self, expected_targets=1):
|
def _setup_devtools_client(self, *, expected_targets=1) -> Devtools:
|
||||||
client = RDPClient()
|
client = RDPClient()
|
||||||
client.connect("127.0.0.1", 6080)
|
client.connect("127.0.0.1", 6080)
|
||||||
root = RootActor(client)
|
root = RootActor(client)
|
||||||
|
@ -355,11 +680,14 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
||||||
if result:
|
if result:
|
||||||
raise result
|
raise result
|
||||||
|
|
||||||
return client, watcher, targets
|
return Devtools(client, watcher, targets)
|
||||||
|
|
||||||
def assert_sources_list(self, expected_sources_by_target: set[tuple[Source]]):
|
def assert_sources_list(
|
||||||
|
self, expected_sources_by_target: set[tuple[Source]], *, devtools: Optional[Devtools] = None
|
||||||
|
):
|
||||||
expected_targets = len(expected_sources_by_target)
|
expected_targets = len(expected_sources_by_target)
|
||||||
client, watcher, targets = self._setup_devtools_client(expected_targets)
|
if devtools is None:
|
||||||
|
devtools = self._setup_devtools_client(expected_targets=expected_targets)
|
||||||
done = Future()
|
done = Future()
|
||||||
# NOTE: breaks if two targets have the same list of source urls.
|
# NOTE: breaks if two targets have the same list of source urls.
|
||||||
# This should really be a multiset, but Python does not have multisets.
|
# This should really be a multiset, but Python does not have multisets.
|
||||||
|
@ -379,22 +707,25 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
||||||
# Send the exception back so it can be raised.
|
# Send the exception back so it can be raised.
|
||||||
done.set_result(e)
|
done.set_result(e)
|
||||||
|
|
||||||
for target in targets:
|
for target in devtools.targets:
|
||||||
client.add_event_listener(
|
devtools.client.add_event_listener(
|
||||||
target["actor"],
|
target["actor"],
|
||||||
Events.Watcher.RESOURCES_AVAILABLE_ARRAY,
|
Events.Watcher.RESOURCES_AVAILABLE_ARRAY,
|
||||||
on_source_resource,
|
on_source_resource,
|
||||||
)
|
)
|
||||||
watcher.watch_resources([Resources.SOURCE])
|
devtools.watcher.watch_resources([Resources.SOURCE])
|
||||||
|
|
||||||
result: Optional[Exception] = done.result(1)
|
result: Optional[Exception] = done.result(1)
|
||||||
if result:
|
if result:
|
||||||
raise result
|
raise result
|
||||||
self.assertEqual(actual_sources_by_target, expected_sources_by_target)
|
self.assertEqual(actual_sources_by_target, expected_sources_by_target)
|
||||||
client.disconnect()
|
devtools.client.disconnect()
|
||||||
|
|
||||||
def assert_source_content(self, expected_source: Source, expected_content: str):
|
def assert_source_content(
|
||||||
client, watcher, targets = self._setup_devtools_client()
|
self, expected_source: Source, expected_content: str, *, devtools: Optional[Devtools] = None
|
||||||
|
):
|
||||||
|
if devtools is None:
|
||||||
|
devtools = self._setup_devtools_client()
|
||||||
|
|
||||||
done = Future()
|
done = Future()
|
||||||
source_actors = {}
|
source_actors = {}
|
||||||
|
@ -410,13 +741,13 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
done.set_result(e)
|
done.set_result(e)
|
||||||
|
|
||||||
for target in targets:
|
for target in devtools.targets:
|
||||||
client.add_event_listener(
|
devtools.client.add_event_listener(
|
||||||
target["actor"],
|
target["actor"],
|
||||||
Events.Watcher.RESOURCES_AVAILABLE_ARRAY,
|
Events.Watcher.RESOURCES_AVAILABLE_ARRAY,
|
||||||
on_source_resource,
|
on_source_resource,
|
||||||
)
|
)
|
||||||
watcher.watch_resources([Resources.SOURCE])
|
devtools.watcher.watch_resources([Resources.SOURCE])
|
||||||
|
|
||||||
result: Optional[Exception] = done.result(1)
|
result: Optional[Exception] = done.result(1)
|
||||||
if result:
|
if result:
|
||||||
|
@ -426,11 +757,11 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
||||||
self.assertIn(expected_source, source_actors)
|
self.assertIn(expected_source, source_actors)
|
||||||
source_actor = source_actors[expected_source]
|
source_actor = source_actors[expected_source]
|
||||||
|
|
||||||
response = client.send_receive({"to": source_actor, "type": "source"})
|
response = devtools.client.send_receive({"to": source_actor, "type": "source"})
|
||||||
|
|
||||||
self.assertEqual(response["source"], expected_content)
|
self.assertEqual(response["source"], expected_content)
|
||||||
|
|
||||||
client.disconnect()
|
devtools.client.disconnect()
|
||||||
|
|
||||||
def get_test_path(self, path: str) -> str:
|
def get_test_path(self, path: str) -> str:
|
||||||
return os.path.join(DevtoolsTests.script_path, os.path.join("devtools_tests", path))
|
return os.path.join(DevtoolsTests.script_path, os.path.join("devtools_tests", path))
|
||||||
|
|
|
@ -6,24 +6,21 @@ const dbg = new Debugger;
|
||||||
const debuggeesToPipelineIds = new Map;
|
const debuggeesToPipelineIds = new Map;
|
||||||
const debuggeesToWorkerIds = new Map;
|
const debuggeesToWorkerIds = new Map;
|
||||||
|
|
||||||
dbg.onNewGlobalObject = function(global) {
|
dbg.uncaughtExceptionHook = function(error) {
|
||||||
|
console.error(`[debugger] Uncaught exception at ${error.fileName}:${error.lineNumber}:${error.columnNumber}: ${error.name}: ${error.message}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
dbg.onNewScript = function(script, /* undefined; seems to be `script.global` now */ global) {
|
dbg.onNewScript = function(script, /* undefined; seems to be `script.global` now */ global) {
|
||||||
try {
|
// TODO: handle wasm (`script.source.introductionType == wasm`)
|
||||||
// TODO: notify script system about new source
|
notifyNewSource({
|
||||||
/* notifyNewSource */({
|
pipelineId: debuggeesToPipelineIds.get(script.global),
|
||||||
pipelineId: debuggeesToPipelineIds.get(script.global),
|
workerId: debuggeesToWorkerIds.get(script.global),
|
||||||
workerId: debuggeesToWorkerIds.get(script.global),
|
spidermonkeyId: script.source.id,
|
||||||
spidermonkeyId: script.source.id,
|
url: script.source.url,
|
||||||
url: script.source.url,
|
urlOverride: script.source.displayURL,
|
||||||
urlOverride: script.source.displayURL,
|
text: script.source.text,
|
||||||
text: script.source.text,
|
introductionType: script.source.introductionType ?? null,
|
||||||
introductionType: script.source.introductionType ?? null,
|
});
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logError(error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
addEventListener("addDebuggee", event => {
|
addEventListener("addDebuggee", event => {
|
||||||
|
@ -36,7 +33,3 @@ addEventListener("addDebuggee", event => {
|
||||||
});
|
});
|
||||||
debuggeesToWorkerIds.set(debuggerObject, workerId);
|
debuggeesToWorkerIds.set(debuggerObject, workerId);
|
||||||
});
|
});
|
||||||
|
|
||||||
function logError(error) {
|
|
||||||
console.log(`[debugger] ERROR at ${error.fileName}:${error.lineNumber}:${error.columnNumber}: ${error.name}: ${error.message}`);
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue