mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
Auto merge of #12563 - emilio:stylo, r=bholley,jdm,pcwalton
stylo: Improve restyling performance This commit adds hooks to the Servo style traversal to avoid traversing all the DOM for every restyle. Additionally it changes the behavior of the dirty flag to be propagated top down, to prevent extra overhead when an element is dirtied. This commit doesn't aim to change the behavior on Servo just yet, since Servo does extra job when dirtying the node related with DOM revision counters that might be necessary. CC @asajeffrey for the DOM revision counters stuff. When a node is dirty, do all its descendants really need to increment the revision counter, or is this an unintended effect? My intuition is that this is hurting performance quite a lot for servo. r? @bholley <!-- Please describe your changes on the following line: --> --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors <!-- Either: --> - [x] These changes do not require tests because no geckolib tests yet. <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/12563) <!-- Reviewable:end -->
This commit is contained in:
commit
944d371b8f
18 changed files with 195 additions and 177 deletions
|
@ -2114,7 +2114,7 @@ impl<Window: WindowMethods> IOCompositor<Window> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if there are any pending frames. If so, the image is not stable yet.
|
// Check if there are any pending frames. If so, the image is not stable yet.
|
||||||
if self.pending_subpages.len() > 0 {
|
if !self.pending_subpages.is_empty() {
|
||||||
return Err(NotReadyToPaint::PendingSubpages(self.pending_subpages.len()));
|
return Err(NotReadyToPaint::PendingSubpages(self.pending_subpages.len()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -828,11 +828,6 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||||
debug!("constellation got load complete message");
|
debug!("constellation got load complete message");
|
||||||
self.handle_load_complete_msg(pipeline_id)
|
self.handle_load_complete_msg(pipeline_id)
|
||||||
}
|
}
|
||||||
// The DOM load event fired on a document
|
|
||||||
FromScriptMsg::DOMLoad(pipeline_id) => {
|
|
||||||
debug!("constellation got dom load message");
|
|
||||||
self.handle_dom_load(pipeline_id)
|
|
||||||
}
|
|
||||||
// Handle a forward or back request
|
// Handle a forward or back request
|
||||||
FromScriptMsg::TraverseHistory(pipeline_id, direction) => {
|
FromScriptMsg::TraverseHistory(pipeline_id, direction) => {
|
||||||
debug!("constellation got traverse history message from script");
|
debug!("constellation got traverse history message from script");
|
||||||
|
@ -1323,10 +1318,13 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_alert(&mut self, pipeline_id: PipelineId, message: String, sender: IpcSender<bool>) {
|
fn handle_alert(&mut self,
|
||||||
|
pipeline_id: PipelineId,
|
||||||
|
message: String,
|
||||||
|
sender: IpcSender<bool>) {
|
||||||
let display_alert_dialog = if PREFS.is_mozbrowser_enabled() {
|
let display_alert_dialog = if PREFS.is_mozbrowser_enabled() {
|
||||||
let parent_pipeline_info = self.pipelines.get(&pipeline_id).and_then(|source| source.parent_info);
|
let parent_pipeline_info = self.pipelines.get(&pipeline_id).and_then(|source| source.parent_info);
|
||||||
if let Some(_) = parent_pipeline_info {
|
if parent_pipeline_info.is_some() {
|
||||||
let root_pipeline_id = self.root_frame_id
|
let root_pipeline_id = self.root_frame_id
|
||||||
.and_then(|root_frame_id| self.frames.get(&root_frame_id))
|
.and_then(|root_frame_id| self.frames.get(&root_frame_id))
|
||||||
.map(|root_frame| root_frame.current.0);
|
.map(|root_frame| root_frame.current.0);
|
||||||
|
@ -1435,15 +1433,6 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_load_complete_msg(&mut self, pipeline_id: PipelineId) {
|
fn handle_load_complete_msg(&mut self, pipeline_id: PipelineId) {
|
||||||
if let Some(frame_id) = self.get_top_level_frame_for_pipeline(Some(pipeline_id)) {
|
|
||||||
let forward = !self.joint_session_future(frame_id).is_empty();
|
|
||||||
let back = !self.joint_session_past(frame_id).is_empty();
|
|
||||||
let root = self.root_frame_id.is_none() || self.root_frame_id == Some(frame_id);
|
|
||||||
self.compositor_proxy.send(ToCompositorMsg::LoadComplete(back, forward, root));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_dom_load(&mut self, pipeline_id: PipelineId) {
|
|
||||||
let mut webdriver_reset = false;
|
let mut webdriver_reset = false;
|
||||||
if let Some((expected_pipeline_id, ref reply_chan)) = self.webdriver.load_channel {
|
if let Some((expected_pipeline_id, ref reply_chan)) = self.webdriver.load_channel {
|
||||||
debug!("Sending load to WebDriver");
|
debug!("Sending load to WebDriver");
|
||||||
|
@ -1455,7 +1444,12 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||||
if webdriver_reset {
|
if webdriver_reset {
|
||||||
self.webdriver.load_channel = None;
|
self.webdriver.load_channel = None;
|
||||||
}
|
}
|
||||||
|
if let Some(frame_id) = self.get_top_level_frame_for_pipeline(Some(pipeline_id)) {
|
||||||
|
let forward = !self.joint_session_future(frame_id).is_empty();
|
||||||
|
let back = !self.joint_session_past(frame_id).is_empty();
|
||||||
|
let root = self.root_frame_id.is_none() || self.root_frame_id == Some(frame_id);
|
||||||
|
self.compositor_proxy.send(ToCompositorMsg::LoadComplete(back, forward, root));
|
||||||
|
}
|
||||||
self.handle_subframe_loaded(pipeline_id);
|
self.handle_subframe_loaded(pipeline_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2073,7 +2067,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are pending loads, wait for those to complete.
|
// If there are pending loads, wait for those to complete.
|
||||||
if self.pending_frames.len() > 0 {
|
if !self.pending_frames.is_empty() {
|
||||||
return ReadyToSave::PendingFrames;
|
return ReadyToSave::PendingFrames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2089,7 +2083,10 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||||
let pipeline_id = frame.current.0;
|
let pipeline_id = frame.current.0;
|
||||||
|
|
||||||
let pipeline = match self.pipelines.get(&pipeline_id) {
|
let pipeline = match self.pipelines.get(&pipeline_id) {
|
||||||
None => { warn!("Pipeline {:?} screenshot while closing.", pipeline_id); continue; },
|
None => {
|
||||||
|
warn!("Pipeline {:?} screenshot while closing.", pipeline_id);
|
||||||
|
continue;
|
||||||
|
},
|
||||||
Some(pipeline) => pipeline,
|
Some(pipeline) => pipeline,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -503,6 +503,10 @@ impl UnprivilegedPipelineContent {
|
||||||
command.env("RUST_BACKTRACE", value);
|
command.env("RUST_BACKTRACE", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Ok(value) = env::var("RUST_LOG") {
|
||||||
|
command.env("RUST_LOG", value);
|
||||||
|
}
|
||||||
|
|
||||||
let profile = content_process_sandbox_profile();
|
let profile = content_process_sandbox_profile();
|
||||||
ChildProcess::Sandboxed(Sandbox::new(profile).start(&mut command)
|
ChildProcess::Sandboxed(Sandbox::new(profile).start(&mut command)
|
||||||
.expect("Failed to start sandboxed child process!"))
|
.expect("Failed to start sandboxed child process!"))
|
||||||
|
@ -517,6 +521,10 @@ impl UnprivilegedPipelineContent {
|
||||||
child_process.env("RUST_BACKTRACE", value);
|
child_process.env("RUST_BACKTRACE", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Ok(value) = env::var("RUST_LOG") {
|
||||||
|
child_process.env("RUST_LOG", value);
|
||||||
|
}
|
||||||
|
|
||||||
ChildProcess::Unsandboxed(child_process.spawn()
|
ChildProcess::Unsandboxed(child_process.spawn()
|
||||||
.expect("Failed to start unsandboxed child process!"))
|
.expect("Failed to start unsandboxed child process!"))
|
||||||
};
|
};
|
||||||
|
|
|
@ -379,8 +379,7 @@ impl<C> PaintThread<C> where C: PaintListener + Send + 'static {
|
||||||
font_cache_thread: FontCacheThread,
|
font_cache_thread: FontCacheThread,
|
||||||
time_profiler_chan: time::ProfilerChan,
|
time_profiler_chan: time::ProfilerChan,
|
||||||
mem_profiler_chan: mem::ProfilerChan) {
|
mem_profiler_chan: mem::ProfilerChan) {
|
||||||
thread::spawn_named(format!("PaintThread {:?}", id),
|
thread::spawn_named(format!("PaintThread {:?}", id), move || {
|
||||||
move || {
|
|
||||||
thread_state::initialize(thread_state::PAINT);
|
thread_state::initialize(thread_state::PAINT);
|
||||||
PipelineId::install(id);
|
PipelineId::install(id);
|
||||||
|
|
||||||
|
@ -425,9 +424,9 @@ impl<C> PaintThread<C> where C: PaintListener + Send + 'static {
|
||||||
let chrome_to_paint = &self.chrome_to_paint_port;
|
let chrome_to_paint = &self.chrome_to_paint_port;
|
||||||
select! {
|
select! {
|
||||||
msg = layout_to_paint.recv() =>
|
msg = layout_to_paint.recv() =>
|
||||||
Msg::FromLayout(msg.unwrap()),
|
Msg::FromLayout(msg.expect("expected message from layout")),
|
||||||
msg = chrome_to_paint.recv() =>
|
msg = chrome_to_paint.recv() =>
|
||||||
Msg::FromChrome(msg.unwrap())
|
Msg::FromChrome(msg.expect("expected message from chrome"))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,9 @@ impl<'lc, N> DomTraversalContext<N> for RecalcStyleAndConstructFlows<'lc>
|
||||||
recalc_style_at(&self.context, self.root, node);
|
recalc_style_at(&self.context, self.root, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_postorder(&self, node: N) { construct_flows_at(&self.context, self.root, node); }
|
fn process_postorder(&self, node: N) {
|
||||||
|
construct_flows_at(&self.context, self.root, node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A bottom-up, parallelizable traversal.
|
/// A bottom-up, parallelizable traversal.
|
||||||
|
@ -96,7 +98,7 @@ fn construct_flows_at<'a, N: LayoutNode>(context: &'a LayoutContext<'a>, root: O
|
||||||
|
|
||||||
// Always reconstruct if incremental layout is turned off.
|
// Always reconstruct if incremental layout is turned off.
|
||||||
let nonincremental_layout = opts::get().nonincremental_layout;
|
let nonincremental_layout = opts::get().nonincremental_layout;
|
||||||
if nonincremental_layout || node.has_dirty_descendants() {
|
if nonincremental_layout || node.is_dirty() || node.has_dirty_descendants() {
|
||||||
let mut flow_constructor = FlowConstructor::new(context);
|
let mut flow_constructor = FlowConstructor::new(context);
|
||||||
if nonincremental_layout || !flow_constructor.repair_if_possible(&tnode) {
|
if nonincremental_layout || !flow_constructor.repair_if_possible(&tnode) {
|
||||||
flow_constructor.process(&tnode);
|
flow_constructor.process(&tnode);
|
||||||
|
|
|
@ -1105,12 +1105,16 @@ impl LayoutThread {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
if data.document_stylesheets.iter().any(|sheet| sheet.dirty_on_viewport_size_change) {
|
if data.document_stylesheets.iter().any(|sheet| sheet.dirty_on_viewport_size_change) {
|
||||||
for node in node.traverse_preorder() {
|
let mut iter = node.traverse_preorder();
|
||||||
|
|
||||||
|
let mut next = iter.next();
|
||||||
|
while let Some(node) = next {
|
||||||
if node.needs_dirty_on_viewport_size_changed() {
|
if node.needs_dirty_on_viewport_size_changed() {
|
||||||
node.dirty_self();
|
// NB: The dirty bit is propagated down the tree.
|
||||||
node.dirty_descendants();
|
unsafe { node.set_dirty(true); }
|
||||||
// TODO(shinglyu): We can skip the traversal if the descendants were already
|
next = iter.next_skipping_children();
|
||||||
// dirtied
|
} else {
|
||||||
|
next = iter.next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1122,7 +1126,9 @@ impl LayoutThread {
|
||||||
let needs_reflow = viewport_size_changed && !needs_dirtying;
|
let needs_reflow = viewport_size_changed && !needs_dirtying;
|
||||||
unsafe {
|
unsafe {
|
||||||
if needs_dirtying {
|
if needs_dirtying {
|
||||||
LayoutThread::dirty_all_nodes(node);
|
// NB: The dirty flag is propagated down during the restyle
|
||||||
|
// process.
|
||||||
|
node.set_dirty(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if needs_reflow {
|
if needs_reflow {
|
||||||
|
@ -1476,16 +1482,6 @@ impl LayoutThread {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn dirty_all_nodes<N: LayoutNode>(node: N) {
|
|
||||||
for node in node.traverse_preorder() {
|
|
||||||
// TODO(cgaebel): mark nodes which are sensitive to media queries as
|
|
||||||
// "changed":
|
|
||||||
// > node.set_changed(true);
|
|
||||||
node.set_dirty(true);
|
|
||||||
node.set_dirty_descendants(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reflow_all_nodes(flow: &mut Flow) {
|
fn reflow_all_nodes(flow: &mut Flow) {
|
||||||
debug!("reflowing all nodes!");
|
debug!("reflowing all nodes!");
|
||||||
flow::mut_base(flow).restyle_damage.insert(REPAINT | STORE_OVERFLOW | REFLOW);
|
flow::mut_base(flow).restyle_damage.insert(REPAINT | STORE_OVERFLOW | REFLOW);
|
||||||
|
|
|
@ -376,6 +376,7 @@ impl Document {
|
||||||
// that workable.
|
// that workable.
|
||||||
match self.GetDocumentElement() {
|
match self.GetDocumentElement() {
|
||||||
Some(root) => {
|
Some(root) => {
|
||||||
|
root.upcast::<Node>().is_dirty() ||
|
||||||
root.upcast::<Node>().has_dirty_descendants() ||
|
root.upcast::<Node>().has_dirty_descendants() ||
|
||||||
!self.modified_elements.borrow().is_empty()
|
!self.modified_elements.borrow().is_empty()
|
||||||
}
|
}
|
||||||
|
@ -1371,6 +1372,7 @@ impl Document {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finish_load(&self, load: LoadType) {
|
pub fn finish_load(&self, load: LoadType) {
|
||||||
|
debug!("Document got finish_load: {:?}", load);
|
||||||
// The parser might need the loader, so restrict the lifetime of the borrow.
|
// The parser might need the loader, so restrict the lifetime of the borrow.
|
||||||
{
|
{
|
||||||
let mut loader = self.loader.borrow_mut();
|
let mut loader = self.loader.borrow_mut();
|
||||||
|
@ -1396,9 +1398,9 @@ impl Document {
|
||||||
// If we don't have a parser, and the reflow timer has been reset, explicitly
|
// If we don't have a parser, and the reflow timer has been reset, explicitly
|
||||||
// trigger a reflow.
|
// trigger a reflow.
|
||||||
if let LoadType::Stylesheet(_) = load {
|
if let LoadType::Stylesheet(_) = load {
|
||||||
self.window().reflow(ReflowGoal::ForDisplay,
|
self.window.reflow(ReflowGoal::ForDisplay,
|
||||||
ReflowQueryType::NoQuery,
|
ReflowQueryType::NoQuery,
|
||||||
ReflowReason::StylesheetLoaded);
|
ReflowReason::StylesheetLoaded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1487,24 +1489,25 @@ impl Document {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.domcontentloaded_dispatched.set(true);
|
self.domcontentloaded_dispatched.set(true);
|
||||||
|
assert!(self.ReadyState() != DocumentReadyState::Complete,
|
||||||
|
"Complete before DOMContentLoaded?");
|
||||||
|
|
||||||
update_with_current_time_ms(&self.dom_content_loaded_event_start);
|
update_with_current_time_ms(&self.dom_content_loaded_event_start);
|
||||||
|
|
||||||
let window = self.window();
|
let window = self.window();
|
||||||
window.dom_manipulation_task_source().queue_event(self.upcast(), atom!("DOMContentLoaded"),
|
window.dom_manipulation_task_source().queue_event(self.upcast(), atom!("DOMContentLoaded"),
|
||||||
EventBubbles::Bubbles, EventCancelable::NotCancelable, window);
|
EventBubbles::Bubbles, EventCancelable::NotCancelable, window);
|
||||||
|
|
||||||
window.reflow(ReflowGoal::ForDisplay,
|
window.reflow(ReflowGoal::ForDisplay,
|
||||||
ReflowQueryType::NoQuery,
|
ReflowQueryType::NoQuery,
|
||||||
ReflowReason::DOMContentLoaded);
|
ReflowReason::DOMContentLoaded);
|
||||||
|
|
||||||
update_with_current_time_ms(&self.dom_content_loaded_event_end);
|
update_with_current_time_ms(&self.dom_content_loaded_event_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn notify_constellation_load(&self) {
|
pub fn notify_constellation_load(&self) {
|
||||||
let pipeline_id = self.window.pipeline();
|
let pipeline_id = self.window.pipeline();
|
||||||
let event = ConstellationMsg::DOMLoad(pipeline_id);
|
let load_event = ConstellationMsg::LoadComplete(pipeline_id);
|
||||||
self.window.constellation_chan().send(event).unwrap();
|
self.window.constellation_chan().send(load_event).unwrap();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_current_parser(&self, script: Option<ParserRef>) {
|
pub fn set_current_parser(&self, script: Option<ParserRef>) {
|
||||||
|
@ -2908,16 +2911,18 @@ impl DocumentProgressHandler {
|
||||||
// http://w3c.github.io/navigation-timing/#widl-PerformanceNavigationTiming-loadEventStart
|
// http://w3c.github.io/navigation-timing/#widl-PerformanceNavigationTiming-loadEventStart
|
||||||
update_with_current_time_ms(&document.load_event_start);
|
update_with_current_time_ms(&document.load_event_start);
|
||||||
|
|
||||||
|
debug!("About to dispatch load for {:?}", document.url());
|
||||||
let _ = wintarget.dispatch_event_with_target(document.upcast(), &event);
|
let _ = wintarget.dispatch_event_with_target(document.upcast(), &event);
|
||||||
|
|
||||||
// http://w3c.github.io/navigation-timing/#widl-PerformanceNavigationTiming-loadEventEnd
|
// http://w3c.github.io/navigation-timing/#widl-PerformanceNavigationTiming-loadEventEnd
|
||||||
update_with_current_time_ms(&document.load_event_end);
|
update_with_current_time_ms(&document.load_event_end);
|
||||||
|
|
||||||
document.notify_constellation_load();
|
|
||||||
|
|
||||||
window.reflow(ReflowGoal::ForDisplay,
|
window.reflow(ReflowGoal::ForDisplay,
|
||||||
ReflowQueryType::NoQuery,
|
ReflowQueryType::NoQuery,
|
||||||
ReflowReason::DocumentLoaded);
|
ReflowReason::DocumentLoaded);
|
||||||
|
|
||||||
|
document.notify_constellation_load();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -479,19 +479,7 @@ impl Node {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Dirty descendants.
|
self.set_flag(IS_DIRTY, true);
|
||||||
fn dirty_subtree(node: &Node) {
|
|
||||||
// Stop if this subtree is already dirty.
|
|
||||||
if node.is_dirty() { return }
|
|
||||||
|
|
||||||
node.set_flag(IS_DIRTY | HAS_DIRTY_DESCENDANTS, true);
|
|
||||||
|
|
||||||
for kid in node.children() {
|
|
||||||
dirty_subtree(kid.r());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dirty_subtree(self);
|
|
||||||
|
|
||||||
// 4. Dirty ancestors.
|
// 4. Dirty ancestors.
|
||||||
for ancestor in self.ancestors() {
|
for ancestor in self.ancestors() {
|
||||||
|
@ -1299,6 +1287,31 @@ impl TreeIterator {
|
||||||
depth: 0,
|
depth: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn next_skipping_children(&mut self) -> Option<Root<Node>> {
|
||||||
|
let current = match self.current.take() {
|
||||||
|
None => return None,
|
||||||
|
Some(current) => current,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.next_skipping_children_impl(current)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_skipping_children_impl(&mut self, current: Root<Node>) -> Option<Root<Node>> {
|
||||||
|
for ancestor in current.inclusive_ancestors() {
|
||||||
|
if self.depth == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if let Some(next_sibling) = ancestor.GetNextSibling() {
|
||||||
|
self.current = Some(next_sibling);
|
||||||
|
return Some(current);
|
||||||
|
}
|
||||||
|
self.depth -= 1;
|
||||||
|
}
|
||||||
|
debug_assert!(self.depth == 0);
|
||||||
|
self.current = None;
|
||||||
|
Some(current)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for TreeIterator {
|
impl Iterator for TreeIterator {
|
||||||
|
@ -1315,19 +1328,8 @@ impl Iterator for TreeIterator {
|
||||||
self.depth += 1;
|
self.depth += 1;
|
||||||
return Some(current);
|
return Some(current);
|
||||||
};
|
};
|
||||||
for ancestor in current.inclusive_ancestors() {
|
|
||||||
if self.depth == 0 {
|
self.next_skipping_children_impl(current)
|
||||||
break;
|
|
||||||
}
|
|
||||||
if let Some(next_sibling) = ancestor.GetNextSibling() {
|
|
||||||
self.current = Some(next_sibling);
|
|
||||||
return Some(current);
|
|
||||||
}
|
|
||||||
self.depth -= 1;
|
|
||||||
}
|
|
||||||
debug_assert!(self.depth == 0);
|
|
||||||
self.current = None;
|
|
||||||
Some(current)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -450,13 +450,15 @@ impl WindowMethods for Window {
|
||||||
// Right now, just print to the console
|
// Right now, just print to the console
|
||||||
// Ensure that stderr doesn't trample through the alert() we use to
|
// Ensure that stderr doesn't trample through the alert() we use to
|
||||||
// communicate test results (see executorservo.py in wptrunner).
|
// communicate test results (see executorservo.py in wptrunner).
|
||||||
let stderr = stderr();
|
{
|
||||||
let mut stderr = stderr.lock();
|
let stderr = stderr();
|
||||||
let stdout = stdout();
|
let mut stderr = stderr.lock();
|
||||||
let mut stdout = stdout.lock();
|
let stdout = stdout();
|
||||||
writeln!(&mut stdout, "ALERT: {}", s).unwrap();
|
let mut stdout = stdout.lock();
|
||||||
stdout.flush().unwrap();
|
writeln!(&mut stdout, "ALERT: {}", s).unwrap();
|
||||||
stderr.flush().unwrap();
|
stdout.flush().unwrap();
|
||||||
|
stderr.flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
let (sender, receiver) = ipc::channel().unwrap();
|
let (sender, receiver) = ipc::channel().unwrap();
|
||||||
self.constellation_chan().send(ConstellationMsg::Alert(self.pipeline(), s.to_string(), sender)).unwrap();
|
self.constellation_chan().send(ConstellationMsg::Alert(self.pipeline(), s.to_string(), sender)).unwrap();
|
||||||
|
|
|
@ -199,15 +199,6 @@ impl<'ln> TNode for ServoLayoutNode<'ln> {
|
||||||
self.node.set_flag(DIRTY_ON_VIEWPORT_SIZE_CHANGE, true);
|
self.node.set_flag(DIRTY_ON_VIEWPORT_SIZE_CHANGE, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_descendants_dirty_on_viewport_size_changed(&self) {
|
|
||||||
for ref child in self.children() {
|
|
||||||
unsafe {
|
|
||||||
child.set_dirty_on_viewport_size_changed();
|
|
||||||
}
|
|
||||||
child.set_descendants_dirty_on_viewport_size_changed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn can_be_fragmented(&self) -> bool {
|
fn can_be_fragmented(&self) -> bool {
|
||||||
unsafe { self.node.get_flag(CAN_BE_FRAGMENTED) }
|
unsafe { self.node.get_flag(CAN_BE_FRAGMENTED) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1198,8 +1198,6 @@ impl ScriptThread {
|
||||||
// https://html.spec.whatwg.org/multipage/#the-end step 7
|
// https://html.spec.whatwg.org/multipage/#the-end step 7
|
||||||
let handler = box DocumentProgressHandler::new(Trusted::new(doc));
|
let handler = box DocumentProgressHandler::new(Trusted::new(doc));
|
||||||
self.dom_manipulation_task_source.queue(handler, GlobalRef::Window(doc.window())).unwrap();
|
self.dom_manipulation_task_source.queue(handler, GlobalRef::Window(doc.window())).unwrap();
|
||||||
|
|
||||||
self.constellation_chan.send(ConstellationMsg::LoadComplete(pipeline)).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_reports(&self, reports_chan: ReportsChan) {
|
fn collect_reports(&self, reports_chan: ReportsChan) {
|
||||||
|
|
|
@ -71,10 +71,6 @@ pub enum ScriptMsg {
|
||||||
CreateWebGLPaintThread(Size2D<i32>,
|
CreateWebGLPaintThread(Size2D<i32>,
|
||||||
GLContextAttributes,
|
GLContextAttributes,
|
||||||
IpcSender<Result<(IpcSender<CanvasMsg>, GLLimits), String>>),
|
IpcSender<Result<(IpcSender<CanvasMsg>, GLLimits), String>>),
|
||||||
/// Dispatched after the DOM load event has fired on a document
|
|
||||||
/// Causes a `load` event to be dispatched to any enclosing frame context element
|
|
||||||
/// for the given pipeline.
|
|
||||||
DOMLoad(PipelineId),
|
|
||||||
/// Notifies the constellation that this frame has received focus.
|
/// Notifies the constellation that this frame has received focus.
|
||||||
Focus(PipelineId),
|
Focus(PipelineId),
|
||||||
/// Re-send a mouse button event that was sent to the parent window.
|
/// Re-send a mouse button event that was sent to the parent window.
|
||||||
|
@ -85,7 +81,8 @@ pub enum ScriptMsg {
|
||||||
GetClipboardContents(IpcSender<String>),
|
GetClipboardContents(IpcSender<String>),
|
||||||
/// <head> tag finished parsing
|
/// <head> tag finished parsing
|
||||||
HeadParsed,
|
HeadParsed,
|
||||||
/// All pending loads are complete.
|
/// All pending loads are complete, and the `load` event for this pipeline
|
||||||
|
/// has been dispatched.
|
||||||
LoadComplete(PipelineId),
|
LoadComplete(PipelineId),
|
||||||
/// A new load has been requested.
|
/// A new load has been requested.
|
||||||
LoadUrl(PipelineId, LoadData),
|
LoadUrl(PipelineId, LoadData),
|
||||||
|
|
|
@ -37,13 +37,13 @@ impl PrivateStyleData {
|
||||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||||
pub struct DomParallelInfo {
|
pub struct DomParallelInfo {
|
||||||
/// The number of children that still need work done.
|
/// The number of children that still need work done.
|
||||||
pub children_count: AtomicIsize,
|
pub children_to_process: AtomicIsize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DomParallelInfo {
|
impl DomParallelInfo {
|
||||||
pub fn new() -> DomParallelInfo {
|
pub fn new() -> DomParallelInfo {
|
||||||
DomParallelInfo {
|
DomParallelInfo {
|
||||||
children_count: AtomicIsize::new(0),
|
children_to_process: AtomicIsize::new(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,33 +111,10 @@ pub trait TNode : Sized + Copy + Clone {
|
||||||
|
|
||||||
unsafe fn set_dirty_descendants(&self, value: bool);
|
unsafe fn set_dirty_descendants(&self, value: bool);
|
||||||
|
|
||||||
fn dirty_self(&self) {
|
|
||||||
unsafe {
|
|
||||||
self.set_dirty(true);
|
|
||||||
self.set_dirty_descendants(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dirty_descendants(&self) {
|
|
||||||
for ref child in self.children() {
|
|
||||||
child.dirty_self();
|
|
||||||
child.dirty_descendants();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn needs_dirty_on_viewport_size_changed(&self) -> bool;
|
fn needs_dirty_on_viewport_size_changed(&self) -> bool;
|
||||||
|
|
||||||
unsafe fn set_dirty_on_viewport_size_changed(&self);
|
unsafe fn set_dirty_on_viewport_size_changed(&self);
|
||||||
|
|
||||||
fn set_descendants_dirty_on_viewport_size_changed(&self) {
|
|
||||||
for ref child in self.children() {
|
|
||||||
unsafe {
|
|
||||||
child.set_dirty_on_viewport_size_changed();
|
|
||||||
}
|
|
||||||
child.set_descendants_dirty_on_viewport_size_changed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn can_be_fragmented(&self) -> bool;
|
fn can_be_fragmented(&self) -> bool;
|
||||||
|
|
||||||
unsafe fn set_can_be_fragmented(&self, value: bool);
|
unsafe fn set_can_be_fragmented(&self, value: bool);
|
||||||
|
@ -215,7 +192,7 @@ pub trait TElement : Sized + Copy + Clone + ElementExt + PresentationalHintsSynt
|
||||||
fn attr_equals(&self, namespace: &Namespace, attr: &Atom, value: &Atom) -> bool;
|
fn attr_equals(&self, namespace: &Namespace, attr: &Atom, value: &Atom) -> bool;
|
||||||
|
|
||||||
/// Properly marks nodes as dirty in response to restyle hints.
|
/// Properly marks nodes as dirty in response to restyle hints.
|
||||||
fn note_restyle_hint(&self, mut hint: RestyleHint) {
|
fn note_restyle_hint(&self, hint: RestyleHint) {
|
||||||
// Bail early if there's no restyling to do.
|
// Bail early if there's no restyling to do.
|
||||||
if hint.is_empty() {
|
if hint.is_empty() {
|
||||||
return;
|
return;
|
||||||
|
@ -233,23 +210,21 @@ pub trait TElement : Sized + Copy + Clone + ElementExt + PresentationalHintsSynt
|
||||||
|
|
||||||
// Process hints.
|
// Process hints.
|
||||||
if hint.contains(RESTYLE_SELF) {
|
if hint.contains(RESTYLE_SELF) {
|
||||||
node.dirty_self();
|
unsafe { node.set_dirty(true); }
|
||||||
|
// XXX(emilio): For now, dirty implies dirty descendants if found.
|
||||||
|
} else if hint.contains(RESTYLE_DESCENDANTS) {
|
||||||
|
let mut current = node.first_child();
|
||||||
|
while let Some(node) = current {
|
||||||
|
unsafe { node.set_dirty(true); }
|
||||||
|
current = node.next_sibling();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME(bholley, #8438): We currently need to RESTYLE_DESCENDANTS in the
|
|
||||||
// RESTYLE_SELF case in order to make sure "inherit" style structs propagate
|
|
||||||
// properly. See the explanation in the github issue.
|
|
||||||
hint.insert(RESTYLE_DESCENDANTS);
|
|
||||||
}
|
|
||||||
if hint.contains(RESTYLE_DESCENDANTS) {
|
|
||||||
unsafe { node.set_dirty_descendants(true); }
|
|
||||||
node.dirty_descendants();
|
|
||||||
}
|
|
||||||
if hint.contains(RESTYLE_LATER_SIBLINGS) {
|
if hint.contains(RESTYLE_LATER_SIBLINGS) {
|
||||||
let mut next = ::selectors::Element::next_sibling_element(self);
|
let mut next = ::selectors::Element::next_sibling_element(self);
|
||||||
while let Some(sib) = next {
|
while let Some(sib) = next {
|
||||||
let sib_node = sib.as_node();
|
let sib_node = sib.as_node();
|
||||||
sib_node.dirty_self();
|
unsafe { sib_node.set_dirty(true) };
|
||||||
sib_node.dirty_descendants();
|
|
||||||
next = ::selectors::Element::next_sibling_element(&sib);
|
next = ::selectors::Element::next_sibling_element(&sib);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -262,12 +237,16 @@ pub struct TreeIterator<ConcreteNode> where ConcreteNode: TNode {
|
||||||
|
|
||||||
impl<ConcreteNode> TreeIterator<ConcreteNode> where ConcreteNode: TNode {
|
impl<ConcreteNode> TreeIterator<ConcreteNode> where ConcreteNode: TNode {
|
||||||
fn new(root: ConcreteNode) -> TreeIterator<ConcreteNode> {
|
fn new(root: ConcreteNode) -> TreeIterator<ConcreteNode> {
|
||||||
let mut stack = vec!();
|
let mut stack = vec![];
|
||||||
stack.push(root);
|
stack.push(root);
|
||||||
TreeIterator {
|
TreeIterator {
|
||||||
stack: stack,
|
stack: stack,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn next_skipping_children(&mut self) -> Option<ConcreteNode> {
|
||||||
|
self.stack.pop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<ConcreteNode> Iterator for TreeIterator<ConcreteNode>
|
impl<ConcreteNode> Iterator for TreeIterator<ConcreteNode>
|
||||||
|
|
|
@ -63,25 +63,40 @@ fn top_down_dom<N, C>(unsafe_nodes: UnsafeNodeList,
|
||||||
// Get a real layout node.
|
// Get a real layout node.
|
||||||
let node = unsafe { N::from_unsafe(&unsafe_node) };
|
let node = unsafe { N::from_unsafe(&unsafe_node) };
|
||||||
|
|
||||||
|
if !context.should_process(node) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Perform the appropriate traversal.
|
// Perform the appropriate traversal.
|
||||||
context.process_preorder(node);
|
context.process_preorder(node);
|
||||||
|
|
||||||
let child_count = node.children_count();
|
// Possibly enqueue the children.
|
||||||
|
let mut children_to_process = 0isize;
|
||||||
|
for kid in node.children() {
|
||||||
|
// Trigger the hook pre-adding the kid to the list. This can (and in
|
||||||
|
// fact uses to) change the result of the should_process operation.
|
||||||
|
//
|
||||||
|
// As of right now, this hook takes care of propagating the restyle
|
||||||
|
// flag down the tree. In the future, more accurate behavior is
|
||||||
|
// probably going to be needed.
|
||||||
|
context.pre_process_child_hook(node, kid);
|
||||||
|
if context.should_process(kid) {
|
||||||
|
children_to_process += 1;
|
||||||
|
discovered_child_nodes.push(kid.to_unsafe())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reset the count of children.
|
// Reset the count of children.
|
||||||
{
|
{
|
||||||
let data = node.mutate_data().unwrap();
|
let data = node.mutate_data().unwrap();
|
||||||
data.parallel.children_count.store(child_count as isize,
|
data.parallel.children_to_process
|
||||||
Ordering::Relaxed);
|
.store(children_to_process,
|
||||||
|
Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Possibly enqueue the children.
|
|
||||||
if child_count != 0 {
|
// If there were no more children, start walking back up.
|
||||||
for kid in node.children() {
|
if children_to_process == 0 {
|
||||||
discovered_child_nodes.push(kid.to_unsafe())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If there were no more children, start walking back up.
|
|
||||||
bottom_up_dom::<N, C>(unsafe_nodes.1, unsafe_node, proxy)
|
bottom_up_dom::<N, C>(unsafe_nodes.1, unsafe_node, proxy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,7 +143,7 @@ fn bottom_up_dom<N, C>(root: OpaqueNode,
|
||||||
|
|
||||||
if parent_data
|
if parent_data
|
||||||
.parallel
|
.parallel
|
||||||
.children_count
|
.children_to_process
|
||||||
.fetch_sub(1, Ordering::Relaxed) != 1 {
|
.fetch_sub(1, Ordering::Relaxed) != 1 {
|
||||||
// Get out of here and find another node to work on.
|
// Get out of here and find another node to work on.
|
||||||
break
|
break
|
||||||
|
@ -138,4 +153,3 @@ fn bottom_up_dom<N, C>(root: OpaqueNode,
|
||||||
node = parent;
|
node = parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,20 +9,29 @@ use traversal::DomTraversalContext;
|
||||||
|
|
||||||
pub fn traverse_dom<N, C>(root: N,
|
pub fn traverse_dom<N, C>(root: N,
|
||||||
shared: &C::SharedContext)
|
shared: &C::SharedContext)
|
||||||
where N: TNode,
|
where N: TNode,
|
||||||
C: DomTraversalContext<N> {
|
C: DomTraversalContext<N>
|
||||||
|
{
|
||||||
fn doit<'a, N, C>(context: &'a C, node: N)
|
fn doit<'a, N, C>(context: &'a C, node: N)
|
||||||
where N: TNode, C: DomTraversalContext<N> {
|
where N: TNode,
|
||||||
|
C: DomTraversalContext<N>
|
||||||
|
{
|
||||||
|
debug_assert!(context.should_process(node));
|
||||||
context.process_preorder(node);
|
context.process_preorder(node);
|
||||||
|
|
||||||
for kid in node.children() {
|
for kid in node.children() {
|
||||||
doit::<N, C>(context, kid);
|
context.pre_process_child_hook(node, kid);
|
||||||
|
if context.should_process(node) {
|
||||||
|
doit::<N, C>(context, kid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.process_postorder(node);
|
context.process_postorder(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
let context = C::new(shared, root.opaque());
|
let context = C::new(shared, root.opaque());
|
||||||
doit::<N, C>(&context, root);
|
if context.should_process(root) {
|
||||||
|
doit::<N, C>(&context, root);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -147,6 +147,30 @@ pub trait DomTraversalContext<N: TNode> {
|
||||||
fn process_preorder(&self, node: N);
|
fn process_preorder(&self, node: N);
|
||||||
/// Process `node` on the way up, after its children have been processed.
|
/// Process `node` on the way up, after its children have been processed.
|
||||||
fn process_postorder(&self, node: N);
|
fn process_postorder(&self, node: N);
|
||||||
|
|
||||||
|
/// Returns if the node should be processed by the preorder traversal (and
|
||||||
|
/// then by the post-order one).
|
||||||
|
///
|
||||||
|
/// Note that this is true unconditionally for servo, since it requires to
|
||||||
|
/// bubble the widths bottom-up for all the DOM.
|
||||||
|
fn should_process(&self, node: N) -> bool {
|
||||||
|
node.is_dirty() || node.has_dirty_descendants()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Do an action over the child before pushing him to the work queue.
|
||||||
|
///
|
||||||
|
/// By default, propagate the IS_DIRTY flag down the tree.
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
fn pre_process_child_hook(&self, parent: N, kid: N) {
|
||||||
|
// NOTE: At this point is completely safe to modify either the parent or
|
||||||
|
// the child, since we have exclusive access to both of them.
|
||||||
|
if parent.is_dirty() {
|
||||||
|
unsafe {
|
||||||
|
kid.set_dirty(true);
|
||||||
|
parent.set_dirty_descendants(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates the style for a single node.
|
/// Calculates the style for a single node.
|
||||||
|
@ -244,22 +268,17 @@ pub fn recalc_style_at<'a, N, C>(context: &'a C,
|
||||||
// NB: flow construction updates the bloom filter on the way up.
|
// NB: flow construction updates the bloom filter on the way up.
|
||||||
put_thread_local_bloom_filter(bf, &unsafe_layout_node, context.shared_context());
|
put_thread_local_bloom_filter(bf, &unsafe_layout_node, context.shared_context());
|
||||||
|
|
||||||
// Mark the node as DIRTY_ON_VIEWPORT_SIZE_CHANGE is it uses viewport percentage units.
|
// Mark the node as DIRTY_ON_VIEWPORT_SIZE_CHANGE is it uses viewport
|
||||||
match node.as_element() {
|
// percentage units.
|
||||||
Some(element) => {
|
if !node.needs_dirty_on_viewport_size_changed() {
|
||||||
match *element.style_attribute() {
|
if let Some(element) = node.as_element() {
|
||||||
Some(ref property_declaration_block) => {
|
if let Some(ref property_declaration_block) = *element.style_attribute() {
|
||||||
if property_declaration_block.declarations().any(|d| d.0.has_viewport_percentage()) {
|
if property_declaration_block.declarations().any(|d| d.0.has_viewport_percentage()) {
|
||||||
unsafe {
|
unsafe {
|
||||||
node.set_dirty_on_viewport_size_changed();
|
node.set_dirty_on_viewport_size_changed();
|
||||||
}
|
|
||||||
node.set_descendants_dirty_on_viewport_size_changed();
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => {}
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -113,13 +113,13 @@ fn restyle_subtree(node: GeckoNode, raw_data: *mut RawServoStyleSet) {
|
||||||
timer: Timer::new(),
|
timer: Timer::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if node.is_dirty() || node.has_dirty_descendants() {
|
// We ensure this is true before calling Servo_RestyleSubtree()
|
||||||
if per_doc_data.num_threads == 1 {
|
debug_assert!(node.is_dirty() || node.has_dirty_descendants());
|
||||||
sequential::traverse_dom::<GeckoNode, RecalcStyleOnly>(node, &shared_style_context);
|
if per_doc_data.num_threads == 1 {
|
||||||
} else {
|
sequential::traverse_dom::<GeckoNode, RecalcStyleOnly>(node, &shared_style_context);
|
||||||
parallel::traverse_dom::<GeckoNode, RecalcStyleOnly>(node, &shared_style_context,
|
} else {
|
||||||
&mut per_doc_data.work_queue);
|
parallel::traverse_dom::<GeckoNode, RecalcStyleOnly>(node, &shared_style_context,
|
||||||
}
|
&mut per_doc_data.work_queue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue