Initial internal support for multiple webviews (#31417)

* Add multiple concurrent top-level browsing contexts

Co-authored-by: Delan Azabani <dazabani@igalia.com>

* Rename variables and comments

There are some variable and comments still use browser as names.
This commit renames them to webview.

* Update log message from web view to webview

* Revert offscreen_framebuffer_id rename

* Rename all web view to webview

* Cargo fmt

* Fix viewport/event/clear coordinates when multiview is disabled

* Only deprecate things when multiview is enabled

* Update WebViewManger with shown and invisible sets

Replace visible_webviews and native_window_is_visible with shown_webviews
and invisible_webviews. Add 4 more methods to set them accordingly. The
behavior of is_effectively_visible will return true if the wbview is in
shown_webviews set but not in invisible_webviews.

* Update variant behaviors

* Rename WebViewVisibilityChanged to MarkWebViewInvisible

* Fix unit test by marking id 3 visible again

* Update MarkWebViewInvisible and add UnmarkWebViewInvisible

* Update format and doc comments

* Clean up doc comments

* Address style and naming changes

* Rename UpdateWebView to UpdateFrameTreeForWebView

* constellation: send frame tree unconditionally over focus and feature

* Clarify shown and invisible sets in constellation WebViewManager

* Eliminate forward_to_constellation!()

* Actually remove the unused macro

* Don’t gate compositor changes on multiview feature flag

* Update todo in mouse event dispatch

* Pass all visible webview ids in a single ReadyToPresent message

* Fix compile and lint errors

* servoshell: fix gap between minibrowser toolbar and webview

* Fix failure in /_mozilla/mozilla/window_resizeTo.html

* Fix compile warnings

* Remove stray dbg!()

* Remove confusing “effectively visible” logic (see #31815, #31816)

* Allow embedder to show/hide/raise webviews without ipc

* Update root pipeline only when painting order actually changes

* Stop gating old focus and SetFrameTree behaviour behind Cargo feature

* Use webview_id and WebViewId in webview-related code

* Improve logging of webview-related embedder events

* Allow webview Show and Raise events to optionally hide all others

* Don’t do anything in response to WebViewPaintingOrder

* Remove WebViewPaintingOrder, since its payload is unreliable

* On MoveResizeWebView, only update root pipeline if rect changed

* Rename IOCompositor methods for clarity

* compositor: add event tracing; log webview ops even without ipc

* Add temporary debug logging

* Add more temporary debug logging

* Remove temporary logging in compositor

* Remove temporary debug logging

* Add temporary debug logging, but defer I/O until panic

* Capture a backtrace with each crash log entry

* Proper error handling without panicking in WebViewManager

* Clean up imports in constellation

---------

Co-authored-by: Delan Azabani <dazabani@igalia.com>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Ngo Iok Ui (Wu Yu Wei) 2024-04-03 20:06:28 +09:00 committed by GitHub
parent 18b37e676b
commit 66878fb834
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 1142 additions and 434 deletions

View file

@ -7,6 +7,7 @@ use std::collections::HashMap;
use std::env;
use std::fs::{create_dir_all, File};
use std::io::Write;
use std::iter::once;
use std::num::NonZeroU32;
use std::rc::Rc;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
@ -27,7 +28,7 @@ use ipc_channel::ipc;
use libc::c_void;
use log::{debug, error, info, trace, warn};
use msg::constellation_msg::{
PipelineId, PipelineIndex, PipelineNamespaceId, TopLevelBrowsingContextId,
PipelineId, PipelineIndex, PipelineNamespaceId, TopLevelBrowsingContextId, WebViewId,
};
use net_traits::image::base::Image;
use net_traits::image_cache::CorsStatus;
@ -44,7 +45,7 @@ use servo_geometry::{DeviceIndependentPixel, FramebufferUintLength};
use style_traits::{CSSPixel, DevicePixel, PinchZoomFactor};
use webrender::{CaptureBits, RenderApi, Transaction};
use webrender_api::units::{
DeviceIntPoint, DeviceIntSize, DevicePoint, LayoutPoint, LayoutRect, LayoutSize,
DeviceIntPoint, DeviceIntSize, DevicePoint, DeviceRect, LayoutPoint, LayoutRect, LayoutSize,
LayoutVector2D, WorldPoint,
};
use webrender_api::{
@ -56,6 +57,7 @@ use webrender_api::{
use crate::gl::RenderTargetInfo;
use crate::touch::{TouchAction, TouchHandler};
use crate::webview::{UnknownWebView, WebView, WebViewAlreadyExists, WebViewManager};
use crate::windowing::{
self, EmbedderCoordinates, MouseWindowEvent, WebRenderDebugOption, WindowMethods,
};
@ -109,11 +111,6 @@ impl FrameTreeId {
}
}
struct RootPipeline {
top_level_browsing_context_id: TopLevelBrowsingContextId,
id: Option<PipelineId>,
}
/// NB: Never block on the constellation, because sometimes the constellation blocks on us.
pub struct IOCompositor<Window: WindowMethods + ?Sized> {
/// The application window.
@ -122,10 +119,8 @@ pub struct IOCompositor<Window: WindowMethods + ?Sized> {
/// The port on which we receive messages.
port: CompositorReceiver,
/// The root content pipeline ie the pipeline which contains the main frame
/// to display. In the WebRender scene, this will be the only child of another
/// pipeline which applies a pinch zoom transformation.
root_content_pipeline: RootPipeline,
/// Our top-level browsing contexts.
webviews: WebViewManager<WebView>,
/// Tracks details about each active pipeline that the compositor knows about.
pipeline_details: HashMap<PipelineId, PipelineDetails>,
@ -292,6 +287,9 @@ struct PipelineDetails {
/// The pipeline associated with this PipelineDetails object.
pipeline: Option<CompositionPipeline>,
/// The id of the parent pipeline, if any.
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>,
@ -318,6 +316,7 @@ impl PipelineDetails {
fn new() -> PipelineDetails {
PipelineDetails {
pipeline: None,
parent_pipeline_id: None,
most_recent_display_list_epoch: None,
animations_running: false,
animation_callbacks_running: false,
@ -376,14 +375,26 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
convert_mouse_to_touch: bool,
top_level_browsing_context_id: TopLevelBrowsingContextId,
) -> Self {
let embedder_coordinates = window.get_coordinates();
let mut webviews = WebViewManager::default();
webviews
.add(
top_level_browsing_context_id,
WebView {
pipeline_id: None,
rect: embedder_coordinates.get_viewport().to_f32(),
},
)
.expect("Infallible with a new WebViewManager");
webviews
.show(top_level_browsing_context_id)
.expect("Infallible due to add");
IOCompositor {
embedder_coordinates: window.get_coordinates(),
window,
port: state.receiver,
root_content_pipeline: RootPipeline {
top_level_browsing_context_id,
id: None,
},
webviews,
pipeline_details: HashMap::new(),
composition_request: CompositionRequest::NoCompositingNecessary,
touch_handler: TouchHandler::new(),
@ -524,6 +535,8 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
}
fn handle_browser_message(&mut self, msg: CompositorMsg) -> bool {
trace_msg_from_constellation!(msg, "{msg:?}");
match self.shutdown_state {
ShutdownState::NotShuttingDown => {},
ShutdownState::ShuttingDown => {
@ -546,11 +559,40 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
self.change_running_animations_state(pipeline_id, animation_state);
},
CompositorMsg::SetFrameTree(frame_tree) => {
self.set_frame_tree(&frame_tree);
CompositorMsg::CreateOrUpdateWebView(frame_tree) => {
self.set_frame_tree_for_webview(&frame_tree);
self.send_scroll_positions_to_layout_for_pipeline(&frame_tree.pipeline.id);
},
CompositorMsg::RemoveWebView(top_level_browsing_context_id) => {
self.remove_webview(top_level_browsing_context_id);
},
CompositorMsg::MoveResizeWebView(webview_id, rect) => {
self.move_resize_webview(webview_id, rect);
},
CompositorMsg::ShowWebView(webview_id, hide_others) => {
if let Err(UnknownWebView(webview_id)) = self.show_webview(webview_id, hide_others)
{
warn!("{webview_id}: ShowWebView on unknown webview id");
}
},
CompositorMsg::HideWebView(webview_id) => {
if let Err(UnknownWebView(webview_id)) = self.hide_webview(webview_id) {
warn!("{webview_id}: HideWebView on unknown webview id");
}
},
CompositorMsg::RaiseWebViewToTop(webview_id, hide_others) => {
if let Err(UnknownWebView(webview_id)) =
self.raise_webview_to_top(webview_id, hide_others)
{
warn!("{webview_id}: RaiseWebViewToTop on unknown webview id");
}
},
CompositorMsg::TouchEventProcessed(result) => {
self.touch_handler.on_event_processed(result);
},
@ -1016,16 +1058,21 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
}
}
/// Set the root pipeline for our WebRender scene. If there is no pinch zoom applied,
/// the root pipeline is the root content pipeline. If there is pinch zoom, the root
/// content pipeline is wrapped in a display list that applies a pinch zoom
/// transformation to it.
fn set_root_content_pipeline_handling_device_scaling(&self, transaction: &mut Transaction) {
let root_content_pipeline = match self.root_content_pipeline.id {
Some(id) => id.to_webrender(),
None => return,
};
/// 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.
fn send_root_pipeline_display_list(&mut self) {
let mut transaction = Transaction::new();
self.send_root_pipeline_display_list_in_transaction(&mut transaction);
self.generate_frame(&mut transaction, RenderReasons::SCENE);
self.webrender_api
.send_transaction(self.webrender_document, transaction);
}
/// 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.
fn send_root_pipeline_display_list_in_transaction(&self, transaction: &mut Transaction) {
// Every display list needs a pipeline, but we'd like to choose one that is unlikely
// to conflict with our content pipelines, which start at (1, 1). (0, 0) is WebRender's
// dummy pipeline, so we choose (0, 1).
@ -1034,10 +1081,6 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
let mut builder = webrender_api::DisplayListBuilder::new(root_pipeline);
builder.begin();
let viewport_size = LayoutSize::new(
self.embedder_coordinates.get_viewport().width() as f32,
self.embedder_coordinates.get_viewport().height() as f32,
);
let zoom_factor = self.device_pixels_per_page_pixel().0;
let zoom_reference_frame = builder.push_reference_frame(
@ -1053,26 +1096,30 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
SpatialTreeItemKey::new(0, 0),
);
let scaled_viewport_rect = LayoutRect::from_origin_and_size(
LayoutPoint::zero(),
LayoutSize::new(
viewport_size.width / zoom_factor,
viewport_size.height / zoom_factor,
),
);
let scaled_viewport_size =
self.embedder_coordinates.get_viewport().size().to_f32() / zoom_factor;
let scaled_viewport_size = LayoutSize::from_untyped(scaled_viewport_size.to_untyped());
let scaled_viewport_rect =
LayoutRect::from_origin_and_size(LayoutPoint::zero(), scaled_viewport_size);
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]);
builder.push_iframe(
scaled_viewport_rect,
scaled_viewport_rect,
&SpaceAndClipInfo {
spatial_id: zoom_reference_frame,
clip_chain_id,
},
root_content_pipeline,
true,
);
for (_, webview) in self.webviews.painting_order() {
if let Some(pipeline_id) = webview.pipeline_id {
let scaled_webview_rect = webview.rect / zoom_factor;
builder.push_iframe(
LayoutRect::from_untyped(&scaled_webview_rect.to_untyped()),
LayoutRect::from_untyped(&scaled_webview_rect.to_untyped()),
&SpaceAndClipInfo {
spatial_id: zoom_reference_frame,
clip_chain_id,
},
pipeline_id.to_webrender(),
true,
);
}
}
let built_display_list = builder.end();
// NB: We are always passing 0 as the epoch here, but this doesn't seem to
@ -1109,29 +1156,163 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
}
}
fn set_frame_tree(&mut self, frame_tree: &SendableFrameTree) {
debug!(
"Setting the frame tree for pipeline {:?}",
frame_tree.pipeline.id
);
fn set_frame_tree_for_webview(&mut self, frame_tree: &SendableFrameTree) {
debug!("{}: Setting frame tree for webview", frame_tree.pipeline.id);
self.root_content_pipeline = RootPipeline {
top_level_browsing_context_id: frame_tree.pipeline.top_level_browsing_context_id,
id: Some(frame_tree.pipeline.id),
};
let top_level_browsing_context_id = frame_tree.pipeline.top_level_browsing_context_id;
if let Some(webview) = self.webviews.get_mut(top_level_browsing_context_id) {
let new_pipeline_id = Some(frame_tree.pipeline.id);
if new_pipeline_id != webview.pipeline_id {
debug!(
"{:?}: Updating webview from pipeline {:?} to {:?}",
top_level_browsing_context_id, webview.pipeline_id, new_pipeline_id
);
}
webview.pipeline_id = new_pipeline_id;
} else {
let top_level_browsing_context_id = frame_tree.pipeline.top_level_browsing_context_id;
let pipeline_id = Some(frame_tree.pipeline.id);
debug!(
"{:?}: Creating new webview with pipeline {:?}",
top_level_browsing_context_id, pipeline_id
);
if let Err(WebViewAlreadyExists(webview_id)) = self.webviews.add(
top_level_browsing_context_id,
WebView {
pipeline_id,
rect: self.embedder_coordinates.get_viewport().to_f32(),
},
) {
error!("{webview_id}: Creating webview that already exists");
return;
}
}
let mut txn = Transaction::new();
self.set_root_content_pipeline_handling_device_scaling(&mut txn);
self.generate_frame(&mut txn, RenderReasons::SCENE);
self.webrender_api
.send_transaction(self.webrender_document, txn);
self.create_pipeline_details_for_frame_tree(frame_tree);
self.reset_scroll_tree_for_unattached_pipelines(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);
self.frame_tree_id.next();
}
fn remove_webview(&mut self, top_level_browsing_context_id: TopLevelBrowsingContextId) {
debug!("{}: Removing", top_level_browsing_context_id);
let Ok(webview) = self.webviews.remove(top_level_browsing_context_id) else {
warn!("{top_level_browsing_context_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);
}
self.frame_tree_id.next();
}
pub fn move_resize_webview(&mut self, webview_id: TopLevelBrowsingContextId, rect: DeviceRect) {
debug!("{webview_id}: Moving and/or resizing webview; rect={rect:?}");
let rect_changed;
let size_changed;
match self.webviews.get_mut(webview_id) {
Some(webview) => {
rect_changed = rect != webview.rect;
size_changed = rect.size() != webview.rect.size();
webview.rect = rect;
},
None => {
warn!("{webview_id}: MoveResizeWebView on unknown webview id");
return;
},
};
if rect_changed {
if size_changed {
self.send_window_size_message_for_top_level_browser_context(rect, webview_id);
}
self.send_root_pipeline_display_list();
}
}
pub fn show_webview(
&mut self,
webview_id: WebViewId,
hide_others: bool,
) -> Result<(), UnknownWebView> {
debug!("{webview_id}: Showing webview; hide_others={hide_others}");
let painting_order_changed = if hide_others {
let result = self
.webviews
.painting_order()
.map(|(&id, _)| id)
.ne(once(webview_id));
self.webviews.hide_all();
self.webviews.show(webview_id)?;
result
} else {
self.webviews.show(webview_id)?
};
if painting_order_changed {
self.send_root_pipeline_display_list();
}
Ok(())
}
pub fn hide_webview(&mut self, webview_id: WebViewId) -> Result<(), UnknownWebView> {
debug!("{webview_id}: Hiding webview");
if self.webviews.hide(webview_id)? {
self.send_root_pipeline_display_list();
}
Ok(())
}
pub fn raise_webview_to_top(
&mut self,
webview_id: WebViewId,
hide_others: bool,
) -> Result<(), UnknownWebView> {
debug!("{webview_id}: Raising webview to top; hide_others={hide_others}");
let painting_order_changed = if hide_others {
let result = self
.webviews
.painting_order()
.map(|(&id, _)| id)
.ne(once(webview_id));
self.webviews.hide_all();
self.webviews.raise_to_top(webview_id)?;
result
} else {
self.webviews.raise_to_top(webview_id)?
};
if painting_order_changed {
self.send_root_pipeline_display_list();
}
Ok(())
}
fn send_window_size_message_for_top_level_browser_context(
&self,
rect: DeviceRect,
top_level_browsing_context_id: TopLevelBrowsingContextId,
) {
// The device pixel ratio used by the style system should include the scale from page pixels
// to device pixels, but not including any pinch zoom.
let device_pixel_ratio = self.device_pixels_per_page_pixel_not_including_page_zoom();
let initial_viewport = rect.size().to_f32() / device_pixel_ratio;
let msg = ConstellationMsg::WindowSize(
top_level_browsing_context_id,
WindowSizeData {
device_pixel_ratio,
initial_viewport,
},
WindowSizeType::Resize,
);
if let Err(e) = self.constellation_chan.send(msg) {
warn!("Sending window resize to constellation failed ({:?}).", e);
}
}
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
@ -1159,11 +1340,35 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
})
}
fn create_pipeline_details_for_frame_tree(&mut self, frame_tree: &SendableFrameTree) {
self.pipeline_details(frame_tree.pipeline.id).pipeline = Some(frame_tree.pipeline.clone());
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_pipeline_details_for_frame_tree(kid);
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);
}
}
@ -1225,9 +1430,10 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
MouseWindowEvent::MouseUp(_, p) => p,
};
let result = match self.hit_test_at_point(point) {
Some(result) => result,
None => return,
let Some(result) = self.hit_test_at_point(point) else {
// TODO: Notify embedder that the event failed to hit test to any webview.
// TODO: Also notify embedder if an event hits a webview but isnt consumed?
return;
};
let (button, event_type) = match mouse_window_event {
@ -1264,13 +1470,6 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
flags: HitTestFlags,
pipeline_id: Option<WebRenderPipelineId>,
) -> Vec<CompositorHitTestResult> {
let root_pipeline_id = match self.root_content_pipeline.id {
Some(root_pipeline_id) => root_pipeline_id,
None => return vec![],
};
if self.pipeline(root_pipeline_id).is_none() {
return vec![];
}
// DevicePoint and WorldPoint are the same for us.
let world_point = WorldPoint::from_untyped(point.to_untyped());
let results =
@ -1526,7 +1725,7 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
let mut transaction = Transaction::new();
if zoom_changed {
self.set_root_content_pipeline_handling_device_scaling(&mut transaction);
self.send_root_pipeline_display_list_in_transaction(&mut transaction);
}
if let Some((pipeline_id, external_id, offset)) = scroll_result {
@ -1670,33 +1869,15 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
}
fn update_after_zoom_or_hidpi_change(&mut self) {
// The device pixel ratio used by the style system should include the scale from page pixels
// to device pixels, but not including any pinch zoom.
let device_pixel_ratio = self.device_pixels_per_page_pixel_not_including_page_zoom();
let initial_viewport =
self.embedder_coordinates.viewport.size().to_f32() / device_pixel_ratio;
let data = WindowSizeData {
device_pixel_ratio,
initial_viewport,
};
let top_level_browsing_context_id =
self.root_content_pipeline.top_level_browsing_context_id;
let msg = ConstellationMsg::WindowSize(
top_level_browsing_context_id,
data,
WindowSizeType::Resize,
);
if let Err(e) = self.constellation_chan.send(msg) {
warn!("Sending window resize to constellation failed ({:?}).", e);
for (top_level_browsing_context_id, webview) in self.webviews.painting_order() {
self.send_window_size_message_for_top_level_browser_context(
webview.rect,
*top_level_browsing_context_id,
);
}
// Update the root transform in WebRender to reflect the new zoom.
let mut transaction = webrender::Transaction::new();
self.set_root_content_pipeline_handling_device_scaling(&mut transaction);
self.webrender_api
.send_transaction(self.webrender_document, transaction);
self.send_root_pipeline_display_list();
}
/// Simulate a pinch zoom
@ -1928,6 +2109,10 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
// and check if it is the one layout is expecting,
let epoch = Epoch(epoch);
if *pending_epoch != epoch {
warn!(
"{}: paint metrics: pending {:?} should be {:?}",
id, pending_epoch, epoch
);
continue;
}
// in which case, we remove it from the list of pending metrics,
@ -2032,12 +2217,11 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
},
};
// Nottify embedder that servo is ready to present.
// Notify embedder that servo is ready to present.
// Embedder should call `present` to tell compositor to continue rendering.
self.waiting_on_present = true;
let msg = ConstellationMsg::ReadyToPresent(
self.root_content_pipeline.top_level_browsing_context_id,
);
let webview_ids = self.webviews.painting_order().map(|(&id, _)| id);
let msg = ConstellationMsg::ReadyToPresent(webview_ids.collect());
if let Err(e) = self.constellation_chan.send(msg) {
warn!("Sending event to constellation failed ({:?}).", e);
}
@ -2077,14 +2261,6 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
self.assert_gl_framebuffer_complete();
// Set the viewport background based on prefs.
let viewport = self.embedder_coordinates.get_flipped_viewport();
gl.scissor(
viewport.min.x,
viewport.min.y,
viewport.size().width,
viewport.size().height,
);
let color = servo_config::pref!(shell.background_color.rgba);
gl.clear_color(
color[0] as f32,
@ -2092,9 +2268,21 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
color[2] as f32,
color[3] as f32,
);
gl.enable(gleam::gl::SCISSOR_TEST);
gl.clear(gleam::gl::COLOR_BUFFER_BIT);
gl.disable(gleam::gl::SCISSOR_TEST);
// Clear the viewport rect of each top-level browsing context.
for (_, webview) in self.webviews.painting_order() {
let rect = self.embedder_coordinates.flip_rect(&webview.rect.to_i32());
gl.scissor(
rect.min.x,
rect.min.y,
rect.size().width,
rect.size().height,
);
gl.enable(gleam::gl::SCISSOR_TEST);
gl.clear(gleam::gl::COLOR_BUFFER_BIT);
gl.disable(gleam::gl::SCISSOR_TEST);
}
self.assert_gl_framebuffer_complete();
}

View file

@ -15,9 +15,13 @@ use webrender_api::DocumentId;
pub use crate::compositor::{CompositeTarget, IOCompositor, ShutdownState};
#[macro_use]
mod tracing;
mod compositor;
mod gl;
mod touch;
pub mod webview;
pub mod windowing;
/// Data used to construct a compositor.

View file

@ -0,0 +1,58 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
/// Log an event from constellation at trace level.
/// - To disable tracing: RUST_LOG='compositor<constellation@=off'
/// - To enable tracing: RUST_LOG='compositor<constellation@'
macro_rules! trace_msg_from_constellation {
// This macro only exists to put the docs in the same file as the target prefix,
// so the macro definition is always the same.
($event:expr, $($rest:tt)+) => {
::log::trace!(target: $crate::tracing::LogTarget::log_target(&$event), $($rest)+)
};
}
/// Get the log target for an event, as a static string.
pub(crate) trait LogTarget {
fn log_target(&self) -> &'static str;
}
mod from_constellation {
use super::LogTarget;
macro_rules! target {
($($name:literal)+) => {
concat!("compositor<constellation@", $($name),+)
};
}
impl LogTarget for compositing_traits::CompositorMsg {
fn log_target(&self) -> &'static str {
match self {
Self::ShutdownComplete => target!("ShutdownComplete"),
Self::ChangeRunningAnimationsState(..) => target!("ChangeRunningAnimationsState"),
Self::CreateOrUpdateWebView(..) => target!("CreateOrUpdateWebView"),
Self::RemoveWebView(..) => target!("RemoveWebView"),
Self::MoveResizeWebView(..) => target!("MoveResizeWebView"),
Self::ShowWebView(..) => target!("ShowWebView"),
Self::HideWebView(..) => target!("HideWebView"),
Self::RaiseWebViewToTop(..) => target!("RaiseWebViewToTop"),
Self::TouchEventProcessed(..) => target!("TouchEventProcessed"),
Self::CreatePng(..) => target!("CreatePng"),
Self::IsReadyToSaveImageReply(..) => target!("IsReadyToSaveImageReply"),
Self::SetThrottled(..) => target!("SetThrottled"),
Self::NewWebRenderFrameReady(..) => target!("NewWebRenderFrameReady"),
Self::PipelineExited(..) => target!("PipelineExited"),
Self::PendingPaintMetric(..) => target!("PendingPaintMetric"),
Self::LoadComplete(..) => target!("LoadComplete"),
Self::WebDriverMouseButtonEvent(..) => target!("WebDriverMouseButtonEvent"),
Self::WebDriverMouseMoveEvent(..) => target!("WebDriverMouseMoveEvent"),
Self::GetClientWindow(..) => target!("GetClientWindow"),
Self::GetScreenSize(..) => target!("GetScreenSize"),
Self::GetScreenAvailSize(..) => target!("GetScreenAvailSize"),
Self::Forwarded(..) => target!("Forwarded"),
}
}
}
}

View file

@ -0,0 +1,245 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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::collections::HashMap;
use msg::constellation_msg::{PipelineId, WebViewId};
use webrender_api::units::DeviceRect;
#[derive(Debug, Default)]
pub struct WebView {
pub pipeline_id: Option<PipelineId>,
pub rect: DeviceRect,
}
#[derive(Debug, Default)]
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.
webviews: HashMap<WebViewId, WebView>,
/// The order to paint them in, topmost last.
painting_order: Vec<WebViewId>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct WebViewAlreadyExists(pub WebViewId);
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct UnknownWebView(pub WebViewId);
impl<WebView> WebViewManager<WebView> {
pub fn add(
&mut self,
webview_id: WebViewId,
webview: WebView,
) -> Result<&mut WebView, WebViewAlreadyExists> {
if self.webviews.contains_key(&webview_id) {
return Err(WebViewAlreadyExists(webview_id));
}
Ok(self.webviews.entry(webview_id).or_insert(webview))
}
pub fn remove(&mut self, webview_id: WebViewId) -> Result<WebView, UnknownWebView> {
self.painting_order.retain(|b| *b != webview_id);
self.webviews
.remove(&webview_id)
.ok_or(UnknownWebView(webview_id))
}
pub fn get(&self, webview_id: WebViewId) -> Option<&WebView> {
self.webviews.get(&webview_id)
}
pub fn get_mut(&mut self, webview_id: WebViewId) -> Option<&mut WebView> {
self.webviews.get_mut(&webview_id)
}
/// Returns true iff the painting order actually changed.
pub fn show(&mut self, webview_id: WebViewId) -> Result<bool, UnknownWebView> {
if !self.webviews.contains_key(&webview_id) {
return Err(UnknownWebView(webview_id));
}
if !self.painting_order.contains(&webview_id) {
self.painting_order.push(webview_id);
return Ok(true);
}
Ok(false)
}
/// Returns true iff the painting order actually changed.
pub fn hide(&mut self, webview_id: WebViewId) -> Result<bool, UnknownWebView> {
if !self.webviews.contains_key(&webview_id) {
return Err(UnknownWebView(webview_id));
}
if self.painting_order.contains(&webview_id) {
self.painting_order.retain(|b| *b != webview_id);
return Ok(true);
}
Ok(false)
}
/// Returns true iff the painting order actually changed.
pub fn hide_all(&mut self) -> bool {
if !self.painting_order.is_empty() {
self.painting_order.clear();
return true;
}
false
}
/// Returns true iff the painting order actually changed.
pub fn raise_to_top(&mut self, webview_id: WebViewId) -> Result<bool, UnknownWebView> {
if !self.webviews.contains_key(&webview_id) {
return Err(UnknownWebView(webview_id));
}
if self.painting_order.last() != Some(&webview_id) {
self.hide(webview_id)?;
self.show(webview_id)?;
return Ok(true);
}
Ok(false)
}
pub fn painting_order(&self) -> impl Iterator<Item = (&WebViewId, &WebView)> {
self.painting_order
.iter()
.flat_map(move |webview_id| self.get(*webview_id).map(|b| (webview_id, b)))
}
}
#[cfg(test)]
mod test {
use std::num::NonZeroU32;
use msg::constellation_msg::{
BrowsingContextId, BrowsingContextIndex, PipelineNamespace, PipelineNamespaceId,
TopLevelBrowsingContextId,
};
use crate::webview::{UnknownWebView, WebViewAlreadyExists, WebViewManager};
fn top_level_id(namespace_id: u32, index: u32) -> TopLevelBrowsingContextId {
TopLevelBrowsingContextId(BrowsingContextId {
namespace_id: PipelineNamespaceId(namespace_id),
index: BrowsingContextIndex(NonZeroU32::new(index).unwrap()),
})
}
fn webviews_sorted<WebView: Clone>(
webviews: &WebViewManager<WebView>,
) -> Vec<(TopLevelBrowsingContextId, WebView)> {
let mut keys = webviews.webviews.keys().collect::<Vec<_>>();
keys.sort();
keys.iter()
.map(|&id| (*id, webviews.webviews.get(id).cloned().unwrap()))
.collect()
}
#[test]
fn test() {
PipelineNamespace::install(PipelineNamespaceId(0));
let mut webviews = WebViewManager::default();
// add() adds the webview to the map, but not the painting order.
assert!(webviews.add(TopLevelBrowsingContextId::new(), 'a').is_ok());
assert!(webviews.add(TopLevelBrowsingContextId::new(), 'b').is_ok());
assert!(webviews.add(TopLevelBrowsingContextId::new(), 'c').is_ok());
assert_eq!(
webviews_sorted(&webviews),
vec![
(top_level_id(0, 1), 'a'),
(top_level_id(0, 2), 'b'),
(top_level_id(0, 3), 'c'),
]
);
assert!(webviews.painting_order.is_empty());
// add() returns WebViewAlreadyExists if the webview id already exists.
assert_eq!(
webviews.add(top_level_id(0, 3), 'd'),
Err(WebViewAlreadyExists(top_level_id(0, 3)))
);
// Other methods return UnknownWebView or None if the webview id doesnt exist.
assert_eq!(
webviews.remove(top_level_id(1, 1)),
Err(UnknownWebView(top_level_id(1, 1)))
);
assert_eq!(webviews.get(top_level_id(1, 1)), None);
assert_eq!(webviews.get_mut(top_level_id(1, 1)), None);
assert_eq!(
webviews.show(top_level_id(1, 1)),
Err(UnknownWebView(top_level_id(1, 1)))
);
assert_eq!(
webviews.hide(top_level_id(1, 1)),
Err(UnknownWebView(top_level_id(1, 1)))
);
assert_eq!(
webviews.raise_to_top(top_level_id(1, 1)),
Err(UnknownWebView(top_level_id(1, 1)))
);
// For webviews not yet visible, both show() and raise_to_top() add the given webview on top.
assert_eq!(webviews.show(top_level_id(0, 2)), Ok(true));
assert_eq!(webviews.show(top_level_id(0, 2)), Ok(false));
assert_eq!(webviews.painting_order, vec![top_level_id(0, 2)]);
assert_eq!(webviews.raise_to_top(top_level_id(0, 1)), Ok(true));
assert_eq!(webviews.raise_to_top(top_level_id(0, 1)), Ok(false));
assert_eq!(
webviews.painting_order,
vec![top_level_id(0, 2), top_level_id(0, 1)]
);
assert_eq!(webviews.show(top_level_id(0, 3)), Ok(true));
assert_eq!(webviews.show(top_level_id(0, 3)), Ok(false));
assert_eq!(
webviews.painting_order,
vec![top_level_id(0, 2), top_level_id(0, 1), top_level_id(0, 3)]
);
// For webviews already visible, show() does nothing, while raise_to_top() makes it on top.
assert_eq!(webviews.show(top_level_id(0, 1)), Ok(false));
assert_eq!(
webviews.painting_order,
vec![top_level_id(0, 2), top_level_id(0, 1), top_level_id(0, 3)]
);
assert_eq!(webviews.raise_to_top(top_level_id(0, 1)), Ok(true));
assert_eq!(webviews.raise_to_top(top_level_id(0, 1)), Ok(false));
assert_eq!(
webviews.painting_order,
vec![top_level_id(0, 2), top_level_id(0, 3), top_level_id(0, 1)]
);
// hide() removes the webview from the painting order, but not the map.
assert_eq!(webviews.hide(top_level_id(0, 3)), Ok(true));
assert_eq!(webviews.hide(top_level_id(0, 3)), Ok(false));
assert_eq!(
webviews.painting_order,
vec![top_level_id(0, 2), top_level_id(0, 1)]
);
assert_eq!(
webviews_sorted(&webviews),
vec![
(top_level_id(0, 1), 'a'),
(top_level_id(0, 2), 'b'),
(top_level_id(0, 3), 'c'),
]
);
// painting_order() returns only the visible webviews, in painting order.
let mut painting_order = webviews.painting_order();
assert_eq!(painting_order.next(), Some((&top_level_id(0, 2), &'b')));
assert_eq!(painting_order.next(), Some((&top_level_id(0, 1), &'a')));
assert_eq!(painting_order.next(), None);
drop(painting_order);
// remove() removes the given webview from both the map and the painting order.
assert!(webviews.remove(top_level_id(0, 1)).is_ok());
assert!(webviews.remove(top_level_id(0, 2)).is_ok());
assert!(webviews.remove(top_level_id(0, 3)).is_ok());
assert!(webviews_sorted(&webviews).is_empty());
assert!(webviews.painting_order.is_empty());
}
}

View file

@ -19,7 +19,7 @@ use script_traits::{
use servo_geometry::DeviceIndependentPixel;
use servo_url::ServoUrl;
use style_traits::DevicePixel;
use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePoint};
use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePoint, DeviceRect};
use webrender_api::ScrollLocation;
#[derive(Clone)]
@ -51,7 +51,7 @@ pub enum EmbedderEvent {
/// message, the window must make the same GL context as in `PrepareRenderingEvent` current.
Refresh,
/// Sent when the window is resized.
Resize,
WindowResize,
/// Sent when a navigation request from script is allowed/refused.
AllowNavigationResponse(PipelineId, bool),
/// Sent when a new URL is to be loaded.
@ -83,15 +83,24 @@ pub enum EmbedderEvent {
Keyboard(KeyboardEvent),
/// Sent when Ctr+R/Apple+R is called to reload the current page.
Reload(TopLevelBrowsingContextId),
/// Create a new top level browsing context
/// Create a new top-level browsing context.
NewWebView(ServoUrl, TopLevelBrowsingContextId),
/// Close a top level browsing context
/// Close a top-level browsing context.
CloseWebView(TopLevelBrowsingContextId),
/// Panic a top level browsing context.
/// Panic a top-level browsing context.
SendError(Option<TopLevelBrowsingContextId>, String),
/// Make a top level browsing context visible, hiding the previous
/// visible one.
/// Move and/or resize a webview to the given rect.
MoveResizeWebView(TopLevelBrowsingContextId, DeviceRect),
/// Start painting a webview, and optionally stop painting all others.
ShowWebView(TopLevelBrowsingContextId, bool),
/// Stop painting a webview.
HideWebView(TopLevelBrowsingContextId),
/// Start painting a webview on top of all others, and optionally stop painting all others.
RaiseWebViewToTop(TopLevelBrowsingContextId, bool),
/// Make a webview focused.
FocusWebView(TopLevelBrowsingContextId),
/// Make none of the webviews focused.
BlurWebView,
/// Toggles a debug flag in WebRender
ToggleWebRenderDebug(WebRenderDebugOption),
/// Capture current WebRender
@ -124,7 +133,7 @@ impl Debug for EmbedderEvent {
match *self {
EmbedderEvent::Idle => write!(f, "Idle"),
EmbedderEvent::Refresh => write!(f, "Refresh"),
EmbedderEvent::Resize => write!(f, "Resize"),
EmbedderEvent::WindowResize => write!(f, "Resize"),
EmbedderEvent::Keyboard(..) => write!(f, "Keyboard"),
EmbedderEvent::AllowNavigationResponse(..) => write!(f, "AllowNavigationResponse"),
EmbedderEvent::LoadUrl(..) => write!(f, "LoadUrl"),
@ -139,10 +148,32 @@ impl Debug for EmbedderEvent {
EmbedderEvent::Navigation(..) => write!(f, "Navigation"),
EmbedderEvent::Quit => write!(f, "Quit"),
EmbedderEvent::Reload(..) => write!(f, "Reload"),
EmbedderEvent::NewWebView(..) => write!(f, "NewWebView"),
EmbedderEvent::NewWebView(_, TopLevelBrowsingContextId(webview_id)) => {
write!(f, "NewWebView({webview_id:?})")
},
EmbedderEvent::SendError(..) => write!(f, "SendError"),
EmbedderEvent::CloseWebView(..) => write!(f, "CloseWebView"),
EmbedderEvent::FocusWebView(..) => write!(f, "FocusWebView"),
EmbedderEvent::CloseWebView(TopLevelBrowsingContextId(webview_id)) => {
write!(f, "CloseWebView({webview_id:?})")
},
EmbedderEvent::MoveResizeWebView(webview_id, _) => {
write!(f, "MoveResizeWebView({webview_id:?})")
},
EmbedderEvent::ShowWebView(TopLevelBrowsingContextId(webview_id), hide_others) => {
write!(f, "ShowWebView({webview_id:?}, {hide_others})")
},
EmbedderEvent::HideWebView(TopLevelBrowsingContextId(webview_id)) => {
write!(f, "HideWebView({webview_id:?})")
},
EmbedderEvent::RaiseWebViewToTop(
TopLevelBrowsingContextId(webview_id),
hide_others,
) => {
write!(f, "RaiseWebViewToTop({webview_id:?}, {hide_others})")
},
EmbedderEvent::FocusWebView(TopLevelBrowsingContextId(webview_id)) => {
write!(f, "FocusWebView({webview_id:?})")
},
EmbedderEvent::BlurWebView => write!(f, "BlurWebView"),
EmbedderEvent::ToggleWebRenderDebug(..) => write!(f, "ToggleWebRenderDebug"),
EmbedderEvent::CaptureWebRender => write!(f, "CaptureWebRender"),
EmbedderEvent::ToggleSamplingProfiler(..) => write!(f, "ToggleSamplingProfiler"),
@ -211,15 +242,20 @@ pub struct EmbedderCoordinates {
impl EmbedderCoordinates {
/// Get the unflipped viewport rectangle for use with the WebRender API.
pub fn get_viewport(&self) -> DeviceIntRect {
DeviceIntRect::from_untyped(&self.viewport.to_untyped())
self.viewport.clone()
}
/// Get the flipped viewport rectangle. This should be used when drawing directly
/// to the framebuffer with OpenGL commands.
/// Flip the given rect.
/// This should be used when drawing directly to the framebuffer with OpenGL commands.
pub fn flip_rect(&self, rect: &DeviceIntRect) -> DeviceIntRect {
let mut result = rect.clone();
result.min.y = self.framebuffer.height - result.min.y - result.size().height;
result
}
/// Get the flipped viewport rectangle.
/// This should be used when drawing directly to the framebuffer with OpenGL commands.
pub fn get_flipped_viewport(&self) -> DeviceIntRect {
let fb_height = self.framebuffer.height;
let mut view = self.viewport;
view.min.y = fb_height - view.min.y - view.size().height;
DeviceIntRect::from_untyped(&view.to_untyped())
self.flip_rect(&self.get_viewport())
}
}