Compare commits

...

21 Commits

Author SHA1 Message Date
Josh Matthews
e37cacce2d Fix test which expected iframe load for initial about:blank. 2024-10-02 21:50:27 -04:00
Josh Matthews
d90ca0bc53 Ensure iframe load event occurs if initial about:blank is recreated with document.open. 2024-10-02 21:46:21 -04:00
Josh Matthews
6aad25dc25 Update expectations. 2024-09-28 00:09:07 -04:00
Josh Matthews
4352b4c7ff Synchronously mark iframes as having pending loads when navigating a window proxy with a frame element. 2024-09-27 23:57:07 -04:00
Josh Matthews
bff5123a99 clear timers when replacing a window. 2024-09-27 01:11:41 -04:00
Josh Matthews
52e1b19c5c Only discard documents when no window replacement has occurred. 2024-09-27 01:11:11 -04:00
Josh Matthews
4aef4d48be Only use window replacement when new document is same-origin. 2024-09-27 00:30:50 -04:00
Josh Matthews
8daf551654 Enter a realm when dispatching DOMContentLoaded. 2024-09-25 22:35:24 -04:00
Josh Matthews
225b581fe5 Treat a missing replaced document as a non-replaced window. 2024-09-25 22:35:08 -04:00
Josh Matthews
6aca6d30f7 Store a pipeline that's being replaced in the load data. 2024-09-25 22:03:25 -04:00
Josh Matthews
2d2e34167e Reset document state notification when replacing a window. 2024-09-25 21:34:48 -04:00
Josh Matthews
addcddf960 about:srcdoc loads are not synchronous. 2024-09-25 21:34:30 -04:00
Josh Matthews
e22b68349b Update python2 test helper to python3. 2024-09-25 20:45:44 -04:00
Josh Matthews
45bdc0f776 Don't suspend windows that have been replaced. 2024-09-25 20:43:26 -04:00
Josh Matthews
f6ba2a77c0 Use existing window when navigating away from initial about:blank document. 2024-09-25 17:41:01 -04:00
Josh Matthews
28c27ff835 Fix panic when about:blank iframe is removed from the document immediately after being added to it. 2024-09-25 09:56:30 -04:00
Josh Matthews
a25beae1f1 Formatting. 2024-09-25 09:56:29 -04:00
Josh Matthews
441266a000 Sync load event for about:blank iframe elements. 2024-09-25 09:56:00 -04:00
Josh Matthews
6a3b468623 Only suppress load event dispatch. 2024-09-25 09:55:06 -04:00
Josh Matthews
004aa169fd Formatting. 2024-09-25 09:55:06 -04:00
Josh Matthews
9976f0e65b Hack: normalize all about:blank load behaviour and prevent load/pageshow events on the content documents. 2024-09-25 09:55:04 -04:00
43 changed files with 461 additions and 337 deletions

View File

