aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDelan Azabani <dazabani@igalia.com>2024-01-24 19:45:54 +0800
committerGitHub <noreply@github.com>2024-01-24 11:45:54 +0000
commiteb95703325aeb48d5f56a8da5b258bad608dd632 (patch)
treee3e636a74578ca431795511a3b30331422fb4a6e
parent6baaa828261af49e790938666ee89f156a736821 (diff)
downloadservo-eb95703325aeb48d5f56a8da5b258bad608dd632.tar.gz
servo-eb95703325aeb48d5f56a8da5b258bad608dd632.zip
constellation: focusing and closing webviews (#30842)
* constellation: focusing, closing, and native window visibility * rename “browser” to “webview”, “unfocus” to “blur” * remove native window visibility from constellation * rename more “browser” to “webview” * guard clauses * don’t automatically focus when no webviews are focused * comment spec steps for window.close() * use format interpolation Co-authored-by: Martin Robinson <mrobinson@igalia.com> * fix formatting * rename “Webview” to “WebView” in types and type parameters * remove unused method * fix libsimpleservo --------- Co-authored-by: Martin Robinson <mrobinson@igalia.com>
-rw-r--r--components/compositing/windowing.rs18
-rw-r--r--components/constellation/constellation.rs240
-rw-r--r--components/constellation/lib.rs1
-rw-r--r--components/constellation/webview.rs200
-rw-r--r--components/script/dom/window.rs11
-rw-r--r--components/script/dom/windowproxy.rs4
-rw-r--r--components/servo/lib.rs23
-rw-r--r--components/shared/compositing/constellation_msg.rs23
-rw-r--r--components/shared/embedder/lib.rs22
-rw-r--r--components/shared/msg/constellation_msg.rs8
-rw-r--r--ports/libsimpleservo/api/src/lib.rs82
-rw-r--r--ports/servoshell/app.rs45
-rw-r--r--ports/servoshell/main.rs2
-rw-r--r--ports/servoshell/minibrowser.rs16
-rw-r--r--ports/servoshell/webview.rs (renamed from ports/servoshell/browser.rs)120
-rw-r--r--python/servo/testing_commands.py1
16 files changed, 532 insertions, 284 deletions
diff --git a/components/compositing/windowing.rs b/components/compositing/windowing.rs
index c78ddb6ba85..1508d6dac70 100644
--- a/components/compositing/windowing.rs
+++ b/components/compositing/windowing.rs
@@ -83,14 +83,14 @@ pub enum EmbedderEvent {
/// Sent when Ctr+R/Apple+R is called to reload the current page.
Reload(TopLevelBrowsingContextId),
/// Create a new top level browsing context
- NewBrowser(ServoUrl, TopLevelBrowsingContextId),
+ NewWebView(ServoUrl, TopLevelBrowsingContextId),
/// Close a top level browsing context
- CloseBrowser(TopLevelBrowsingContextId),
+ CloseWebView(TopLevelBrowsingContextId),
/// Panic a top level browsing context.
SendError(Option<TopLevelBrowsingContextId>, String),
/// Make a top level browsing context visible, hiding the previous
/// visible one.
- SelectBrowser(TopLevelBrowsingContextId),
+ FocusWebView(TopLevelBrowsingContextId),
/// Toggles a debug flag in WebRender
ToggleWebRenderDebug(WebRenderDebugOption),
/// Capture current WebRender
@@ -102,8 +102,8 @@ pub enum EmbedderEvent {
/// Sent when the user triggers a media action through the UA exposed media UI
/// (play, pause, seek, etc.).
MediaSessionAction(MediaSessionActionType),
- /// Set browser visibility. A hidden browser will not tick the animations.
- ChangeBrowserVisibility(TopLevelBrowsingContextId, bool),
+ /// The visibility of the webview has changed.
+ WebViewVisibilityChanged(TopLevelBrowsingContextId, bool),
/// Virtual keyboard was dismissed
IMEDismissed,
/// Sent on platforms like Android where the native widget surface can be
@@ -136,16 +136,16 @@ impl Debug for EmbedderEvent {
EmbedderEvent::Navigation(..) => write!(f, "Navigation"),
EmbedderEvent::Quit => write!(f, "Quit"),
EmbedderEvent::Reload(..) => write!(f, "Reload"),
- EmbedderEvent::NewBrowser(..) => write!(f, "NewBrowser"),
+ EmbedderEvent::NewWebView(..) => write!(f, "NewWebView"),
EmbedderEvent::SendError(..) => write!(f, "SendError"),
- EmbedderEvent::CloseBrowser(..) => write!(f, "CloseBrowser"),
- EmbedderEvent::SelectBrowser(..) => write!(f, "SelectBrowser"),
+ EmbedderEvent::CloseWebView(..) => write!(f, "CloseWebView"),
+ EmbedderEvent::FocusWebView(..) => write!(f, "FocusWebView"),
EmbedderEvent::ToggleWebRenderDebug(..) => write!(f, "ToggleWebRenderDebug"),
EmbedderEvent::CaptureWebRender => write!(f, "CaptureWebRender"),
EmbedderEvent::ToggleSamplingProfiler(..) => write!(f, "ToggleSamplingProfiler"),
EmbedderEvent::ExitFullScreen(..) => write!(f, "ExitFullScreen"),
EmbedderEvent::MediaSessionAction(..) => write!(f, "MediaSessionAction"),
- EmbedderEvent::ChangeBrowserVisibility(..) => write!(f, "ChangeBrowserVisibility"),
+ EmbedderEvent::WebViewVisibilityChanged(..) => write!(f, "WebViewVisibilityChanged"),
EmbedderEvent::IMEDismissed => write!(f, "IMEDismissed"),
EmbedderEvent::ClearCache => write!(f, "ClearCache"),
EmbedderEvent::InvalidateNativeSurface => write!(f, "InvalidateNativeSurface"),
diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs
index b2cbcbb9a7b..cfc6ced6cb3 100644
--- a/components/constellation/constellation.rs
+++ b/components/constellation/constellation.rs
@@ -31,7 +31,7 @@
//! to a forest whose roots are top-level browsing context. The logical
//! relationship between these types is:
//!
-//! ```
+//! ```text
//! +------------+ +------------+ +---------+
//! | Browsing | ------parent?------> | Pipeline | --event_loop--> | Event |
//! | Context | ------current------> | | | Loop |
@@ -172,6 +172,7 @@ use crate::session_history::{
JointSessionHistory, NeedsToReload, SessionHistoryChange, SessionHistoryDiff,
};
use crate::timer_scheduler::TimerScheduler;
+use crate::webview::WebViewManager;
type PendingApprovalNavigations = HashMap<PipelineId, (LoadData, HistoryEntryReplacement)>;
@@ -223,15 +224,15 @@ struct WebrenderWGPU {
wgpu_image_map: Arc<Mutex<HashMap<u64, webgpu::PresentationData>>>,
}
-/// Servo supports tabs (referred to as browsers), so `Constellation` needs to
-/// store browser specific data for bookkeeping.
-struct Browser {
- /// The currently focused browsing context in this browser for key events.
+/// Servo supports multiple top-level browsing contexts or “webviews”, so `Constellation` needs to
+/// store webview-specific data for bookkeeping.
+struct WebView {
+ /// The currently focused browsing context in this webview for key events.
/// The focused pipeline is the current entry of the focused browsing
/// context.
focused_browsing_context_id: BrowsingContextId,
- /// The joint session history for this browser.
+ /// The joint session history for this webview.
session_history: JointSessionHistory,
}
@@ -325,13 +326,8 @@ pub struct Constellation<Message, LTF, STF, SWF> {
/// constellation to send messages to the compositor thread.
compositor_proxy: CompositorProxy,
- /// The last frame tree sent to WebRender, denoting the browser (tab) user
- /// has currently selected. This also serves as the key to retrieve data
- /// about the current active browser from `browsers`.
- active_browser_id: Option<TopLevelBrowsingContextId>,
-
- /// Bookkeeping data for all browsers in constellation.
- browsers: HashMap<TopLevelBrowsingContextId, Browser>,
+ /// Bookkeeping data for all webviews in the constellation.
+ webviews: WebViewManager<WebView>,
/// Channels for the constellation to send messages to the public
/// resource-related threads. There are two groups of resource threads: one
@@ -758,8 +754,7 @@ where
network_listener_receiver: network_listener_receiver,
embedder_proxy: state.embedder_proxy,
compositor_proxy: state.compositor_proxy,
- active_browser_id: None,
- browsers: HashMap::new(),
+ webviews: WebViewManager::default(),
devtools_sender: state.devtools_sender,
bluetooth_ipc_sender: state.bluetooth_thread,
public_resource_threads: state.public_resource_threads,
@@ -1352,9 +1347,7 @@ where
self.handle_get_pipeline(browsing_context_id, response_sender);
},
FromCompositorMsg::GetFocusTopLevelBrowsingContext(resp_chan) => {
- // The focused browsing context's top-level browsing context is
- // the active browser's id itself.
- let _ = resp_chan.send(self.active_browser_id);
+ let _ = resp_chan.send(self.webviews.focused_webview().map(|(id, _)| id));
},
FromCompositorMsg::Keyboard(key_event) => {
self.handle_key_msg(key_event);
@@ -1468,11 +1461,11 @@ where
},
// Create a new top level browsing context. Will use response_chan to return
// the browsing context id.
- FromCompositorMsg::NewBrowser(url, top_level_browsing_context_id) => {
+ FromCompositorMsg::NewWebView(url, top_level_browsing_context_id) => {
self.handle_new_top_level_browsing_context(url, top_level_browsing_context_id);
},
// Close a top level browsing context.
- FromCompositorMsg::CloseBrowser(top_level_browsing_context_id) => {
+ FromCompositorMsg::CloseWebView(top_level_browsing_context_id) => {
self.handle_close_top_level_browsing_context(top_level_browsing_context_id);
},
// Panic a top level browsing context.
@@ -1483,9 +1476,23 @@ where
}
self.handle_panic(top_level_browsing_context_id, error, None);
},
- // Send frame tree to WebRender. Make it visible.
- FromCompositorMsg::SelectBrowser(top_level_browsing_context_id) => {
- self.send_frame_tree(top_level_browsing_context_id);
+ FromCompositorMsg::FocusWebView(top_level_browsing_context_id) => {
+ if self.webviews.get(top_level_browsing_context_id).is_none() {
+ return warn!("{top_level_browsing_context_id}: FocusWebView on unknown top-level browsing context");
+ }
+ self.webviews.focus(top_level_browsing_context_id);
+ self.embedder_proxy.send((
+ Some(top_level_browsing_context_id),
+ EmbedderMsg::WebViewFocused(top_level_browsing_context_id),
+ ));
+ if !cfg!(feature = "multiview") {
+ self.update_frame_tree_if_focused(top_level_browsing_context_id);
+ }
+ },
+ FromCompositorMsg::BlurWebView => {
+ self.webviews.unfocus();
+ self.embedder_proxy
+ .send((None, EmbedderMsg::WebViewBlurred));
},
// Handle a forward or back request
FromCompositorMsg::TraverseHistory(top_level_browsing_context_id, direction) => {
@@ -1534,8 +1541,8 @@ where
FromCompositorMsg::MediaSessionAction(action) => {
self.handle_media_session_action_msg(action);
},
- FromCompositorMsg::ChangeBrowserVisibility(top_level_browsing_context_id, visible) => {
- self.handle_change_browser_visibility(top_level_browsing_context_id, visible);
+ FromCompositorMsg::WebViewVisibilityChanged(webview_id, visible) => {
+ self.notify_webview_visibility(webview_id, visible);
},
FromCompositorMsg::ReadyToPresent(top_level_browsing_context_id) => {
self.embedder_proxy.send((
@@ -2934,7 +2941,7 @@ where
let pipeline_id = PipelineId::new();
let msg = (
Some(top_level_browsing_context_id),
- EmbedderMsg::BrowserCreated(top_level_browsing_context_id),
+ EmbedderMsg::WebViewOpened(top_level_browsing_context_id),
);
self.embedder_proxy.send(msg);
let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);
@@ -2950,11 +2957,11 @@ where
let is_private = false;
let is_visible = true;
- // Register this new top-level browsing context id as a browser and set
+ // Register this new top-level browsing context id as a webview and set
// its focused browsing context to be itself.
- self.browsers.insert(
+ self.webviews.add(
top_level_browsing_context_id,
- Browser {
+ WebView {
focused_browsing_context_id: browsing_context_id,
session_history: JointSessionHistory::new(),
},
@@ -3000,22 +3007,45 @@ where
&mut self,
top_level_browsing_context_id: TopLevelBrowsingContextId,
) {
+ debug!("{top_level_browsing_context_id}: Closing");
let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);
- self.close_browsing_context(browsing_context_id, ExitPipelineMode::Normal);
- self.browsers.remove(&top_level_browsing_context_id);
- if self.active_browser_id == Some(top_level_browsing_context_id) {
- self.active_browser_id = None;
+ let browsing_context =
+ self.close_browsing_context(browsing_context_id, ExitPipelineMode::Normal);
+ if self.webviews.focused_webview().map(|(id, _)| id) == Some(top_level_browsing_context_id)
+ {
+ self.embedder_proxy
+ .send((None, EmbedderMsg::WebViewBlurred));
}
- let browsing_context = match self.browsing_contexts.get(&browsing_context_id) {
- Some(bc) => bc,
- None => {
- warn!("Browsing context closed before it started");
- return;
- },
+ self.webviews.remove(top_level_browsing_context_id);
+ // TODO Send the compositor a RemoveWebView event.
+ self.embedder_proxy.send((
+ Some(top_level_browsing_context_id),
+ EmbedderMsg::WebViewClosed(top_level_browsing_context_id),
+ ));
+
+ let Some(browsing_context) = browsing_context else {
+ return;
};
// https://html.spec.whatwg.org/multipage/#bcg-remove
- self.browsing_context_group_set
- .remove(&browsing_context.bc_group_id);
+ let bc_group_id = browsing_context.bc_group_id;
+ let Some(bc_group) = self.browsing_context_group_set.get_mut(&bc_group_id) else {
+ warn!("{}: Browsing context group not found!", bc_group_id);
+ return;
+ };
+ if !bc_group
+ .top_level_browsing_context_set
+ .remove(&top_level_browsing_context_id)
+ {
+ warn!(
+ "{top_level_browsing_context_id}: Top-level browsing context not found in {bc_group_id}",
+ );
+ }
+ if bc_group.top_level_browsing_context_set.is_empty() {
+ self.browsing_context_group_set
+ .remove(&browsing_context.bc_group_id);
+ }
+
+ debug!("{top_level_browsing_context_id}: Closed");
}
fn handle_iframe_size_msg(&mut self, iframe_sizes: Vec<IFrameSizeMsg>) {
@@ -3312,9 +3342,9 @@ where
assert!(!self.pipelines.contains_key(&new_pipeline_id));
self.pipelines.insert(new_pipeline_id, pipeline);
- self.browsers.insert(
+ self.webviews.add(
new_top_level_browsing_context_id,
- Browser {
+ WebView {
focused_browsing_context_id: new_browsing_context_id,
session_history: JointSessionHistory::new(),
},
@@ -3785,7 +3815,7 @@ where
self.notify_history_changed(top_level_browsing_context_id);
self.trim_history(top_level_browsing_context_id);
- self.update_frame_tree_if_active(top_level_browsing_context_id);
+ self.update_frame_tree_if_focused(top_level_browsing_context_id);
}
fn update_browsing_context(
@@ -3944,9 +3974,9 @@ where
response_sender: IpcSender<u32>,
) {
let length = self
- .browsers
- .get(&top_level_browsing_context_id)
- .map(|browser| browser.session_history.history_length())
+ .webviews
+ .get(top_level_browsing_context_id)
+ .map(|webview| webview.session_history.history_length())
.unwrap_or(1);
let _ = response_sender.send(length as u32);
}
@@ -4017,9 +4047,9 @@ where
fn handle_ime_dismissed(&mut self) {
// Send to the focused browsing contexts' current pipeline.
let focused_browsing_context_id = self
- .active_browser_id
- .and_then(|browser_id| self.browsers.get(&browser_id))
- .map(|browser| browser.focused_browsing_context_id);
+ .webviews
+ .focused_webview()
+ .map(|(_, webview)| webview.focused_browsing_context_id);
if let Some(browsing_context_id) = focused_browsing_context_id {
let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
Some(ctx) => ctx.pipeline_id,
@@ -4048,9 +4078,9 @@ where
// Send to the focused browsing contexts' current pipeline. If it
// doesn't exist, fall back to sending to the compositor.
let focused_browsing_context_id = self
- .active_browser_id
- .and_then(|browser_id| self.browsers.get(&browser_id))
- .map(|browser| browser.focused_browsing_context_id);
+ .webviews
+ .focused_webview()
+ .map(|(_, webview)| webview.focused_browsing_context_id);
match focused_browsing_context_id {
Some(browsing_context_id) => {
let event = CompositorEvent::KeyboardEvent(event);
@@ -4075,6 +4105,7 @@ where
}
},
None => {
+ warn!("No focused browsing context! Falling back to sending key to compositor");
let event = (None, EmbedderMsg::Keyboard(event));
self.embedder_proxy.send(event);
},
@@ -4182,10 +4213,17 @@ where
None => return warn!("{}: Focus parent after closure", pipeline_id),
};
- // Update the focused browsing context in its browser in `browsers`.
- match self.browsers.get_mut(&top_level_browsing_context_id) {
- Some(browser) => {
- browser.focused_browsing_context_id = browsing_context_id;
+ // Focus the top-level browsing context.
+ self.webviews.focus(top_level_browsing_context_id);
+ self.embedder_proxy.send((
+ Some(top_level_browsing_context_id),
+ EmbedderMsg::WebViewFocused(top_level_browsing_context_id),
+ ));
+
+ // Update the webview’s focused browsing context.
+ match self.webviews.get_mut(top_level_browsing_context_id) {
+ Some(webview) => {
+ webview.focused_browsing_context_id = browsing_context_id;
},
None => {
return warn!(
@@ -4428,7 +4466,7 @@ where
}
}
- fn handle_change_browser_visibility(
+ fn notify_webview_visibility(
&mut self,
top_level_browsing_context_id: TopLevelBrowsingContextId,
visible: bool,
@@ -4437,14 +4475,11 @@ where
let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
Some(browsing_context) => browsing_context.pipeline_id,
None => {
- return warn!(
- "{}: Got visibility change event after closure",
- browsing_context_id
- );
+ return warn!("{browsing_context_id}: Tried to notify visibility after closure");
},
};
match self.pipelines.get(&pipeline_id) {
- None => return warn!("{}: Got visibility change event after closure", pipeline_id),
+ None => return warn!("{pipeline_id}: Tried to notify visibility after closure"),
Some(pipeline) => pipeline.notify_visibility(visible),
};
}
@@ -4456,8 +4491,8 @@ where
// LoadData of inner frames are ignored and replaced with the LoadData
// of the parent.
- let session_history = match self.browsers.get(&top_level_browsing_context_id) {
- Some(browser) => &browser.session_history,
+ let session_history = match self.webviews.get(top_level_browsing_context_id) {
+ Some(webview) => &webview.session_history,
None => {
return warn!(
"{}: Session history does not exist for browsing context",
@@ -4602,11 +4637,9 @@ where
// context in which the page is being loaded, then update the focused
// browsing context to be the one where the page is being loaded.
if self.focused_browsing_context_is_descendant_of(change.browsing_context_id) {
- self.browsers
- .entry(change.top_level_browsing_context_id)
- .and_modify(|browser| {
- browser.focused_browsing_context_id = change.browsing_context_id
- });
+ if let Some(webview) = self.webviews.get_mut(change.top_level_browsing_context_id) {
+ webview.focused_browsing_context_id = change.browsing_context_id;
+ }
}
let (old_pipeline_id, top_level_id) =
@@ -4745,7 +4778,7 @@ where
}
self.notify_history_changed(change.top_level_browsing_context_id);
- self.update_frame_tree_if_active(change.top_level_browsing_context_id);
+ self.update_frame_tree_if_focused(change.top_level_browsing_context_id);
}
fn focused_browsing_context_is_descendant_of(
@@ -4753,9 +4786,9 @@ where
browsing_context_id: BrowsingContextId,
) -> bool {
let focused_browsing_context_id = self
- .active_browser_id
- .and_then(|browser_id| self.browsers.get(&browser_id))
- .map(|browser| browser.focused_browsing_context_id);
+ .webviews
+ .focused_webview()
+ .map(|(_, webview)| webview.focused_browsing_context_id);
focused_browsing_context_id.map_or(false, |focus_ctx_id| {
focus_ctx_id == browsing_context_id ||
self.fully_active_descendant_browsing_contexts_iter(browsing_context_id)
@@ -4912,10 +4945,8 @@ where
//
// If there is no focus browsing context yet, the initial page has
// not loaded, so there is nothing to save yet.
- let top_level_browsing_context_id = match self.active_browser_id {
- Some(id) => id,
- None => return ReadyToSave::NoTopLevelBrowsingContext,
- };
+ let Some(top_level_browsing_context_id) = self.webviews.focused_webview().map(|(id, _)| id)
+ else { return ReadyToSave::NoTopLevelBrowsingContext };
// If there are pending loads, wait for those to complete.
if !self.pending_changes.is_empty() {
@@ -5141,12 +5172,12 @@ where
}
}
- // Close a browsing context (and all children)
+ // Close and return the browsing context with the given id (and its children), if it exists.
fn close_browsing_context(
&mut self,
browsing_context_id: BrowsingContextId,
exit_mode: ExitPipelineMode,
- ) {
+ ) -> Option<BrowsingContext> {
debug!("{}: Closing", browsing_context_id);
self.close_browsing_context_children(
@@ -5157,7 +5188,10 @@ where
let browsing_context = match self.browsing_contexts.remove(&browsing_context_id) {
Some(ctx) => ctx,
- None => return warn!("{}: Closing twice", browsing_context_id),
+ None => {
+ warn!("{browsing_context_id}: Closing twice");
+ return None;
+ },
};
{
@@ -5168,12 +5202,13 @@ where
if let Some(parent_pipeline_id) = browsing_context.parent_pipeline_id {
match self.pipelines.get_mut(&parent_pipeline_id) {
None => {
- return warn!("{}: Child closed after parent", parent_pipeline_id);
+ warn!("{parent_pipeline_id}: Child closed after parent");
},
Some(parent_pipeline) => parent_pipeline.remove_child(browsing_context_id),
};
}
debug!("{}: Closed", browsing_context_id);
+ Some(browsing_context)
}
// Close the children of a browsing context
@@ -5212,13 +5247,13 @@ where
top_level_browsing_context_id: TopLevelBrowsingContextId,
pipeline_id: PipelineId,
) {
- match self.browsers.get_mut(&top_level_browsing_context_id) {
- Some(browser) => {
+ match self.webviews.get_mut(top_level_browsing_context_id) {
+ Some(webview) => {
let load_data = match self.pipelines.get(&pipeline_id) {
Some(pipeline) => pipeline.load_data.clone(),
None => return warn!("{}: Discarding closed pipeline", pipeline_id),
};
- browser.session_history.replace_reloader(
+ webview.session_history.replace_reloader(
NeedsToReload::No(pipeline_id),
NeedsToReload::Yes(pipeline_id, load_data),
);
@@ -5350,17 +5385,10 @@ where
&mut self,
top_level_id: TopLevelBrowsingContextId,
) -> &mut JointSessionHistory {
- &mut self
- .browsers
- .entry(top_level_id)
- // This shouldn't be necessary since `get_joint_session_history` is
- // invoked for existing browsers but we need this to satisfy the
- // type system.
- .or_insert_with(|| Browser {
- focused_browsing_context_id: BrowsingContextId::from(top_level_id),
- session_history: JointSessionHistory::new(),
- })
- .session_history
+ self.webviews
+ .get_mut(top_level_id)
+ .map(|webview| &mut webview.session_history)
+ .expect("Unknown top-level browsing context")
}
// Convert a browsing context to a sendable form to pass to the compositor
@@ -5392,29 +5420,23 @@ where
})
}
- /// Re-send the frame tree to the compositor.
- fn update_frame_tree_if_active(
+ /// Send the frame tree for the given webview to the compositor.
+ fn update_frame_tree_if_focused(
&mut self,
top_level_browsing_context_id: TopLevelBrowsingContextId,
) {
- // Only send the frame tree if it's the active one or if no frame tree
- // has been sent yet.
- if self.active_browser_id.is_none() ||
- Some(top_level_browsing_context_id) == self.active_browser_id
- {
- self.send_frame_tree(top_level_browsing_context_id);
+ // Only send the frame tree if the given webview is focused.
+ if let Some(focused_webview_id) = self.webviews.focused_webview().map(|(id, _)| id) {
+ if top_level_browsing_context_id != focused_webview_id {
+ return;
+ }
}
- }
-
- /// Send the current frame tree to compositor
- fn send_frame_tree(&mut self, top_level_browsing_context_id: TopLevelBrowsingContextId) {
// Note that this function can panic, due to ipc-channel creation failure.
// avoiding this panic would require a mechanism for dealing
// with low-resource scenarios.
let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);
if let Some(frame_tree) = self.browsing_context_to_sendable(browsing_context_id) {
debug!("{}: Sending frame tree", browsing_context_id);
- self.active_browser_id = Some(top_level_browsing_context_id);
self.compositor_proxy
.send(CompositorMsg::SetFrameTree(frame_tree));
}
diff --git a/components/constellation/lib.rs b/components/constellation/lib.rs
index 17b83ba9a13..8cf7b30fed3 100644
--- a/components/constellation/lib.rs
+++ b/components/constellation/lib.rs
@@ -14,6 +14,7 @@ mod sandboxing;
mod serviceworker;
mod session_history;
mod timer_scheduler;
+mod webview;
pub use crate::constellation::{Constellation, InitialConstellationState};
pub use crate::logging::{FromCompositorLogger, FromScriptLogger};
diff --git a/components/constellation/webview.rs b/components/constellation/webview.rs
new file mode 100644
index 00000000000..cb2b85de468
--- /dev/null
+++ b/components/constellation/webview.rs
@@ -0,0 +1,200 @@
+/* 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::TopLevelBrowsingContextId;
+
+#[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<TopLevelBrowsingContextId, WebView>,
+
+ /// The order in which they were focused, latest last.
+ focus_order: Vec<TopLevelBrowsingContextId>,
+
+ /// Whether the latest webview in focus order is currently focused.
+ is_focused: bool,
+}
+
+impl<WebView> Default for WebViewManager<WebView> {
+ fn default() -> Self {
+ Self {
+ webviews: HashMap::default(),
+ focus_order: Vec::default(),
+ is_focused: false,
+ }
+ }
+}
+
+impl<WebView> WebViewManager<WebView> {
+ pub fn add(
+ &mut self,
+ top_level_browsing_context_id: TopLevelBrowsingContextId,
+ webview: WebView,
+ ) {
+ self.webviews.insert(top_level_browsing_context_id, webview);
+ }
+
+ pub fn remove(
+ &mut self,
+ top_level_browsing_context_id: TopLevelBrowsingContextId,
+ ) -> Option<WebView> {
+ if self.focus_order.last() == Some(&top_level_browsing_context_id) {
+ self.is_focused = false;
+ }
+ self.focus_order
+ .retain(|b| *b != top_level_browsing_context_id);
+ self.webviews.remove(&top_level_browsing_context_id)
+ }
+
+ pub fn get(
+ &self,
+ top_level_browsing_context_id: TopLevelBrowsingContextId,
+ ) -> Option<&WebView> {
+ self.webviews.get(&top_level_browsing_context_id)
+ }
+
+ pub fn get_mut(
+ &mut self,
+ top_level_browsing_context_id: TopLevelBrowsingContextId,
+ ) -> Option<&mut WebView> {
+ self.webviews.get_mut(&top_level_browsing_context_id)
+ }
+
+ pub fn focused_webview(&self) -> Option<(TopLevelBrowsingContextId, &WebView)> {
+ if !self.is_focused {
+ return None;
+ }
+
+ if let Some(top_level_browsing_context_id) = self.focus_order.last().cloned() {
+ debug_assert!(
+ self.webviews.contains_key(&top_level_browsing_context_id),
+ "BUG: webview in .focus_order not in .webviews!",
+ );
+ self.get(top_level_browsing_context_id)
+ .map(|webview| (top_level_browsing_context_id, webview))
+ } else {
+ debug_assert!(false, "BUG: .is_focused but no webviews in .focus_order!");
+ None
+ }
+ }
+
+ pub fn focus(&mut self, top_level_browsing_context_id: TopLevelBrowsingContextId) {
+ debug_assert!(self.webviews.contains_key(&top_level_browsing_context_id));
+ self.focus_order
+ .retain(|b| *b != top_level_browsing_context_id);
+ self.focus_order.push(top_level_browsing_context_id);
+ self.is_focused = true;
+ }
+
+ pub fn unfocus(&mut self) {
+ self.is_focused = false;
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use std::num::NonZeroU32;
+
+ use msg::constellation_msg::{
+ BrowsingContextId, BrowsingContextIndex, PipelineNamespace, PipelineNamespaceId,
+ TopLevelBrowsingContextId,
+ };
+
+ use crate::webview::WebViewManager;
+
+ fn top_level_id(namespace_id: u32, index: u32) -> TopLevelBrowsingContextId {
+ TopLevelBrowsingContextId(BrowsingContextId {
+ namespace_id: PipelineNamespaceId(namespace_id),
+ index: BrowsingContextIndex(NonZeroU32::new(index).expect("Incorrect test case")),
+ })
+ }
+
+ 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()
+ .expect("Incorrect test case"),
+ )
+ })
+ .collect()
+ }
+
+ #[test]
+ fn test() {
+ PipelineNamespace::install(PipelineNamespaceId(0));
+ let mut webviews = WebViewManager::default();
+
+ // add() adds the webview to the map, but does not focus it.
+ webviews.add(TopLevelBrowsingContextId::new(), 'a');
+ webviews.add(TopLevelBrowsingContextId::new(), 'b');
+ webviews.add(TopLevelBrowsingContextId::new(), 'c');
+ 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.focus_order.is_empty());
+ assert_eq!(webviews.is_focused, false);
+
+ // focus() makes the given webview the latest in focus order.
+ webviews.focus(top_level_id(0, 2));
+ assert_eq!(webviews.focus_order, vec![top_level_id(0, 2)]);
+ assert_eq!(webviews.is_focused, true);
+ webviews.focus(top_level_id(0, 1));
+ assert_eq!(
+ webviews.focus_order,
+ vec![top_level_id(0, 2), top_level_id(0, 1)]
+ );
+ assert_eq!(webviews.is_focused, true);
+ webviews.focus(top_level_id(0, 3));
+ assert_eq!(
+ webviews.focus_order,
+ vec![top_level_id(0, 2), top_level_id(0, 1), top_level_id(0, 3)]
+ );
+ assert_eq!(webviews.is_focused, true);
+
+ // unfocus() clears the “is focused” flag, but does not touch the focus order.
+ webviews.unfocus();
+ assert_eq!(
+ webviews.focus_order,
+ vec![top_level_id(0, 2), top_level_id(0, 1), top_level_id(0, 3)]
+ );
+ assert_eq!(webviews.is_focused, false);
+
+ // focus() avoids duplicates in focus order, when the given webview has been focused before.
+ webviews.focus(top_level_id(0, 1));
+ assert_eq!(
+ webviews.focus_order,
+ vec![top_level_id(0, 2), top_level_id(0, 3), top_level_id(0, 1)]
+ );
+ assert_eq!(webviews.is_focused, true);
+
+ // remove() clears the “is focused” flag iff the given webview was focused.
+ webviews.remove(top_level_id(0, 2));
+ assert_eq!(webviews.is_focused, true);
+ webviews.remove(top_level_id(0, 1));
+ assert_eq!(webviews.is_focused, false);
+ webviews.remove(top_level_id(0, 3));
+ assert_eq!(webviews.is_focused, false);
+
+ // remove() removes the given webview from both the map and the focus order.
+ assert!(webviews_sorted(&webviews).is_empty());
+ assert!(webviews.focus_order.is_empty());
+ }
+}
diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs
index 10d6097b374..83bdc3363ed 100644
--- a/components/script/dom/window.rs
+++ b/components/script/dom/window.rs
@@ -790,13 +790,14 @@ impl WindowMethods for Window {
let window = this.root();
let document = window.Document();
// https://html.spec.whatwg.org/multipage/#closing-browsing-contexts
- // Step 1, prompt to unload.
+ // Step 1, check if traversable is closing, was already done above.
+ // Steps 2 and 3, prompt to unload for all inclusive descendant navigables.
+ // TODO: We should be prompting for all inclusive descendant navigables,
+ // but we pass false here, which suggests we are not doing that. Why?
if document.prompt_to_unload(false) {
- // Step 2, unload.
+ // Step 4, unload.
document.unload(false);
- // Step 3, remove from the user interface
- let _ = window.send_to_embedder(EmbedderMsg::CloseBrowser);
- // Step 4, discard browsing context.
+
// https://html.spec.whatwg.org/multipage/#a-browsing-context-is-discarded
// which calls into https://html.spec.whatwg.org/multipage/#discard-a-document.
window.discard_browsing_context();
diff --git a/components/script/dom/windowproxy.rs b/components/script/dom/windowproxy.rs
index 612476b76e2..886d63dc17c 100644
--- a/components/script/dom/windowproxy.rs
+++ b/components/script/dom/windowproxy.rs
@@ -293,7 +293,7 @@ impl WindowProxy {
.and_then(|id| ScriptThread::find_document(id))
.and_then(|doc| Some(DomRoot::from_ref(doc.window())))
.unwrap();
- let msg = EmbedderMsg::AllowOpeningBrowser(chan);
+ let msg = EmbedderMsg::AllowOpeningWebView(chan);
window.send_to_embedder(msg);
if port.recv().unwrap() {
let new_top_level_browsing_context_id = TopLevelBrowsingContextId::new();
@@ -337,7 +337,7 @@ impl WindowProxy {
let constellation_msg = ScriptMsg::ScriptNewAuxiliary(load_info, pipeline_sender);
window.send_to_constellation(constellation_msg);
ScriptThread::process_attach_layout(new_layout_info, document.origin().clone());
- let msg = EmbedderMsg::BrowserCreated(new_top_level_browsing_context_id);
+ let msg = EmbedderMsg::WebViewOpened(new_top_level_browsing_context_id);
window.send_to_embedder(msg);
// TODO: if noopener is false, copy the sessionStorage storage area of the creator origin.
// See step 14 of https://html.spec.whatwg.org/multipage/#creating-a-new-browsing-context
diff --git a/components/servo/lib.rs b/components/servo/lib.rs
index 662a958460c..11af054132a 100644
--- a/components/servo/lib.rs
+++ b/components/servo/lib.rs
@@ -633,8 +633,8 @@ where
self.compositor.capture_webrender();
},
- EmbedderEvent::NewBrowser(url, top_level_browsing_context_id) => {
- let msg = ConstellationMsg::NewBrowser(url, top_level_browsing_context_id);
+ EmbedderEvent::NewWebView(url, top_level_browsing_context_id) => {
+ let msg = ConstellationMsg::NewWebView(url, top_level_browsing_context_id);
if let Err(e) = self.constellation_chan.send(msg) {
warn!(
"Sending NewBrowser message to constellation failed ({:?}).",
@@ -643,18 +643,18 @@ where
}
},
- EmbedderEvent::SelectBrowser(top_level_browsing_context_id) => {
- let msg = ConstellationMsg::SelectBrowser(top_level_browsing_context_id);
+ EmbedderEvent::FocusWebView(top_level_browsing_context_id) => {
+ let msg = ConstellationMsg::FocusWebView(top_level_browsing_context_id);
if let Err(e) = self.constellation_chan.send(msg) {
warn!(
- "Sending SelectBrowser message to constellation failed ({:?}).",
+ "Sending FocusBrowser message to constellation failed ({:?}).",
e
);
}
},
- EmbedderEvent::CloseBrowser(top_level_browsing_context_id) => {
- let msg = ConstellationMsg::CloseBrowser(top_level_browsing_context_id);
+ EmbedderEvent::CloseWebView(top_level_browsing_context_id) => {
+ let msg = ConstellationMsg::CloseWebView(top_level_browsing_context_id);
if let Err(e) = self.constellation_chan.send(msg) {
warn!(
"Sending CloseBrowser message to constellation failed ({:?}).",
@@ -683,14 +683,11 @@ where
}
},
- EmbedderEvent::ChangeBrowserVisibility(top_level_browsing_context_id, visible) => {
- let msg = ConstellationMsg::ChangeBrowserVisibility(
- top_level_browsing_context_id,
- visible,
- );
+ EmbedderEvent::WebViewVisibilityChanged(webview_id, visible) => {
+ let msg = ConstellationMsg::WebViewVisibilityChanged(webview_id, visible);
if let Err(e) = self.constellation_chan.send(msg) {
warn!(
- "Sending ChangeBrowserVisibility to constellation failed ({:?}).",
+ "Sending WebViewVisibilityChanged to constellation failed ({:?}).",
e
);
}
diff --git a/components/shared/compositing/constellation_msg.rs b/components/shared/compositing/constellation_msg.rs
index 49b7353adfb..12b74cb2f67 100644
--- a/components/shared/compositing/constellation_msg.rs
+++ b/components/shared/compositing/constellation_msg.rs
@@ -55,13 +55,15 @@ pub enum ConstellationMsg {
/// A log entry, with the top-level browsing context id and thread name
LogEntry(Option<TopLevelBrowsingContextId>, Option<String>, LogEntry),
/// Create a new top level browsing context.
- NewBrowser(ServoUrl, TopLevelBrowsingContextId),
+ NewWebView(ServoUrl, TopLevelBrowsingContextId),
/// Close a top level browsing context.
- CloseBrowser(TopLevelBrowsingContextId),
+ CloseWebView(TopLevelBrowsingContextId),
/// Panic a top level browsing context.
SendError(Option<TopLevelBrowsingContextId>, String),
- /// Make browser visible.
- SelectBrowser(TopLevelBrowsingContextId),
+ /// Make a top-level browsing context focused.
+ FocusWebView(TopLevelBrowsingContextId),
+ /// Make none of the top-level browsing contexts focused.
+ BlurWebView,
/// Forward an event to the script task of the given pipeline.
ForwardEvent(PipelineId, CompositorEvent),
/// Requesting a change to the onscreen cursor.
@@ -74,8 +76,8 @@ pub enum ConstellationMsg {
ExitFullScreen(TopLevelBrowsingContextId),
/// Media session action.
MediaSessionAction(MediaSessionActionType),
- /// Toggle browser visibility.
- ChangeBrowserVisibility(TopLevelBrowsingContextId, bool),
+ /// The visibility of the webview has changed.
+ WebViewVisibilityChanged(TopLevelBrowsingContextId, bool),
/// Virtual keyboard was dismissed
IMEDismissed,
/// Compositing done, but external code needs to present.
@@ -100,17 +102,18 @@ impl fmt::Debug for ConstellationMsg {
WebDriverCommand(..) => "WebDriverCommand",
Reload(..) => "Reload",
LogEntry(..) => "LogEntry",
- NewBrowser(..) => "NewBrowser",
- CloseBrowser(..) => "CloseBrowser",
+ NewWebView(..) => "NewWebView",
+ CloseWebView(..) => "CloseWebView",
+ FocusWebView(..) => "FocusWebView",
+ BlurWebView => "BlurWebView",
SendError(..) => "SendError",
- SelectBrowser(..) => "SelectBrowser",
ForwardEvent(..) => "ForwardEvent",
SetCursor(..) => "SetCursor",
EnableProfiler(..) => "EnableProfiler",
DisableProfiler => "DisableProfiler",
ExitFullScreen(..) => "ExitFullScreen",
MediaSessionAction(..) => "MediaSessionAction",
- ChangeBrowserVisibility(..) => "ChangeBrowserVisibility",
+ WebViewVisibilityChanged(..) => "WebViewVisibilityChanged",
IMEDismissed => "IMEDismissed",
ClearCache => "ClearCache",
ReadyToPresent(..) => "ReadyToPresent",
diff --git a/components/shared/embedder/lib.rs b/components/shared/embedder/lib.rs
index 8c86f8d0a44..0337884ea1a 100644
--- a/components/shared/embedder/lib.rs
+++ b/components/shared/embedder/lib.rs
@@ -155,9 +155,15 @@ pub enum EmbedderMsg {
/// Whether or not to allow a pipeline to load a url.
AllowNavigationRequest(PipelineId, ServoUrl),
/// Whether or not to allow script to open a new tab/browser
- AllowOpeningBrowser(IpcSender<bool>),
- /// A new browser was created by script
- BrowserCreated(TopLevelBrowsingContextId),
+ AllowOpeningWebView(IpcSender<bool>),
+ /// A browser was created
+ WebViewOpened(TopLevelBrowsingContextId),
+ /// A browser was destroyed
+ WebViewClosed(TopLevelBrowsingContextId),
+ /// A browser gained focus for keyboard events
+ WebViewFocused(TopLevelBrowsingContextId),
+ /// All browsers lost focus for keyboard events
+ WebViewBlurred,
/// Wether or not to unload a document
AllowUnload(IpcSender<bool>),
/// Sends an unconsumed key event back to the embedder.
@@ -180,8 +186,6 @@ pub enum EmbedderMsg {
LoadStart,
/// The load of a page has completed
LoadComplete,
- /// A browser is to be closed
- CloseBrowser,
/// A pipeline panicked. First string is the reason, second one is the backtrace.
Panic(String, Option<String>),
/// Open dialog to select bluetooth device.
@@ -241,7 +245,6 @@ impl Debug for EmbedderMsg {
EmbedderMsg::SetCursor(..) => write!(f, "SetCursor"),
EmbedderMsg::NewFavicon(..) => write!(f, "NewFavicon"),
EmbedderMsg::HeadParsed => write!(f, "HeadParsed"),
- EmbedderMsg::CloseBrowser => write!(f, "CloseBrowser"),
EmbedderMsg::HistoryChanged(..) => write!(f, "HistoryChanged"),
EmbedderMsg::SetFullscreenState(..) => write!(f, "SetFullscreenState"),
EmbedderMsg::LoadStart => write!(f, "LoadStart"),
@@ -253,8 +256,11 @@ impl Debug for EmbedderMsg {
EmbedderMsg::ShowIME(..) => write!(f, "ShowIME"),
EmbedderMsg::HideIME => write!(f, "HideIME"),
EmbedderMsg::Shutdown => write!(f, "Shutdown"),
- EmbedderMsg::AllowOpeningBrowser(..) => write!(f, "AllowOpeningBrowser"),
- EmbedderMsg::BrowserCreated(..) => write!(f, "BrowserCreated"),
+ EmbedderMsg::AllowOpeningWebView(..) => write!(f, "AllowOpeningWebView"),
+ EmbedderMsg::WebViewOpened(..) => write!(f, "WebViewOpened"),
+ EmbedderMsg::WebViewClosed(..) => write!(f, "WebViewClosed"),
+ EmbedderMsg::WebViewFocused(..) => write!(f, "WebViewFocused"),
+ EmbedderMsg::WebViewBlurred => write!(f, "WebViewUnfocused"),
EmbedderMsg::ReportProfile(..) => write!(f, "ReportProfile"),
EmbedderMsg::MediaSessionEvent(..) => write!(f, "MediaSessionEvent"),
EmbedderMsg::OnDevtoolsStarted(..) => write!(f, "OnDevtoolsStarted"),
diff --git a/components/shared/msg/constellation_msg.rs b/components/shared/msg/constellation_msg.rs
index 889f53e6d73..46c947de67b 100644
--- a/components/shared/msg/constellation_msg.rs
+++ b/components/shared/msg/constellation_msg.rs
@@ -257,8 +257,13 @@ impl BrowsingContextId {
}
}
-#[derive(Clone, Default, Eq, Hash, PartialEq)]
+#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct BrowsingContextGroupId(pub u32);
+impl fmt::Display for BrowsingContextGroupId {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "BrowsingContextGroup{:?}", self)
+ }
+}
thread_local!(pub static TOP_LEVEL_BROWSING_CONTEXT_ID: Cell<Option<TopLevelBrowsingContextId>> = Cell::new(None));
@@ -266,6 +271,7 @@ thread_local!(pub static TOP_LEVEL_BROWSING_CONTEXT_ID: Cell<Option<TopLevelBrow
Clone, Copy, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize,
)]
pub struct TopLevelBrowsingContextId(pub BrowsingContextId);
+pub type WebViewId = TopLevelBrowsingContextId;
size_of_test!(TopLevelBrowsingContextId, 8);
size_of_test!(Option<TopLevelBrowsingContextId>, 8);
diff --git a/ports/libsimpleservo/api/src/lib.rs b/ports/libsimpleservo/api/src/lib.rs
index 5a3d1e4cd74..069e5745ae5 100644
--- a/ports/libsimpleservo/api/src/lib.rs
+++ b/ports/libsimpleservo/api/src/lib.rs
@@ -31,7 +31,7 @@ use servo::embedder_traits::{
use servo::euclid::{Point2D, Rect, Scale, Size2D, Vector2D};
use servo::keyboard_types::{Key, KeyState, KeyboardEvent};
pub use servo::msg::constellation_msg::InputMethodType;
-use servo::msg::constellation_msg::TraversalDirection;
+use servo::msg::constellation_msg::{TraversalDirection, WebViewId};
pub use servo::script_traits::{MediaSessionActionType, MouseButton};
use servo::script_traits::{TouchEventType, TouchId};
use servo::servo_config::{opts, pref};
@@ -168,19 +168,25 @@ pub struct ServoGlue {
servo: Servo<ServoWindowCallbacks>,
batch_mode: bool,
callbacks: Rc<ServoWindowCallbacks>,
- /// id of the top level browsing context. It is unique as tabs
- /// are not supported yet. None until created.
- browser_id: Option<BrowserId>,
- // A rudimentary stack of "tabs".
- // EmbedderMsg::BrowserCreated will push onto it.
- // EmbedderMsg::CloseBrowser will pop from it,
- // and exit if it is empty afterwards.
- browsers: Vec<BrowserId>,
events: Vec<EmbedderEvent>,
-
context_menu_sender: Option<IpcSender<ContextMenuResult>>,
+
+ /// List of top-level browsing contexts.
+ /// Modified by EmbedderMsg::WebViewOpened and EmbedderMsg::WebViewClosed,
+ /// and we exit if it ever becomes empty.
+ webviews: HashMap<WebViewId, WebView>,
+
+ /// The order in which the webviews were created.
+ creation_order: Vec<WebViewId>,
+
+ /// The webview that is currently focused.
+ /// Modified by EmbedderMsg::WebViewFocused and EmbedderMsg::WebViewBlurred.
+ focused_webview_id: Option<WebViewId>,
}
+#[derive(Debug)]
+pub struct WebView {}
+
pub fn servo_version() -> String {
format!(
"Servo {}-{}",
@@ -312,12 +318,13 @@ pub fn init(
servo: servo.servo,
batch_mode: false,
callbacks: window_callbacks,
- browser_id: None,
- browsers: vec![],
events: vec![],
context_menu_sender: None,
+ webviews: HashMap::default(),
+ creation_order: vec![],
+ focused_webview_id: None,
};
- let _ = servo_glue.process_event(EmbedderEvent::NewBrowser(url, servo.browser_id));
+ let _ = servo_glue.process_event(EmbedderEvent::NewWebView(url, servo.browser_id));
*s.borrow_mut() = Some(servo_glue);
});
@@ -330,11 +337,11 @@ pub fn deinit() {
impl ServoGlue {
fn get_browser_id(&self) -> Result<BrowserId, &'static str> {
- let browser_id = match self.browser_id {
+ let webview_id = match self.focused_webview_id {
Some(id) => id,
- None => return Err("No BrowserId set yet."),
+ None => return Err("No focused WebViewId yet."),
};
- Ok(browser_id)
+ Ok(webview_id)
}
/// Request shutdown. Will call on_shutdown_complete.
@@ -604,7 +611,7 @@ impl ServoGlue {
pub fn change_visibility(&mut self, visible: bool) -> Result<(), &'static str> {
info!("change_visibility");
if let Ok(id) = self.get_browser_id() {
- let event = EmbedderEvent::ChangeBrowserVisibility(id, visible);
+ let event = EmbedderEvent::WebViewVisibilityChanged(id, visible);
self.process_event(event)
} else {
// Ignore visibility change if no browser has been created yet.
@@ -714,21 +721,35 @@ impl ServoGlue {
.push(EmbedderEvent::SendError(browser_id, reason));
}
},
- EmbedderMsg::AllowOpeningBrowser(response_chan) => {
+ EmbedderMsg::AllowOpeningWebView(response_chan) => {
// Note: would be a place to handle pop-ups config.
// see Step 7 of #the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name
if let Err(e) = response_chan.send(true) {
warn!("Failed to send AllowOpeningBrowser response: {}", e);
};
},
- EmbedderMsg::BrowserCreated(new_browser_id) => {
- // TODO: properly handle a new "tab"
- self.browsers.push(new_browser_id);
- if self.browser_id.is_none() {
- self.browser_id = Some(new_browser_id);
- }
+ EmbedderMsg::WebViewOpened(new_webview_id) => {
+ self.webviews.insert(new_webview_id, WebView {});
+ self.creation_order.push(new_webview_id);
self.events
- .push(EmbedderEvent::SelectBrowser(new_browser_id));
+ .push(EmbedderEvent::FocusWebView(new_webview_id));
+ },
+ EmbedderMsg::WebViewClosed(webview_id) => {
+ self.webviews.retain(|&id, _| id != webview_id);
+ self.creation_order.retain(|&id| id != webview_id);
+ self.focused_webview_id = None;
+ if let Some(&newest_webview_id) = self.creation_order.last() {
+ self.events
+ .push(EmbedderEvent::FocusWebView(newest_webview_id));
+ } else {
+ self.events.push(EmbedderEvent::Quit);
+ }
+ },
+ EmbedderMsg::WebViewFocused(webview_id) => {
+ self.focused_webview_id = Some(webview_id);
+ },
+ EmbedderMsg::WebViewBlurred => {
+ self.focused_webview_id = None;
},
EmbedderMsg::GetClipboardContents(sender) => {
let contents = self.callbacks.host_callbacks.get_clipboard_contents();
@@ -737,17 +758,6 @@ impl ServoGlue {
EmbedderMsg::SetClipboardContents(text) => {
self.callbacks.host_callbacks.set_clipboard_contents(text);
},
- EmbedderMsg::CloseBrowser => {
- // TODO: close the appropriate "tab".
- let _ = self.browsers.pop();
- if let Some(prev_browser_id) = self.browsers.last() {
- self.browser_id = Some(*prev_browser_id);
- self.events
- .push(EmbedderEvent::SelectBrowser(*prev_browser_id));
- } else {
- self.events.push(EmbedderEvent::Quit);
- }
- },
EmbedderMsg::Shutdown => {
self.callbacks.host_callbacks.on_shutdown_complete();
},
diff --git a/ports/servoshell/app.rs b/ports/servoshell/app.rs
index b7e8b86b5b2..59b7709769f 100644
--- a/ports/servoshell/app.rs
+++ b/ports/servoshell/app.rs
@@ -23,17 +23,17 @@ use winit::event::WindowEvent;
use winit::event_loop::EventLoopWindowTarget;
use winit::window::WindowId;
-use crate::browser::Browser;
use crate::embedder::EmbedderCallbacks;
use crate::events_loop::{EventsLoop, WakerEvent};
use crate::minibrowser::Minibrowser;
use crate::parser::get_default_url;
+use crate::webview::WebViewManager;
use crate::window_trait::WindowPortsMethods;
use crate::{headed_window, headless_window};
pub struct App {
servo: Option<Servo<dyn WindowPortsMethods>>,
- browser: RefCell<Browser<dyn WindowPortsMethods>>,
+ webviews: RefCell<WebViewManager<dyn WindowPortsMethods>>,
event_queue: RefCell<Vec<EmbedderEvent>>,
suspended: Cell<bool>,
windows: HashMap<WindowId, Rc<dyn WindowPortsMethods>>,
@@ -81,7 +81,7 @@ impl App {
};
// Handle browser state.
- let browser = Browser::new(window.clone());
+ let webviews = WebViewManager::new(window.clone());
let initial_url = get_default_url(
url.as_ref().map(String::as_str),
env::current_dir().unwrap(),
@@ -90,7 +90,7 @@ impl App {
let mut app = App {
event_queue: RefCell::new(vec![]),
- browser: RefCell::new(browser),
+ webviews: RefCell::new(webviews),
servo: None,
suspended: Cell::new(false),
windows: HashMap::new(),
@@ -200,7 +200,7 @@ impl App {
);
let mut servo = servo_data.servo;
- servo.handle_events(vec![EmbedderEvent::NewBrowser(
+ servo.handle_events(vec![EmbedderEvent::NewWebView(
initial_url.to_owned(),
servo_data.browser_id,
)]);
@@ -297,9 +297,9 @@ impl App {
// Consume and handle any events from the Minibrowser.
if let Some(minibrowser) = app.minibrowser() {
- let browser = &mut app.browser.borrow_mut();
+ let webviews = &mut app.webviews.borrow_mut();
let app_event_queue = &mut app.event_queue.borrow_mut();
- minibrowser.queue_embedder_events_for_minibrowser_events(browser, app_event_queue);
+ minibrowser.queue_embedder_events_for_minibrowser_events(webviews, app_event_queue);
}
match app.handle_events() {
@@ -316,8 +316,8 @@ impl App {
} => {
if history_changed {
if let Some(mut minibrowser) = app.minibrowser() {
- let browser = &mut app.browser.borrow_mut();
- if minibrowser.update_location_in_toolbar(browser) {
+ let webviews = &mut app.webviews.borrow_mut();
+ if minibrowser.update_location_in_toolbar(webviews) {
// Update the minibrowser immediately. While we could update by requesting a
// redraw, doing so would delay the location update by two frames.
minibrowser.update(
@@ -422,18 +422,11 @@ impl App {
/// towards Servo and embedder messages flow away from Servo, and also runs the compositor.
///
/// As the embedder, we push embedder events through our event queues, from the App queue and
- /// Window queues to the Browser queue, and from the Browser queue to Servo. We receive and
- /// collect embedder messages from the various Servo components, then take them out of the
- /// Servo interface so that the Browser can handle them.
+ /// Window queues to the WebViewManager queue, and from the WebViewManager queue to Servo. We
+ /// receive and collect embedder messages from the various Servo components, then take them out
+ /// of the Servo interface so that the WebViewManager can handle them.
fn handle_events(&mut self) -> PumpResult {
- let mut browser = self.browser.borrow_mut();
-
- // FIXME:
- // As of now, we support only one browser (self.browser)
- // but have multiple windows (dom.webxr.glwindow). We forward
- // the events of all the windows combined to that single
- // browser instance. Pressing the "a" key on the glwindow
- // will send a key event to the servo window.
+ let mut webviews = self.webviews.borrow_mut();
// Take any outstanding embedder events from the App and its Windows.
let mut embedder_events = self.get_events();
@@ -441,8 +434,8 @@ impl App {
embedder_events.extend(window.get_events());
}
- // Catch some keyboard events, and push the rest onto the Browser event queue.
- browser.handle_window_events(embedder_events);
+ // Catch some keyboard events, and push the rest onto the WebViewManager event queue.
+ webviews.handle_window_events(embedder_events);
// Take any new embedder messages from Servo itself.
let mut embedder_messages = self.servo.as_mut().unwrap().get_events();
@@ -451,19 +444,19 @@ impl App {
let mut history_changed = false;
loop {
// Consume and handle those embedder messages.
- let servo_event_response = browser.handle_servo_events(embedder_messages);
+ let servo_event_response = webviews.handle_servo_events(embedder_messages);
need_present |= servo_event_response.need_present;
history_changed |= servo_event_response.history_changed;
- // Route embedder events from the Browser to the relevant Servo components,
+ // Route embedder events from the WebViewManager to the relevant Servo components,
// receives and collects embedder messages from various Servo components,
// and runs the compositor.
need_resize |= self
.servo
.as_mut()
.unwrap()
- .handle_events(browser.get_events());
- if browser.shutdown_requested() {
+ .handle_events(webviews.get_events());
+ if webviews.shutdown_requested() {
return PumpResult::Shutdown;
}
diff --git a/ports/servoshell/main.rs b/ports/servoshell/main.rs
index fd56fcdc61b..a215089bfea 100644
--- a/ports/servoshell/main.rs
+++ b/ports/servoshell/main.rs
@@ -31,7 +31,6 @@ cfg_if::cfg_if! {
mod app;
mod backtrace;
- mod browser;
mod crash_handler;
mod egui_glue;
mod embedder;
@@ -45,6 +44,7 @@ cfg_if::cfg_if! {
mod parser;
mod prefs;
mod resources;
+ mod webview;
mod window_trait;
pub mod platform {
diff --git a/ports/servoshell/minibrowser.rs b/ports/servoshell/minibrowser.rs
index 42659ec1964..b7bbabafdb5 100644
--- a/ports/servoshell/minibrowser.rs
+++ b/ports/servoshell/minibrowser.rs
@@ -20,11 +20,11 @@ use servo::servo_geometry::DeviceIndependentPixel;
use servo::servo_url::ServoUrl;
use servo::webrender_surfman::WebrenderSurfman;
-use crate::browser::Browser;
use crate::egui_glue::EguiGlow;
use crate::events_loop::EventsLoop;
use crate::geometry::winit_position_to_euclid_point;
use crate::parser::location_bar_input_to_url;
+use crate::webview::WebViewManager;
use crate::window_trait::WindowPortsMethods;
pub struct Minibrowser {
@@ -260,13 +260,13 @@ impl Minibrowser {
/// routing those to the App event queue.
pub fn queue_embedder_events_for_minibrowser_events(
&self,
- browser: &Browser<dyn WindowPortsMethods>,
+ browser: &WebViewManager<dyn WindowPortsMethods>,
app_event_queue: &mut Vec<EmbedderEvent>,
) {
for event in self.event_queue.borrow_mut().drain(..) {
match event {
MinibrowserEvent::Go => {
- let browser_id = browser.browser_id().unwrap();
+ let browser_id = browser.webview_id().unwrap();
let location = self.location.borrow();
if let Some(url) = location_bar_input_to_url(&location.clone()) {
app_event_queue.push(EmbedderEvent::LoadUrl(browser_id, url));
@@ -276,14 +276,14 @@ impl Minibrowser {
}
},
MinibrowserEvent::Back => {
- let browser_id = browser.browser_id().unwrap();
+ let browser_id = browser.webview_id().unwrap();
app_event_queue.push(EmbedderEvent::Navigation(
browser_id,
TraversalDirection::Back(1),
));
},
MinibrowserEvent::Forward => {
- let browser_id = browser.browser_id().unwrap();
+ let browser_id = browser.webview_id().unwrap();
app_event_queue.push(EmbedderEvent::Navigation(
browser_id,
TraversalDirection::Forward(1),
@@ -293,11 +293,11 @@ impl Minibrowser {
}
}
- /// Updates the location field from the given [Browser], unless the user has started editing it
- /// without clicking Go, returning true iff the location has changed (needing an egui update).
+ /// Updates the location field from the given [BrowserManager], unless the user has started
+ /// editing it without clicking Go, returning true iff it has changed (needing an egui update).
pub fn update_location_in_toolbar(
&mut self,
- browser: &mut Browser<dyn WindowPortsMethods>,
+ browser: &mut WebViewManager<dyn WindowPortsMethods>,
) -> bool {
// User edited without clicking Go?
if self.location_dirty.get() {
diff --git a/ports/servoshell/browser.rs b/ports/servoshell/webview.rs
index 92e31f18c4e..cf0400faff7 100644
--- a/ports/servoshell/browser.rs
+++ b/ports/servoshell/webview.rs
@@ -2,6 +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::collections::HashMap;
use std::fs::File;
use std::io::Write;
use std::rc::Rc;
@@ -17,7 +18,7 @@ use servo::embedder_traits::{
CompositorEventVariant, ContextMenuResult, EmbedderMsg, FilterPattern, PermissionPrompt,
PermissionRequest, PromptDefinition, PromptOrigin, PromptResult,
};
-use servo::msg::constellation_msg::{TopLevelBrowsingContextId as BrowserId, TraversalDirection};
+use servo::msg::constellation_msg::{TopLevelBrowsingContextId as WebViewId, TraversalDirection};
use servo::script_traits::TouchEventType;
use servo::servo_config::opts;
use servo::servo_url::ServoUrl;
@@ -28,19 +29,21 @@ use crate::keyutils::{CMD_OR_ALT, CMD_OR_CONTROL};
use crate::parser::location_bar_input_to_url;
use crate::window_trait::{WindowPortsMethods, LINE_HEIGHT};
-pub struct Browser<Window: WindowPortsMethods + ?Sized> {
+pub struct WebViewManager<Window: WindowPortsMethods + ?Sized> {
current_url: Option<ServoUrl>,
current_url_string: Option<String>,
- /// id of the top level browsing context. It is unique as tabs
- /// are not supported yet. None until created.
- browser_id: Option<BrowserId>,
+ /// List of top-level browsing contexts.
+ /// Modified by EmbedderMsg::WebViewOpened and EmbedderMsg::WebViewClosed,
+ /// and we exit if it ever becomes empty.
+ webviews: HashMap<WebViewId, WebView>,
- // A rudimentary stack of "tabs".
- // EmbedderMsg::BrowserCreated will push onto it.
- // EmbedderMsg::CloseBrowser will pop from it,
- // and exit if it is empty afterwards.
- browsers: Vec<BrowserId>,
+ /// The order in which the webviews were created.
+ creation_order: Vec<WebViewId>,
+
+ /// The webview that is currently focused.
+ /// Modified by EmbedderMsg::WebViewFocused and EmbedderMsg::WebViewBlurred.
+ focused_webview_id: Option<WebViewId>,
title: Option<String>,
@@ -50,22 +53,26 @@ pub struct Browser<Window: WindowPortsMethods + ?Sized> {
shutdown_requested: bool,
}
+#[derive(Debug)]
+pub struct WebView {}
+
pub struct ServoEventResponse {
pub need_present: bool,
pub history_changed: bool,
}
-impl<Window> Browser<Window>
+impl<Window> WebViewManager<Window>
where
Window: WindowPortsMethods + ?Sized,
{
- pub fn new(window: Rc<Window>) -> Browser<Window> {
- Browser {
+ pub fn new(window: Rc<Window>) -> WebViewManager<Window> {
+ WebViewManager {
title: None,
current_url: None,
current_url_string: None,
- browser_id: None,
- browsers: Vec::new(),
+ webviews: HashMap::default(),
+ creation_order: vec![],
+ focused_webview_id: None,
window,
clipboard: match Clipboard::new() {
Ok(c) => Some(c),
@@ -79,8 +86,8 @@ where
}
}
- pub fn browser_id(&self) -> Option<BrowserId> {
- self.browser_id
+ pub fn webview_id(&self) -> Option<WebViewId> {
+ self.focused_webview_id
}
pub fn current_url_string(&self) -> Option<&str> {
@@ -113,7 +120,7 @@ where
fn handle_key_from_window(&mut self, key_event: KeyboardEvent) {
ShortcutMatcher::from_event(key_event.clone())
.shortcut(CMD_OR_CONTROL, 'R', || {
- if let Some(id) = self.browser_id {
+ if let Some(id) = self.focused_webview_id {
self.event_queue.push(EmbedderEvent::Reload(id));
}
})
@@ -128,7 +135,7 @@ where
let input = tinyfiledialogs::input_box(title, title, &tiny_dialog_escape(&url));
if let Some(input) = input {
if let Some(url) = location_bar_input_to_url(&input) {
- if let Some(id) = self.browser_id {
+ if let Some(id) = self.focused_webview_id {
self.event_queue.push(EmbedderEvent::LoadUrl(id, url));
}
}
@@ -171,13 +178,13 @@ where
));
})
.shortcut(CMD_OR_ALT, Key::ArrowRight, || {
- if let Some(id) = self.browser_id {
+ if let Some(id) = self.focused_webview_id {
let event = EmbedderEvent::Navigation(id, TraversalDirection::Forward(1));
self.event_queue.push(event);
}
})
.shortcut(CMD_OR_ALT, Key::ArrowLeft, || {
- if let Some(id) = self.browser_id {
+ if let Some(id) = self.focused_webview_id {
let event = EmbedderEvent::Navigation(id, TraversalDirection::Back(1));
self.event_queue.push(event);
}
@@ -185,7 +192,7 @@ where
.shortcut(Modifiers::empty(), Key::Escape, || {
let state = self.window.get_fullscreen();
if state {
- if let Some(id) = self.browser_id {
+ if let Some(id) = self.focused_webview_id {
let event = EmbedderEvent::ExitFullScreen(id);
self.event_queue.push(event);
}
@@ -198,7 +205,7 @@ where
#[cfg(not(target_os = "win"))]
fn platform_handle_key(&mut self, key_event: KeyboardEvent) {
- if let Some(id) = self.browser_id {
+ if let Some(id) = self.focused_webview_id {
if let Some(event) = ShortcutMatcher::from_event(key_event.clone())
.shortcut(CMD_OR_CONTROL, '[', || {
EmbedderEvent::Navigation(id, TraversalDirection::Back(1))
@@ -217,7 +224,7 @@ where
fn platform_handle_key(&mut self, _key_event: KeyboardEvent) {}
/// Handle key events after they have been handled by Servo.
- fn handle_key_from_servo(&mut self, _: Option<BrowserId>, event: KeyboardEvent) {
+ fn handle_key_from_servo(&mut self, _: Option<WebViewId>, event: KeyboardEvent) {
ShortcutMatcher::from_event(event)
.shortcut(CMD_OR_CONTROL, '=', || {
self.event_queue.push(EmbedderEvent::Zoom(1.1))
@@ -285,14 +292,14 @@ where
/// Returns true if the caller needs to manually present a new frame.
pub fn handle_servo_events(
&mut self,
- events: Vec<(Option<BrowserId>, EmbedderMsg)>,
+ events: Vec<(Option<WebViewId>, EmbedderMsg)>,
) -> ServoEventResponse {
let mut need_present = false;
let mut history_changed = false;
- for (browser_id, msg) in events {
+ for (webview_id, msg) in events {
trace!(
"embedder <- servo EmbedderMsg ({:?}, {:?})",
- browser_id.map(|x| format!("{}", x)),
+ webview_id.map(|x| format!("{}", x)),
msg
);
match msg {
@@ -395,7 +402,7 @@ where
if let Err(e) = res {
let reason = format!("Failed to send Prompt response: {}", e);
self.event_queue
- .push(EmbedderEvent::SendError(browser_id, reason));
+ .push(EmbedderEvent::SendError(webview_id, reason));
}
},
EmbedderMsg::AllowUnload(sender) => {
@@ -403,35 +410,47 @@ where
if let Err(e) = sender.send(true) {
let reason = format!("Failed to send AllowUnload response: {}", e);
self.event_queue
- .push(EmbedderEvent::SendError(browser_id, reason));
+ .push(EmbedderEvent::SendError(webview_id, reason));
}
},
EmbedderMsg::AllowNavigationRequest(pipeline_id, _url) => {
- if let Some(_browser_id) = browser_id {
+ if let Some(_webview_id) = webview_id {
self.event_queue
.push(EmbedderEvent::AllowNavigationResponse(pipeline_id, true));
}
},
- EmbedderMsg::AllowOpeningBrowser(response_chan) => {
+ EmbedderMsg::AllowOpeningWebView(response_chan) => {
// Note: would be a place to handle pop-ups config.
// see Step 7 of #the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name
if let Err(e) = response_chan.send(true) {
- warn!("Failed to send AllowOpeningBrowser response: {}", e);
+ warn!("Failed to send AllowOpeningWebView response: {}", e);
};
},
- EmbedderMsg::BrowserCreated(new_browser_id) => {
- // TODO: properly handle a new "tab"
- self.browsers.push(new_browser_id);
- if self.browser_id.is_none() {
- self.browser_id = Some(new_browser_id);
+ EmbedderMsg::WebViewOpened(new_webview_id) => {
+ self.webviews.insert(new_webview_id, WebView {});
+ self.creation_order.push(new_webview_id);
+ self.event_queue
+ .push(EmbedderEvent::FocusWebView(new_webview_id));
+ },
+ EmbedderMsg::WebViewClosed(webview_id) => {
+ self.webviews.retain(|&id, _| id != webview_id);
+ self.creation_order.retain(|&id| id != webview_id);
+ self.focused_webview_id = None;
+ if let Some(&newest_webview_id) = self.creation_order.last() {
+ self.event_queue
+ .push(EmbedderEvent::FocusWebView(newest_webview_id));
} else {
- error!("Multiple top level browsing contexts not supported yet.");
+ self.event_queue.push(EmbedderEvent::Quit);
}
- self.event_queue
- .push(EmbedderEvent::SelectBrowser(new_browser_id));
+ },
+ EmbedderMsg::WebViewFocused(webview_id) => {
+ self.focused_webview_id = Some(webview_id);
+ },
+ EmbedderMsg::WebViewBlurred => {
+ self.focused_webview_id = None;
},
EmbedderMsg::Keyboard(key_event) => {
- self.handle_key_from_servo(browser_id, key_event);
+ self.handle_key_from_servo(webview_id, key_event);
},
EmbedderMsg::GetClipboardContents(sender) => {
let contents = self
@@ -476,17 +495,6 @@ where
EmbedderMsg::LoadComplete => {
// FIXME: surface the loading state in the UI somehow
},
- EmbedderMsg::CloseBrowser => {
- // TODO: close the appropriate "tab".
- let _ = self.browsers.pop();
- if let Some(prev_browser_id) = self.browsers.last() {
- self.browser_id = Some(*prev_browser_id);
- self.event_queue
- .push(EmbedderEvent::SelectBrowser(*prev_browser_id));
- } else {
- self.event_queue.push(EmbedderEvent::Quit);
- }
- },
EmbedderMsg::Shutdown => {
self.shutdown_requested = true;
},
@@ -545,10 +553,10 @@ where
EmbedderMsg::ReadyToPresent => {
need_present = true;
},
- EmbedderMsg::EventDelivered(event) => match (browser_id, event) {
- (Some(browser_id), CompositorEventVariant::MouseButtonEvent) => {
- // TODO Focus browser and/or raise to top if needed.
- trace!("{}: Got a mouse button event", browser_id);
+ EmbedderMsg::EventDelivered(event) => match (webview_id, event) {
+ (Some(webview_id), CompositorEventVariant::MouseButtonEvent) => {
+ // TODO Focus webview and/or raise to top if needed.
+ trace!("{}: Got a mouse button event", webview_id);
},
(_, _) => {},
},
diff --git a/python/servo/testing_commands.py b/python/servo/testing_commands.py
index 13dd21e9405..2a2080b9f42 100644
--- a/python/servo/testing_commands.py
+++ b/python/servo/testing_commands.py
@@ -138,6 +138,7 @@ class MachCommands(CommandBase):
"servo_config",
"servo_remutex",
"crown",
+ "constellation",
]
if not packages:
packages = set(os.listdir(path.join(self.context.topdir, "tests", "unit"))) - set(['.DS_Store'])