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

@ -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());
}
}