| /* 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 std::collections::hash_map::{Entry, Values, ValuesMut}; |
| |
| use base::id::WebViewId; |
| |
| use crate::webview_renderer::{UnknownWebView, WebViewRenderer}; |
| |
| #[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. |
| webviews: HashMap<WebViewId, WebView>, |
| |
| /// The order to paint them in, topmost last. |
| pub(crate) painting_order: Vec<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); |
| 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))) |
| } |
| |
| 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() |
| } |
| } |
| |
| impl WebViewManager<WebViewRenderer> { |
| pub(crate) fn scroll_trees_memory_usage( |
| &self, |
| ops: &mut malloc_size_of::MallocSizeOfOps, |
| ) -> usize { |
| self.iter() |
| .map(|renderer| renderer.scroll_trees_memory_usage(ops)) |
| .sum::<usize>() |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use base::id::{BrowsingContextId, Index, PipelineNamespace, PipelineNamespaceId, WebViewId}; |
| |
| use crate::webview_manager::WebViewManager; |
| use crate::webview_renderer::UnknownWebView; |
| |
| fn top_level_id(namespace_id: u32, index: u32) -> WebViewId { |
| WebViewId::mock_for_testing(BrowsingContextId { |
| namespace_id: PipelineNamespaceId(namespace_id), |
| index: Index::new(index).unwrap(), |
| }) |
| } |
| |
| fn webviews_sorted<WebView: Clone>( |
| webviews: &WebViewManager<WebView>, |
| ) -> Vec<(WebViewId, WebView)> { |
| let mut keys = webviews.webviews.keys().collect::<Vec<_>>(); |
| keys.sort_unstable(); |
| keys.iter() |
| .map(|&id| (*id, webviews.webviews.get(id).cloned().unwrap())) |
| .collect() |
| } |
| |
| #[test] |
| fn test() { |
| PipelineNamespace::install(PipelineNamespaceId(0)); |
| let mut webviews = WebViewManager::default(); |
| |
| // entry() adds the webview to the map, but not the painting order. |
| webviews.entry(WebViewId::new()).or_insert('a'); |
| webviews.entry(WebViewId::new()).or_insert('b'); |
| webviews.entry(WebViewId::new()).or_insert('c'); |
| assert!(webviews.get(top_level_id(0, 1)).is_some()); |
| assert!(webviews.get(top_level_id(0, 2)).is_some()); |
| assert!(webviews.get(top_level_id(0, 3)).is_some()); |
| 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. |
| webviews.entry(top_level_id(0, 3)).or_insert('d'); |
| assert!(webviews.get(top_level_id(0, 3)).is_some()); |
| |
| // Other methods return UnknownWebView or None if the webview id doesn’t 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()); |
| } |
| } |