diff options
-rw-r--r-- | components/script/dom/bindings/trace.rs | 3 | ||||
-rw-r--r-- | components/script/dom/document.rs | 44 | ||||
-rw-r--r-- | components/script/dom/htmliframeelement.rs | 28 | ||||
-rw-r--r-- | components/script/dom/htmlscriptelement.rs | 5 |
4 files changed, 59 insertions, 21 deletions
diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 1e5cc6a3a72..4fecf083737 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -49,6 +49,7 @@ use crate::dom::bindings::utils::WindowProxyHandler; use crate::dom::document::PendingRestyle; use crate::dom::htmlimageelement::SourceSet; use crate::dom::htmlmediaelement::MediaFrameRenderer; +use crate::task::TaskBox; use crossbeam_channel::{Receiver, Sender}; use cssparser::RGBA; use devtools_traits::{CSSError, TimelineMarkerType, WorkerId}; @@ -139,6 +140,8 @@ pub unsafe trait JSTraceable { unsafe fn trace(&self, trc: *mut JSTracer); } +unsafe_no_jsmanaged_fields!(Box<dyn TaskBox>); + unsafe_no_jsmanaged_fields!(CSSError); unsafe_no_jsmanaged_fields!(&'static Encoding); diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 709305f697a..66b9e379d75 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -101,6 +101,7 @@ use crate::dom::windowproxy::WindowProxy; use crate::fetch::FetchCanceller; use crate::script_runtime::{CommonScriptMsg, ScriptThreadEventCategory}; use crate::script_thread::{MainThreadScriptMsg, ScriptThread}; +use crate::task::TaskBox; use crate::task_source::{TaskSource, TaskSourceName}; use crate::timers::OneshotTimerCallback; use devtools_traits::ScriptToDevtoolsControlMsg; @@ -410,8 +411,11 @@ pub struct Document { responsive_images: DomRefCell<Vec<Dom<HTMLImageElement>>>, /// Number of redirects for the document load redirect_count: Cell<u16>, - /// + /// Number of outstanding requests to prevent JS or layout from running. script_and_layout_blockers: Cell<u32>, + /// List of tasks to execute as soon as last script/layout blocker is removed. + #[ignore_malloc_size_of = "Measuring trait objects is hard"] + delayed_tasks: DomRefCell<Vec<Box<dyn TaskBox>>>, } #[derive(JSTraceable, MallocSizeOf)] @@ -2698,24 +2702,48 @@ impl Document { responsive_images: Default::default(), redirect_count: Cell::new(0), script_and_layout_blockers: Cell::new(0), + delayed_tasks: Default::default(), } } + /// Prevent any JS or layout from running until the corresponding call to + /// `remove_script_and_layout_blocker`. Used to isolate periods in which + /// the DOM is in an unstable state and should not be exposed to arbitrary + /// web content. Any attempts to invoke content JS or query layout during + /// that time will trigger a panic. `add_delayed_task` will cause the + /// provided task to be executed as soon as the last blocker is removed. pub fn add_script_and_layout_blocker(&self) { - self.script_and_layout_blockers.set( - self.script_and_layout_blockers.get() + 1 - ); + self.script_and_layout_blockers + .set(self.script_and_layout_blockers.get() + 1); } + /// Terminate the period in which JS or layout is disallowed from running. + /// If no further blockers remain, any delayed tasks in the queue will + /// be executed in queue order until the queue is empty. pub fn remove_script_and_layout_blocker(&self) { assert!(self.script_and_layout_blockers.get() > 0); - self.script_and_layout_blockers.set( - self.script_and_layout_blockers.get() - 1 - ); + self.script_and_layout_blockers + .set(self.script_and_layout_blockers.get() - 1); + while self.script_and_layout_blockers.get() == 0 && !self.delayed_tasks.borrow().is_empty() + { + let task = self.delayed_tasks.borrow_mut().remove(0); + task.run_box(); + } } + /// Enqueue a task to run as soon as any JS and layout blockers are removed. + pub fn add_delayed_task<T: 'static + TaskBox>(&self, task: T) { + self.delayed_tasks.borrow_mut().push(Box::new(task)); + } + + /// Assert that the DOM is in a state that will allow running content JS or + /// performing a layout operation. pub fn ensure_safe_to_run_script_or_layout(&self) { - assert_eq!(self.script_and_layout_blockers.get(), 0); + assert_eq!( + self.script_and_layout_blockers.get(), + 0, + "Attempt to use script or layout while DOM not in a stable state" + ); } // https://dom.spec.whatwg.org/#dom-document-document diff --git a/components/script/dom/htmliframeelement.rs b/components/script/dom/htmliframeelement.rs index ae87091e0a3..e471e78aeaf 100644 --- a/components/script/dom/htmliframeelement.rs +++ b/components/script/dom/htmliframeelement.rs @@ -623,18 +623,22 @@ impl VirtualMethods for HTMLIFrameElement { s.bind_to_tree(tree_in_doc); } - // https://html.spec.whatwg.org/multipage/#the-iframe-element - // "When an iframe element is inserted into a document that has - // a browsing context, the user agent must create a new - // browsing context, set the element's nested browsing context - // to the newly-created browsing context, and then process the - // iframe attributes for the "first time"." - if self.upcast::<Node>().is_in_doc_with_browsing_context() { - debug!("iframe bound to browsing context."); - debug_assert!(tree_in_doc, "is_in_doc_with_bc, but not tree_in_doc"); - self.create_nested_browsing_context(); - self.process_the_iframe_attributes(ProcessingMode::FirstTime); - } + let iframe = Trusted::new(self); + document_from_node(self).add_delayed_task(task!(IFrameDelayedInitialize: move || { + let this = iframe.root(); + // https://html.spec.whatwg.org/multipage/#the-iframe-element + // "When an iframe element is inserted into a document that has + // a browsing context, the user agent must create a new + // browsing context, set the element's nested browsing context + // to the newly-created browsing context, and then process the + // iframe attributes for the "first time"." + if this.upcast::<Node>().is_in_doc_with_browsing_context() { + debug!("iframe bound to browsing context."); + debug_assert!(tree_in_doc, "is_in_doc_with_bc, but not tree_in_doc"); + this.create_nested_browsing_context(); + this.process_the_iframe_attributes(ProcessingMode::FirstTime); + } + })); } fn unbind_from_tree(&self, context: &UnbindContext) { diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index a76f506f352..5bbb5b45dc5 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -777,7 +777,10 @@ impl VirtualMethods for HTMLScriptElement { } if tree_in_doc && !self.parser_inserted.get() { - self.prepare(); + let script = Trusted::new(self); + document_from_node(self).add_delayed_task(task!(ScriptDelayedInitialize: move || { + script.root().prepare(); + })); } } |