mirror of
https://github.com/servo/servo.git
synced 2025-08-02 20:20:14 +01:00
script: Improve dirty propagation and fix script-layout synchronization.
This fixes race conditions whereby layout and script could be running simultaneously.
This commit is contained in:
parent
1bc2c8a639
commit
d101c1dd91
8 changed files with 220 additions and 257 deletions
|
@ -13,9 +13,9 @@ use dom::node::{Node, NodeHelpers};
|
|||
use dom::window::Window;
|
||||
use layout_interface::{
|
||||
ContentBoxQuery, ContentBoxResponse, ContentBoxesQuery, ContentBoxesResponse,
|
||||
GetRPCMsg, HitTestResponse, LayoutChan, LayoutRPC, MouseOverResponse, NoQuery,
|
||||
Reflow, ReflowForDisplay, ReflowForScriptQuery, ReflowGoal, ReflowMsg,
|
||||
ReflowQueryType, TrustedNodeAddress
|
||||
GetRPCMsg, HitTestResponse, LayoutChan, LayoutRPC, MouseOverResponse, Reflow,
|
||||
ReflowForScriptQuery, ReflowGoal, ReflowMsg, ReflowQueryType,
|
||||
TrustedNodeAddress
|
||||
};
|
||||
use script_traits::{UntrustedNodeAddress, ScriptControlChan};
|
||||
|
||||
|
@ -29,7 +29,7 @@ use servo_net::storage_task::StorageTask;
|
|||
use servo_util::geometry::{Au, MAX_RECT};
|
||||
use servo_util::geometry;
|
||||
use servo_util::str::DOMString;
|
||||
use servo_util::smallvec::{SmallVec1, SmallVec};
|
||||
use servo_util::smallvec::SmallVec;
|
||||
use std::cell::{Cell, Ref, RefMut};
|
||||
use std::comm::{channel, Receiver, Empty, Disconnected};
|
||||
use std::mem::replace;
|
||||
|
@ -77,9 +77,6 @@ pub struct Page {
|
|||
/// Pending resize event, if any.
|
||||
pub resize_event: Cell<Option<WindowSizeData>>,
|
||||
|
||||
/// Any nodes that need to be dirtied before the next reflow.
|
||||
pub pending_dirty_nodes: DOMRefCell<SmallVec1<UntrustedNodeAddress>>,
|
||||
|
||||
/// Pending scroll to fragment event, if any
|
||||
pub fragment_name: DOMRefCell<Option<String>>,
|
||||
|
||||
|
@ -95,15 +92,6 @@ pub struct Page {
|
|||
// Child Pages.
|
||||
pub children: DOMRefCell<Vec<Rc<Page>>>,
|
||||
|
||||
/// Whether layout needs to be run at all.
|
||||
pub damaged: Cell<bool>,
|
||||
|
||||
/// Number of pending reflows that were sent while layout was active.
|
||||
pub pending_reflows: Cell<int>,
|
||||
|
||||
/// Number of unnecessary potential reflows that were skipped since the last reflow
|
||||
pub avoided_reflows: Cell<int>,
|
||||
|
||||
/// An enlarged rectangle around the page contents visible in the viewport, used
|
||||
/// to prevent creating display list items for content that is far away from the viewport.
|
||||
pub page_clip_rect: Cell<Rect<Au>>,
|
||||
|
@ -165,57 +153,35 @@ impl Page {
|
|||
url: DOMRefCell::new(None),
|
||||
next_subpage_id: Cell::new(SubpageId(0)),
|
||||
resize_event: Cell::new(None),
|
||||
pending_dirty_nodes: DOMRefCell::new(SmallVec1::new()),
|
||||
fragment_name: DOMRefCell::new(None),
|
||||
last_reflow_id: Cell::new(0),
|
||||
resource_task: resource_task,
|
||||
storage_task: storage_task,
|
||||
constellation_chan: constellation_chan,
|
||||
children: DOMRefCell::new(vec!()),
|
||||
damaged: Cell::new(false),
|
||||
pending_reflows: Cell::new(0),
|
||||
avoided_reflows: Cell::new(0),
|
||||
page_clip_rect: Cell::new(MAX_RECT),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flush_layout(&self, query: ReflowQueryType) {
|
||||
// If we are damaged, we need to force a full reflow, so that queries interact with
|
||||
// an accurate flow tree.
|
||||
let (reflow_goal, force_reflow) = if self.damaged.get() {
|
||||
(ReflowForDisplay, true)
|
||||
} else {
|
||||
match query {
|
||||
ContentBoxQuery(_) | ContentBoxesQuery(_) => (ReflowForScriptQuery, true),
|
||||
NoQuery => (ReflowForDisplay, false),
|
||||
}
|
||||
};
|
||||
|
||||
if force_reflow {
|
||||
let frame = self.frame();
|
||||
let window = frame.as_ref().unwrap().window.root();
|
||||
self.reflow(reflow_goal, window.control_chan().clone(), &mut **window.compositor(), query);
|
||||
} else {
|
||||
self.avoided_reflows.set(self.avoided_reflows.get() + 1);
|
||||
}
|
||||
pub fn flush_layout(&self, goal: ReflowGoal, query: ReflowQueryType) {
|
||||
let frame = self.frame();
|
||||
let window = frame.as_ref().unwrap().window.root();
|
||||
self.reflow(goal, window.control_chan().clone(), &mut **window.compositor(), query);
|
||||
}
|
||||
|
||||
pub fn layout(&self) -> &LayoutRPC {
|
||||
self.flush_layout(NoQuery);
|
||||
self.join_layout(); //FIXME: is this necessary, or is layout_rpc's mutex good enough?
|
||||
let layout_rpc: &LayoutRPC = &*self.layout_rpc;
|
||||
layout_rpc
|
||||
pub fn layout(&self) -> &LayoutRPC {
|
||||
&*self.layout_rpc
|
||||
}
|
||||
|
||||
pub fn content_box_query(&self, content_box_request: TrustedNodeAddress) -> Rect<Au> {
|
||||
self.flush_layout(ContentBoxQuery(content_box_request));
|
||||
self.flush_layout(ReflowForScriptQuery, ContentBoxQuery(content_box_request));
|
||||
self.join_layout(); //FIXME: is this necessary, or is layout_rpc's mutex good enough?
|
||||
let ContentBoxResponse(rect) = self.layout_rpc.content_box();
|
||||
rect
|
||||
}
|
||||
|
||||
pub fn content_boxes_query(&self, content_boxes_request: TrustedNodeAddress) -> Vec<Rect<Au>> {
|
||||
self.flush_layout(ContentBoxesQuery(content_boxes_request));
|
||||
self.flush_layout(ReflowForScriptQuery, ContentBoxesQuery(content_boxes_request));
|
||||
self.join_layout(); //FIXME: is this necessary, or is layout_rpc's mutex good enough?
|
||||
let ContentBoxesResponse(rects) = self.layout_rpc.content_boxes();
|
||||
rects
|
||||
|
@ -276,6 +242,13 @@ impl Page {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dirty_all_nodes(&self) {
|
||||
match *self.frame.borrow() {
|
||||
None => {}
|
||||
Some(ref frame) => frame.document.root().dirty_all_nodes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator<Rc<Page>> for PageIterator {
|
||||
|
@ -334,7 +307,7 @@ impl Page {
|
|||
|
||||
/// Sends a ping to layout and waits for the response. The response will arrive when the
|
||||
/// layout task has finished any pending request messages.
|
||||
pub fn join_layout(&self) {
|
||||
fn join_layout(&self) {
|
||||
let mut layout_join_port = self.layout_join_port.borrow_mut();
|
||||
if layout_join_port.is_some() {
|
||||
let join_port = replace(&mut *layout_join_port, None);
|
||||
|
@ -358,11 +331,11 @@ impl Page {
|
|||
}
|
||||
}
|
||||
|
||||
/// Reflows the page if it's possible to do so. This method will wait until the layout task has
|
||||
/// completed its current action, join the layout task, and then request a new layout run. It
|
||||
/// won't wait for the new layout computation to finish.
|
||||
/// Reflows the page if it's possible to do so and the page is dirty. This method will wait
|
||||
/// for the layout thread to complete (but see the `TODO` below). If there is no window size
|
||||
/// yet, the page is presumed invisible and no reflow is performed.
|
||||
///
|
||||
/// If there is no window size yet, the page is presumed invisible and no reflow is performed.
|
||||
/// TODO(pcwalton): Only wait for style recalc, since we have off-main-thread layout.
|
||||
pub fn reflow(&self,
|
||||
goal: ReflowGoal,
|
||||
script_chan: ScriptControlChan,
|
||||
|
@ -375,54 +348,54 @@ impl Page {
|
|||
}
|
||||
};
|
||||
|
||||
match root.root() {
|
||||
None => {},
|
||||
Some(root) => {
|
||||
debug!("avoided {:d} reflows", self.avoided_reflows.get());
|
||||
self.avoided_reflows.set(0);
|
||||
let root = match root.root() {
|
||||
None => return,
|
||||
Some(root) => root,
|
||||
};
|
||||
|
||||
debug!("script: performing reflow for goal {}", goal);
|
||||
debug!("script: performing reflow for goal {}", goal);
|
||||
|
||||
// Now, join the layout so that they will see the latest changes we have made.
|
||||
self.join_layout();
|
||||
|
||||
// Layout will let us know when it's done.
|
||||
let (join_chan, join_port) = channel();
|
||||
let mut layout_join_port = self.layout_join_port.borrow_mut();
|
||||
*layout_join_port = Some(join_port);
|
||||
|
||||
let last_reflow_id = &self.last_reflow_id;
|
||||
last_reflow_id.set(last_reflow_id.get() + 1);
|
||||
|
||||
let root: JSRef<Node> = NodeCast::from_ref(*root);
|
||||
|
||||
let window_size = self.window_size.get();
|
||||
self.damaged.set(false);
|
||||
|
||||
// Send new document and relevant styles to layout.
|
||||
let reflow = box Reflow {
|
||||
document_root: root.to_trusted_node_address(),
|
||||
url: self.get_url(),
|
||||
iframe: self.subpage_id.is_some(),
|
||||
goal: goal,
|
||||
window_size: window_size,
|
||||
script_chan: script_chan,
|
||||
script_join_chan: join_chan,
|
||||
id: last_reflow_id.get(),
|
||||
query_type: query_type,
|
||||
page_clip_rect: self.page_clip_rect.get(),
|
||||
};
|
||||
|
||||
let LayoutChan(ref chan) = self.layout_chan;
|
||||
chan.send(ReflowMsg(reflow));
|
||||
|
||||
debug!("script: layout forked")
|
||||
}
|
||||
let root: JSRef<Node> = NodeCast::from_ref(*root);
|
||||
if !root.get_has_dirty_descendants() {
|
||||
debug!("root has no dirty descendants; avoiding reflow");
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
pub fn damage(&self) {
|
||||
self.damaged.set(true);
|
||||
debug!("script: performing reflow for goal {}", goal);
|
||||
|
||||
// Layout will let us know when it's done.
|
||||
let (join_chan, join_port) = channel();
|
||||
|
||||
{
|
||||
let mut layout_join_port = self.layout_join_port.borrow_mut();
|
||||
*layout_join_port = Some(join_port);
|
||||
}
|
||||
|
||||
let last_reflow_id = &self.last_reflow_id;
|
||||
last_reflow_id.set(last_reflow_id.get() + 1);
|
||||
|
||||
let window_size = self.window_size.get();
|
||||
|
||||
// Send new document and relevant styles to layout.
|
||||
let reflow = box Reflow {
|
||||
document_root: root.to_trusted_node_address(),
|
||||
url: self.get_url(),
|
||||
iframe: self.subpage_id.is_some(),
|
||||
goal: goal,
|
||||
window_size: window_size,
|
||||
script_chan: script_chan,
|
||||
script_join_chan: join_chan,
|
||||
id: last_reflow_id.get(),
|
||||
query_type: query_type,
|
||||
page_clip_rect: self.page_clip_rect.get(),
|
||||
};
|
||||
|
||||
let LayoutChan(ref chan) = self.layout_chan;
|
||||
chan.send(ReflowMsg(reflow));
|
||||
|
||||
debug!("script: layout forked");
|
||||
|
||||
self.join_layout();
|
||||
}
|
||||
|
||||
/// Attempt to find a named element in this page's document.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue