mirror of
https://github.com/servo/servo.git
synced 2025-08-12 08:55:32 +01:00
devtools: Expose introductionType
to devtools clients (#38541)
in the devtools protocol, [source forms](https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#loading-script-sources) announced in `resources-available-array` messages can include the `introductionType`, which more or less mirrors the field of the same name in SpiderMonkey’s CompileOptions. this patch exposes `introductionType` accordingly, allowing us to check for the correct values in automated tests. Testing: new coverage in 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
23c0947072
commit
c9541f2906
17 changed files with 232 additions and 42 deletions
|
@ -27,6 +27,8 @@ pub(crate) struct SourceForm {
|
||||||
/// URL of the script, or URL of the page for inline scripts.
|
/// URL of the script, or URL of the page for inline scripts.
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub is_black_boxed: bool,
|
pub is_black_boxed: bool,
|
||||||
|
/// `introductionType` in SpiderMonkey `CompileOptionsWrapper`.
|
||||||
|
pub introduction_type: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
@ -53,6 +55,9 @@ pub struct SourceActor {
|
||||||
|
|
||||||
pub content: Option<String>,
|
pub content: Option<String>,
|
||||||
pub content_type: Option<String>,
|
pub content_type: Option<String>,
|
||||||
|
|
||||||
|
/// `introductionType` in SpiderMonkey `CompileOptionsWrapper`.
|
||||||
|
pub introduction_type: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
@ -91,6 +96,7 @@ impl SourceActor {
|
||||||
url: ServoUrl,
|
url: ServoUrl,
|
||||||
content: Option<String>,
|
content: Option<String>,
|
||||||
content_type: Option<String>,
|
content_type: Option<String>,
|
||||||
|
introduction_type: String,
|
||||||
) -> SourceActor {
|
) -> SourceActor {
|
||||||
SourceActor {
|
SourceActor {
|
||||||
name,
|
name,
|
||||||
|
@ -98,6 +104,7 @@ impl SourceActor {
|
||||||
content,
|
content,
|
||||||
content_type,
|
content_type,
|
||||||
is_black_boxed: false,
|
is_black_boxed: false,
|
||||||
|
introduction_type,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,10 +114,17 @@ impl SourceActor {
|
||||||
url: ServoUrl,
|
url: ServoUrl,
|
||||||
content: Option<String>,
|
content: Option<String>,
|
||||||
content_type: Option<String>,
|
content_type: Option<String>,
|
||||||
|
introduction_type: String,
|
||||||
) -> &SourceActor {
|
) -> &SourceActor {
|
||||||
let source_actor_name = actors.new_name("source");
|
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(Box::new(source_actor));
|
||||||
actors.register_source_actor(pipeline_id, &source_actor_name);
|
actors.register_source_actor(pipeline_id, &source_actor_name);
|
||||||
|
|
||||||
|
@ -122,6 +136,7 @@ impl SourceActor {
|
||||||
actor: self.name.clone(),
|
actor: self.name.clone(),
|
||||||
url: self.url.to_string(),
|
url: self.url.to_string(),
|
||||||
is_black_boxed: self.is_black_boxed,
|
is_black_boxed: self.is_black_boxed,
|
||||||
|
introduction_type: self.introduction_type.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.introduction_type,
|
||||||
);
|
);
|
||||||
let source_actor_name = source_actor.name.clone();
|
let source_actor_name = source_actor.name.clone();
|
||||||
let source_form = source_actor.source_form();
|
let source_form = source_actor.source_form();
|
||||||
|
|
|
@ -59,7 +59,9 @@ 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::{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_queue::{QueuedTask, QueuedTaskConversion, TaskQueue};
|
||||||
use crate::task_source::{SendableTaskSource, TaskSourceName};
|
use crate::task_source::{SendableTaskSource, TaskSourceName};
|
||||||
|
|
||||||
|
@ -517,6 +519,10 @@ impl DedicatedWorkerGlobalScope {
|
||||||
let pipeline_id = global_scope.pipeline_id();
|
let pipeline_id = global_scope.pipeline_id();
|
||||||
let source_info = SourceInfo {
|
let source_info = SourceInfo {
|
||||||
url: metadata.final_url,
|
url: metadata.final_url,
|
||||||
|
introduction_type: IntroductionType::WORKER
|
||||||
|
.to_str()
|
||||||
|
.expect("Guaranteed by definition")
|
||||||
|
.to_owned(),
|
||||||
external: true, // Worker scripts are always external.
|
external: true, // Worker scripts are always external.
|
||||||
worker_id: Some(global.upcast::<WorkerGlobalScope>().get_worker_id()),
|
worker_id: Some(global.upcast::<WorkerGlobalScope>().get_worker_id()),
|
||||||
content: Some(source.to_string()),
|
content: Some(source.to_string()),
|
||||||
|
|
|
@ -1095,14 +1095,23 @@ impl HTMLScriptElement {
|
||||||
if let Some(chan) = self.global().devtools_chan() {
|
if let Some(chan) = self.global().devtools_chan() {
|
||||||
let pipeline_id = self.global().pipeline_id();
|
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 {
|
let content = match &script.code {
|
||||||
SourceCode::Text(text) => text.to_string(),
|
SourceCode::Text(text) => text.to_string(),
|
||||||
SourceCode::Compiled(compiled) => compiled.original_text.to_string(),
|
SourceCode::Compiled(compiled) => compiled.original_text.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// content_type: https://html.spec.whatwg.org/multipage/#scriptingLanguages
|
// 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 {
|
} else {
|
||||||
// TODO: if needed, fetch the page again, in the same way as in the original request.
|
// 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).
|
// 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.
|
// fetch, the server could return a different response.
|
||||||
|
|
||||||
// TODO: handle cases where Content-Type is not text/html.
|
// 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 {
|
let source_info = SourceInfo {
|
||||||
url,
|
url,
|
||||||
|
introduction_type: introduction_type.to_owned(),
|
||||||
external: is_external,
|
external: is_external,
|
||||||
worker_id: None,
|
worker_id: None,
|
||||||
content,
|
content,
|
||||||
|
|
|
@ -72,7 +72,7 @@ use crate::dom::workernavigator::WorkerNavigator;
|
||||||
use crate::fetch::{CspViolationsProcessor, Fetch, load_whole_resource};
|
use crate::fetch::{CspViolationsProcessor, Fetch, load_whole_resource};
|
||||||
use crate::messaging::{CommonScriptMsg, ScriptEventLoopReceiver, ScriptEventLoopSender};
|
use crate::messaging::{CommonScriptMsg, ScriptEventLoopReceiver, ScriptEventLoopSender};
|
||||||
use crate::realms::{InRealm, enter_realm};
|
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::task::TaskCanceller;
|
||||||
use crate::timers::{IsInterval, TimerCallback};
|
use crate::timers::{IsInterval, TimerCallback};
|
||||||
|
|
||||||
|
@ -644,12 +644,13 @@ impl WorkerGlobalScope {
|
||||||
let _aes = AutoEntryScript::new(self.upcast());
|
let _aes = AutoEntryScript::new(self.upcast());
|
||||||
let cx = self.runtime.borrow().as_ref().unwrap().cx();
|
let cx = self.runtime.borrow().as_ref().unwrap().cx();
|
||||||
rooted!(in(cx) let mut rval = UndefinedValue());
|
rooted!(in(cx) let mut rval = UndefinedValue());
|
||||||
let options = self
|
let mut options = self
|
||||||
.runtime
|
.runtime
|
||||||
.borrow()
|
.borrow()
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.new_compile_options(self.worker_url.borrow().as_str(), 1);
|
.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(
|
match self.runtime.borrow().as_ref().unwrap().evaluate_script(
|
||||||
self.reflector().get_jsobject(),
|
self.reflector().get_jsobject(),
|
||||||
&source,
|
&source,
|
||||||
|
|
|
@ -1249,7 +1249,21 @@ impl Runnable {
|
||||||
pub(crate) use script_bindings::script_runtime::CanGc;
|
pub(crate) use script_bindings::script_runtime::CanGc;
|
||||||
|
|
||||||
/// `introductionType` values in SpiderMonkey TransitiveCompileOptions.
|
/// `introductionType` values in SpiderMonkey TransitiveCompileOptions.
|
||||||
|
///
|
||||||
|
/// Value definitions are based on the SpiderMonkey Debugger API docs:
|
||||||
|
/// <https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.Source.html#introductiontype>
|
||||||
|
// 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 {
|
||||||
pub const INLINE_SCRIPT: &'static CStr = c"inlineScript";
|
/// `introductionType` for code belonging to `<script src="file.js">` elements.
|
||||||
|
/// This includes `<script type="module" src="...">`.
|
||||||
|
pub const SRC_SCRIPT: &CStr = c"srcScript";
|
||||||
|
|
||||||
|
/// `introductionType` for code belonging to `<script>code;</script>` elements.
|
||||||
|
/// This includes `<script type="module" src="...">`.
|
||||||
|
pub const INLINE_SCRIPT: &CStr = c"inlineScript";
|
||||||
|
|
||||||
|
/// `introductionType` for web workers.
|
||||||
|
/// <https://searchfox.org/mozilla-central/rev/202069c4c5113a1a9052d84fa4679d4c1b22113e/devtools/docs/user/debugger-api/debugger.source/index.rst#96>
|
||||||
|
pub const WORKER: &CStr = c"Worker";
|
||||||
}
|
}
|
||||||
|
|
|
@ -583,6 +583,7 @@ impl fmt::Display for ShadowRootMode {
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct SourceInfo {
|
pub struct SourceInfo {
|
||||||
pub url: ServoUrl,
|
pub url: ServoUrl,
|
||||||
|
pub introduction_type: String,
|
||||||
pub external: bool,
|
pub external: bool,
|
||||||
pub worker_id: Option<WorkerId>,
|
pub worker_id: Option<WorkerId>,
|
||||||
pub content: Option<String>,
|
pub content: Option<String>,
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
# except according to those terms.
|
# except according to those terms.
|
||||||
|
|
||||||
from concurrent.futures import Future
|
from concurrent.futures import Future
|
||||||
|
from dataclasses import dataclass
|
||||||
import logging
|
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
|
||||||
|
@ -30,6 +31,12 @@ from servo.command_base import BuildType
|
||||||
LOG_REQUESTS = False
|
LOG_REQUESTS = False
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Source:
|
||||||
|
introduction_type: str
|
||||||
|
url: str
|
||||||
|
|
||||||
|
|
||||||
class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
||||||
# /path/to/servo/python/servo
|
# /path/to/servo/python/servo
|
||||||
script_path = None
|
script_path = None
|
||||||
|
@ -50,67 +57,163 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
||||||
# - <https://html.spec.whatwg.org/multipage/#fetch-a-module-worker-script-tree>
|
# - <https://html.spec.whatwg.org/multipage/#fetch-a-module-worker-script-tree>
|
||||||
# Non-worker(?) script sources can be inline, external, or blob.
|
# Non-worker(?) script sources can be inline, external, or blob.
|
||||||
# Worker script sources can be external or blob.
|
# Worker script sources can be external or blob.
|
||||||
|
|
||||||
|
# Sources list
|
||||||
|
|
||||||
def test_sources_list(self):
|
def test_sources_list(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()
|
self.run_servoshell()
|
||||||
self.assert_sources_list(
|
self.assert_sources_list(
|
||||||
2,
|
|
||||||
set(
|
set(
|
||||||
[
|
[
|
||||||
|
# TODO: update expectations when we fix ES modules
|
||||||
tuple(
|
tuple(
|
||||||
[
|
[
|
||||||
f"{self.base_urls[0]}/classic.js",
|
Source("srcScript", f"{self.base_urls[0]}/classic.js"),
|
||||||
f"{self.base_urls[0]}/test.html",
|
Source("inlineScript", f"{self.base_urls[0]}/test.html"),
|
||||||
f"{self.base_urls[1]}/classic.js",
|
Source("srcScript", f"{self.base_urls[1]}/classic.js"),
|
||||||
f"{self.base_urls[0]}/test.html",
|
Source("inlineScript", f"{self.base_urls[0]}/test.html"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
tuple([f"{self.base_urls[0]}/worker.js"]),
|
tuple([Source("Worker", f"{self.base_urls[0]}/classic_worker.js")]),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_sources_list_with_data_no_scripts(self):
|
def test_sources_list_with_data_no_scripts(self):
|
||||||
self.run_servoshell(url="data:text/html,")
|
self.run_servoshell(url="data:text/html,")
|
||||||
self.assert_sources_list(1, set([tuple()]))
|
self.assert_sources_list(set([tuple()]))
|
||||||
|
|
||||||
|
# Sources list for `introductionType` = `inlineScript` and `srcScript`
|
||||||
|
|
||||||
def test_sources_list_with_data_empty_inline_classic_script(self):
|
def test_sources_list_with_data_empty_inline_classic_script(self):
|
||||||
self.run_servoshell(url="data:text/html,<script></script>")
|
self.run_servoshell(url="data:text/html,<script></script>")
|
||||||
self.assert_sources_list(1, set([tuple()]))
|
self.assert_sources_list(set([tuple()]))
|
||||||
|
|
||||||
def test_sources_list_with_data_inline_classic_script(self):
|
def test_sources_list_with_data_inline_classic_script(self):
|
||||||
self.run_servoshell(url="data:text/html,<script>;</script>")
|
self.run_servoshell(url="data:text/html,<script>;</script>")
|
||||||
self.assert_sources_list(1, set([tuple(["data:text/html,<script>;</script>"])]))
|
self.assert_sources_list(set([tuple([Source("inlineScript", "data:text/html,<script>;</script>")])]))
|
||||||
|
|
||||||
def test_sources_list_with_data_external_classic_script(self):
|
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.start_web_server(test_dir=os.path.join(DevtoolsTests.script_path, "devtools_tests/sources"))
|
||||||
self.run_servoshell(url=f'data:text/html,<script src="{self.base_urls[0]}/classic.js"></script>')
|
self.run_servoshell(url=f'data:text/html,<script src="{self.base_urls[0]}/classic.js"></script>')
|
||||||
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):
|
def test_sources_list_with_data_empty_inline_module_script(self):
|
||||||
self.run_servoshell(url="data:text/html,<script type=module></script>")
|
self.run_servoshell(url="data:text/html,<script type=module></script>")
|
||||||
self.assert_sources_list(1, set([tuple()]))
|
self.assert_sources_list(set([tuple()]))
|
||||||
|
|
||||||
def test_sources_list_with_data_inline_module_script(self):
|
def test_sources_list_with_data_inline_module_script(self):
|
||||||
self.run_servoshell(url="data:text/html,<script type=module>;</script>")
|
self.run_servoshell(url="data:text/html,<script type=module>;</script>")
|
||||||
self.assert_sources_list(1, set([tuple(["data:text/html,<script type=module>;</script>"])]))
|
self.assert_sources_list(
|
||||||
|
set([tuple([Source("inlineScript", "data:text/html,<script type=module>;</script>")])])
|
||||||
|
)
|
||||||
|
|
||||||
|
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):
|
def test_source_content_inline_script(self):
|
||||||
script_tag = "<script>console.log('Hello, world!')</script>"
|
script_tag = "<script>console.log('Hello, world!')</script>"
|
||||||
self.run_servoshell(url=f"data:text/html,{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):
|
def test_source_content_external_script(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'data:text/html,<script src="{self.base_urls[0]}/classic.js"></script>')
|
self.run_servoshell(url=f'data:text/html,<script src="{self.base_urls[0]}/classic.js"></script>')
|
||||||
expected_content = 'console.log("external classic");\n'
|
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):
|
def test_source_content_html_file(self):
|
||||||
self.start_web_server(test_dir=self.get_test_path("sources"))
|
self.start_web_server(test_dir=self.get_test_path("sources"))
|
||||||
self.run_servoshell()
|
self.run_servoshell()
|
||||||
expected_content = open(self.get_test_path("sources/test.html")).read()
|
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):
|
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"))
|
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(
|
expected_content = open(
|
||||||
self.get_test_path("sources_content_with_inline_module_import_external/test.html")
|
self.get_test_path("sources_content_with_inline_module_import_external/test.html")
|
||||||
).read()
|
).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
|
# 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')
|
# (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):
|
def test_source_content_inline_script_with_inner_html(self):
|
||||||
script_tag = '<div id="el"></div><script>el.innerHTML="<p>test"</script>'
|
script_tag = '<div id="el"></div><script>el.innerHTML="<p>test"</script>'
|
||||||
self.run_servoshell(url=f"data:text/html,{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
|
# 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')
|
# (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):
|
def test_source_content_inline_script_with_outer_html(self):
|
||||||
script_tag = '<div id="el"></div><script>el.outerHTML="<p>test"</script>'
|
script_tag = '<div id="el"></div><script>el.outerHTML="<p>test"</script>'
|
||||||
self.run_servoshell(url=f"data:text/html,{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
|
# 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')
|
# (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):
|
def test_source_content_inline_script_with_domparser(self):
|
||||||
script_tag = '<script>(new DOMParser).parseFromString("<p>test","text/html")</script>'
|
script_tag = '<script>(new DOMParser).parseFromString("<p>test","text/html")</script>'
|
||||||
self.run_servoshell(url=f"data:text/html,{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
|
# 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')
|
# (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.start_web_server(test_dir=self.get_test_path("sources_content_with_responsexml"))
|
||||||
self.run_servoshell()
|
self.run_servoshell()
|
||||||
expected_content = open(self.get_test_path("sources_content_with_responsexml/test.html")).read()
|
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`.
|
# Sets `base_url` and `web_server` and `web_server_thread`.
|
||||||
def start_web_server(self, *, test_dir=None, num_servers=2):
|
def start_web_server(self, *, test_dir=None, num_servers=2):
|
||||||
|
@ -254,21 +357,22 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
||||||
|
|
||||||
return client, watcher, targets
|
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)
|
client, watcher, targets = self._setup_devtools_client(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.
|
||||||
actual_urls_by_target: set[tuple[str]] = set()
|
actual_sources_by_target: set[tuple[Source]] = set()
|
||||||
|
|
||||||
def on_source_resource(data):
|
def on_source_resource(data):
|
||||||
for [resource_type, sources] in data["array"]:
|
for [resource_type, sources] in data["array"]:
|
||||||
try:
|
try:
|
||||||
self.assertEqual(resource_type, "source")
|
self.assertEqual(resource_type, "source")
|
||||||
source_urls = tuple([source["url"] for source in sources])
|
source_urls = tuple([Source(source["introductionType"], source["url"]) for source in sources])
|
||||||
self.assertFalse(source_urls in sources) # See NOTE above
|
self.assertFalse(source_urls in actual_sources_by_target) # See NOTE above
|
||||||
actual_urls_by_target.add(source_urls)
|
actual_sources_by_target.add(source_urls)
|
||||||
if len(actual_urls_by_target) == expected_targets:
|
if len(actual_sources_by_target) == expected_targets:
|
||||||
done.set_result(None)
|
done.set_result(None)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Raising here does nothing, for some reason.
|
# Raising here does nothing, for some reason.
|
||||||
|
@ -286,10 +390,10 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
||||||
result: Optional[Exception] = done.result(1)
|
result: Optional[Exception] = done.result(1)
|
||||||
if result:
|
if result:
|
||||||
raise 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()
|
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()
|
client, watcher, targets = self._setup_devtools_client()
|
||||||
|
|
||||||
done = Future()
|
done = Future()
|
||||||
|
@ -300,8 +404,8 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
||||||
try:
|
try:
|
||||||
self.assertEqual(resource_type, "source")
|
self.assertEqual(resource_type, "source")
|
||||||
for source in sources:
|
for source in sources:
|
||||||
if source["url"] == source_url:
|
if Source(source["introductionType"], source["url"]) == expected_source:
|
||||||
source_actors[source_url] = source["actor"]
|
source_actors[expected_source] = source["actor"]
|
||||||
done.set_result(None)
|
done.set_result(None)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
done.set_result(e)
|
done.set_result(e)
|
||||||
|
@ -319,8 +423,8 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
||||||
raise result
|
raise result
|
||||||
|
|
||||||
# We found at least one source with the given url.
|
# We found at least one source with the given url.
|
||||||
self.assertIn(source_url, source_actors)
|
self.assertIn(expected_source, source_actors)
|
||||||
source_actor = source_actors[source_url]
|
source_actor = source_actors[expected_source]
|
||||||
|
|
||||||
response = client.send_receive({"to": source_actor, "type": "source"})
|
response = client.send_receive({"to": source_actor, "type": "source"})
|
||||||
|
|
||||||
|
|
4
python/servo/devtools_tests/sources/classic_worker.js
Normal file
4
python/servo/devtools_tests/sources/classic_worker.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
console.log("external classic worker");
|
||||||
|
|
||||||
|
// Prevent worker exiting before devtools client can query sources
|
||||||
|
setInterval(() => {}, 0);
|
4
python/servo/devtools_tests/sources/module_worker.js
Normal file
4
python/servo/devtools_tests/sources/module_worker.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
console.log("external module worker");
|
||||||
|
|
||||||
|
// Prevent worker exiting before devtools client can query sources
|
||||||
|
setInterval(() => {}, 0);
|
|
@ -2,7 +2,7 @@
|
||||||
<script src="classic.js"></script>
|
<script src="classic.js"></script>
|
||||||
<script>
|
<script>
|
||||||
console.log("inline classic");
|
console.log("inline classic");
|
||||||
new Worker("worker.js");
|
new Worker("classic_worker.js");
|
||||||
</script>
|
</script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import module from "./module.js";
|
import module from "./module.js";
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<!doctype html><meta charset=utf-8>
|
||||||
|
<script>
|
||||||
|
console.log("inline classic");
|
||||||
|
new Worker("classic_worker.js");
|
||||||
|
</script>
|
|
@ -0,0 +1,2 @@
|
||||||
|
<!doctype html><meta charset=utf-8>
|
||||||
|
<script type="module" src="module.js"></script>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<!doctype html><meta charset=utf-8>
|
||||||
|
<script type="module">
|
||||||
|
console.log("inline module");
|
||||||
|
import("./module.js");
|
||||||
|
</script>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<!doctype html><meta charset=utf-8>
|
||||||
|
<script>
|
||||||
|
console.log("inline classic");
|
||||||
|
new Worker("module_worker.js");
|
||||||
|
</script>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<!doctype html><meta charset=utf-8>
|
||||||
|
<script type="module">
|
||||||
|
import module from "./module.js";
|
||||||
|
console.log("inline module");
|
||||||
|
</script>
|
|
@ -1 +0,0 @@
|
||||||
console.log("external classic worker");
|
|
Loading…
Add table
Add a link
Reference in a new issue