@@ -944,7 +944,6 @@ where
#[allow(clippy::too_many_arguments)]
fn new_pipeline(
&mut self,
pipeline_id: PipelineId,
browsing_context_id: BrowsingContextId,
top_level_browsing_context_id: TopLevelBrowsingContextId,
parent_pipeline_id: Option<PipelineId>,
@@ -962,6 +961,7 @@ where
if self.shutting_down {
return;
}
let pipeline_id = load_data.new_pipeline_id.clone();
debug!(
"{}: Creating new pipeline in {}",
pipeline_id, browsing_context_id
@@ -1431,6 +1431,8 @@ where
Referrer::NoReferrer,
None,
None,
None,
false,
);
let ctx_id = BrowsingContextId::from(top_level_browsing_context_id);
let pipeline_id = match self.browsing_contexts.get(&ctx_id) {
@@ -1676,8 +1678,8 @@ where
self.handle_abort_load_url_msg(source_pipeline_id);
},
// A page loaded has completed all parsing, script, and reflow messages have been sent.
FromScriptMsg::LoadComplete => {
self.handle_load_complete_msg(source_top_ctx_id, source_pipeline_id)
FromScriptMsg::LoadComplete(is_initial_about_blank) => {
self.handle_load_complete_msg(source_top_ctx_id, source_pipeline_id, is_initial_about_blank)
},
// Handle navigating to a fragment
FromScriptMsg::NavigatedToFragment(new_url, replacement_enabled) => {
@@ -2867,6 +2869,7 @@ where
let new_pipeline_id = PipelineId::new();
let new_load_data = LoadData {
new_pipeline_id,
crash: Some(
backtrace
.map(|b| format!("{}\n{}", reason, b))
@@ -2878,7 +2881,6 @@ where
let sandbox = IFrameSandboxState::IFrameSandboxed;
let is_private = false;
self.new_pipeline(
new_pipeline_id,
browsing_context_id,
top_level_browsing_context_id,
None,
@@ -2988,7 +2990,6 @@ where
top_level_browsing_context_id: TopLevelBrowsingContextId,
) {
let window_size = self.window_size.initial_viewport;
let pipeline_id = PipelineId::new();
let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);
let load_data = LoadData::new(
LoadOrigin::Constellation,
@@ -2997,7 +2998,10 @@ where
Referrer::NoReferrer,
None,
None,
None,
false,
);
let pipeline_id = load_data.new_pipeline_id.clone();
let sandbox = IFrameSandboxState::IFrameUnsandboxed;
let is_private = false;
let throttled = false;
@@ -3022,7 +3026,6 @@ where
.insert(new_bc_group_id, new_bc_group);
self.new_pipeline(
pipeline_id,
browsing_context_id,
top_level_browsing_context_id,
None,
@@ -3113,7 +3116,7 @@ where
}
#[tracing::instrument(skip(self), fields(servo_profiling = true))]
fn handle_subframe_loaded(&mut self, pipeline_id: PipelineId) {
fn handle_subframe_loaded(&mut self, pipeline_id: PipelineId, is_initial_about_blank: bool) {
let browsing_context_id = match self.pipelines.get(&pipeline_id) {
Some(pipeline) => pipeline.browsing_context_id,
None => return warn!("{}: Subframe loaded after closure", pipeline_id),
@@ -3138,6 +3141,7 @@ where
target: browsing_context_id,
parent: parent_pipeline_id,
child: pipeline_id,
is_initial_about_blank,
};
let result = match self.pipelines.get(&parent_pipeline_id) {
Some(parent) => parent.event_loop.send(msg),
@@ -3180,7 +3184,6 @@ where
parent_pipeline_id,
browsing_context_id,
top_level_browsing_context_id,
new_pipeline_id,
is_private,
mut replace,
..
@@ -3191,6 +3194,8 @@ where
.old_pipeline_id
.and_then(|id| self.pipelines.get(&id));
let new_pipeline_id = load_info.load_data.new_pipeline_id.clone();
// Replacement enabled also takes into account whether the document is "completely loaded",
// see https://html.spec.whatwg.org/multipage/#the-iframe-element:completely-loaded
if let Some(old_pipeline) = old_pipeline {
@@ -3259,7 +3264,6 @@ where
// Create the new pipeline, attached to the parent and push to pending changes
self.new_pipeline(
new_pipeline_id,
browsing_context_id,
top_level_browsing_context_id,
Some(parent_pipeline_id),
@@ -3285,13 +3289,14 @@ where
fn handle_script_new_iframe(&mut self, load_info: IFrameLoadInfoWithData) {
let IFrameLoadInfo {
parent_pipeline_id,
new_pipeline_id,
browsing_context_id,
top_level_browsing_context_id,
is_private,
..
} = load_info.info;
let new_pipeline_id = load_info.load_data.new_pipeline_id.clone();
let (script_sender, parent_browsing_context_id) =
match self.pipelines.get(&parent_pipeline_id) {
Some(pipeline) => (pipeline.event_loop.clone(), pipeline.browsing_context_id),
@@ -3349,10 +3354,11 @@ where
opener_pipeline_id,
new_top_level_browsing_context_id,
new_browsing_context_id,
new_pipeline_id,
} = load_info;
let (script_sender, opener_browsing_context_id) =
let new_pipeline_id = load_data.new_pipeline_id.clone();
let (_script_sender, opener_browsing_context_id) =
match self.pipelines.get(&opener_pipeline_id) {
Some(pipeline) => (pipeline.event_loop.clone(), pipeline.browsing_context_id),
None => {
@@ -3372,7 +3378,18 @@ where
);
},
};
let pipeline = Pipeline::new(
self.new_pipeline(
new_browsing_context_id,
new_top_level_browsing_context_id,
Some(opener_pipeline_id),
Some(opener_browsing_context_id),
self.window_size.initial_viewport,
load_data,
IFrameSandboxState::IFrameUnsandboxed, //XXXjdm
is_opener_private,
is_opener_throttled,
);
/*let pipeline = Pipeline::new(
new_pipeline_id,
new_browsing_context_id,
new_top_level_browsing_context_id,
@@ -3384,7 +3401,7 @@ where
);
assert!(!self.pipelines.contains_key(&new_pipeline_id));
self.pipelines.insert(new_pipeline_id, pipeline);
self.pipelines.insert(new_pipeline_id, pipeline);*/
self.webviews.add(
new_top_level_browsing_context_id,
WebView {
@@ -3490,7 +3507,7 @@ where
);
},
Entry::Vacant(entry) => {
let _ = entry.insert((load_data.clone(), replace));
let _ = entry.insert((load_data.clone(), if load_data.replacing_pipeline.is_some() { HistoryEntryReplacement::Enabled } else { replace }));
},
};
// Allow the embedder to handle the url itself
@@ -3531,6 +3548,7 @@ where
return None;
},
};
let new_pipeline_id = load_data.new_pipeline_id.clone();
let (window_size, pipeline_id, parent_pipeline_id, is_private, is_throttled) =
match self.browsing_contexts.get(&browsing_context_id) {
Some(ctx) => (
@@ -3602,10 +3620,8 @@ where
HistoryEntryReplacement::Disabled => None,
};
let new_pipeline_id = PipelineId::new();
let sandbox = IFrameSandboxState::IFrameUnsandboxed;
self.new_pipeline(
new_pipeline_id,
browsing_context_id,
top_level_browsing_context_id,
None,
@@ -3653,6 +3669,7 @@ where
&mut self,
top_level_browsing_context_id: TopLevelBrowsingContextId,
pipeline_id: PipelineId,
is_initial_about_blank: bool,
) {
let mut webdriver_reset = false;
if let Some((expected_pipeline_id, ref reply_chan)) = self.webdriver.load_channel {
@@ -3693,7 +3710,7 @@ where
.send(CompositorMsg::LoadComplete(top_level_browsing_context_id));
}
} else {
self.handle_subframe_loaded(pipeline_id);
self.handle_subframe_loaded(pipeline_id, is_initial_about_blank);
}
}
@@ -3909,9 +3926,8 @@ where
Some(pipeline) => pipeline.opener,
None => None,
};
let new_pipeline_id = PipelineId::new();
let new_pipeline_id = load_data.new_pipeline_id.clone();
self.new_pipeline(
new_pipeline_id,
browsing_context_id,
top_level_id,
parent_pipeline_id,

View File

@@ -229,10 +229,12 @@ impl Pipeline {
window_size: state.window_size,
};
if let Err(e) =
script_chan.send(ConstellationControlMsg::AttachLayout(new_layout_info))
{
warn!("Sending to script during pipeline creation failed ({})", e);
if !state.load_data.synchronously_loaded {
if let Err(e) =
script_chan.send(ConstellationControlMsg::AttachLayout(new_layout_info))
{
warn!("Sending to script during pipeline creation failed ({})", e);
}
}
(script_chan, None)
},

View File

@@ -1,3 +1,4 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
@@ -156,7 +157,7 @@ mod from_script {
Self::GetTopForBrowsingContext(..) => target!("GetTopForBrowsingContext"),
Self::GetBrowsingContextInfo(..) => target!("GetBrowsingContextInfo"),
Self::GetChildBrowsingContextId(..) => target!("GetChildBrowsingContextId"),
Self::LoadComplete => target!("LoadComplete"),
Self::LoadComplete(..) => target!("LoadComplete"),
Self::LoadUrl(..) => target!("LoadUrl"),
Self::AbortLoadUrl => target!("AbortLoadUrl"),
Self::PostMessage { .. } => target!("PostMessage"),

View File

@@ -262,7 +262,7 @@ impl CanvasState {
CanvasImageSource::HTMLCanvasElement(canvas) => canvas.origin_is_clean(),
CanvasImageSource::OffscreenCanvas(canvas) => canvas.origin_is_clean(),
CanvasImageSource::HTMLImageElement(image) => {
image.same_origin(GlobalScope::entry().origin())
image.same_origin(&GlobalScope::entry().origin())
},
CanvasImageSource::CSSStyleValue(_) => true,
}

View File

@@ -3020,7 +3020,7 @@ create_global_object(
raw.as_ptr() as *const libc::c_void,
_trace,
obj.handle_mut(),
origin);
&origin);
assert!(!obj.is_null());
let root = raw.reflect_with(obj.get());

View File

@@ -162,8 +162,7 @@ pub unsafe fn create_global_object(
// Initialize the reserved slots before doing anything that can GC, to
// avoid getting trace hooks called on a partially initialized object.
let private_val = PrivateValue(private);
JS_SetReservedSlot(rval.get(), DOM_OBJECT_SLOT, &private_val);
set_reflector_object(rval.handle(), private);
let proto_array: Box<ProtoOrIfaceArray> =
Box::new([ptr::null_mut::<JSObject>(); PrototypeList::PROTO_OR_IFACE_LENGTH]);
let val = PrivateValue(Box::into_raw(proto_array) as *const libc::c_void);
@@ -173,6 +172,11 @@ pub unsafe fn create_global_object(
JS_FireOnNewGlobalObject(*cx, rval.handle());
}
pub(crate) unsafe fn set_reflector_object(reflector: HandleObject, private: *const libc::c_void) {
let private_val = PrivateValue(private);
JS_SetReservedSlot(reflector.get(), DOM_OBJECT_SLOT, &private_val);
}
/// Choose the compartment to create a new global object in.
fn select_compartment(cx: SafeJSContext, options: &mut RealmOptions) {
type Data = *mut Compartment;

View File

@@ -178,7 +178,7 @@ use crate::dom::wheelevent::WheelEvent;
use crate::dom::window::{ReflowReason, Window};
use crate::dom::windowproxy::WindowProxy;
use crate::fetch::FetchCanceller;
use crate::realms::{AlreadyInRealm, InRealm};
use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
use crate::script_runtime::{CanGc, CommonScriptMsg, ScriptThreadEventCategory};
use crate::script_thread::{MainThreadScriptMsg, ScriptThread};
use crate::stylesheet_set::StylesheetSetRef;
@@ -486,6 +486,9 @@ pub struct Document {
visibility_state: Cell<DocumentVisibilityState>,
/// <https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml>
status_code: Option<u16>,
inhibit_load_and_pageshow: Cell<bool>,
window_replaced: Cell<bool>,
is_initial_about_blank: Cell<bool>,
}
#[derive(JSTraceable, MallocSizeOf)]
@@ -539,6 +542,10 @@ impl CollectionFilter for AnchorsFilter {
#[allow(non_snake_case)]
impl Document {
pub(crate) fn is_initial_about_blank(&self) -> bool {
self.is_initial_about_blank.get()
}
pub fn note_node_with_dirty_descendants(&self, node: &Node) {
debug_assert!(*node.owner_doc() == *self);
if !node.is_connected() {
@@ -708,7 +715,9 @@ impl Document {
ClientContextId::build(pipeline_id.namespace_id.0, pipeline_id.index.0.get());
if activity != DocumentActivity::FullyActive {
self.window().suspend();
if self.is_window_relevant() {
self.window().suspend();
}
media.suspend(&client_context_id);
return;
}
@@ -2320,8 +2329,10 @@ impl Document {
if !self.salvageable.get() {
// Step 1 of clean-up steps.
global_scope.close_event_sources();
let msg = ScriptMsg::DiscardDocument;
let _ = global_scope.script_to_constellation_chan().send(msg);
if self.is_window_relevant() {
let msg = ScriptMsg::DiscardDocument;
let _ = global_scope.script_to_constellation_chan().send(msg);
}
}
// https://w3c.github.io/FileAPI/#lifeTime
global_scope.clean_up_all_file_resources();
@@ -2358,17 +2369,20 @@ impl Document {
// Step 7.
debug!("Document loads are complete.");
let document = Trusted::new(self);
let was_initial_about_blank = self.is_initial_about_blank.get();
self.window
.task_manager()
.dom_manipulation_task_source()
.queue(
task!(fire_load_event: move || {
task!(prepare_for_post_load_tasks: move || {
let document = document.root();
let window = document.window();
if !window.is_alive() {
return;
}
notify_constellation_load(&window, was_initial_about_blank);
// Step 7.1.
document.set_ready_state(DocumentReadyState::Complete);
@@ -2387,37 +2401,19 @@ impl Document {
// http://w3c.github.io/navigation-timing/#widl-PerformanceNavigationTiming-loadEventStart
update_with_current_instant(&document.load_event_start);
debug!("About to dispatch load for {:?}", document.url());
// FIXME(nox): Why are errors silenced here?
let _ = window.dispatch_event_with_target_override(
&event,
);
if !document.inhibit_load_and_pageshow.get() {
debug!("About to dispatch load for {:?}", document.url());
// FIXME(nox): Why are errors silenced here?
let _ = window.dispatch_event_with_target_override(
&event,
);
}
// http://w3c.github.io/navigation-timing/#widl-PerformanceNavigationTiming-loadEventEnd
update_with_current_instant(&document.load_event_end);
if let Some(fragment) = document.url().fragment() {
document.check_and_scroll_fragment(fragment);
}
}),
self.window.upcast(),
)
.unwrap();
// Step 8.
let document = Trusted::new(self);
if document.root().browsing_context().is_some() {
self.window
.task_manager()
.dom_manipulation_task_source()
.queue(
task!(fire_pageshow_event: move || {
let document = document.root();
let window = document.window();
if document.page_showing.get() || !window.is_alive() {
return;
}
//assert!(!document.page_showing.get());
if !document.page_showing.get() {
document.page_showing.set(true);
let event = PageTransitionEvent::new(
@@ -2434,11 +2430,15 @@ impl Document {
let _ = window.dispatch_event_with_target_override(
event,
);
}),
self.window.upcast(),
)
.unwrap();
}
}
if let Some(fragment) = document.url().fragment() {
document.check_and_scroll_fragment(fragment);
}
}),
self.window.upcast(),
)
.unwrap();
// Step 9.
// TODO: pending application cache download process tasks.
@@ -2482,10 +2482,10 @@ impl Document {
}),
Duration::from_secs(*time),
);
}
};
// Note: this will, among others, result in the "iframe-load-event-steps" being run.
// https://html.spec.whatwg.org/multipage/#iframe-load-event-steps
document.notify_constellation_load();
//document.notify_constellation_load();
}),
self.window.upcast(),
)
@@ -2641,9 +2641,10 @@ impl Document {
.dom_manipulation_task_source()
.queue(
task!(fire_dom_content_loaded_event: move || {
let document = document.root();
document.upcast::<EventTarget>().fire_bubbling_event(atom!("DOMContentLoaded"));
update_with_current_instant(&document.dom_content_loaded_event_end);
let document = document.root();
let _realm = enter_realm(&*document);
document.upcast::<EventTarget>().fire_bubbling_event(atom!("DOMContentLoaded"));
update_with_current_instant(&document.dom_content_loaded_event_end);
}),
window.upcast(),
)
@@ -2701,10 +2702,6 @@ impl Document {
}
}
pub fn notify_constellation_load(&self) {
self.window().send_to_constellation(ScriptMsg::LoadComplete);
}
pub fn set_current_parser(&self, script: Option<&ServoParser>) {
self.current_parser.set(script);
}
@@ -3180,6 +3177,7 @@ impl Document {
referrer_policy: Option<ReferrerPolicy>,
status_code: Option<u16>,
canceller: FetchCanceller,
inhibit_load_and_pageshow: bool,
) -> Document {
let url = url.unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap());
@@ -3206,6 +3204,7 @@ impl Document {
.and_then(|charset| Encoding::for_label(charset.as_str().as_bytes()))
.unwrap_or(UTF_8);
let is_initial_about_blank = url.as_str() == "about:blank";
let has_browsing_context = has_browsing_context == HasBrowsingContext::Yes;
Document {
node: Node::new_document_node(),
@@ -3327,9 +3326,20 @@ impl Document {
fonts: Default::default(),
visibility_state: Cell::new(DocumentVisibilityState::Hidden),
status_code,
inhibit_load_and_pageshow: Cell::new(inhibit_load_and_pageshow),
window_replaced: Cell::new(false),
is_initial_about_blank: Cell::new(is_initial_about_blank),
}
}
pub(crate) fn disown_window(&self) {
self.window_replaced.set(true);
}
pub(crate) fn is_window_relevant(&self) -> bool {
!self.window_replaced.get()
}
/// Note a pending animation tick, to be processed at the next `update_the_rendering` task.
pub fn note_pending_animation_tick(&self, tick_type: AnimationTickType) {
self.pending_animation_ticks.borrow_mut().extend(tick_type);
@@ -3475,6 +3485,7 @@ impl Document {
None,
Default::default(),
can_gc,
false,
))
}
@@ -3495,6 +3506,7 @@ impl Document {
status_code: Option<u16>,
canceller: FetchCanceller,
can_gc: CanGc,
inhibit_load_and_pageshow: bool,
) -> DomRoot<Document> {
Self::new_with_proto(
window,
@@ -3513,6 +3525,7 @@ impl Document {
status_code,
canceller,
can_gc,
inhibit_load_and_pageshow,
)
}
@@ -3534,6 +3547,7 @@ impl Document {
status_code: Option<u16>,
canceller: FetchCanceller,
can_gc: CanGc,
inhibit_load_and_pageshow: bool,
) -> DomRoot<Document> {
let document = reflect_dom_object_with_proto(
Box::new(Document::new_inherited(
@@ -3551,6 +3565,7 @@ impl Document {
referrer_policy,
status_code,
canceller,
inhibit_load_and_pageshow,
)),
window,
proto,
@@ -3676,6 +3691,7 @@ impl Document {
None,
Default::default(),
can_gc,
false,
);
new_doc
.appropriate_template_contents_owner_document
@@ -5277,12 +5293,16 @@ impl DocumentMethods for Document {
self.set_url(new_url);
}
self.inhibit_load_and_pageshow.set(false);
// Step 13
// TODO: https://github.com/servo/servo/issues/21938
// Step 14
self.set_quirks_mode(QuirksMode::NoQuirks);
self.is_initial_about_blank.set(false);
// Step 15
let resource_threads = self
.window
@@ -5652,3 +5672,8 @@ fn is_named_element_with_id_attribute(elem: &Element) -> bool {
// behaviour is actually implemented
elem.is::<HTMLImageElement>() && elem.get_name().is_some_and(|name| !name.is_empty())
}
fn notify_constellation_load(window: &Window, is_initial_about_blank: bool) {
window.send_to_constellation(ScriptMsg::LoadComplete(is_initial_about_blank));
}

View File

@@ -160,6 +160,7 @@ impl DOMImplementationMethods for DOMImplementation {
None,
Default::default(),
can_gc,
false,
);
{

View File

@@ -89,6 +89,7 @@ impl DOMParserMethods for DOMParser {
None,
Default::default(),
can_gc,
false,
);
ServoParser::parse_html_document(&document, Some(s), url, CanGc::note());
document.set_ready_state(DocumentReadyState::Complete);
@@ -111,6 +112,7 @@ impl DOMParserMethods for DOMParser {
None,
Default::default(),
can_gc,
false,
);
ServoParser::parse_xml_document(&document, Some(s), url, CanGc::note());
document.set_ready_state(DocumentReadyState::Complete);

View File

@@ -3,10 +3,10 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::borrow::Cow;
use std::cell::Cell;
use std::cell::{Cell, RefCell, Ref};
use std::collections::hash_map::Entry;
use std::collections::{HashMap, VecDeque};
use std::ops::Index;
use std::ops::{Deref, Index};
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
@@ -208,7 +208,7 @@ pub struct GlobalScope {
/// Pipeline id associated with this global.
#[no_trace]
pipeline_id: PipelineId,
pipeline_id: Cell<PipelineId>,
/// A flag to indicate whether the developer tools has requested
/// live updates from the worker.
@@ -243,7 +243,7 @@ pub struct GlobalScope {
/// A handle for communicating messages to the constellation thread.
#[ignore_malloc_size_of = "channels are hard"]
#[no_trace]
script_to_constellation_chan: ScriptToConstellationChan,
script_to_constellation_chan: RefCell<ScriptToConstellationChan>,
#[ignore_malloc_size_of = "channels are hard"]
#[no_trace]
@@ -266,11 +266,11 @@ pub struct GlobalScope {
/// The origin of the globalscope
#[no_trace]
origin: MutableOrigin,
origin: RefCell<MutableOrigin>,
/// <https://html.spec.whatwg.org/multipage/#concept-environment-creation-url>
#[no_trace]
creation_url: Option<ServoUrl>,
creation_url: RefCell<Option<ServoUrl>>,
/// A map for storing the previous permission state read results.
permission_state_invocation_results: DomRefCell<HashMap<String, PermissionState>>,
@@ -779,7 +779,7 @@ impl GlobalScope {
crypto: Default::default(),
registration_map: DomRefCell::new(HashMapTracedValues::new()),
worker_map: DomRefCell::new(HashMapTracedValues::new()),
pipeline_id,
pipeline_id: Cell::new(pipeline_id),
devtools_wants_updates: Default::default(),
console_timers: DomRefCell::new(Default::default()),
module_map: DomRefCell::new(Default::default()),
@@ -787,14 +787,14 @@ impl GlobalScope {
devtools_chan,
mem_profiler_chan,
time_profiler_chan,
script_to_constellation_chan,
script_to_constellation_chan: RefCell::new(script_to_constellation_chan),
scheduler_chan: scheduler_chan.clone(),
in_error_reporting_mode: Default::default(),
resource_threads,
timers: OneshotTimers::new(scheduler_chan),
init_timers: Default::default(),
origin,
creation_url,
origin: RefCell::new(origin),
creation_url: RefCell::new(creation_url),
permission_state_invocation_results: Default::default(),
microtask_queue,
list_auto_close_worker: Default::default(),
@@ -2318,7 +2318,7 @@ impl GlobalScope {
pub fn issue_page_warning(&self, warning: &str) {
if let Some(ref chan) = self.devtools_chan {
let _ = chan.send(ScriptToDevtoolsControlMsg::ReportPageError(
self.pipeline_id,
self.pipeline_id.get(),
PageError {
type_: "PageError".to_string(),
error_message: warning.to_string(),
@@ -2352,8 +2352,8 @@ impl GlobalScope {
}
/// Get a sender to the constellation thread.
pub fn script_to_constellation_chan(&self) -> &ScriptToConstellationChan {
&self.script_to_constellation_chan
pub fn script_to_constellation_chan(&self) -> Ref<ScriptToConstellationChan> {
self.script_to_constellation_chan.borrow()
}
pub fn send_to_embedder(&self, msg: EmbedderMsg) {
@@ -2370,17 +2370,17 @@ impl GlobalScope {
/// Get the `PipelineId` for this global scope.
pub fn pipeline_id(&self) -> PipelineId {
self.pipeline_id
self.pipeline_id.get()
}
/// Get the origin for this global scope
pub fn origin(&self) -> &MutableOrigin {
&self.origin
pub fn origin(&self) -> Ref<MutableOrigin> {
self.origin.borrow()
}
/// Get the creation_url for this global scope
pub fn creation_url(&self) -> &Option<ServoUrl> {
&self.creation_url
pub fn creation_url(&self) -> Ref<Option<ServoUrl>> {
self.creation_url.borrow()
}
pub fn image_cache(&self) -> Arc<dyn ImageCache> {
@@ -2513,7 +2513,7 @@ impl GlobalScope {
} else if self.is::<Window>() {
if let Some(ref chan) = self.devtools_chan {
let _ = chan.send(ScriptToDevtoolsControlMsg::ReportPageError(
self.pipeline_id,
self.pipeline_id.get(),
PageError {
type_: "PageError".to_string(),
error_message: error_info.message.clone(),
@@ -3129,7 +3129,7 @@ impl GlobalScope {
if Some(false) == self.inherited_secure_context {
return false;
}
if let Some(creation_url) = self.creation_url() {
if let Some(creation_url) = self.creation_url().deref() {
if creation_url.scheme() == "blob" && Some(true) == self.inherited_secure_context {
return true;
}
@@ -3400,6 +3400,27 @@ impl GlobalScope {
Ok(message_clone.get())
}
pub(crate) fn replace_contents(&self, data: ReplaceData) {
let ReplaceData {
pipeline_id,
script_to_constellation_chan,
creator_url,
origin,
} = data;
self.pipeline_id.set(pipeline_id);
*self.script_to_constellation_chan.borrow_mut() = script_to_constellation_chan;
*self.creation_url.borrow_mut() = Some(creator_url);
*self.origin.borrow_mut() = origin;
self.timers.reset();
}
}
pub(crate) struct ReplaceData {
pub pipeline_id: PipelineId,
pub script_to_constellation_chan: ScriptToConstellationChan,
pub creator_url: ServoUrl,
pub origin: MutableOrigin,
}
/// Returns the Rust global scope from a JS global object.

View File

@@ -63,6 +63,7 @@ use crate::dom::htmldatalistelement::HTMLDataListElement;
use crate::dom::htmlelement::HTMLElement;
use crate::dom::htmlfieldsetelement::HTMLFieldSetElement;
use crate::dom::htmlformcontrolscollection::HTMLFormControlsCollection;
use crate::dom::htmliframeelement::HTMLIFrameElement;
use crate::dom::htmlimageelement::HTMLImageElement;
use crate::dom::htmlinputelement::{HTMLInputElement, InputType};
use crate::dom::htmllabelelement::HTMLLabelElement;
@@ -831,6 +832,8 @@ impl HTMLFormElement {
target_window.upcast::<GlobalScope>().get_referrer(),
target_document.get_referrer_policy(),
Some(target_window.upcast::<GlobalScope>().is_secure_context()),
(doc.url().as_str() == "about:blank").then(|| doc.window().pipeline_id()),
false,
);
// Step 22
@@ -988,6 +991,14 @@ impl HTMLFormElement {
load_data.referrer = referrer;
load_data.referrer_policy = referrer_policy;
let new_pipeline_id = load_data.new_pipeline_id.clone();
let proxy = target.window_proxy();
if let Some(frame) = proxy.frame_element() {
if let Some(frame) = frame.downcast::<HTMLIFrameElement>() {
frame.update_pending_pipeline_id(new_pipeline_id);
}
}
// Step 4.
let this = Trusted::new(self);
let window = Trusted::new(target);

View File

@@ -8,6 +8,7 @@ use base::id::{BrowsingContextId, PipelineId, TopLevelBrowsingContextId};
use bitflags::bitflags;
use dom_struct::dom_struct;
use html5ever::{local_name, namespace_url, ns, LocalName, Prefix};
use js::jsapi::JSAutoRealm;
use js::rust::HandleObject;
use profile_traits::ipc as ProfiledIpc;
use script_traits::IFrameSandboxState::{IFrameSandboxed, IFrameUnsandboxed};
@@ -40,6 +41,8 @@ use crate::dom::node::{
};
use crate::dom::virtualmethods::VirtualMethods;
use crate::dom::windowproxy::WindowProxy;
use crate::microtask::{Microtask, MicrotaskRunnable};
use crate::realms::enter_realm;
use crate::script_runtime::CanGc;
use crate::script_thread::ScriptThread;
@@ -177,7 +180,7 @@ impl HTMLIFrameElement {
let window = window_from_node(self);
let old_pipeline_id = self.pipeline_id();
let new_pipeline_id = PipelineId::new();
let new_pipeline_id = load_data.new_pipeline_id.clone();
self.pending_pipeline_id.set(Some(new_pipeline_id));
let global_scope = window.upcast::<GlobalScope>();
@@ -185,7 +188,6 @@ impl HTMLIFrameElement {
parent_pipeline_id: global_scope.pipeline_id(),
browsing_context_id,
top_level_browsing_context_id,
new_pipeline_id,
is_private: false, // FIXME
inherited_secure_context: load_data.inherited_secure_context,
replace,
@@ -261,6 +263,8 @@ impl HTMLIFrameElement {
window.upcast::<GlobalScope>().get_referrer(),
document.get_referrer_policy(),
Some(window.upcast::<GlobalScope>().is_secure_context()),
None, //XXXjdm
false,
);
let element = self.upcast::<Element>();
load_data.srcdoc = String::from(element.get_string_attribute(&local_name!("srcdoc")));
@@ -288,9 +292,14 @@ impl HTMLIFrameElement {
}
}
if mode == ProcessingMode::FirstTime &&
!self.upcast::<Element>().has_attribute(&local_name!("src"))
{
let element = self.upcast::<Element>();
let src = element.get_string_attribute(&local_name!("src"));
if mode == ProcessingMode::FirstTime && (src.is_empty() || src == "about:blank") {
let task = IframeElementMicrotask {
elem: DomRoot::from_ref(self),
about_blank_pipeline: self.about_blank_pipeline_id.get().unwrap(),
};
ScriptThread::await_stable_state(Microtask::IframeElement(task));
return;
}
@@ -332,6 +341,9 @@ impl HTMLIFrameElement {
};
let document = document_from_node(self);
let pipeline_id = self.pipeline_id();
let is_about_blank = pipeline_id.is_some() && pipeline_id == self.about_blank_pipeline_id.get();
let replaced_pipeline = is_about_blank.then(|| self.about_blank_pipeline_id.get().unwrap());
let load_data = LoadData::new(
LoadOrigin::Script(document.origin().immutable().clone()),
url,
@@ -339,13 +351,12 @@ impl HTMLIFrameElement {
window.upcast::<GlobalScope>().get_referrer(),
document.get_referrer_policy(),
Some(window.upcast::<GlobalScope>().is_secure_context()),
replaced_pipeline,
false,
);
let pipeline_id = self.pipeline_id();
// If the initial `about:blank` page is the current page, load with replacement enabled,
// see https://html.spec.whatwg.org/multipage/#the-iframe-element:about:blank-3
let is_about_blank =
pipeline_id.is_some() && pipeline_id == self.about_blank_pipeline_id.get();
let replace = if is_about_blank {
HistoryEntryReplacement::Enabled
} else {
@@ -381,6 +392,8 @@ impl HTMLIFrameElement {
window.upcast::<GlobalScope>().get_referrer(),
document.get_referrer_policy(),
Some(window.upcast::<GlobalScope>().is_secure_context()),
None,
true,
);
let browsing_context_id = BrowsingContextId::new();
let top_level_browsing_context_id = window.window_proxy().top_level_browsing_context_id();
@@ -405,6 +418,10 @@ impl HTMLIFrameElement {
self.browsing_context_id.set(None);
}
pub(crate) fn update_pending_pipeline_id(&self, pending: PipelineId) {
self.pending_pipeline_id.set(Some(pending));
}
pub fn update_pipeline_id(
&self,
new_pipeline_id: PipelineId,
@@ -488,13 +505,24 @@ impl HTMLIFrameElement {
}
/// <https://html.spec.whatwg.org/multipage/#iframe-load-event-steps> steps 1-4
pub fn iframe_load_event_steps(&self, loaded_pipeline: PipelineId, can_gc: CanGc) {
pub fn iframe_load_event_steps(
&self,
loaded_pipeline: PipelineId,
can_gc: CanGc,
dispatch_load_for_about_blank: bool,
) {
// TODO(#9592): assert that the load blocker is present at all times when we
// can guarantee that it's created for the case of iframe.reload().
if Some(loaded_pipeline) != self.pending_pipeline_id.get() {
return;
}
if Some(loaded_pipeline) == self.about_blank_pipeline_id.get() &&
!dispatch_load_for_about_blank
{
return;
}
// TODO A cross-origin child document would not be easily accessible
// from this script thread. It's unclear how to implement
// steps 2, 3, and 5 efficiently in this case.
@@ -788,3 +816,21 @@ impl VirtualMethods for HTMLIFrameElement {
self.destroy_nested_browsing_context();
}
}
#[derive(JSTraceable, MallocSizeOf)]
pub struct IframeElementMicrotask {
elem: DomRoot<HTMLIFrameElement>,
#[no_trace]
about_blank_pipeline: PipelineId,
}
impl MicrotaskRunnable for IframeElementMicrotask {
fn handler(&self, can_gc: CanGc) {
self.elem
.iframe_load_event_steps(self.about_blank_pipeline, can_gc, true);
}
fn enter_realm(&self) -> JSAutoRealm {
enter_realm(&*self.elem)
}
}

View File

@@ -2,6 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Ref;
use dom_struct::dom_struct;
use net_traits::request::Referrer;
use script_traits::{HistoryEntryReplacement, LoadData, LoadOrigin};
@@ -123,6 +125,8 @@ impl Location {
referrer,
referrer_policy,
None, // Top navigation doesn't inherit secure context
(self.window.Document().url().as_str() == "about:blank").then(|| self.window.pipeline_id()),
false,
);
self.window
.load_url(replacement_flag, reload_triggered, load_data);
@@ -249,7 +253,7 @@ impl Location {
}
#[allow(dead_code)]
pub fn origin(&self) -> &MutableOrigin {
pub fn origin(&self) -> Ref<MutableOrigin> {
self.window.origin()
}
}
@@ -389,11 +393,14 @@ impl LocationMethods for Location {
if self.has_document() {
// Note: no call to self.check_same_origin_domain()
// Step 2: Parse the given value relative to the entry settings object.
// If that failed, throw a TypeError exception.
// If that failed, throw a SyntaxError exception.
let base_url = self.entry_settings_object().api_base_url();
let url = match base_url.join(&value.0) {
Ok(url) => url,
Err(e) => return Err(Error::Type(format!("Couldn't parse URL: {}", e))),
Err(e) => {
info!("Couldn't parse URL: {}", e);
return Err(Error::Syntax);
}
};
// Step 3: Location-object navigate to the resulting URL record.
self.navigate(

View File

@@ -2299,6 +2299,7 @@ impl Node {
document.status_code(),
Default::default(),
CanGc::note(),
false,
);
DomRoot::upcast::<Node>(document)
},

View File

@@ -216,6 +216,7 @@ impl ServoParser {
None,
Default::default(),
can_gc,
false,
);
// Step 2.

View File

@@ -3,7 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::borrow::{Cow, ToOwned};
use std::cell::{Cell, RefCell, RefMut};
use std::cell::{Cell, RefCell, RefMut, Ref as StdRef};
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::default::Default;
@@ -185,7 +185,7 @@ pub struct Window {
globalscope: GlobalScope,
#[ignore_malloc_size_of = "trait objects are hard"]
script_chan: MainThreadScriptChan,
task_manager: TaskManager,
task_manager: RefCell<TaskManager>,
#[no_trace]
#[ignore_malloc_size_of = "TODO: Add MallocSizeOf support to layout"]
layout: RefCell<Box<dyn Layout>>,
@@ -357,8 +357,8 @@ pub struct Window {
}
impl Window {
pub fn task_manager(&self) -> &TaskManager {
&self.task_manager
pub fn task_manager(&self) -> StdRef<TaskManager> {
self.task_manager.borrow()
}
pub fn layout(&self) -> Ref<Box<dyn Layout>> {
@@ -405,7 +405,8 @@ impl Window {
/// Cancel all current, and ignore all subsequently queued, tasks.
pub fn ignore_all_tasks(&self) {
let mut ignore_flags = self.task_manager.task_cancellers.borrow_mut();
let task_manager = self.task_manager.borrow();
let mut ignore_flags = task_manager.task_cancellers.borrow_mut();
for task_source_name in TaskSourceName::all() {
let flag = ignore_flags.entry(*task_source_name).or_default();
flag.store(true, Ordering::SeqCst);
@@ -417,7 +418,7 @@ impl Window {
self.globalscope.time_profiler_chan()
}
pub fn origin(&self) -> &MutableOrigin {
pub fn origin(&self) -> Ref<MutableOrigin> {
self.globalscope.origin()
}
@@ -1635,7 +1636,8 @@ impl Window {
/// This sets the current `task_manager.task_cancellers` sentinel value to
/// `true` and replaces it with a brand new one for future tasks.
pub fn cancel_all_tasks(&self) {
let mut ignore_flags = self.task_manager.task_cancellers.borrow_mut();
let task_manager = self.task_manager.borrow();
let mut ignore_flags = task_manager.task_cancellers.borrow_mut();
for task_source_name in TaskSourceName::all() {
let flag = ignore_flags.entry(*task_source_name).or_default();
let cancelled = std::mem::take(&mut *flag);
@@ -1647,7 +1649,8 @@ impl Window {
/// This sets the current sentinel value to
/// `true` and replaces it with a brand new one for future tasks.
pub fn cancel_all_tasks_from_source(&self, task_source_name: TaskSourceName) {
let mut ignore_flags = self.task_manager.task_cancellers.borrow_mut();
let task_manager = self.task_manager.borrow();
let mut ignore_flags = task_manager.task_cancellers.borrow_mut();
let flag = ignore_flags.entry(task_source_name).or_default();
let cancelled = std::mem::take(&mut *flag);
cancelled.store(true, Ordering::SeqCst);
@@ -2207,13 +2210,13 @@ impl Window {
#[allow(unsafe_code)]
pub fn init_window_proxy(&self, window_proxy: &WindowProxy) {
assert!(self.window_proxy.get().is_none());
//assert!(self.window_proxy.get().is_none());
self.window_proxy.set(Some(window_proxy));
}
#[allow(unsafe_code)]
pub fn init_document(&self, document: &Document) {
assert!(self.document.get().is_none());
//assert!(self.document.get().is_none());
assert!(document.window() == self);
self.document.set(Some(document));
if !self.unminify_js {
@@ -2267,6 +2270,7 @@ impl Window {
ScriptThreadEventCategory::DomEvent,
Box::new(
self.task_manager
.borrow()
.task_canceller(TaskSourceName::DOMManipulation)
.wrap_task(task),
),
@@ -2300,7 +2304,7 @@ impl Window {
// TODO: step 11, navigationType.
// Step 12, 13
ScriptThread::navigate(
window_proxy.browsing_context_id(),
&window_proxy,
pipeline_id,
load_data,
replace,
@@ -2589,7 +2593,7 @@ impl Window {
inherited_secure_context,
),
script_chan,
task_manager,
task_manager: RefCell::new(task_manager),
layout: RefCell::new(layout),
image_cache_chan,
image_cache,
@@ -2646,7 +2650,21 @@ impl Window {
current_event: DomRefCell::new(None),
});
unsafe { WindowBinding::Wrap(JSContext::from_ptr(runtime.cx()), win) }
/*if let Some(reflector) = existing_window_reflector {
unsafe {
let raw = Root::new(MaybeUnreflectedDom::from_box(win));
let jsobject = reflector.get_jsobject();
let old_window: *const Window = crate::dom::bindings::conversions::native_from_object_static(jsobject.get()).unwrap();
set_reflector_object(jsobject, raw.as_ptr() as *const _);
let root = raw.reflect_with(jsobject.get());
let old_window_box = Box::from_raw(old_window as *mut _);
// create a new reflector for the replaced window object.
let _ = WindowBinding::Wrap(JSContext::from_ptr(runtime.cx()), old_window_box);
root
}
} else {*/
unsafe { WindowBinding::Wrap(JSContext::from_ptr(runtime.cx()), win) }
//}
}
pub fn pipeline_id(&self) -> PipelineId {
@@ -2660,6 +2678,27 @@ impl Window {
{
LayoutValue::new(self.layout_marker.borrow().clone(), value)
}
pub(crate) fn replace_contents(&self, data: ReplaceData) {
self.globalscope.replace_contents(crate::dom::globalscope::ReplaceData {
pipeline_id: data.pipeline_id,
script_to_constellation_chan: data.script_to_constellation_chan,
creator_url: data.creator_url,
origin: data.origin,
});
*self.task_manager.borrow_mut() = data.task_manager;
*self.layout.borrow_mut() = data.layout;
self.has_sent_idle_message.set(false);
}
}
pub(crate) struct ReplaceData {
pub script_to_constellation_chan: ScriptToConstellationChan,
pub task_manager: TaskManager,
pub layout: Box<dyn Layout>,
pub pipeline_id: PipelineId,
pub creator_url: ServoUrl,
pub origin: MutableOrigin,
}
/// An instance of a value associated with a particular snapshot of layout. This stored
@@ -2798,6 +2837,7 @@ impl Window {
ScriptThreadEventCategory::DomEvent,
Box::new(
self.task_manager
.borrow()
.task_canceller(TaskSourceName::DOMManipulation)
.wrap_task(task),
),

View File

@@ -298,7 +298,6 @@ impl WindowProxy {
let new_top_level_browsing_context_id = TopLevelBrowsingContextId::new();
let new_browsing_context_id =
BrowsingContextId::from(new_top_level_browsing_context_id);
let new_pipeline_id = PipelineId::new();
let document = self
.currently_active
.get()
@@ -313,13 +312,15 @@ impl WindowProxy {
document.global().get_referrer(),
document.get_referrer_policy(),
None, // Doesn't inherit secure context
None,
true,
);
let new_pipeline_id = load_data.new_pipeline_id.clone();
let load_info = AuxiliaryBrowsingContextLoadInfo {
load_data: load_data.clone(),
opener_pipeline_id: self.currently_active.get().unwrap(),
new_browsing_context_id,
new_top_level_browsing_context_id,
new_pipeline_id,
};
let new_layout_info = NewLayoutInfo {
@@ -500,7 +501,7 @@ impl WindowProxy {
.and_then(ScriptThread::find_document)
.unwrap();
// Step 14.1
let url = match existing_document.url().join(&url) {
let url = match existing_document.base_url().join(&url) {
Ok(url) => url,
Err(_) => return Err(Error::Syntax),
};
@@ -514,6 +515,7 @@ impl WindowProxy {
let referrer_policy = target_document.get_referrer_policy();
let pipeline_id = target_window.upcast::<GlobalScope>().pipeline_id();
let secure = target_window.upcast::<GlobalScope>().is_secure_context();
let is_about_blank = target_document.url().as_str() == "about:blank";
let load_data = LoadData::new(
LoadOrigin::Script(existing_document.origin().immutable().clone()),
url,
@@ -521,6 +523,8 @@ impl WindowProxy {
referrer,
referrer_policy,
Some(secure),
is_about_blank.then_some(pipeline_id),
false,
);
let replacement_flag = if new {
HistoryEntryReplacement::Enabled

View File

@@ -57,6 +57,7 @@ impl XMLDocument {
None,
None,
Default::default(),
false,
),
}
}

View File

@@ -1544,6 +1544,7 @@ impl XMLHttpRequest {
None,
Default::default(),
can_gc,
false,
)
}

View File

@@ -20,7 +20,7 @@ use crate::dom::htmlareaelement::HTMLAreaElement;
use crate::dom::htmlformelement::HTMLFormElement;
use crate::dom::htmllinkelement::HTMLLinkElement;
use crate::dom::node::document_from_node;
use crate::dom::types::{Element, GlobalScope};
use crate::dom::types::{Element, GlobalScope, HTMLIFrameElement};
use crate::task_source::TaskSource;
bitflags::bitflags! {
@@ -428,7 +428,17 @@ pub fn follow_hyperlink(
referrer,
referrer_policy,
Some(secure),
(document.url().as_str() == "about:blank").then(|| document.window().pipeline_id()),
false,
);
let new_pipeline_id = load_data.new_pipeline_id.clone();
if let Some(frame) = chosen.frame_element() {
if let Some(frame) = frame.downcast::<HTMLIFrameElement>() {
frame.update_pending_pipeline_id(new_pipeline_id);
}
}
let target = Trusted::new(target_window);
let task = task!(navigate_follow_hyperlink: move || {
debug!("following hyperlink to {}", load_data.url);

View File

@@ -19,6 +19,7 @@ use crate::dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback;
use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction;
use crate::dom::bindings::root::DomRoot;
use crate::dom::globalscope::GlobalScope;
use crate::dom::htmliframeelement::IframeElementMicrotask;
use crate::dom::htmlimageelement::ImageElementMicrotask;
use crate::dom::htmlmediaelement::MediaElementMicrotask;
use crate::dom::mutationobserver::MutationObserver;
@@ -41,6 +42,7 @@ pub enum Microtask {
User(UserMicrotask),
MediaElement(MediaElementMicrotask),
ImageElement(ImageElementMicrotask),
IframeElement(IframeElementMicrotask),
CustomElementReaction,
NotifyMutationObservers,
}
@@ -134,6 +136,10 @@ impl MicrotaskQueue {
let _realm = task.enter_realm();
task.handler(can_gc);
},
Microtask::IframeElement(ref task) => {
let _realm = task.enter_realm();
task.handler(can_gc);
},
Microtask::CustomElementReaction => {
ScriptThread::invoke_backup_element_queue(can_gc);
},

View File

@@ -221,6 +221,9 @@ struct InProgressLoad {
canceller: FetchCanceller,
/// If inheriting the security context
inherited_secure_context: Option<bool>,
///
#[no_trace]
replacing_pipeline: Option<PipelineId>,
}
impl InProgressLoad {
@@ -236,6 +239,7 @@ impl InProgressLoad {
url: ServoUrl,
origin: MutableOrigin,
inherited_secure_context: Option<bool>,
replacing_pipeline: Option<PipelineId>,
) -> InProgressLoad {
let navigation_start = CrossProcessInstant::now();
InProgressLoad {
@@ -252,6 +256,7 @@ impl InProgressLoad {
navigation_start,
canceller: Default::default(),
inherited_secure_context,
replacing_pipeline,
}
}
}
@@ -834,6 +839,7 @@ impl ScriptThreadFactory for ScriptThread {
load_data.url.clone(),
origin,
secure,
None,
);
script_thread.pre_page_load(new_load, load_data);
@@ -995,7 +1001,8 @@ impl ScriptThread {
/// Step 13 of <https://html.spec.whatwg.org/multipage/#navigate>
pub fn navigate(
browsing_context: BrowsingContextId,
//browsing_context: BrowsingContextId,
window_proxy: &WindowProxy,
pipeline_id: PipelineId,
mut load_data: LoadData,
replace: HistoryEntryReplacement,
@@ -1007,6 +1014,7 @@ impl ScriptThread {
};
let script_thread = unsafe { &*script_thread };
let is_javascript = load_data.url.scheme() == "javascript";
let browsing_context = window_proxy.browsing_context_id();
// If resource is a request whose url's scheme is "javascript"
// https://html.spec.whatwg.org/multipage/#javascript-protocol
if is_javascript {
@@ -1040,6 +1048,14 @@ impl ScriptThread {
));
}
if let Some(frame) = window_proxy.frame_element() {
if let Some(frame) = frame.downcast::<HTMLIFrameElement>() {
frame.update_pending_pipeline_id(
load_data.new_pipeline_id.clone(),
);
}
}
script_thread
.script_sender
.send((pipeline_id, ScriptMsg::LoadUrl(load_data, replace)))
@@ -1843,6 +1859,11 @@ impl ScriptThread {
ScriptThreadEventCategory::AttachLayout,
Some(pipeline_id),
|| {
if new_layout_info.load_data.synchronously_loaded {
assert_eq!(new_layout_info.load_data.url.as_str(), "about:blank");
return;
}
// If this is an about:blank or about:srcdoc load, it must share the
// creator's origin. This must match the logic in the constellation
// when creating a new pipeline
@@ -2173,9 +2194,8 @@ impl ScriptThread {
TickAllAnimations(id, ..) => Some(id),
WebFontLoaded(id, ..) => Some(id),
DispatchIFrameLoadEvent {
target: _,
parent: id,
child: _,
..
} => Some(id),
DispatchStorageEvent(id, ..) => Some(id),
ReportCSSError(id, ..) => Some(id),
@@ -2380,7 +2400,8 @@ impl ScriptThread {
target: browsing_context_id,
parent: parent_id,
child: child_id,
} => self.handle_iframe_load_event(parent_id, browsing_context_id, child_id, can_gc),
is_initial_about_blank,
} => self.handle_iframe_load_event(parent_id, browsing_context_id, child_id, is_initial_about_blank, can_gc),
ConstellationControlMsg::DispatchStorageEvent(
pipeline_id,
storage,
@@ -2919,6 +2940,7 @@ impl ScriptThread {
load_data.url.clone(),
origin,
load_data.inherited_secure_context,
load_data.replacing_pipeline,
);
if load_data.url.as_str() == "about:blank" {
self.start_page_load_about_blank(new_load, load_data.js_eval_result);
@@ -3306,8 +3328,10 @@ impl ScriptThread {
parser.abort(can_gc);
}
debug!("{id}: Shutting down layout");
document.window().layout_mut().exit_now();
if document.is_window_relevant() {
debug!("{id}: Shutting down layout");
document.window().layout_mut().exit_now();
}
debug!("{id}: Sending PipelineExited message to constellation");
self.script_sender
@@ -3325,15 +3349,17 @@ impl ScriptThread {
}
}
// We discard the browsing context after requesting layout shut down,
// to avoid running layout on detached iframes.
let window = document.window();
if discard_bc == DiscardBrowsingContext::Yes {
window.discard_browsing_context();
}
if document.is_window_relevant() {
// We discard the browsing context after requesting layout shut down,
// to avoid running layout on detached iframes.
let window = document.window();
if discard_bc == DiscardBrowsingContext::Yes {
window.discard_browsing_context();
}
debug!("{id}: Clearing JavaScript runtime");
window.clear_js_runtime();
debug!("{id}: Clearing JavaScript runtime");
window.clear_js_runtime();
}
}
debug!("{id}: Finished pipeline exit");
@@ -3441,6 +3467,7 @@ impl ScriptThread {
parent_id: PipelineId,
browsing_context_id: BrowsingContextId,
child_id: PipelineId,
is_initial_about_blank: bool,
can_gc: CanGc,
) {
let iframe = self
@@ -3448,7 +3475,7 @@ impl ScriptThread {
.borrow()
.find_iframe(parent_id, browsing_context_id);
match iframe {
Some(iframe) => iframe.iframe_load_event_steps(child_id, can_gc),
Some(iframe) => iframe.iframe_load_event_steps(child_id, can_gc, !is_initial_about_blank),
None => warn!("Message sent to closed pipeline {}.", parent_id),
}
}
@@ -3666,44 +3693,62 @@ impl ScriptThread {
};
// Create the window and document objects.
let window = Window::new(
self.js_runtime.clone(),
MainThreadScriptChan(sender.clone()),
task_manager,
self.layout_factory.create(layout_config),
self.image_cache_channel.clone(),
self.image_cache.clone(),
self.resource_threads.clone(),
self.bluetooth_thread.clone(),
self.mem_profiler_chan.clone(),
self.time_profiler_chan.clone(),
self.devtools_chan.clone(),
script_to_constellation_chan,
self.control_chan.clone(),
self.scheduler_chan.clone(),
incomplete.pipeline_id,
incomplete.parent_info,
incomplete.window_size,
origin.clone(),
final_url.clone(),
incomplete.navigation_start,
self.webgl_chan.as_ref().map(|chan| chan.channel()),
self.webxr_registry.clone(),
self.microtask_queue.clone(),
self.webrender_document,
self.webrender_api_sender.clone(),
self.relayout_event,
self.prepare_for_screenshot,
self.unminify_js,
self.local_script_source.clone(),
self.userscripts_path.clone(),
self.headless,
self.replace_surrogates,
self.user_agent.clone(),
self.player_context.clone(),
self.gpu_id_hub.clone(),
incomplete.inherited_secure_context,
);
let old_document = incomplete.replacing_pipeline.and_then(ScriptThread::find_document);
let replacement_same_origin = old_document.as_ref().map_or(false, |doc| {
let old_origin = doc.origin();
final_url.origin().same_origin(old_origin)
});
let window = if let (Some(old_document), true) = (old_document, replacement_same_origin) {
old_document.window().replace_contents(crate::dom::window::ReplaceData {
script_to_constellation_chan,
task_manager,
layout: self.layout_factory.create(layout_config),
pipeline_id: incomplete.pipeline_id,
creator_url: final_url.clone(),
origin: origin.clone(),
});
old_document.disown_window();
DomRoot::from_ref(old_document.window())
} else {
Window::new(
self.js_runtime.clone(),
MainThreadScriptChan(sender.clone()),
task_manager,
self.layout_factory.create(layout_config),
self.image_cache_channel.clone(),
self.image_cache.clone(),
self.resource_threads.clone(),
self.bluetooth_thread.clone(),
self.mem_profiler_chan.clone(),
self.time_profiler_chan.clone(),
self.devtools_chan.clone(),
script_to_constellation_chan,
self.control_chan.clone(),
self.scheduler_chan.clone(),
incomplete.pipeline_id,
incomplete.parent_info,
incomplete.window_size,
origin.clone(),
final_url.clone(),
incomplete.navigation_start,
self.webgl_chan.as_ref().map(|chan| chan.channel()),
self.webxr_registry.clone(),
self.microtask_queue.clone(),
self.webrender_document,
self.webrender_api_sender.clone(),
self.relayout_event,
self.prepare_for_screenshot,
self.unminify_js,
self.local_script_source.clone(),
self.userscripts_path.clone(),
self.headless,
self.replace_surrogates,
self.user_agent.clone(),
self.player_context.clone(),
self.gpu_id_hub.clone(),
incomplete.inherited_secure_context,
)
};
let _realm = enter_realm(&*window);
@@ -3715,6 +3760,7 @@ impl ScriptThread {
incomplete.parent_info,
incomplete.opener,
);
window_proxy.set_currently_active(&window);
if window_proxy.parent().is_some() {
// https://html.spec.whatwg.org/multipage/#navigating-across-documents:delaying-load-events-mode-2
// The user agent must take this nested browsing context
@@ -3785,6 +3831,7 @@ impl ScriptThread {
Some(status_code),
incomplete.canceller,
can_gc,
final_url.as_str() == "about:blank",
);
document.set_ready_state(DocumentReadyState::Loading);

View File

@@ -136,6 +136,13 @@ impl OneshotTimers {
}
}
pub(crate) fn reset(&self) {
self.js_timers.reset();
self.next_timer_handle.set(OneshotTimerHandle(1));
self.timers.borrow_mut().clear();
self.expected_event_id.set(TimerEventId(0));
}
pub fn setup_scheduling(&self, timer_event_chan: IpcSender<TimerEvent>) {
let mut chan = self.timer_event_chan.borrow_mut();
assert!(chan.is_none());
@@ -415,6 +422,11 @@ impl Default for JsTimers {
}
impl JsTimers {
pub(crate) fn reset(&self) {
self.next_timer_handle.set(JsTimerHandle(1));
self.active_timers.borrow_mut().clear();
}
// see https://html.spec.whatwg.org/multipage/#timer-initialisation-steps
pub fn set_timeout_or_interval(
&self,

View File

@@ -128,6 +128,8 @@ pub enum LoadOrigin {
/// parameters or headers
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct LoadData {
///
pub new_pipeline_id: PipelineId,
/// The origin where the load started.
pub load_origin: LoadOrigin,
/// The URL.
@@ -162,6 +164,11 @@ pub struct LoadData {
/// Servo internal: if crash details are present, trigger a crash error page with these details.
pub crash: Option<String>,
///
pub replacing_pipeline: Option<PipelineId>,
///
pub synchronously_loaded: bool,
}
/// The result of evaluating a javascript scheme url.
@@ -183,6 +190,8 @@ impl LoadData {
referrer: Referrer,
referrer_policy: Option<ReferrerPolicy>,
inherited_secure_context: Option<bool>,
replacing_pipeline: Option<PipelineId>,
synchronously_loaded: bool,
) -> LoadData {
LoadData {
load_origin,
@@ -197,6 +206,9 @@ impl LoadData {
srcdoc: "".to_string(),
inherited_secure_context,
crash: None,
replacing_pipeline,
synchronously_loaded,
new_pipeline_id: PipelineId::new(),
}
}
}
@@ -358,6 +370,8 @@ pub enum ConstellationControlMsg {
parent: PipelineId,
/// The pipeline that has completed loading.
child: PipelineId,
///
is_initial_about_blank: bool,
},
/// Cause a `storage` event to be dispatched at the appropriate window.
/// The strings are key, old value and new value.
@@ -692,8 +706,6 @@ pub struct AuxiliaryBrowsingContextLoadInfo {
pub new_top_level_browsing_context_id: TopLevelBrowsingContextId,
/// The new browsing context ID.
pub new_browsing_context_id: BrowsingContextId,
/// The new pipeline ID for the auxiliary.
pub new_pipeline_id: PipelineId,
}
/// Specifies the information required to load an iframe.
@@ -705,8 +717,6 @@ pub struct IFrameLoadInfo {
pub browsing_context_id: BrowsingContextId,
/// The ID for the top-level ancestor browsing context of this iframe's nested browsing context.
pub top_level_browsing_context_id: TopLevelBrowsingContextId,
/// The new pipeline ID that the iframe has generated.
pub new_pipeline_id: PipelineId,
/// Whether this iframe should be considered private
pub is_private: bool,
/// Whether this iframe should be considered secure

View File

@@ -181,7 +181,7 @@ pub enum ScriptMsg {
),
/// All pending loads are complete, and the `load` event for this pipeline
/// has been dispatched.
LoadComplete,
LoadComplete(bool),
/// A new load has been requested, with an option to replace the current entry once loaded
/// instead of adding a new entry.
LoadUrl(LoadData, HistoryEntryReplacement),
@@ -293,7 +293,7 @@ impl fmt::Debug for ScriptMsg {
GetBrowsingContextInfo(..) => "GetBrowsingContextInfo",
GetTopForBrowsingContext(..) => "GetParentBrowsingContext",
GetChildBrowsingContextId(..) => "GetChildBrowsingContextId",
LoadComplete => "LoadComplete",
LoadComplete(..) => "LoadComplete",
LoadUrl(..) => "LoadUrl",
AbortLoadUrl => "AbortLoadUrl",
PostMessage { .. } => "PostMessage",

View File

@@ -658,6 +658,8 @@ impl Handler {
Referrer::NoReferrer,
None,
None,
None,
false,
);
let cmd_msg = WebDriverCommandMsg::LoadUrl(
top_level_browsing_context_id,

View File

@@ -1,9 +0,0 @@
[iframe-nosrc.html]
[window.open]
expected: FAIL
[link click]
expected: FAIL
[form submission]
expected: FAIL

View File

@@ -1,9 +0,0 @@
[iframe-src-204.html]
[Navigating to a different document with window.open]
expected: FAIL
[Navigating to a different document with link click]
expected: FAIL
[Navigating to a different document with form submission]
expected: FAIL

View File

@@ -1,9 +0,0 @@
[iframe-src-aboutblank-navigate-immediately.html]
[Navigating to a different document with window.open]
expected: FAIL
[Navigating to a different document with link click]
expected: FAIL
[Navigating to a different document with form submission]
expected: FAIL

View File

@@ -1,18 +0,0 @@
[iframe-src-aboutblank-wait-for-load.html]
[Navigating to a different document with src]
expected: FAIL
[Navigating to a different document with location.href]
expected: FAIL
[Navigating to a different document with location.assign]
expected: FAIL
[Navigating to a different document with window.open]
expected: FAIL
[Navigating to a different document with link click]
expected: FAIL
[Navigating to a different document with form submission]
expected: FAIL

View File

@@ -1,18 +0,0 @@
[initial-content-replacement.html]
[Content synchronously added to <iframe> with src='' won't get replaced]
expected: FAIL
[Content synchronously added to <iframe> with src='about:blank' won't get replaced]
expected: FAIL
[Content synchronously added to <iframe> with src='about:blank#foo' won't get replaced]
expected: FAIL
[Content synchronously added to <iframe> with src='about:blank?foo' won't get replaced]
expected: FAIL
[Content synchronously added to window.open('about:blank')-ed document won't get replaced]
expected: FAIL
[Content synchronously added to window.open('about:blank?foo')-ed document won't get replaced]
expected: FAIL

View File

@@ -1,21 +0,0 @@
[load-pageshow-events-iframe-contentWindow.html]
[load & pageshow event do not fire on contentWindow of <iframe> element created with no src]
expected: FAIL
[load & pageshow events do not fire on contentWindow of <iframe> element created with src='']
expected: FAIL
[load & pageshow events do not fire on contentWindow of <iframe> element created with src='about:blank?foo']
expected: FAIL
[load & pageshow events do not fire on contentWindow of <iframe> element created with src='about:blank#foo']
expected: FAIL
[load & pageshow events do not fire on contentWindow of <iframe> element created with src='about:blank']
expected: FAIL
[load & pageshow events do not fire on contentWindow of <iframe> element created with src='']
expected: FAIL
[load & pageshow events do not fire on contentWindow of <iframe> element created with src='about:blank']
expected: FAIL

View File

@@ -1,18 +0,0 @@
[load-pageshow-events-window-open.html]
[load event does not fire on window.open()]
expected: FAIL
[load event does not fire on window.open('about:blank')]
expected: FAIL
[load event does not fire on window.open('')]
expected: FAIL
[load event does not fire on window.open('about:blank#foo')]
expected: FAIL
[load event does not fire on window.open('about:blank?foo')]
expected: FAIL
[load event does not fire on window.open('about:blank')]
expected: FAIL

View File

@@ -1,6 +0,0 @@
[window-open-aboutblank.html]
[location.href]
expected: FAIL
[location.assign]
expected: FAIL

View File

@@ -1,6 +0,0 @@
[window-open-nourl.html]
[location.href]
expected: FAIL
[location.assign]
expected: FAIL

View File

@@ -1,4 +0,0 @@
[javascript-url-load-as-html.xhtml]
expected: TIMEOUT
[javascript: URL navigation to a string must create a HTML document using the correct properties]
expected: TIMEOUT

View File

@@ -1,10 +0,0 @@
[javascript-url-no-beforeunload.window.html]
expected: TIMEOUT
[Navigating an opened window with an iframe via location.href to a javascript: URL must not fire beforeunload on the iframe: string completion]
expected: NOTRUN
[Navigating an opened window via location.href to a javascript: URL must not fire beforeunload: string completion]
expected: TIMEOUT
[Navigating an opened window with an iframe via location.href to a javascript: URL must not fire beforeunload on the iframe: undefined completion]
expected: FAIL

View File

@@ -1,10 +0,0 @@
[javascript-url-referrer.window.html]
expected: TIMEOUT
[unsafe-url referrer policy used to create the starting page]
expected: FAIL
[origin referrer policy used to create the starting page]
expected: FAIL
[no-referrer referrer policy used to create the starting page]
expected: TIMEOUT

View File

@@ -1,6 +0,0 @@
[navigate-to-unparseable-url.html]
[location.href setter throws a SyntaxError DOMException for unparseable URLs]
expected: FAIL
[<a> tag navigate fails for unparseable URLs]
expected: FAIL

View File

@@ -1,3 +0,0 @@
[a-click.html]
[aElement.click() before the load event must NOT replace]
expected: FAIL

View File

@@ -10414,7 +10414,7 @@
],
"resource": {
"file-submission.py": [
"5f65cebd05ce89b3a51c2382a39b6a6438133b43",
"b03a4c9a5ddebc78af2bf8d0a35ebaca27bb4314",
[]
],
"upload.txt": [
@@ -13227,7 +13227,7 @@
]
],
"form_submit_about.html": [
"ec572ab0bc608c8cf5dd43f4159d3a67fc31a0de",
"71342b9b6b3ae6f037829b2e472bdd2fd1e93692",
[
null,
{}

View File

@@ -6,17 +6,17 @@
def fail(msg):
return ([("Content-Type", "text/plain")], "FAIL: " + msg)
return ([("Content-Type", "text/plain")], b"FAIL: " + msg)
def main(request, response):
content_type = request.headers.get(b'Content-Type').split(b"; ")
if len(content_type) != 2:
return fail("content type length is incorrect")
return fail(b"content type length is incorrect")
if content_type[0] != b'multipart/form-data':
return fail("content type first field is incorrect")
return fail(b"content type first field is incorrect")
boundary = content_type[1].strip(b"boundary=")
@@ -24,6 +24,6 @@ def main(request, response):
body += b"\r\n" + b"content-type: text/plain\r\n\r\nHello\r\n--" + boundary + b"--\r\n"
if body != request.body:
return fail("request body doesn't match: " + body + "+++++++" + request.body)
return fail(b"request body doesn't match: " + body + b"+++++++" + request.body)
return ([("Content-Type", "text/plain")], "OK")

View File

@@ -9,7 +9,7 @@
var numOnLoads = 0
var t = async_test("about:blank as form target")
var iframe = document.getElementById('foo')
iframe.onload = t.step_func(function(e) { if (++numOnLoads == 2) t.done() })
iframe.onload = t.step_func(function(e) { t.step_timeout(() => t.done(), 100); iframe.onload = t.unreached_func(); })
</script>
</body>
</html>