compositor: Make PipelineDetails and pending paint metrics per-WebView (#35701)

This is one of the first big steps toward making the compositor work
per-WebView. It moves the collection of pipelines into the per-WebView
data structure in the compositor as well as the pending paint metrics.

This means that more messages need to carry information about the
WebView they apply to. Note that there are still a few places that we
need to map from `PipelineId` to `WebViewId`, so this also includes a
shared mapping which tracks this. The mapping can be removed once event
handling is fully per-WebView.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Delan Azabani <dazabani@igalia.com>
This commit is contained in:
Martin Robinson 2025-03-04 03:31:23 +01:00 committed by GitHub
parent 0d0bcdeb4d
commit f3e6e4f04e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 537 additions and 373 deletions

View file

@ -2,7 +2,7 @@
* 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::Cell;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::env;
use std::fs::{File, create_dir_all};
@ -26,7 +26,7 @@ use embedder_traits::{
ShutdownState, TouchEvent, TouchEventType, TouchId,
};
use euclid::{Box2D, Point2D, Rect, Scale, Size2D, Transform3D, Vector2D};
use fnv::{FnvHashMap, FnvHashSet};
use fnv::FnvHashMap;
use ipc_channel::ipc::{self, IpcSharedMemory};
use libc::c_void;
use log::{debug, info, trace, warn};
@ -92,6 +92,11 @@ pub struct ServoRenderer {
/// Our top-level browsing contexts.
webviews: WebViewManager<WebView>,
/// This is a temporary map between [`PipelineId`]s and their associated [`WebViewId`]. Once
/// all renderer operations become per-`WebView` this map can be removed, but we still sometimes
/// need to work backwards to figure out what `WebView` is associated with a `Pipeline`.
pipeline_to_webview_map: Rc<RefCell<HashMap<PipelineId, WebViewId>>>,
/// Tracks whether we are in the process of shutting down, or have shut down and should close
/// the compositor. This is shared with the `Servo` instance.
shutdown_state: Rc<Cell<ShutdownState>>,
@ -128,9 +133,6 @@ pub struct IOCompositor {
/// The application window.
pub window: Rc<dyn WindowMethods>,
/// Tracks details about each active pipeline that the compositor knows about.
pipeline_details: HashMap<PipelineId, PipelineDetails>,
/// "Mobile-style" zoom that does not reflow the page.
viewport_zoom: PinchZoomFactor,
@ -169,13 +171,6 @@ pub struct IOCompositor {
/// The surfman instance that webrender targets
rendering_context: Rc<dyn RenderingContext>,
/// A per-pipeline queue of display lists that have not yet been rendered by WebRender. Layout
/// expects WebRender to paint each given epoch. Once the compositor paints a frame with that
/// epoch's display list, it will be removed from the queue and the paint time will be recorded
/// as a metric. In case new display lists come faster than painting a metric might never be
/// recorded.
pending_paint_metrics: HashMap<PipelineId, Vec<Epoch>>,
/// The coordinates of the native window, its view and the screen.
embedder_coordinates: EmbedderCoordinates,
@ -232,39 +227,86 @@ bitflags! {
}
}
struct PipelineDetails {
pub(crate) struct PipelineDetails {
/// The pipeline associated with this PipelineDetails object.
pipeline: Option<CompositionPipeline>,
pub pipeline: Option<CompositionPipeline>,
/// The [`PipelineId`] of this pipeline.
pub id: PipelineId,
/// The id of the parent pipeline, if any.
parent_pipeline_id: Option<PipelineId>,
pub parent_pipeline_id: Option<PipelineId>,
/// The epoch of the most recent display list for this pipeline. Note that this display
/// list might not be displayed, as WebRender processes display lists asynchronously.
most_recent_display_list_epoch: Option<WebRenderEpoch>,
pub most_recent_display_list_epoch: Option<WebRenderEpoch>,
/// Whether animations are running
animations_running: bool,
pub animations_running: bool,
/// Whether there are animation callbacks
animation_callbacks_running: bool,
pub animation_callbacks_running: bool,
/// Whether to use less resources by stopping animations.
throttled: bool,
pub throttled: bool,
/// Hit test items for this pipeline. This is used to map WebRender hit test
/// information to the full information necessary for Servo.
hit_test_items: Vec<HitTestInfo>,
pub hit_test_items: Vec<HitTestInfo>,
/// The compositor-side [ScrollTree]. This is used to allow finding and scrolling
/// nodes in the compositor before forwarding new offsets to WebRender.
scroll_tree: ScrollTree,
pub scroll_tree: ScrollTree,
/// A per-pipeline queue of display lists that have not yet been rendered by WebRender. Layout
/// expects WebRender to paint each given epoch. Once the compositor paints a frame with that
/// epoch's display list, it will be removed from the queue and the paint time will be recorded
/// as a metric. In case new display lists come faster than painting a metric might never be
/// recorded.
pub pending_paint_metrics: Vec<Epoch>,
}
impl PipelineDetails {
fn new() -> PipelineDetails {
pub(crate) fn animations_or_animation_callbacks_running(&self) -> bool {
self.animations_running || self.animation_callbacks_running
}
pub(crate) fn animation_callbacks_running(&self) -> bool {
self.animation_callbacks_running
}
pub(crate) fn tick_animations(&self, compositor: &IOCompositor) -> bool {
let animation_callbacks_running = self.animation_callbacks_running;
let animations_running = self.animations_running;
if !animation_callbacks_running && !animations_running {
return false;
}
if self.throttled {
return false;
}
let mut tick_type = AnimationTickType::empty();
if animations_running {
tick_type.insert(AnimationTickType::CSS_ANIMATIONS_AND_TRANSITIONS);
}
if animation_callbacks_running {
tick_type.insert(AnimationTickType::REQUEST_ANIMATION_FRAME);
}
let msg = ConstellationMsg::TickAnimation(self.id, tick_type);
if let Err(e) = compositor.global.constellation_sender.send(msg) {
warn!("Sending tick to constellation failed ({:?}).", e);
}
true
}
}
impl PipelineDetails {
pub(crate) fn new(id: PipelineId) -> PipelineDetails {
PipelineDetails {
pipeline: None,
id,
parent_pipeline_id: None,
most_recent_display_list_epoch: None,
animations_running: false,
@ -272,6 +314,7 @@ impl PipelineDetails {
throttled: false,
hit_test_items: Vec::new(),
scroll_tree: ScrollTree::default(),
pending_paint_metrics: Vec::new(),
}
}
@ -310,6 +353,7 @@ impl IOCompositor {
global: ServoRenderer {
shutdown_state: state.shutdown_state,
webviews: WebViewManager::default(),
pipeline_to_webview_map: Default::default(),
compositor_receiver: state.receiver,
constellation_sender: state.constellation_chan,
time_profiler_chan: state.time_profiler_chan,
@ -321,7 +365,6 @@ impl IOCompositor {
},
embedder_coordinates: window.get_coordinates(),
window,
pipeline_details: HashMap::new(),
needs_repaint: Cell::default(),
touch_handler: TouchHandler::new(),
pending_scroll_zoom_events: Vec::new(),
@ -335,7 +378,6 @@ impl IOCompositor {
webrender: Some(state.webrender),
webrender_document: state.webrender_document,
rendering_context: state.rendering_context,
pending_paint_metrics: HashMap::new(),
cursor: Cursor::None,
cursor_pos: DevicePoint::new(0.0, 0.0),
convert_mouse_to_touch,
@ -378,22 +420,24 @@ impl IOCompositor {
Some(cursor) if cursor != self.cursor => cursor,
_ => return,
};
let Some(webview_id) = self
.pipeline_details(result.pipeline_id)
.pipeline
.as_ref()
.map(|composition_pipeline| composition_pipeline.top_level_browsing_context_id)
.global
.pipeline_to_webview_map
.borrow()
.get(&result.pipeline_id)
.cloned()
else {
warn!(
"Updating cursor for not-yet-rendered pipeline: {}",
result.pipeline_id
);
warn!("Couldn't update cursor for non-WebView-associated pipeline");
return;
};
self.cursor = cursor;
let msg = ConstellationMsg::SetCursor(webview_id, cursor);
if let Err(e) = self.global.constellation_sender.send(msg) {
if let Err(e) = self
.global
.constellation_sender
.send(ConstellationMsg::SetCursor(webview_id, cursor))
{
warn!("Sending event to constellation failed ({:?}).", e);
}
}
@ -433,13 +477,34 @@ impl IOCompositor {
}
match msg {
CompositorMsg::ChangeRunningAnimationsState(pipeline_id, animation_state) => {
self.change_running_animations_state(pipeline_id, animation_state);
CompositorMsg::ChangeRunningAnimationsState(
webview_id,
pipeline_id,
animation_state,
) => {
let mut throttled = true;
if let Some(webview) = self.global.webviews.get_mut(webview_id) {
throttled =
webview.change_running_animations_state(pipeline_id, animation_state);
}
// These operations should eventually happen per-WebView, but they are global now as rendering
// is still global to all WebViews.
if !throttled && animation_state == AnimationState::AnimationsPresent {
self.set_needs_repaint(RepaintReason::ChangedAnimationState);
}
if !throttled && animation_state == AnimationState::AnimationCallbacksPresent {
// We need to fetch the WebView again in order to avoid a double borrow.
if let Some(webview) = self.global.webviews.get(webview_id) {
webview.tick_animations_for_pipeline(pipeline_id, self);
}
}
},
CompositorMsg::CreateOrUpdateWebView(frame_tree) => {
self.set_frame_tree_for_webview(&frame_tree);
self.send_scroll_positions_to_layout_for_pipeline(&frame_tree.pipeline.id);
self.send_scroll_positions_to_layout_for_pipeline(frame_tree.pipeline.id);
},
CompositorMsg::RemoveWebView(top_level_browsing_context_id) => {
@ -474,14 +539,21 @@ impl IOCompositor {
self.set_needs_repaint(RepaintReason::ReadyForScreenshot);
},
CompositorMsg::SetThrottled(pipeline_id, throttled) => {
self.pipeline_details(pipeline_id).throttled = throttled;
self.process_animations(true);
CompositorMsg::SetThrottled(webview_id, pipeline_id, throttled) => {
if let Some(webview) = self.global.webviews.get_mut(webview_id) {
webview.set_throttled(pipeline_id, throttled);
self.process_animations(true);
}
},
CompositorMsg::PipelineExited(pipeline_id, sender) => {
debug!("Compositor got pipeline exited: {:?}", pipeline_id);
self.remove_pipeline_root_layer(pipeline_id);
CompositorMsg::PipelineExited(webview_id, pipeline_id, sender) => {
debug!(
"Compositor got pipeline exited: {:?} {:?}",
webview_id, pipeline_id
);
if let Some(webview) = self.global.webviews.get_mut(webview_id) {
webview.remove_pipeline(pipeline_id);
}
let _ = sender.send(());
},
@ -494,7 +566,7 @@ impl IOCompositor {
}
}
if recomposite_needed || self.animation_callbacks_active() {
if recomposite_needed || self.animation_callbacks_running() {
self.set_needs_repaint(RepaintReason::NewWebRenderFrame);
}
},
@ -521,11 +593,10 @@ impl IOCompositor {
self.dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent { point }));
},
CompositorMsg::PendingPaintMetric(pipeline_id, epoch) => {
self.pending_paint_metrics
.entry(pipeline_id)
.or_default()
.push(epoch);
CompositorMsg::PendingPaintMetric(webview_id, pipeline_id, epoch) => {
if let Some(webview) = self.global.webviews.get_mut(webview_id) {
webview.add_pending_paint_metric(pipeline_id, epoch);
}
},
CompositorMsg::CrossProcess(cross_proces_message) => {
@ -552,14 +623,18 @@ impl IOCompositor {
},
CrossProcessCompositorMessage::SendScrollNode(
webview_id,
pipeline_id,
point,
external_scroll_id,
) => {
let Some(webview) = self.global.webviews.get_mut(webview_id) else {
return;
};
let pipeline_id = pipeline_id.into();
let pipeline_details = match self.pipeline_details.get_mut(&pipeline_id) {
Some(details) => details,
None => return,
let Some(pipeline_details) = webview.pipelines.get_mut(&pipeline_id) else {
return;
};
let offset = LayoutVector2D::new(point.x, point.y);
@ -589,6 +664,7 @@ impl IOCompositor {
},
CrossProcessCompositorMessage::SendDisplayList {
webview_id,
display_list_info,
display_list_descriptor,
display_list_receiver,
@ -633,8 +709,13 @@ impl IOCompositor {
servo_profiling = true,
)
.entered();
let Some(webview) = self.global.webviews.get_mut(webview_id) else {
return warn!("Could not find WebView for incoming display list");
};
let pipeline_id = display_list_info.pipeline_id;
let details = self.pipeline_details(pipeline_id.into());
let details = webview.pipeline_details(pipeline_id.into());
details.most_recent_display_list_epoch = Some(display_list_info.epoch);
details.hit_test_items = display_list_info.hit_test_info;
details.install_new_scroll_tree(display_list_info.scroll_tree);
@ -777,9 +858,14 @@ impl IOCompositor {
/// compositor no longer does any WebRender frame generation.
fn handle_browser_message_while_shutting_down(&mut self, msg: CompositorMsg) {
match msg {
CompositorMsg::PipelineExited(pipeline_id, sender) => {
debug!("Compositor got pipeline exited: {:?}", pipeline_id);
self.remove_pipeline_root_layer(pipeline_id);
CompositorMsg::PipelineExited(webview_id, pipeline_id, sender) => {
debug!(
"Compositor got pipeline exited: {:?} {:?}",
webview_id, pipeline_id
);
if let Some(webview) = self.global.webviews.get_mut(webview_id) {
webview.remove_pipeline(pipeline_id);
}
let _ = sender.send(());
},
CompositorMsg::CrossProcess(CrossProcessCompositorMessage::GenerateImageKey(
@ -838,61 +924,6 @@ impl IOCompositor {
transaction.generate_frame(0, true /* present */, reason);
}
/// Sets or unsets the animations-running flag for the given pipeline, and schedules a
/// recomposite if necessary.
fn change_running_animations_state(
&mut self,
pipeline_id: PipelineId,
animation_state: AnimationState,
) {
match animation_state {
AnimationState::AnimationsPresent => {
let throttled = self.pipeline_details(pipeline_id).throttled;
self.pipeline_details(pipeline_id).animations_running = true;
if !throttled {
self.set_needs_repaint(RepaintReason::ChangedAnimationState);
}
},
AnimationState::AnimationCallbacksPresent => {
let throttled = self.pipeline_details(pipeline_id).throttled;
self.pipeline_details(pipeline_id)
.animation_callbacks_running = true;
if !throttled {
self.tick_animations_for_pipeline(pipeline_id);
}
},
AnimationState::NoAnimationsPresent => {
self.pipeline_details(pipeline_id).animations_running = false;
},
AnimationState::NoAnimationCallbacksPresent => {
self.pipeline_details(pipeline_id)
.animation_callbacks_running = false;
},
}
}
fn pipeline_details(&mut self, pipeline_id: PipelineId) -> &mut PipelineDetails {
self.pipeline_details
.entry(pipeline_id)
.or_insert_with(PipelineDetails::new);
self.pipeline_details
.get_mut(&pipeline_id)
.expect("Insert then get failed!")
}
pub fn pipeline(&self, pipeline_id: PipelineId) -> Option<&CompositionPipeline> {
match self.pipeline_details.get(&pipeline_id) {
Some(details) => details.pipeline.as_ref(),
None => {
warn!(
"Compositor layer has an unknown pipeline ({:?}).",
pipeline_id
);
None
},
}
}
/// Set the root pipeline for our WebRender scene to a display list that consists of an iframe
/// for each visible top-level browsing context, applying a transformation on the root for
/// pinch zoom, page zoom, and HiDPI scaling.
@ -942,7 +973,7 @@ impl IOCompositor {
let root_clip_id = builder.define_clip_rect(zoom_reference_frame, scaled_viewport_rect);
let clip_chain_id = builder.define_clip_chain(None, [root_clip_id]);
for (_, webview) in self.global.webviews.painting_order() {
if let Some(pipeline_id) = webview.pipeline_id {
if let Some(pipeline_id) = webview.root_pipeline_id {
let scaled_webview_rect = webview.rect / zoom_factor;
builder.push_iframe(
LayoutRect::from_untyped(&scaled_webview_rect.to_untyped()),
@ -975,20 +1006,23 @@ impl IOCompositor {
/// TODO(mrobinson): Could we only send offsets for the branch being modified
/// and not the entire scene?
fn update_transaction_with_all_scroll_offsets(&self, transaction: &mut Transaction) {
for details in self.pipeline_details.values() {
for node in details.scroll_tree.nodes.iter() {
let (Some(offset), Some(external_id)) = (node.offset(), node.external_id()) else {
continue;
};
for webview in self.global.webviews.iter() {
for details in webview.pipelines.values() {
for node in details.scroll_tree.nodes.iter() {
let (Some(offset), Some(external_id)) = (node.offset(), node.external_id())
else {
continue;
};
let offset = LayoutVector2D::new(-offset.x, -offset.y);
transaction.set_scroll_offsets(
external_id,
vec![SampledScrollOffset {
offset,
generation: 0,
}],
);
let offset = LayoutVector2D::new(-offset.x, -offset.y);
transaction.set_scroll_offsets(
external_id,
vec![SampledScrollOffset {
offset,
generation: 0,
}],
);
}
}
}
}
@ -996,8 +1030,11 @@ impl IOCompositor {
pub fn add_webview(&mut self, webview_id: WebViewId) {
let size = self.rendering_context.size2d().to_f32();
self.global.webviews.entry(webview_id).or_insert(WebView {
pipeline_id: None,
id: webview_id,
root_pipeline_id: None,
rect: Box2D::from_origin_and_size(Point2D::origin(), size),
pipelines: Default::default(),
pipeline_to_webview_map: self.global.pipeline_to_webview_map.clone(),
});
}
@ -1012,31 +1049,18 @@ impl IOCompositor {
return;
};
let new_pipeline_id = Some(frame_tree.pipeline.id);
if new_pipeline_id != webview.pipeline_id {
debug!(
"{webview_id:?}: Updating webview from pipeline {:?} to {new_pipeline_id:?}",
webview.pipeline_id
);
}
webview.pipeline_id = new_pipeline_id;
webview.set_frame_tree(frame_tree);
self.send_root_pipeline_display_list();
self.create_or_update_pipeline_details_with_frame_tree(frame_tree, None);
self.reset_scroll_tree_for_unattached_pipelines(frame_tree);
}
fn remove_webview(&mut self, top_level_browsing_context_id: TopLevelBrowsingContextId) {
debug!("{}: Removing", top_level_browsing_context_id);
let Ok(webview) = self.global.webviews.remove(top_level_browsing_context_id) else {
warn!("{top_level_browsing_context_id}: Removing unknown webview");
fn remove_webview(&mut self, webview_id: WebViewId) {
debug!("{}: Removing", webview_id);
if self.global.webviews.remove(webview_id).is_err() {
warn!("{webview_id}: Removing unknown webview");
return;
};
self.send_root_pipeline_display_list();
if let Some(pipeline_id) = webview.pipeline_id {
self.remove_pipeline_details_recursively(pipeline_id);
}
}
pub fn move_resize_webview(&mut self, webview_id: TopLevelBrowsingContextId, rect: DeviceRect) {
@ -1144,69 +1168,6 @@ impl IOCompositor {
}
}
fn reset_scroll_tree_for_unattached_pipelines(&mut self, frame_tree: &SendableFrameTree) {
// TODO(mrobinson): Eventually this can selectively preserve the scroll trees
// state for some unattached pipelines in order to preserve scroll position when
// navigating backward and forward.
fn collect_pipelines(
pipelines: &mut FnvHashSet<PipelineId>,
frame_tree: &SendableFrameTree,
) {
pipelines.insert(frame_tree.pipeline.id);
for kid in &frame_tree.children {
collect_pipelines(pipelines, kid);
}
}
let mut attached_pipelines: FnvHashSet<PipelineId> = FnvHashSet::default();
collect_pipelines(&mut attached_pipelines, frame_tree);
self.pipeline_details
.iter_mut()
.filter(|(id, _)| !attached_pipelines.contains(id))
.for_each(|(_, details)| {
details.scroll_tree.nodes.iter_mut().for_each(|node| {
node.set_offset(LayoutVector2D::zero());
})
})
}
fn create_or_update_pipeline_details_with_frame_tree(
&mut self,
frame_tree: &SendableFrameTree,
parent_pipeline_id: Option<PipelineId>,
) {
let pipeline_id = frame_tree.pipeline.id;
let pipeline_details = self.pipeline_details(pipeline_id);
pipeline_details.pipeline = Some(frame_tree.pipeline.clone());
pipeline_details.parent_pipeline_id = parent_pipeline_id;
for kid in &frame_tree.children {
self.create_or_update_pipeline_details_with_frame_tree(kid, Some(pipeline_id));
}
}
fn remove_pipeline_details_recursively(&mut self, pipeline_id: PipelineId) {
self.pipeline_details.remove(&pipeline_id);
let children = self
.pipeline_details
.iter()
.filter(|(_, pipeline_details)| {
pipeline_details.parent_pipeline_id == Some(pipeline_id)
})
.map(|(&pipeline_id, _)| pipeline_id)
.collect::<Vec<_>>();
for kid in children {
self.remove_pipeline_details_recursively(kid);
}
}
fn remove_pipeline_root_layer(&mut self, pipeline_id: PipelineId) {
self.pipeline_details.remove(&pipeline_id);
}
pub fn on_embedder_window_moved(&mut self) {
self.embedder_coordinates = self.window.get_coordinates();
}
@ -1333,10 +1294,7 @@ impl IOCompositor {
.iter()
.filter_map(|item| {
let pipeline_id = item.pipeline.into();
let details = match self.pipeline_details.get(&pipeline_id) {
Some(details) => details,
None => return None,
};
let details = self.details_for_pipeline(pipeline_id)?;
// If the epoch in the tag does not match the current epoch of the pipeline,
// then the hit test is against an old version of the display list and we
@ -1760,7 +1718,7 @@ impl IOCompositor {
generation: 0,
}],
);
self.send_scroll_positions_to_layout_for_pipeline(&pipeline_id);
self.send_scroll_positions_to_layout_for_pipeline(pipeline_id);
}
self.generate_frame(&mut transaction, RenderReasons::APZ);
@ -1804,10 +1762,9 @@ impl IOCompositor {
..
} in hit_test_results.iter()
{
let pipeline_details = self.details_for_pipeline_mut(*pipeline_id)?;
if previous_pipeline_id.replace(pipeline_id) != Some(pipeline_id) {
let scroll_result = self
.pipeline_details
.get_mut(pipeline_id)?
let scroll_result = pipeline_details
.scroll_tree
.scroll_node_or_ancestor(scroll_tree_node, scroll_location);
if let Some((external_id, offset)) = scroll_result {
@ -1831,50 +1788,24 @@ impl IOCompositor {
}
self.last_animation_tick = Instant::now();
let mut pipeline_ids = vec![];
for (pipeline_id, pipeline_details) in &self.pipeline_details {
if (pipeline_details.animations_running || pipeline_details.animation_callbacks_running) &&
!pipeline_details.throttled
{
pipeline_ids.push(*pipeline_id);
}
}
#[cfg(feature = "webxr")]
let webxr_running = self.global.webxr_main_thread.running();
#[cfg(not(feature = "webxr"))]
let webxr_running = false;
let animation_state = if pipeline_ids.is_empty() && !webxr_running {
let any_webviews_animating = !self
.global
.webviews
.iter()
.all(|webview| !webview.tick_all_animations(self));
let animation_state = if !any_webviews_animating && !webxr_running {
windowing::AnimationState::Idle
} else {
windowing::AnimationState::Animating
};
self.window.set_animation_state(animation_state);
for pipeline_id in &pipeline_ids {
self.tick_animations_for_pipeline(*pipeline_id)
}
}
fn tick_animations_for_pipeline(&mut self, pipeline_id: PipelineId) {
let animation_callbacks_running = self
.pipeline_details(pipeline_id)
.animation_callbacks_running;
let animations_running = self.pipeline_details(pipeline_id).animations_running;
if !animation_callbacks_running && !animations_running {
return;
}
let mut tick_type = AnimationTickType::empty();
if animations_running {
tick_type.insert(AnimationTickType::CSS_ANIMATIONS_AND_TRANSITIONS);
}
if animation_callbacks_running {
tick_type.insert(AnimationTickType::REQUEST_ANIMATION_FRAME);
}
let msg = ConstellationMsg::TickAnimation(pipeline_id, tick_type);
if let Err(e) = self.global.constellation_sender.send(msg) {
warn!("Sending tick to constellation failed ({:?}).", e);
}
}
fn hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
@ -1943,10 +1874,40 @@ impl IOCompositor {
}
}
fn send_scroll_positions_to_layout_for_pipeline(&self, pipeline_id: &PipelineId) {
let details = match self.pipeline_details.get(pipeline_id) {
Some(details) => details,
None => return,
fn details_for_pipeline(&self, pipeline_id: PipelineId) -> Option<&PipelineDetails> {
let webview_id = self
.global
.pipeline_to_webview_map
.borrow()
.get(&pipeline_id)
.cloned()?;
self.global
.webviews
.get(webview_id)?
.pipelines
.get(&pipeline_id)
}
fn details_for_pipeline_mut(
&mut self,
pipeline_id: PipelineId,
) -> Option<&mut PipelineDetails> {
let webview_id = self
.global
.pipeline_to_webview_map
.borrow()
.get(&pipeline_id)
.cloned()?;
self.global
.webviews
.get_mut(webview_id)?
.pipelines
.get_mut(&pipeline_id)
}
fn send_scroll_positions_to_layout_for_pipeline(&self, pipeline_id: PipelineId) {
let Some(details) = self.details_for_pipeline(pipeline_id) else {
return;
};
let mut scroll_states = Vec::new();
@ -1960,32 +1921,25 @@ impl IOCompositor {
});
if let Some(pipeline) = details.pipeline.as_ref() {
let message = ScriptThreadMessage::SetScrollStates(*pipeline_id, scroll_states);
let message = ScriptThreadMessage::SetScrollStates(pipeline_id, scroll_states);
let _ = pipeline.script_chan.send(message);
}
}
// Check if any pipelines currently have active animations or animation callbacks.
fn animations_active(&self) -> bool {
for details in self.pipeline_details.values() {
// If animations are currently running, then don't bother checking
// with the constellation if the output image is stable.
if details.animations_running {
return true;
}
if details.animation_callbacks_running {
return true;
}
}
false
fn animations_or_animation_callbacks_running(&self) -> bool {
self.global
.webviews
.iter()
.any(WebView::animations_or_animation_callbacks_running)
}
/// Returns true if any animation callbacks (ie `requestAnimationFrame`) are waiting for a response.
fn animation_callbacks_active(&self) -> bool {
self.pipeline_details
.values()
.any(|details| details.animation_callbacks_running)
fn animation_callbacks_running(&self) -> bool {
self.global
.webviews
.iter()
.any(WebView::animation_callbacks_running)
}
/// Query the constellation to see if the current compositor
@ -2001,7 +1955,7 @@ impl IOCompositor {
// This gets sent to the constellation for comparison with the current
// frame tree.
let mut pipeline_epochs = HashMap::new();
for id in self.pipeline_details.keys() {
for id in self.global.webviews.iter().flat_map(WebView::pipeline_ids) {
if let Some(WebRenderEpoch(epoch)) = self
.webrender
.as_ref()
@ -2111,7 +2065,7 @@ impl IOCompositor {
// The current image may be ready to output. However, if there are animations active,
// tick those instead and continue waiting for the image output to be stable AND
// all active animations to complete.
if self.animations_active() {
if self.animations_or_animation_callbacks_running() {
self.process_animations(false);
return Err(UnableToComposite::NotReadyToPaintImage(
NotReadyToPaint::AnimationsActive,
@ -2153,62 +2107,51 @@ impl IOCompositor {
/// current time, inform the constellation about it and remove the pending metric from
/// the list.
fn send_pending_paint_metrics_messages_after_composite(&mut self) {
if self.pending_paint_metrics.is_empty() {
return;
}
let paint_time = CrossProcessInstant::now();
let mut pipelines_to_remove = Vec::new();
let pending_paint_metrics = &mut self.pending_paint_metrics;
for webview_details in self.global.webviews.iter_mut() {
// For each pipeline, determine the current epoch and update paint timing if necessary.
for (pipeline_id, pipeline) in webview_details.pipelines.iter_mut() {
if pipeline.pending_paint_metrics.is_empty() {
continue;
}
let Some(composition_pipeline) = pipeline.pipeline.as_ref() else {
continue;
};
// For each pending paint metrics pipeline id, determine the current
// epoch and update paint timing if necessary.
for (pipeline_id, pending_epochs) in pending_paint_metrics.iter_mut() {
let Some(WebRenderEpoch(current_epoch)) = self
.webrender
.as_ref()
.and_then(|wr| wr.current_epoch(self.webrender_document, pipeline_id.into()))
else {
continue;
};
let Some(WebRenderEpoch(current_epoch)) = self
.webrender
.as_ref()
.and_then(|wr| wr.current_epoch(self.webrender_document, pipeline_id.into()))
else {
continue;
};
// If the pipeline is unknown, stop trying to send paint metrics for it.
let Some(pipeline) = self
.pipeline_details
.get(pipeline_id)
.and_then(|pipeline_details| pipeline_details.pipeline.as_ref())
else {
pipelines_to_remove.push(*pipeline_id);
continue;
};
let current_epoch = Epoch(current_epoch);
let Some(index) = pipeline
.pending_paint_metrics
.iter()
.position(|epoch| *epoch == current_epoch)
else {
continue;
};
let current_epoch = Epoch(current_epoch);
let Some(index) = pending_epochs
.iter()
.position(|epoch| *epoch == current_epoch)
else {
continue;
};
// Remove all epochs that were pending before the current epochs. They were not and will not,
// be painted.
pipeline.pending_paint_metrics.drain(0..index);
// Remove all epochs that were pending before the current epochs. They were not and will not,
// be painted.
pending_epochs.drain(0..index);
if let Err(error) = pipeline
.script_chan
.send(ScriptThreadMessage::SetEpochPaintTime(
*pipeline_id,
current_epoch,
paint_time,
))
{
warn!("Sending RequestLayoutPaintMetric message to layout failed ({error:?}).");
if let Err(error) =
composition_pipeline
.script_chan
.send(ScriptThreadMessage::SetEpochPaintTime(
*pipeline_id,
current_epoch,
paint_time,
))
{
warn!("Sending RequestLayoutPaintMetric message to layout failed ({error:?}).");
}
}
}
for pipeline_id in pipelines_to_remove.iter() {
self.pending_paint_metrics.remove(pipeline_id);
}
}
fn clear_background(&self) {

View file

@ -2,19 +2,190 @@
* 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::RefCell;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::collections::hash_map::{Entry, Keys, Values, ValuesMut};
use std::rc::Rc;
use base::id::{PipelineId, WebViewId};
use webrender_api::units::DeviceRect;
use compositing_traits::SendableFrameTree;
use fnv::FnvHashSet;
use log::debug;
use script_traits::AnimationState;
use webrender_api::units::{DeviceRect, LayoutVector2D};
#[derive(Debug, Default)]
pub struct WebView {
pub pipeline_id: Option<PipelineId>,
use crate::IOCompositor;
use crate::compositor::PipelineDetails;
pub(crate) struct WebView {
/// The [`WebViewId`] of the `WebView` associated with this [`WebViewDetails`].
pub id: WebViewId,
/// The root [`PipelineId`] of the currently displayed page in this WebView.
pub root_pipeline_id: Option<PipelineId>,
pub rect: DeviceRect,
/// Tracks details about each active pipeline that the compositor knows about.
pub pipelines: HashMap<PipelineId, PipelineDetails>,
/// This is a temporary map between [`PipelineId`]s and their associated [`WebViewId`]. Once
/// all renderer operations become per-`WebView` this map can be removed, but we still sometimes
/// need to work backwards to figure out what `WebView` is associated with a `Pipeline`.
pub pipeline_to_webview_map: Rc<RefCell<HashMap<PipelineId, WebViewId>>>,
}
#[derive(Debug, Default)]
impl Drop for WebView {
fn drop(&mut self) {
self.pipeline_to_webview_map
.borrow_mut()
.retain(|_, webview_id| self.id != *webview_id);
}
}
impl WebView {
pub(crate) fn animations_or_animation_callbacks_running(&self) -> bool {
self.pipelines
.values()
.any(PipelineDetails::animations_or_animation_callbacks_running)
}
pub(crate) fn animation_callbacks_running(&self) -> bool {
self.pipelines
.values()
.any(PipelineDetails::animation_callbacks_running)
}
pub(crate) fn pipeline_ids(&self) -> Keys<'_, PipelineId, PipelineDetails> {
self.pipelines.keys()
}
pub(crate) fn pipeline_details(&mut self, pipeline_id: PipelineId) -> &mut PipelineDetails {
self.pipelines.entry(pipeline_id).or_insert_with(|| {
self.pipeline_to_webview_map
.borrow_mut()
.insert(pipeline_id, self.id);
PipelineDetails::new(pipeline_id)
})
}
pub(crate) fn set_throttled(&mut self, pipeline_id: PipelineId, throttled: bool) {
self.pipeline_details(pipeline_id).throttled = throttled;
}
pub(crate) fn remove_pipeline(&mut self, pipeline_id: PipelineId) {
self.pipeline_to_webview_map
.borrow_mut()
.remove(&pipeline_id);
self.pipelines.remove(&pipeline_id);
}
pub(crate) fn set_frame_tree(&mut self, frame_tree: &SendableFrameTree) {
let pipeline_id = frame_tree.pipeline.id;
let old_pipeline_id = std::mem::replace(&mut self.root_pipeline_id, Some(pipeline_id));
if old_pipeline_id != self.root_pipeline_id {
debug!(
"Updating webview ({:?}) from pipeline {:?} to {:?}",
3, old_pipeline_id, self.root_pipeline_id
);
}
self.set_frame_tree_on_pipeline_details(frame_tree, None);
self.reset_scroll_tree_for_unattached_pipelines(frame_tree);
}
pub(crate) fn set_frame_tree_on_pipeline_details(
&mut self,
frame_tree: &SendableFrameTree,
parent_pipeline_id: Option<PipelineId>,
) {
let pipeline_id = frame_tree.pipeline.id;
let pipeline_details = self.pipeline_details(pipeline_id);
pipeline_details.pipeline = Some(frame_tree.pipeline.clone());
pipeline_details.parent_pipeline_id = parent_pipeline_id;
for kid in &frame_tree.children {
self.set_frame_tree_on_pipeline_details(kid, Some(pipeline_id));
}
}
pub(crate) fn reset_scroll_tree_for_unattached_pipelines(
&mut self,
frame_tree: &SendableFrameTree,
) {
// TODO(mrobinson): Eventually this can selectively preserve the scroll trees
// state for some unattached pipelines in order to preserve scroll position when
// navigating backward and forward.
fn collect_pipelines(
pipelines: &mut FnvHashSet<PipelineId>,
frame_tree: &SendableFrameTree,
) {
pipelines.insert(frame_tree.pipeline.id);
for kid in &frame_tree.children {
collect_pipelines(pipelines, kid);
}
}
let mut attached_pipelines: FnvHashSet<PipelineId> = FnvHashSet::default();
collect_pipelines(&mut attached_pipelines, frame_tree);
self.pipelines
.iter_mut()
.filter(|(id, _)| !attached_pipelines.contains(id))
.for_each(|(_, details)| {
details.scroll_tree.nodes.iter_mut().for_each(|node| {
node.set_offset(LayoutVector2D::zero());
})
})
}
/// Sets or unsets the animations-running flag for the given pipeline, and schedules a
/// recomposite if necessary. Returns true if the pipeline is throttled.
pub(crate) fn change_running_animations_state(
&mut self,
pipeline_id: PipelineId,
animation_state: AnimationState,
) -> bool {
let pipeline_details = self.pipeline_details(pipeline_id);
match animation_state {
AnimationState::AnimationsPresent => {
pipeline_details.animations_running = true;
},
AnimationState::AnimationCallbacksPresent => {
pipeline_details.animation_callbacks_running = true;
},
AnimationState::NoAnimationsPresent => {
pipeline_details.animations_running = false;
},
AnimationState::NoAnimationCallbacksPresent => {
pipeline_details.animation_callbacks_running = false;
},
}
pipeline_details.throttled
}
pub(crate) fn tick_all_animations(&self, compositor: &IOCompositor) -> bool {
let mut ticked_any = false;
for pipeline_details in self.pipelines.values() {
ticked_any = pipeline_details.tick_animations(compositor) || ticked_any;
}
ticked_any
}
pub(crate) fn tick_animations_for_pipeline(
&self,
pipeline_id: PipelineId,
compositor: &IOCompositor,
) {
if let Some(pipeline_details) = self.pipelines.get(&pipeline_id) {
pipeline_details.tick_animations(compositor);
}
}
pub(crate) fn add_pending_paint_metric(&mut self, pipeline_id: PipelineId, epoch: base::Epoch) {
self.pipeline_details(pipeline_id)
.pending_paint_metrics
.push(epoch);
}
}
#[derive(Debug)]
pub struct WebViewManager<WebView> {
/// Our top-level browsing contexts. In the WebRender scene, their pipelines are the children of
/// a single root pipeline that also applies any pinch zoom transformation.
@ -27,6 +198,15 @@ pub struct WebViewManager<WebView> {
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct UnknownWebView(pub WebViewId);
impl<WebView> Default for WebViewManager<WebView> {
fn default() -> Self {
Self {
webviews: Default::default(),
painting_order: Default::default(),
}
}
}
impl<WebView> WebViewManager<WebView> {
pub fn remove(&mut self, webview_id: WebViewId) -> Result<WebView, UnknownWebView> {
self.painting_order.retain(|b| *b != webview_id);
@ -98,6 +278,14 @@ impl<WebView> WebViewManager<WebView> {
pub fn entry(&mut self, webview_id: WebViewId) -> Entry<'_, WebViewId, WebView> {
self.webviews.entry(webview_id)
}
pub fn iter(&self) -> Values<'_, WebViewId, WebView> {
self.webviews.values()
}
pub fn iter_mut(&mut self) -> ValuesMut<'_, WebViewId, WebView> {
self.webviews.values_mut()
}
}
#[cfg(test)]

View file

@ -1446,7 +1446,7 @@ where
let (source_pipeline_id, content) = message;
trace_script_msg!(content, "{source_pipeline_id}: {content:?}");
let source_top_ctx_id = match self
let webview_id = match self
.pipelines
.get(&source_pipeline_id)
.map(|pipeline| pipeline.top_level_browsing_context_id)
@ -1521,10 +1521,10 @@ where
self.handle_pipeline_exited(source_pipeline_id);
},
FromScriptMsg::DiscardDocument => {
self.handle_discard_document(source_top_ctx_id, source_pipeline_id);
self.handle_discard_document(webview_id, source_pipeline_id);
},
FromScriptMsg::DiscardTopLevelBrowsingContext => {
self.handle_close_top_level_browsing_context(source_top_ctx_id);
self.handle_close_top_level_browsing_context(webview_id);
},
FromScriptMsg::ScriptLoadedURLInIFrame(load_info) => {
self.handle_script_loaded_url_in_iframe_msg(load_info);
@ -1541,7 +1541,7 @@ where
// Ask the embedder for permission to load a new page.
FromScriptMsg::LoadUrl(load_data, history_handling) => {
self.schedule_navigation(
source_top_ctx_id,
webview_id,
source_pipeline_id,
load_data,
history_handling,
@ -1552,7 +1552,7 @@ where
},
// 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)
self.handle_load_complete_msg(webview_id, source_pipeline_id)
},
// Handle navigating to a fragment
FromScriptMsg::NavigatedToFragment(new_url, replacement_enabled) => {
@ -1560,7 +1560,7 @@ where
},
// Handle a forward or back request
FromScriptMsg::TraverseHistory(direction) => {
self.handle_traverse_history_msg(source_top_ctx_id, direction);
self.handle_traverse_history_msg(webview_id, direction);
},
// Handle a push history state request.
FromScriptMsg::PushHistoryState(history_state_id, url) => {
@ -1571,7 +1571,7 @@ where
},
// Handle a joint session history length request.
FromScriptMsg::JointSessionHistoryLength(response_sender) => {
self.handle_joint_session_history_length(source_top_ctx_id, response_sender);
self.handle_joint_session_history_length(webview_id, response_sender);
},
// Notification that the new document is ready to become active
FromScriptMsg::ActivateDocument => {
@ -1627,7 +1627,7 @@ where
response_sender.send(true).unwrap_or_default();
},
FromScriptMsg::LogEntry(thread_name, entry) => {
self.handle_log_entry(Some(source_top_ctx_id), thread_name, entry);
self.handle_log_entry(Some(webview_id), thread_name, entry);
},
FromScriptMsg::TouchEventProcessed(result) => self
.compositor_proxy
@ -1714,19 +1714,19 @@ where
}
self.active_media_session = Some(pipeline_id);
self.embedder_proxy
.send(EmbedderMsg::MediaSessionEvent(source_top_ctx_id, event));
.send(EmbedderMsg::MediaSessionEvent(webview_id, event));
},
#[cfg(feature = "webgpu")]
FromScriptMsg::RequestAdapter(response_sender, options, ids) => self
.handle_wgpu_request(
source_pipeline_id,
BrowsingContextId::from(source_top_ctx_id),
BrowsingContextId::from(webview_id),
FromScriptMsg::RequestAdapter(response_sender, options, ids),
),
#[cfg(feature = "webgpu")]
FromScriptMsg::GetWebGPUChan(response_sender) => self.handle_wgpu_request(
source_pipeline_id,
BrowsingContextId::from(source_top_ctx_id),
BrowsingContextId::from(webview_id),
FromScriptMsg::GetWebGPUChan(response_sender),
),
FromScriptMsg::TitleChanged(pipeline, title) => {
@ -2008,8 +2008,8 @@ where
fn handle_request_from_layout(&mut self, message: FromLayoutMsg) {
trace_layout_msg!(message, "{message:?}");
match message {
FromLayoutMsg::PendingPaintMetric(pipeline_id, epoch) => {
self.handle_pending_paint_metric(pipeline_id, epoch);
FromLayoutMsg::PendingPaintMetric(webview_id, pipeline_id, epoch) => {
self.handle_pending_paint_metric(webview_id, pipeline_id, epoch);
},
}
}
@ -3440,9 +3440,18 @@ where
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
fn handle_pending_paint_metric(&self, pipeline_id: PipelineId, epoch: Epoch) {
fn handle_pending_paint_metric(
&self,
webview_id: WebViewId,
pipeline_id: PipelineId,
epoch: Epoch,
) {
self.compositor_proxy
.send(CompositorMsg::PendingPaintMetric(pipeline_id, epoch))
.send(CompositorMsg::PendingPaintMetric(
webview_id,
pipeline_id,
epoch,
))
}
#[cfg_attr(
@ -3468,6 +3477,7 @@ where
pipeline.animation_state = animation_state;
self.compositor_proxy
.send(CompositorMsg::ChangeRunningAnimationsState(
pipeline.top_level_browsing_context_id,
pipeline_id,
animation_state,
))

View file

@ -386,8 +386,11 @@ impl Pipeline {
// It's OK for the constellation to block on the compositor,
// since the compositor never blocks on the constellation.
if let Ok((sender, receiver)) = ipc::channel() {
self.compositor_proxy
.send(CompositorMsg::PipelineExited(self.id, sender));
self.compositor_proxy.send(CompositorMsg::PipelineExited(
self.top_level_browsing_context_id,
self.id,
sender,
));
if let Err(e) = receiver.recv() {
warn!("Sending exit message failed ({:?}).", e);
}
@ -455,7 +458,8 @@ impl Pipeline {
/// running timers at a heavily limited rate.
pub fn set_throttled(&self, throttled: bool) {
let script_msg = ScriptThreadMessage::SetThrottled(self.id, throttled);
let compositor_msg = CompositorMsg::SetThrottled(self.id, throttled);
let compositor_msg =
CompositorMsg::SetThrottled(self.top_level_browsing_context_id, self.id, throttled);
let err = self.event_loop.send(script_msg);
if let Err(e) = err {
warn!("Sending SetThrottled to script failed ({}).", e);

View file

@ -794,8 +794,11 @@ impl LayoutThread {
self.paint_time_metrics
.maybe_observe_paint_time(self, epoch, is_contentful.0);
self.compositor_api
.send_display_list(compositor_info, builder.end().1);
self.compositor_api.send_display_list(
self.webview_id,
compositor_info,
builder.end().1,
);
let (keys, instance_keys) = self
.font_context
@ -1040,6 +1043,7 @@ impl LayoutThread {
let point = Point2D::new(-state.scroll_offset.x, -state.scroll_offset.y);
self.compositor_api.send_scroll_node(
self.webview_id,
self.id.into(),
units::LayoutPoint::from_untyped(point),
state.scroll_id,

View file

@ -820,6 +820,7 @@ impl LayoutThread {
.insert(state.scroll_id, state.scroll_offset);
let point = Point2D::new(-state.scroll_offset.x, -state.scroll_offset.y);
self.compositor_api.send_scroll_node(
self.webview_id,
self.id.into(),
units::LayoutPoint::from_untyped(point),
state.scroll_id,
@ -890,8 +891,11 @@ impl LayoutThread {
.maybe_observe_paint_time(self, epoch, is_contentful);
if reflow_goal.needs_display() {
self.compositor_api
.send_display_list(display_list.compositor_info, display_list.wr.end().1);
self.compositor_api.send_display_list(
self.webview_id,
display_list.compositor_info,
display_list.wr.end().1,
);
let (keys, instance_keys) = self
.font_context

View file

@ -9,7 +9,7 @@ use std::time::Duration;
use base::Epoch;
use base::cross_process_instant::CrossProcessInstant;
use base::id::PipelineId;
use base::id::{PipelineId, WebViewId};
use ipc_channel::ipc::IpcSender;
use log::warn;
use malloc_size_of_derive::MallocSizeOf;
@ -255,6 +255,7 @@ pub struct PaintTimeMetrics {
navigation_start: CrossProcessInstant,
first_paint: Cell<Option<CrossProcessInstant>>,
first_contentful_paint: Cell<Option<CrossProcessInstant>>,
webview_id: WebViewId,
pipeline_id: PipelineId,
time_profiler_chan: ProfilerChan,
constellation_chan: IpcSender<LayoutMsg>,
@ -264,6 +265,7 @@ pub struct PaintTimeMetrics {
impl PaintTimeMetrics {
pub fn new(
webview_id: WebViewId,
pipeline_id: PipelineId,
time_profiler_chan: ProfilerChan,
constellation_chan: IpcSender<LayoutMsg>,
@ -276,6 +278,7 @@ impl PaintTimeMetrics {
navigation_start,
first_paint: Cell::new(None),
first_contentful_paint: Cell::new(None),
webview_id,
pipeline_id,
time_profiler_chan,
constellation_chan,
@ -308,7 +311,7 @@ impl PaintTimeMetrics {
// Send the pending metric information to the compositor thread.
// The compositor will record the current time after painting the
// frame with the given ID and will send the metric back to us.
let msg = LayoutMsg::PendingPaintMetric(self.pipeline_id, epoch);
let msg = LayoutMsg::PendingPaintMetric(self.webview_id, self.pipeline_id, epoch);
if let Err(e) = self.constellation_chan.send(msg) {
warn!("Failed to send PendingPaintMetric {:?}", e);
}

View file

@ -3051,6 +3051,7 @@ impl ScriptThread {
};
let paint_time_metrics = PaintTimeMetrics::new(
incomplete.top_level_browsing_context_id,
incomplete.pipeline_id,
self.senders.time_profiler_sender.clone(),
self.senders.layout_to_constellation_ipc_sender.clone(),

View file

@ -9,7 +9,7 @@ mod constellation_msg;
use std::fmt::{Debug, Error, Formatter};
use base::Epoch;
use base::id::{PipelineId, TopLevelBrowsingContextId};
use base::id::{PipelineId, TopLevelBrowsingContextId, WebViewId};
pub use constellation_msg::ConstellationMsg;
use crossbeam_channel::{Receiver, Sender};
use embedder_traits::{EventLoopWaker, MouseButton, MouseButtonAction};
@ -59,7 +59,7 @@ impl CompositorReceiver {
/// Messages from (or via) the constellation thread to the compositor.
pub enum CompositorMsg {
/// Alerts the compositor that the given pipeline has changed whether it is running animations.
ChangeRunningAnimationsState(PipelineId, AnimationState),
ChangeRunningAnimationsState(WebViewId, PipelineId, AnimationState),
/// Create or update a webview, given its frame tree.
CreateOrUpdateWebView(SendableFrameTree),
/// Remove a webview.
@ -71,7 +71,7 @@ pub enum CompositorMsg {
/// A reply to the compositor asking if the output image is stable.
IsReadyToSaveImageReply(bool),
/// Set whether to use less resources by stopping animations.
SetThrottled(PipelineId, bool),
SetThrottled(WebViewId, PipelineId, bool),
/// WebRender has produced a new frame. This message informs the compositor that
/// the frame is ready. It contains a bool to indicate if it needs to composite and the
/// `DocumentId` of the new frame.
@ -81,11 +81,11 @@ pub enum CompositorMsg {
// when it shuts down a pipeline, to the compositor; when the compositor
// sends a reply on the IpcSender, the constellation knows it's safe to
// tear down the other threads associated with this pipeline.
PipelineExited(PipelineId, IpcSender<()>),
PipelineExited(WebViewId, PipelineId, IpcSender<()>),
/// Indicates to the compositor that it needs to record the time when the frame with
/// the given ID (epoch) is painted and report it to the layout of the given
/// pipeline ID.
PendingPaintMetric(PipelineId, Epoch),
/// WebViewId and PipelienId.
PendingPaintMetric(WebViewId, PipelineId, Epoch),
/// The load of a page has completed
LoadComplete(TopLevelBrowsingContextId),
/// WebDriver mouse button event
@ -114,7 +114,7 @@ pub struct CompositionPipeline {
impl Debug for CompositorMsg {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
match *self {
CompositorMsg::ChangeRunningAnimationsState(_, state) => {
CompositorMsg::ChangeRunningAnimationsState(_, _, state) => {
write!(f, "ChangeRunningAnimationsState({:?})", state)
},
CompositorMsg::CreateOrUpdateWebView(..) => write!(f, "CreateOrUpdateWebView"),

View file

@ -9,7 +9,7 @@ use base::Epoch;
use base::id::{
BroadcastChannelRouterId, BrowsingContextId, HistoryStateId, MessagePortId,
MessagePortRouterId, PipelineId, ServiceWorkerId, ServiceWorkerRegistrationId,
TopLevelBrowsingContextId,
TopLevelBrowsingContextId, WebViewId,
};
use canvas_traits::canvas::{CanvasId, CanvasMsg};
use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId};
@ -49,7 +49,7 @@ pub struct IFrameSizeMsg {
pub enum LayoutMsg {
/// Requests that the constellation inform the compositor that it needs to record
/// the time when the frame with the given ID (epoch) is painted.
PendingPaintMetric(PipelineId, Epoch),
PendingPaintMetric(WebViewId, PipelineId, Epoch),
}
impl fmt::Debug for LayoutMsg {

View file

@ -11,7 +11,7 @@ use core::fmt;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use base::id::PipelineId;
use base::id::{PipelineId, WebViewId};
use display_list::{CompositorDisplayListInfo, ScrollTreeNodeId};
use embedder_traits::Cursor;
use euclid::default::Size2D as UntypedSize2D;
@ -33,9 +33,16 @@ pub enum CrossProcessCompositorMessage {
/// Inform WebRender of the existence of this pipeline.
SendInitialTransaction(WebRenderPipelineId),
/// Perform a scroll operation.
SendScrollNode(WebRenderPipelineId, LayoutPoint, ExternalScrollId),
SendScrollNode(
WebViewId,
WebRenderPipelineId,
LayoutPoint,
ExternalScrollId,
),
/// Inform WebRender of a new display list for the given pipeline.
SendDisplayList {
/// The [`WebViewId`] that this display list belongs to.
webview_id: WebViewId,
/// The [CompositorDisplayListInfo] that describes the display list being sent.
display_list_info: Box<CompositorDisplayListInfo>,
/// A descriptor of this display list used to construct this display list from raw data.
@ -138,11 +145,13 @@ impl CrossProcessCompositorApi {
/// Perform a scroll operation.
pub fn send_scroll_node(
&self,
webview_id: WebViewId,
pipeline_id: WebRenderPipelineId,
point: LayoutPoint,
scroll_id: ExternalScrollId,
) {
if let Err(e) = self.0.send(CrossProcessCompositorMessage::SendScrollNode(
webview_id,
pipeline_id,
point,
scroll_id,
@ -154,12 +163,14 @@ impl CrossProcessCompositorApi {
/// Inform WebRender of a new display list for the given pipeline.
pub fn send_display_list(
&self,
webview_id: WebViewId,
display_list_info: CompositorDisplayListInfo,
list: BuiltDisplayList,
) {
let (display_list_data, display_list_descriptor) = list.into_data();
let (display_list_sender, display_list_receiver) = ipc::bytes_channel().unwrap();
if let Err(e) = self.0.send(CrossProcessCompositorMessage::SendDisplayList {
webview_id,
display_list_info: Box::new(display_list_info),
display_list_descriptor,
display_list_receiver,

View file

@ -431,14 +431,8 @@ impl Handler {
target_y: i64,
tick_start: Instant,
) {
let pointer_input_state = match self
.session
.as_mut()
.unwrap()
.input_state_table
.get_mut(source_id)
.unwrap()
{
let session = self.session.as_mut().unwrap();
let pointer_input_state = match session.input_state_table.get_mut(source_id).unwrap() {
InputSourceState::Null => unreachable!(),
InputSourceState::Key(_) => unreachable!(),
InputSourceState::Pointer(pointer_input_state) => pointer_input_state,

View file

@ -4,7 +4,7 @@
use base::Epoch;
use base::cross_process_instant::CrossProcessInstant;
use base::id::TEST_PIPELINE_ID;
use base::id::{TEST_PIPELINE_ID, TEST_WEBVIEW_ID};
use ipc_channel::ipc;
use metrics::{PaintTimeMetrics, ProfilerMetadataFactory, ProgressiveWebMetric};
use profile_traits::time::{ProfilerChan, TimerMetadata};
@ -25,6 +25,7 @@ fn test_paint_metrics_construction() {
let (script_sender, _) = ipc::channel().unwrap();
let start_time = CrossProcessInstant::now();
let paint_time_metrics = PaintTimeMetrics::new(
TEST_WEBVIEW_ID,
TEST_PIPELINE_ID,
profiler_chan,
layout_sender,
@ -56,6 +57,7 @@ fn test_common(display_list_is_contentful: bool, epoch: Epoch) -> PaintTimeMetri
let (script_sender, _) = ipc::channel().unwrap();
let start_time = CrossProcessInstant::now();
let mut paint_time_metrics = PaintTimeMetrics::new(
TEST_WEBVIEW_ID,
TEST_PIPELINE_ID,
profiler_chan,
layout_sender,