mirror of
https://github.com/servo/servo
synced 2026-04-26 01:25:32 +02:00
script: Align javascript: URL evaluation closer to the spec. (#43496)
The commit contains several related changes: * iframes that load javascript: URLs as part of the initial insertion (ie. `<iframe src='javascript:...'>`) get a synchronous load event dispatched * javascript: URL evaluation that does not result in a string no longer treated like a 204 response * iframes that perform a javascript: URL navigation that does not result in a new document no longer block the parent document load event Testing: Lots of new tests passing. Fixes: #24901 --------- Signed-off-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
@@ -20,8 +20,8 @@ use script_bindings::script_runtime::temp_cx;
|
||||
use script_traits::{NewPipelineInfo, UpdatePipelineIdReason};
|
||||
use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
|
||||
use servo_constellation_traits::{
|
||||
IFrameLoadInfo, IFrameLoadInfoWithData, JsEvalResult, LoadData, LoadOrigin,
|
||||
NavigationHistoryBehavior, ScriptToConstellationMessage,
|
||||
IFrameLoadInfo, IFrameLoadInfoWithData, LoadData, LoadOrigin, NavigationHistoryBehavior,
|
||||
ScriptToConstellationMessage,
|
||||
};
|
||||
use servo_url::ServoUrl;
|
||||
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
|
||||
@@ -35,6 +35,7 @@ use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::Wind
|
||||
use crate::dom::bindings::codegen::UnionTypes::TrustedHTMLOrString;
|
||||
use crate::dom::bindings::error::Fallible;
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::refcounted::Trusted;
|
||||
use crate::dom::bindings::reflector::DomGlobal;
|
||||
use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
|
||||
use crate::dom::bindings::str::{DOMString, USVString};
|
||||
@@ -62,8 +63,8 @@ enum PipelineType {
|
||||
Navigation,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum ProcessingMode {
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub(crate) enum ProcessingMode {
|
||||
FirstTime,
|
||||
NotFirstTime,
|
||||
}
|
||||
@@ -116,7 +117,10 @@ pub(crate) struct HTMLIFrameElement {
|
||||
|
||||
impl HTMLIFrameElement {
|
||||
/// <https://html.spec.whatwg.org/multipage/#shared-attribute-processing-steps-for-iframe-and-frame-elements>,
|
||||
fn shared_attribute_processing_steps_for_iframe_and_frame_elements(&self) -> Option<ServoUrl> {
|
||||
fn shared_attribute_processing_steps_for_iframe_and_frame_elements(
|
||||
&self,
|
||||
_mode: ProcessingMode,
|
||||
) -> Option<ServoUrl> {
|
||||
let element = self.upcast::<Element>();
|
||||
// Step 2. If element has a src attribute specified, and its value is not the empty string, then:
|
||||
let url = element
|
||||
@@ -150,6 +154,7 @@ impl HTMLIFrameElement {
|
||||
&self,
|
||||
load_data: LoadData,
|
||||
history_handling: NavigationHistoryBehavior,
|
||||
mode: ProcessingMode,
|
||||
cx: &mut js::context::JSContext,
|
||||
) {
|
||||
// In case we fired a synchronous load event, but navigate away
|
||||
@@ -160,7 +165,13 @@ impl HTMLIFrameElement {
|
||||
// of whether we synchronously fired a load in the same microtask.
|
||||
self.already_fired_synchronous_load_event.set(false);
|
||||
|
||||
self.start_new_pipeline(load_data, PipelineType::Navigation, history_handling, cx);
|
||||
self.start_new_pipeline(
|
||||
load_data,
|
||||
PipelineType::Navigation,
|
||||
history_handling,
|
||||
mode,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
fn start_new_pipeline(
|
||||
@@ -168,7 +179,65 @@ impl HTMLIFrameElement {
|
||||
mut load_data: LoadData,
|
||||
pipeline_type: PipelineType,
|
||||
history_handling: NavigationHistoryBehavior,
|
||||
mode: ProcessingMode,
|
||||
cx: &mut js::context::JSContext,
|
||||
) {
|
||||
let document = self.owner_document();
|
||||
|
||||
{
|
||||
let load_blocker = &self.load_blocker;
|
||||
// Any oustanding load is finished from the point of view of the blocked
|
||||
// document; the new navigation will continue blocking it.
|
||||
LoadBlocker::terminate(load_blocker, cx);
|
||||
|
||||
*load_blocker.borrow_mut() = Some(LoadBlocker::new(
|
||||
&document,
|
||||
LoadType::Subframe(load_data.url.clone()),
|
||||
));
|
||||
}
|
||||
|
||||
if load_data.url.scheme() != "javascript" {
|
||||
self.continue_navigation(load_data, pipeline_type, history_handling);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(jdm): The spec uses the navigate algorithm here, but
|
||||
// our iframe navigation is not yet unified enough to follow that.
|
||||
// Eventually we should remove the task and invoke ScriptThread::navigate instead.
|
||||
let iframe = Trusted::new(self);
|
||||
let doc = Trusted::new(&*document);
|
||||
document
|
||||
.global()
|
||||
.task_manager()
|
||||
.networking_task_source()
|
||||
.queue(task!(navigate_to_javascript: move |cx| {
|
||||
let this = iframe.root();
|
||||
let window_proxy = this.GetContentWindow();
|
||||
if let Some(window_proxy) = window_proxy {
|
||||
// If this method returns false we are not creating a new
|
||||
// document and the frame can be considered loaded.
|
||||
if !ScriptThread::navigate_to_javascript_url(
|
||||
cx,
|
||||
&this.owner_global(),
|
||||
&window_proxy.global(),
|
||||
&mut load_data,
|
||||
Some(this.upcast()),
|
||||
Some(mode == ProcessingMode::FirstTime),
|
||||
) {
|
||||
LoadBlocker::terminate(&this.load_blocker, cx);
|
||||
return;
|
||||
}
|
||||
load_data.about_base_url = doc.root().about_base_url();
|
||||
}
|
||||
this.continue_navigation(load_data, pipeline_type, history_handling);
|
||||
}));
|
||||
}
|
||||
|
||||
fn continue_navigation(
|
||||
&self,
|
||||
load_data: LoadData,
|
||||
pipeline_type: PipelineType,
|
||||
history_handling: NavigationHistoryBehavior,
|
||||
) {
|
||||
let browsing_context_id = match self.browsing_context_id() {
|
||||
None => return warn!("Attempted to start a new pipeline on an unattached iframe."),
|
||||
@@ -180,42 +249,6 @@ impl HTMLIFrameElement {
|
||||
Some(id) => id,
|
||||
};
|
||||
|
||||
let document = self.owner_document();
|
||||
|
||||
{
|
||||
let load_blocker = &self.load_blocker;
|
||||
// Any oustanding load is finished from the point of view of the blocked
|
||||
// document; the new navigation will continue blocking it.
|
||||
LoadBlocker::terminate(load_blocker, cx);
|
||||
}
|
||||
|
||||
if load_data.url.scheme() == "javascript" {
|
||||
let window_proxy = self.GetContentWindow();
|
||||
if let Some(window_proxy) = window_proxy {
|
||||
if !ScriptThread::navigate_to_javascript_url(
|
||||
cx,
|
||||
&document.global(),
|
||||
&window_proxy.global(),
|
||||
&mut load_data,
|
||||
Some(self.upcast()),
|
||||
) {
|
||||
return;
|
||||
}
|
||||
load_data.about_base_url = document.about_base_url();
|
||||
}
|
||||
}
|
||||
|
||||
match load_data.js_eval_result {
|
||||
Some(JsEvalResult::NoContent) => (),
|
||||
_ => {
|
||||
let mut load_blocker = self.load_blocker.borrow_mut();
|
||||
*load_blocker = Some(LoadBlocker::new(
|
||||
&document,
|
||||
LoadType::Subframe(load_data.url.clone()),
|
||||
));
|
||||
},
|
||||
};
|
||||
|
||||
let window = self.owner_window();
|
||||
let old_pipeline_id = self.pipeline_id();
|
||||
let new_pipeline_id = PipelineId::new();
|
||||
@@ -301,7 +334,12 @@ impl HTMLIFrameElement {
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#navigate-an-iframe-or-frame>
|
||||
fn navigate_an_iframe_or_frame(&self, cx: &mut js::context::JSContext, load_data: LoadData) {
|
||||
fn navigate_an_iframe_or_frame(
|
||||
&self,
|
||||
cx: &mut js::context::JSContext,
|
||||
load_data: LoadData,
|
||||
mode: ProcessingMode,
|
||||
) {
|
||||
// Step 2. If element's content navigable's active document is not completely loaded,
|
||||
// then set historyHandling to "replace".
|
||||
let history_handling = if !self
|
||||
@@ -320,7 +358,7 @@ impl HTMLIFrameElement {
|
||||
// Step 4. Navigate element's content navigable to url using element's node document,
|
||||
// with historyHandling set to historyHandling, referrerPolicy set to referrerPolicy,
|
||||
// documentResource set to srcdocString, and initialInsertion set to initialInsertion.
|
||||
self.navigate_or_reload_child_browsing_context(load_data, history_handling, cx);
|
||||
self.navigate_or_reload_child_browsing_context(load_data, history_handling, mode, cx);
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#will-lazy-load-element-steps>
|
||||
@@ -335,7 +373,11 @@ impl HTMLIFrameElement {
|
||||
}
|
||||
|
||||
/// Step 1.3. of <https://html.spec.whatwg.org/multipage/#process-the-iframe-attributes>
|
||||
fn navigate_to_the_srcdoc_resource(&self, cx: &mut js::context::JSContext) {
|
||||
fn navigate_to_the_srcdoc_resource(
|
||||
&self,
|
||||
mode: ProcessingMode,
|
||||
cx: &mut js::context::JSContext,
|
||||
) {
|
||||
// Step 1.3. Navigate to the srcdoc resource: Navigate an iframe or frame given element,
|
||||
// about:srcdoc, the empty string, and the value of element's srcdoc attribute.
|
||||
let url = ServoUrl::parse("about:srcdoc").unwrap();
|
||||
@@ -361,7 +403,7 @@ impl HTMLIFrameElement {
|
||||
.get_string_attribute(&local_name!("srcdoc")),
|
||||
);
|
||||
|
||||
self.navigate_an_iframe_or_frame(cx, load_data);
|
||||
self.navigate_an_iframe_or_frame(cx, load_data, mode);
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#the-iframe-element:potentially-delays-the-load-event>
|
||||
@@ -375,6 +417,7 @@ impl HTMLIFrameElement {
|
||||
/// <https://html.spec.whatwg.org/multipage/#process-the-iframe-attributes>
|
||||
fn process_the_iframe_attributes(&self, mode: ProcessingMode, cx: &mut js::context::JSContext) {
|
||||
let element = self.upcast::<Element>();
|
||||
|
||||
// Step 1. If `element`'s `srcdoc` attribute is specified, then:
|
||||
//
|
||||
// Note that this also includes the empty string
|
||||
@@ -396,7 +439,7 @@ impl HTMLIFrameElement {
|
||||
}
|
||||
// Step 1.3. Navigate to the srcdoc resource: Navigate an iframe or frame given element,
|
||||
// about:srcdoc, the empty string, and the value of element's srcdoc attribute.
|
||||
self.navigate_to_the_srcdoc_resource(cx);
|
||||
self.navigate_to_the_srcdoc_resource(mode, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -418,7 +461,7 @@ impl HTMLIFrameElement {
|
||||
|
||||
// Step 2.1. Let url be the result of running the shared attribute processing steps
|
||||
// for iframe and frame elements given element and initialInsertion.
|
||||
let Some(url) = self.shared_attribute_processing_steps_for_iframe_and_frame_elements()
|
||||
let Some(url) = self.shared_attribute_processing_steps_for_iframe_and_frame_elements(mode)
|
||||
else {
|
||||
// Step 2.2. If url is null, then return.
|
||||
return;
|
||||
@@ -429,14 +472,7 @@ impl HTMLIFrameElement {
|
||||
// We should **not** send a load event in `iframe_load_event_steps`.
|
||||
self.already_fired_synchronous_load_event.set(true);
|
||||
// Step 2.3.1. Run the iframe load event steps given element.
|
||||
//
|
||||
// Note: we are not actually calling that method. That's because
|
||||
// `iframe_load_event_steps` currently doesn't adhere to the spec
|
||||
// at all. In this case, WPT tests only care about the load event,
|
||||
// so we can fire that. Following https://github.com/servo/servo/issues/31973
|
||||
// we should call `iframe_load_event_steps` once it is spec-compliant.
|
||||
self.upcast::<EventTarget>()
|
||||
.fire_event(atom!("load"), CanGc::from_cx(cx));
|
||||
self.run_iframe_load_event_steps(cx);
|
||||
// Step 2.3.2. Return.
|
||||
return;
|
||||
}
|
||||
@@ -516,7 +552,7 @@ impl HTMLIFrameElement {
|
||||
NavigationHistoryBehavior::Push
|
||||
};
|
||||
|
||||
self.navigate_or_reload_child_browsing_context(load_data, history_handling, cx);
|
||||
self.navigate_or_reload_child_browsing_context(load_data, history_handling, mode, cx);
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#create-a-new-child-navigable>
|
||||
@@ -556,6 +592,7 @@ impl HTMLIFrameElement {
|
||||
load_data,
|
||||
PipelineType::InitialAboutBlank,
|
||||
NavigationHistoryBehavior::Push,
|
||||
ProcessingMode::FirstTime,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
@@ -728,15 +765,32 @@ impl HTMLIFrameElement {
|
||||
// do not fire if there is a pending navigation.
|
||||
!self.pending_navigation.get()
|
||||
};
|
||||
|
||||
// If we already fired a synchronous load event, we shouldn't fire another
|
||||
// one in this method.
|
||||
let should_fire_event =
|
||||
!self.already_fired_synchronous_load_event.replace(false) && should_fire_event;
|
||||
if should_fire_event {
|
||||
// Step 6. Fire an event named load at element.
|
||||
self.upcast::<EventTarget>()
|
||||
.fire_event(atom!("load"), CanGc::from_cx(cx));
|
||||
self.run_iframe_load_event_steps(cx);
|
||||
} else {
|
||||
debug!(
|
||||
"suppressing load event for iframe, loaded {:?}",
|
||||
loaded_pipeline
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#iframe-load-event-steps>
|
||||
pub(crate) fn run_iframe_load_event_steps(&self, cx: &mut JSContext) {
|
||||
// TODO 1. Assert: element's content navigable is not null.
|
||||
|
||||
// TODO 2-4 Mark resource timing.
|
||||
|
||||
// TODO 5 Set childDocument's iframe load in progress flag.
|
||||
|
||||
// Step 6. Fire an event named load at element.
|
||||
self.upcast::<EventTarget>()
|
||||
.fire_event(atom!("load"), CanGc::from_cx(cx));
|
||||
|
||||
let blocker = &self.load_blocker;
|
||||
LoadBlocker::terminate(blocker, cx);
|
||||
@@ -1101,7 +1155,7 @@ impl VirtualMethods for HTMLIFrameElement {
|
||||
LazyLoadResumptionSteps::None => (),
|
||||
LazyLoadResumptionSteps::SrcDoc => {
|
||||
// Step 4. Invoke resumptionSteps.
|
||||
self.navigate_to_the_srcdoc_resource(cx);
|
||||
self.navigate_to_the_srcdoc_resource(ProcessingMode::NotFirstTime, cx);
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -1194,7 +1248,9 @@ impl<'a> IframeContext<'a> {
|
||||
Self {
|
||||
element,
|
||||
url: element
|
||||
.shared_attribute_processing_steps_for_iframe_and_frame_elements()
|
||||
.shared_attribute_processing_steps_for_iframe_and_frame_elements(
|
||||
ProcessingMode::NotFirstTime,
|
||||
)
|
||||
.expect("Must always have a URL when navigating"),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user