Prevent JS execution and layout operations while DOM in inconsistent state.

This commit is contained in:
Josh Matthews 2018-12-09 22:08:33 -05:00
parent 231a37be24
commit 14b0de30db
4 changed files with 40 additions and 0 deletions

View file

@ -4,12 +4,15 @@
//! Base classes to work with IDL callbacks.
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods;
use crate::dom::bindings::error::{report_pending_exception, Error, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::DomObject;
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::settings_stack::{AutoEntryScript, AutoIncumbentScript};
use crate::dom::bindings::utils::AsCCharPtrPtr;
use crate::dom::globalscope::GlobalScope;
use crate::dom::window::Window;
use js::jsapi::Heap;
use js::jsapi::JSAutoCompartment;
use js::jsapi::{AddRawValueRoot, IsCallable, JSContext, JSObject};
@ -242,6 +245,9 @@ impl CallSetup {
#[allow(unrooted_must_root)]
pub fn new<T: CallbackContainer>(callback: &T, handling: ExceptionHandling) -> CallSetup {
let global = unsafe { GlobalScope::from_object(callback.callback()) };
if let Some(window) = global.downcast::<Window>() {
window.Document().ensure_safe_to_run_script_or_layout();
}
let cx = global.get_cx();
let aes = AutoEntryScript::new(&global);

View file

@ -410,6 +410,8 @@ pub struct Document {
responsive_images: DomRefCell<Vec<Dom<HTMLImageElement>>>,
/// Number of redirects for the document load
redirect_count: Cell<u16>,
///
script_and_layout_blockers: Cell<u32>,
}
#[derive(JSTraceable, MallocSizeOf)]
@ -2695,9 +2697,27 @@ impl Document {
fired_unload: Cell::new(false),
responsive_images: Default::default(),
redirect_count: Cell::new(0),
script_and_layout_blockers: Cell::new(0),
}
}
pub fn add_script_and_layout_blocker(&self) {
self.script_and_layout_blockers.set(
self.script_and_layout_blockers.get() + 1
);
}
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
);
}
pub fn ensure_safe_to_run_script_or_layout(&self) {
assert_eq!(self.script_and_layout_blockers.get(), 0);
}
// https://dom.spec.whatwg.org/#dom-document-document
pub fn Constructor(window: &Window) -> Fallible<DomRoot<Document>> {
let doc = window.Document();

View file

@ -1504,8 +1504,11 @@ impl Node {
// https://dom.spec.whatwg.org/#concept-node-adopt
pub fn adopt(node: &Node, document: &Document) {
document.add_script_and_layout_blocker();
// Step 1.
let old_doc = node.owner_doc();
old_doc.add_script_and_layout_blocker();
// Step 2.
node.remove_self();
// Step 3.
@ -1530,6 +1533,9 @@ impl Node {
vtable_for(&descendant).adopting_steps(&old_doc);
}
}
old_doc.remove_script_and_layout_blocker();
document.remove_script_and_layout_blocker();
}
// https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
@ -1685,6 +1691,7 @@ impl Node {
child: Option<&Node>,
suppress_observers: SuppressObserver,
) {
node.owner_doc().add_script_and_layout_blocker();
debug_assert!(&*node.owner_doc() == &*parent.owner_doc());
debug_assert!(child.map_or(true, |child| Some(parent) == child.GetParentNode().r()));
@ -1774,10 +1781,12 @@ impl Node {
};
MutationObserver::queue_a_mutation_record(&parent, mutation);
}
node.owner_doc().remove_script_and_layout_blocker();
}
// https://dom.spec.whatwg.org/#concept-node-replace-all
pub fn replace_all(node: Option<&Node>, parent: &Node) {
parent.owner_doc().add_script_and_layout_blocker();
// Step 1.
if let Some(node) = node {
Node::adopt(node, &*parent.owner_doc());
@ -1819,6 +1828,7 @@ impl Node {
};
MutationObserver::queue_a_mutation_record(&parent, mutation);
}
parent.owner_doc().remove_script_and_layout_blocker();
}
// https://dom.spec.whatwg.org/#concept-node-pre-remove
@ -1839,6 +1849,7 @@ impl Node {
// https://dom.spec.whatwg.org/#concept-node-remove
fn remove(node: &Node, parent: &Node, suppress_observers: SuppressObserver) {
parent.owner_doc().add_script_and_layout_blocker();
assert!(
node.GetParentNode()
.map_or(false, |node_parent| &*node_parent == parent)
@ -1884,6 +1895,7 @@ impl Node {
};
MutationObserver::queue_a_mutation_record(&parent, mutation);
}
parent.owner_doc().remove_script_and_layout_blocker();
}
// https://dom.spec.whatwg.org/#concept-node-clone

View file

@ -1361,6 +1361,7 @@ impl Window {
/// Returns true if layout actually happened, false otherwise.
#[allow(unsafe_code)]
pub fn force_reflow(&self, reflow_goal: ReflowGoal, reason: ReflowReason) -> bool {
self.Document().ensure_safe_to_run_script_or_layout();
// Check if we need to unsuppress reflow. Note that this needs to be
// *before* any early bailouts, or reflow might never be unsuppresed!
match reason {
@ -1497,6 +1498,7 @@ impl Window {
/// may happen in the only case a query reflow may bail out, that is, if the
/// viewport size is not present). See #11223 for an example of that.
pub fn reflow(&self, reflow_goal: ReflowGoal, reason: ReflowReason) -> bool {
self.Document().ensure_safe_to_run_script_or_layout();
let for_display = reflow_goal == ReflowGoal::Full;
let mut issued_reflow = false;