constellation/
webview_manager.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use base::id::WebViewId;
6use rustc_hash::FxHashMap;
7
8#[derive(Debug)]
9pub struct WebViewManager<WebView> {
10    /// Our top-level browsing contexts. In the WebRender scene, their pipelines are the children of
11    /// a single root pipeline that also applies any pinch zoom transformation.
12    webviews: FxHashMap<WebViewId, WebView>,
13
14    /// The order in which they were focused, latest last.
15    focus_order: Vec<WebViewId>,
16
17    /// Whether the latest webview in focus order is currently focused.
18    is_focused: bool,
19}
20
21impl<WebView> Default for WebViewManager<WebView> {
22    fn default() -> Self {
23        Self {
24            webviews: FxHashMap::default(),
25            focus_order: Vec::default(),
26            is_focused: false,
27        }
28    }
29}
30
31impl<WebView> WebViewManager<WebView> {
32    pub fn add(&mut self, webview_id: WebViewId, webview: WebView) {
33        self.webviews.insert(webview_id, webview);
34    }
35
36    pub fn remove(&mut self, webview_id: WebViewId) -> Option<WebView> {
37        if self.focus_order.last() == Some(&webview_id) {
38            self.is_focused = false;
39        }
40        self.focus_order.retain(|b| *b != webview_id);
41        self.webviews.remove(&webview_id)
42    }
43
44    pub fn get(&self, webview_id: WebViewId) -> Option<&WebView> {
45        self.webviews.get(&webview_id)
46    }
47
48    pub fn get_mut(&mut self, webview_id: WebViewId) -> Option<&mut WebView> {
49        self.webviews.get_mut(&webview_id)
50    }
51
52    pub fn focused_webview(&self) -> Option<(WebViewId, &WebView)> {
53        if !self.is_focused {
54            return None;
55        }
56
57        if let Some(webview_id) = self.focus_order.last().cloned() {
58            debug_assert!(
59                self.webviews.contains_key(&webview_id),
60                "BUG: webview in .focus_order not in .webviews!",
61            );
62            self.get(webview_id).map(|webview| (webview_id, webview))
63        } else {
64            debug_assert!(false, "BUG: .is_focused but no webviews in .focus_order!");
65            None
66        }
67    }
68
69    pub fn focus(&mut self, webview_id: WebViewId) -> Result<(), ()> {
70        if !self.webviews.contains_key(&webview_id) {
71            return Err(());
72        }
73        self.focus_order.retain(|b| *b != webview_id);
74        self.focus_order.push(webview_id);
75        self.is_focused = true;
76        Ok(())
77    }
78
79    pub fn unfocus(&mut self) {
80        self.is_focused = false;
81    }
82}
83
84#[cfg(test)]
85mod test {
86    use base::id::{
87        BrowsingContextId, Index, PipelineNamespace, PipelineNamespaceId, TEST_PAINTER_ID,
88        WebViewId,
89    };
90
91    use crate::webview_manager::WebViewManager;
92
93    fn id(namespace_id: u32, index: u32) -> WebViewId {
94        WebViewId::mock_for_testing(BrowsingContextId {
95            namespace_id: PipelineNamespaceId(namespace_id),
96            index: Index::new(index).expect("Incorrect test case"),
97        })
98    }
99
100    fn webviews_sorted<WebView: Clone>(
101        webviews: &WebViewManager<WebView>,
102    ) -> Vec<(WebViewId, WebView)> {
103        let mut keys = webviews.webviews.keys().collect::<Vec<_>>();
104        keys.sort_unstable();
105        keys.iter()
106            .map(|&id| {
107                (
108                    *id,
109                    webviews
110                        .webviews
111                        .get(id)
112                        .cloned()
113                        .expect("Incorrect test case"),
114                )
115            })
116            .collect()
117    }
118
119    #[test]
120    fn test() {
121        PipelineNamespace::install(PipelineNamespaceId(0));
122        let mut webviews = WebViewManager::default();
123
124        // add() adds the webview to the map, but does not focus it.
125        webviews.add(WebViewId::new(TEST_PAINTER_ID), 'a');
126        webviews.add(WebViewId::new(TEST_PAINTER_ID), 'b');
127        webviews.add(WebViewId::new(TEST_PAINTER_ID), 'c');
128        assert_eq!(
129            webviews_sorted(&webviews),
130            vec![(id(0, 1), 'a'), (id(0, 2), 'b'), (id(0, 3), 'c'),]
131        );
132        assert!(webviews.focus_order.is_empty());
133        assert_eq!(webviews.is_focused, false);
134
135        // focus() makes the given webview the latest in focus order.
136        let _ = webviews.focus(id(0, 2));
137        assert_eq!(webviews.focus_order, vec![id(0, 2)]);
138        assert_eq!(webviews.is_focused, true);
139        let _ = webviews.focus(id(0, 1));
140        assert_eq!(webviews.focus_order, vec![id(0, 2), id(0, 1)]);
141        assert_eq!(webviews.is_focused, true);
142        let _ = webviews.focus(id(0, 3));
143        assert_eq!(webviews.focus_order, vec![id(0, 2), id(0, 1), id(0, 3)]);
144        assert_eq!(webviews.is_focused, true);
145
146        // unfocus() clears the “is focused” flag, but does not touch the focus order.
147        webviews.unfocus();
148        assert_eq!(webviews.focus_order, vec![id(0, 2), id(0, 1), id(0, 3)]);
149        assert_eq!(webviews.is_focused, false);
150
151        // focus() avoids duplicates in focus order, when the given webview has been focused before.
152        let _ = webviews.focus(id(0, 1));
153        assert_eq!(webviews.focus_order, vec![id(0, 2), id(0, 3), id(0, 1)]);
154        assert_eq!(webviews.is_focused, true);
155
156        // remove() clears the “is focused” flag iff the given webview was focused.
157        webviews.remove(id(1, 1));
158        assert_eq!(webviews.is_focused, true);
159        webviews.remove(id(1, 2));
160        assert_eq!(webviews.is_focused, true);
161        webviews.remove(id(2, 1));
162        assert_eq!(webviews.is_focused, true);
163        webviews.remove(id(2, 2));
164        assert_eq!(webviews.is_focused, true);
165        webviews.remove(id(2, 3));
166        assert_eq!(webviews.is_focused, true);
167        webviews.remove(id(2, 4));
168        assert_eq!(webviews.is_focused, true);
169        webviews.remove(id(3, 1));
170        assert_eq!(webviews.is_focused, true);
171        webviews.remove(id(4, 1));
172        assert_eq!(webviews.is_focused, true);
173        webviews.remove(id(0, 2));
174        assert_eq!(webviews.is_focused, true);
175        webviews.remove(id(0, 1));
176        assert_eq!(webviews.is_focused, false);
177        webviews.remove(id(0, 3));
178        assert_eq!(webviews.is_focused, false);
179
180        // remove() removes the given webview from both the map and the focus order.
181        assert!(webviews_sorted(&webviews).is_empty());
182        assert!(webviews.focus_order.is_empty());
183    }
184}