mirror of
https://github.com/servo/servo
synced 2026-04-26 01:25:32 +02:00
script: Clean up prepare the script element related code and remove ModuleOwner (#44179)
Drop the `ModuleOwner` logic in favour of closures passed down by the script fetching initiator. When processing inline module scripts a task in now queued on the networking task source. Since `Rc<ModuleTree>` is not `Send`, a `result` field is now introduced to `HTMLScriptElement`, which is initialized before queueing the task. This slightly improves `inline-async-inserted-execorder.html`, which now fails at the fourth assertion instead of stopping at the second one (the inline module script with no dependencies still resolves after the one that has a parse error). Testing: Covered by existing tests. --------- Signed-off-by: Gae24 <96017547+Gae24@users.noreply.github.com>
This commit is contained in:
@@ -63,7 +63,7 @@ use crate::dom::window::Window;
|
||||
use crate::fetch::{RequestWithGlobalScope, create_a_potential_cors_request};
|
||||
use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
|
||||
use crate::script_module::{
|
||||
ImportMap, ModuleOwner, ModuleTree, ScriptFetchOptions, fetch_an_external_module_script,
|
||||
ImportMap, ModuleTree, ScriptFetchOptions, fetch_an_external_module_script,
|
||||
fetch_inline_module_script, parse_an_import_map_string, register_import_map,
|
||||
};
|
||||
use crate::script_runtime::{CanGc, IntroductionType};
|
||||
@@ -117,6 +117,9 @@ pub(crate) struct HTMLScriptElement {
|
||||
/// Used to keep track whether we consider this script element render blocking during
|
||||
/// `prepare`
|
||||
marked_as_render_blocking: Cell<bool>,
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#concept-script-result>
|
||||
result: DomRefCell<Option<ScriptResult>>,
|
||||
}
|
||||
|
||||
impl HTMLScriptElement {
|
||||
@@ -140,6 +143,7 @@ impl HTMLScriptElement {
|
||||
from_an_external_file: Cell::new(false),
|
||||
blocking: Default::default(),
|
||||
marked_as_render_blocking: Default::default(),
|
||||
result: DomRefCell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,18 +171,13 @@ impl HTMLScriptElement {
|
||||
|
||||
/// Marks that element as delaying the load event or not.
|
||||
///
|
||||
/// Nothing happens if the element was already delaying the load event and
|
||||
/// we pass true to that method again.
|
||||
///
|
||||
/// <https://html.spec.whatwg.org/multipage/#concept-script-delay-load>
|
||||
/// <https://html.spec.whatwg.org/multipage/#delaying-the-load-event-flag>
|
||||
pub(crate) fn delay_load_event(&self, url: ServoUrl) {
|
||||
let document = self.get_script_active_document();
|
||||
fn delay_load_event(&self, document: &Document, url: ServoUrl) {
|
||||
debug_assert!(self.delaying_the_load_event.borrow().is_none());
|
||||
|
||||
let blocker = &self.delaying_the_load_event;
|
||||
if blocker.borrow().is_none() {
|
||||
*blocker.borrow_mut() = Some(LoadBlocker::new(&document, LoadType::Script(url)));
|
||||
}
|
||||
*self.delaying_the_load_event.borrow_mut() =
|
||||
Some(LoadBlocker::new(document, LoadType::Script(url)));
|
||||
}
|
||||
|
||||
/// Helper method to determine the script kind based on attributes and insertion context.
|
||||
@@ -187,39 +186,23 @@ impl HTMLScriptElement {
|
||||
/// script's active document without full preparation.
|
||||
///
|
||||
/// <https://html.spec.whatwg.org/multipage/#prepare-the-script-element>
|
||||
pub(crate) fn get_script_kind(&self) -> ExternalScriptKind {
|
||||
fn get_script_kind(&self, script_type: ScriptType) -> ExternalScriptKind {
|
||||
let element = self.upcast::<Element>();
|
||||
let was_parser_inserted = self.parser_inserted.get();
|
||||
let asynch = element.has_attribute(&local_name!("async"));
|
||||
let mut script_kind = ExternalScriptKind::Asap;
|
||||
|
||||
match self.get_script_type() {
|
||||
Some(ScriptType::Classic) => {
|
||||
if element.has_attribute(&local_name!("defer")) && was_parser_inserted && !asynch {
|
||||
script_kind = ExternalScriptKind::Deferred
|
||||
} else if was_parser_inserted && !asynch {
|
||||
script_kind = ExternalScriptKind::ParsingBlocking
|
||||
} else if !asynch && !self.non_blocking.get() {
|
||||
script_kind = ExternalScriptKind::AsapInOrder
|
||||
}
|
||||
},
|
||||
Some(ScriptType::Module) => {
|
||||
if !asynch && was_parser_inserted {
|
||||
script_kind = ExternalScriptKind::Deferred
|
||||
} else if !asynch && !self.non_blocking.get() {
|
||||
script_kind = ExternalScriptKind::AsapInOrder
|
||||
}
|
||||
},
|
||||
Some(ScriptType::ImportMap) => (),
|
||||
None => (),
|
||||
if element.has_attribute(&local_name!("async")) || self.non_blocking.get() {
|
||||
ExternalScriptKind::Asap
|
||||
} else if !self.parser_inserted.get() {
|
||||
ExternalScriptKind::AsapInOrder
|
||||
} else if element.has_attribute(&local_name!("defer")) || script_type == ScriptType::Module
|
||||
{
|
||||
ExternalScriptKind::Deferred
|
||||
} else {
|
||||
ExternalScriptKind::ParsingBlocking
|
||||
}
|
||||
|
||||
script_kind
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#prepare-the-script-element>
|
||||
pub(crate) fn get_script_active_document(&self) -> DomRoot<Document> {
|
||||
let script_kind = self.get_script_kind();
|
||||
fn get_script_active_document(&self, script_kind: ExternalScriptKind) -> DomRoot<Document> {
|
||||
match script_kind {
|
||||
ExternalScriptKind::Asap => self.preparation_time_document.get().unwrap(),
|
||||
ExternalScriptKind::AsapInOrder => self.preparation_time_document.get().unwrap(),
|
||||
@@ -329,38 +312,35 @@ impl ScriptOrigin {
|
||||
}
|
||||
}
|
||||
|
||||
/// Final steps of <https://html.spec.whatwg.org/multipage/#prepare-the-script-element>
|
||||
pub(crate) fn finish_fetching_a_script(
|
||||
/// <https://html.spec.whatwg.org/multipage/#steps-to-run-when-the-result-is-ready>
|
||||
fn finish_fetching_a_script(
|
||||
elem: &HTMLScriptElement,
|
||||
script_kind: ExternalScriptKind,
|
||||
load: ScriptResult,
|
||||
cx: &mut js::context::JSContext,
|
||||
cx: &mut JSContext,
|
||||
) {
|
||||
// Step 33. The "steps to run when the result is ready" for each type of script in 33.2-33.5.
|
||||
// of https://html.spec.whatwg.org/multipage/#prepare-the-script-element
|
||||
let document;
|
||||
let load = elem.result.take().expect("Result must be ready to proceed");
|
||||
|
||||
// Step 2. If el's steps to run when the result is ready are not null, then run them.
|
||||
match script_kind {
|
||||
ExternalScriptKind::Asap => {
|
||||
document = elem.preparation_time_document.get().unwrap();
|
||||
let document = elem.preparation_time_document.get().unwrap();
|
||||
document.asap_script_loaded(cx, elem, load)
|
||||
},
|
||||
ExternalScriptKind::AsapInOrder => {
|
||||
document = elem.preparation_time_document.get().unwrap();
|
||||
let document = elem.preparation_time_document.get().unwrap();
|
||||
document.asap_in_order_script_loaded(cx, elem, load)
|
||||
},
|
||||
ExternalScriptKind::Deferred => {
|
||||
document = elem.parser_document.as_rooted();
|
||||
let document = elem.parser_document.as_rooted();
|
||||
document.deferred_script_loaded(cx, elem, load);
|
||||
},
|
||||
ExternalScriptKind::ParsingBlocking => {
|
||||
document = elem.parser_document.as_rooted();
|
||||
let document = elem.parser_document.as_rooted();
|
||||
document.pending_parsing_blocking_script_loaded(elem, load, cx);
|
||||
},
|
||||
}
|
||||
|
||||
// <https://html.spec.whatwg.org/multipage/#steps-to-run-when-the-result-is-ready>
|
||||
// Step 4
|
||||
// Step 4. Set el's delaying the load event to false.
|
||||
LoadBlocker::terminate(&elem.delaying_the_load_event, cx);
|
||||
}
|
||||
|
||||
@@ -458,11 +438,14 @@ impl FetchResponseListener for ClassicContext {
|
||||
response: Result<(), NetworkError>,
|
||||
timing: ResourceFetchTiming,
|
||||
) {
|
||||
let elem = self.elem.root();
|
||||
|
||||
match (response.as_ref(), self.status.as_ref()) {
|
||||
(Err(error), _) | (_, Err(error)) => {
|
||||
error!("Fetching classic script failed {:?} ({})", error, self.url);
|
||||
// Step 6, response is an error.
|
||||
finish_fetching_a_script(&self.elem.root(), self.kind, Err(()), cx);
|
||||
*elem.result.borrow_mut() = Some(Err(()));
|
||||
finish_fetching_a_script(&elem, self.kind, cx);
|
||||
|
||||
// Resource timing is expected to be available before "error" or "load" events are fired.
|
||||
network_listener::submit_timing(cx, &self, &response, &timing);
|
||||
@@ -484,7 +467,6 @@ impl FetchResponseListener for ClassicContext {
|
||||
// Step 5.5. Let sourceText be the result of decoding bodyBytes to Unicode, using encoding as the fallback encoding.
|
||||
let (mut source_text, _, _) = encoding.decode(&self.data);
|
||||
|
||||
let elem = self.elem.root();
|
||||
let global = elem.global();
|
||||
|
||||
if let Some(window) = global.downcast::<Window>() {
|
||||
@@ -537,8 +519,8 @@ impl FetchResponseListener for ClassicContext {
|
||||
.is_null());
|
||||
}
|
||||
} else {*/
|
||||
let load = Script::Classic(script);
|
||||
finish_fetching_a_script(&elem, self.kind, Ok(load), cx);
|
||||
*elem.result.borrow_mut() = Some(Ok(Script::Classic(script)));
|
||||
finish_fetching_a_script(&elem, self.kind, cx);
|
||||
// }
|
||||
|
||||
network_listener::submit_timing(cx, &self, &response, &timing);
|
||||
@@ -825,12 +807,12 @@ impl HTMLScriptElement {
|
||||
// If the element has a nonce content attribute but is not nonceable strip the nonce to prevent injection attacks.
|
||||
// Elements without a nonce content attribute (e.g. JS-created with .nonce = "abc")
|
||||
// use the internal slot directly — the nonceable check only applies to parser-created elements.
|
||||
let el = self.upcast::<Element>();
|
||||
let cryptographic_nonce = if el.is_nonceable() || !el.has_attribute(&local_name!("nonce")) {
|
||||
el.nonce_value().trim().to_owned()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let cryptographic_nonce =
|
||||
if element.is_nonceable() || !element.has_attribute(&local_name!("nonce")) {
|
||||
element.nonce_value().trim().to_owned()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
// Step 25. If el has an integrity attribute, then let integrity metadata be that attribute's value.
|
||||
// Otherwise, let integrity metadata be the empty string.
|
||||
@@ -842,7 +824,7 @@ impl HTMLScriptElement {
|
||||
};
|
||||
|
||||
// Step 26. Let referrer policy be the current state of el's referrerpolicy content attribute.
|
||||
let referrer_policy = referrer_policy_for_element(self.upcast::<Element>());
|
||||
let referrer_policy = referrer_policy_for_element(element);
|
||||
|
||||
// TODO: Step 27. Fetch priority.
|
||||
|
||||
@@ -865,12 +847,11 @@ impl HTMLScriptElement {
|
||||
};
|
||||
|
||||
// Step 30. Let settings object be el's node document's relevant settings object.
|
||||
// This is done by passing ModuleOwner in step 31.11 and step 32.2.
|
||||
// What we actually need is global's import map eventually.
|
||||
|
||||
let base_url = doc.base_url();
|
||||
|
||||
let kind = self.get_script_kind();
|
||||
let kind = self.get_script_kind(script_type);
|
||||
let delayed_document = self.get_script_active_document(kind);
|
||||
|
||||
if let Some(src) = element.get_attribute(&local_name!("src")) {
|
||||
// Step 31. If el has a src content attribute, then:
|
||||
@@ -912,7 +893,7 @@ impl HTMLScriptElement {
|
||||
}
|
||||
|
||||
// Step 31.8. Set el's delaying the load event to true.
|
||||
self.delay_load_event(url.clone());
|
||||
self.delay_load_event(&delayed_document, url.clone());
|
||||
|
||||
// Step 31.9. If el is currently render-blocking, then set options's render-blocking to true.
|
||||
if self.marked_as_render_blocking.get() {
|
||||
@@ -934,12 +915,20 @@ impl HTMLScriptElement {
|
||||
.resolve_a_module_integrity_metadata(&url);
|
||||
}
|
||||
|
||||
let script = DomRoot::from_ref(self);
|
||||
|
||||
// Step 31.11. Fetch an external module script graph.
|
||||
fetch_an_external_module_script(
|
||||
cx,
|
||||
url,
|
||||
ModuleOwner::Window(Trusted::new(self)),
|
||||
global,
|
||||
options,
|
||||
move |cx, module_tree| {
|
||||
let load = module_tree.map(Script::Module).ok_or(());
|
||||
*script.result.borrow_mut() = Some(load);
|
||||
|
||||
finish_fetching_a_script(&script, kind, cx);
|
||||
},
|
||||
);
|
||||
},
|
||||
ScriptType::ImportMap => (),
|
||||
@@ -982,11 +971,8 @@ impl HTMLScriptElement {
|
||||
return;
|
||||
},
|
||||
ScriptType::Module => {
|
||||
// Just to make sure we running in the correct document incase the script has been moved
|
||||
let doc = self.get_script_active_document();
|
||||
|
||||
// Step 32.2.2.1 Set el's delaying the load event to true.
|
||||
self.delay_load_event(base_url.clone());
|
||||
self.delay_load_event(&delayed_document, base_url.clone());
|
||||
|
||||
// Step 32.2.2.2 If el is potentially render-blocking, then:
|
||||
if self.potentially_render_blocking() &&
|
||||
@@ -1000,32 +986,40 @@ impl HTMLScriptElement {
|
||||
options.render_blocking = true;
|
||||
}
|
||||
|
||||
match kind {
|
||||
ExternalScriptKind::Deferred => doc.add_deferred_script(self),
|
||||
ExternalScriptKind::ParsingBlocking => {},
|
||||
ExternalScriptKind::AsapInOrder => doc.push_asap_in_order_script(self),
|
||||
ExternalScriptKind::Asap => doc.add_asap_script(self),
|
||||
}
|
||||
|
||||
let script = DomRoot::from_ref(self);
|
||||
// Step 32.2.2.3 Fetch an inline module script graph, given source text, base
|
||||
// URL, settings object, options, and with the following steps given result:
|
||||
fetch_inline_module_script(
|
||||
cx,
|
||||
ModuleOwner::Window(Trusted::new(self)),
|
||||
global,
|
||||
text_rc,
|
||||
base_url,
|
||||
options,
|
||||
self.line_number as u32,
|
||||
introduction_type,
|
||||
move |_, module_tree| {
|
||||
let load = module_tree.map(Script::Module).ok_or(());
|
||||
*script.result.borrow_mut() = Some(load);
|
||||
|
||||
let trusted = Trusted::new(&*script);
|
||||
|
||||
// Queue an element task on the networking task source given el to perform the following steps:
|
||||
script
|
||||
.owner_global()
|
||||
.task_manager()
|
||||
.networking_task_source()
|
||||
.queue(task!(terminate_module_fetch: move |cx| {
|
||||
// Mark as ready el given result.
|
||||
finish_fetching_a_script(&trusted.root(), kind, cx);
|
||||
}));
|
||||
},
|
||||
);
|
||||
return;
|
||||
},
|
||||
ScriptType::ImportMap => {
|
||||
// Step 32.1 Let result be the result of creating an import map
|
||||
// parse result given source text and base URL.
|
||||
let import_map_result = parse_an_import_map_string(
|
||||
ModuleOwner::Window(Trusted::new(self)),
|
||||
Rc::clone(&text_rc),
|
||||
base_url.clone(),
|
||||
);
|
||||
let import_map_result =
|
||||
parse_an_import_map_string(global, Rc::clone(&text_rc), base_url.clone());
|
||||
let script = Script::ImportMap(ScriptOrigin::internal(
|
||||
text_rc,
|
||||
base_url,
|
||||
@@ -1042,21 +1036,14 @@ impl HTMLScriptElement {
|
||||
}
|
||||
}
|
||||
|
||||
// Just to make sure we running in the correct document incase the script has been moved
|
||||
let doc = self.get_script_active_document();
|
||||
|
||||
// Step 33.2/33.3/33.4/33.5, substeps 1-2. Add el to the corresponding script list.
|
||||
match kind {
|
||||
ExternalScriptKind::Deferred => doc.add_deferred_script(self),
|
||||
ExternalScriptKind::Deferred => delayed_document.add_deferred_script(self),
|
||||
ExternalScriptKind::ParsingBlocking => {
|
||||
if Some(element.get_attribute(&local_name!("src"))).is_some() &&
|
||||
script_type == ScriptType::Classic
|
||||
{
|
||||
doc.set_pending_parsing_blocking_script(self, None);
|
||||
}
|
||||
delayed_document.set_pending_parsing_blocking_script(self, None);
|
||||
},
|
||||
ExternalScriptKind::AsapInOrder => doc.push_asap_in_order_script(self),
|
||||
ExternalScriptKind::Asap => doc.add_asap_script(self),
|
||||
ExternalScriptKind::AsapInOrder => delayed_document.push_asap_in_order_script(self),
|
||||
ExternalScriptKind::Asap => delayed_document.add_asap_script(self),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1545,7 +1532,7 @@ pub(crate) fn substitute_with_local_script(
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) enum ExternalScriptKind {
|
||||
enum ExternalScriptKind {
|
||||
Deferred,
|
||||
ParsingBlocking,
|
||||
AsapInOrder,
|
||||
|
||||
Reference in New Issue
Block a user