aboutsummaryrefslogtreecommitdiffstats
path: root/components/constellation
diff options
context:
space:
mode:
Diffstat (limited to 'components/constellation')
-rw-r--r--components/constellation/constellation.rs524
-rw-r--r--components/constellation/pipeline.rs5
-rw-r--r--components/constellation/tracing.rs5
3 files changed, 389 insertions, 145 deletions
diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs
index 2175028a81b..ad89c435717 100644
--- a/components/constellation/constellation.rs
+++ b/components/constellation/constellation.rs
@@ -115,9 +115,10 @@ use constellation_traits::{
AuxiliaryWebViewCreationRequest, AuxiliaryWebViewCreationResponse, BroadcastMsg, DocumentState,
EmbedderToConstellationMessage, IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState,
IFrameSizeMsg, Job, LoadData, LoadOrigin, LogEntry, MessagePortMsg, NavigationHistoryBehavior,
- PaintMetricEvent, PortMessageTask, SWManagerMsg, SWManagerSenders, ScriptToConstellationChan,
- ScriptToConstellationMessage, ScrollState, ServiceWorkerManagerFactory, ServiceWorkerMsg,
- StructuredSerializedData, TraversalDirection, WindowSizeType,
+ PaintMetricEvent, PortMessageTask, PortTransferInfo, SWManagerMsg, SWManagerSenders,
+ ScriptToConstellationChan, ScriptToConstellationMessage, ScrollState,
+ ServiceWorkerManagerFactory, ServiceWorkerMsg, StructuredSerializedData, TraversalDirection,
+ WindowSizeType,
};
use crossbeam_channel::{Receiver, Select, Sender, unbounded};
use devtools_traits::{
@@ -127,10 +128,10 @@ use devtools_traits::{
use embedder_traits::resources::{self, Resource};
use embedder_traits::user_content_manager::UserContentManager;
use embedder_traits::{
- AnimationState, CompositorHitTestResult, Cursor, EmbedderMsg, EmbedderProxy, ImeEvent,
- InputEvent, MediaSessionActionType, MediaSessionEvent, MediaSessionPlaybackState, MouseButton,
- MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverCommandMsg,
- WebDriverLoadStatus,
+ AnimationState, CompositorHitTestResult, Cursor, EmbedderMsg, EmbedderProxy,
+ FocusSequenceNumber, ImeEvent, InputEvent, MediaSessionActionType, MediaSessionEvent,
+ MediaSessionPlaybackState, MouseButton, MouseButtonAction, MouseButtonEvent, Theme,
+ ViewportDetails, WebDriverCommandMsg, WebDriverLoadStatus,
};
use euclid::Size2D;
use euclid::default::Size2D as UntypedSize2D;
@@ -202,9 +203,6 @@ enum TransferState {
/// While a completion failed, another global requested to complete the transfer.
/// We are still buffering messages, and awaiting the return of the buffer from the global who failed.
CompletionRequested(MessagePortRouterId, VecDeque<PortMessageTask>),
- /// The entangled port has been removed while the port was in-transfer,
- /// the current port should be removed as well once it is managed again.
- EntangledRemoved,
}
#[derive(Debug)]
@@ -1043,6 +1041,44 @@ where
}
}
+ /// Enumerate the specified browsing context's ancestor pipelines up to
+ /// the top-level pipeline.
+ fn ancestor_pipelines_of_browsing_context_iter(
+ &self,
+ browsing_context_id: BrowsingContextId,
+ ) -> impl Iterator<Item = &Pipeline> + '_ {
+ let mut state: Option<PipelineId> = self
+ .browsing_contexts
+ .get(&browsing_context_id)
+ .and_then(|browsing_context| browsing_context.parent_pipeline_id);
+ std::iter::from_fn(move || {
+ if let Some(pipeline_id) = state {
+ let pipeline = self.pipelines.get(&pipeline_id)?;
+ let browsing_context = self.browsing_contexts.get(&pipeline.browsing_context_id)?;
+ state = browsing_context.parent_pipeline_id;
+ Some(pipeline)
+ } else {
+ None
+ }
+ })
+ }
+
+ /// Enumerate the specified browsing context's ancestor-or-self pipelines up
+ /// to the top-level pipeline.
+ fn ancestor_or_self_pipelines_of_browsing_context_iter(
+ &self,
+ browsing_context_id: BrowsingContextId,
+ ) -> impl Iterator<Item = &Pipeline> + '_ {
+ let this_pipeline = self
+ .browsing_contexts
+ .get(&browsing_context_id)
+ .map(|browsing_context| browsing_context.pipeline_id)
+ .and_then(|pipeline_id| self.pipelines.get(&pipeline_id));
+ this_pipeline
+ .into_iter()
+ .chain(self.ancestor_pipelines_of_browsing_context_iter(browsing_context_id))
+ }
+
/// Create a new browsing context and update the internal bookkeeping.
#[allow(clippy::too_many_arguments)]
fn new_browsing_context(
@@ -1486,12 +1522,12 @@ where
ScriptToConstellationMessage::NewMessagePort(router_id, port_id) => {
self.handle_new_messageport(router_id, port_id);
},
- ScriptToConstellationMessage::RemoveMessagePort(port_id) => {
- self.handle_remove_messageport(port_id);
- },
ScriptToConstellationMessage::EntanglePorts(port1, port2) => {
self.handle_entangle_messageports(port1, port2);
},
+ ScriptToConstellationMessage::DisentanglePorts(port1, port2) => {
+ self.handle_disentangle_messageports(port1, port2);
+ },
ScriptToConstellationMessage::NewBroadcastChannelRouter(
router_id,
response_sender,
@@ -1621,8 +1657,15 @@ where
data,
);
},
- ScriptToConstellationMessage::Focus => {
- self.handle_focus_msg(source_pipeline_id);
+ ScriptToConstellationMessage::Focus(focused_child_browsing_context_id, sequence) => {
+ self.handle_focus_msg(
+ source_pipeline_id,
+ focused_child_browsing_context_id,
+ sequence,
+ );
+ },
+ ScriptToConstellationMessage::FocusRemoteDocument(focused_browsing_context_id) => {
+ self.handle_focus_remote_document_msg(focused_browsing_context_id);
},
ScriptToConstellationMessage::SetThrottledComplete(throttled) => {
self.handle_set_throttled_complete(source_pipeline_id, throttled);
@@ -2072,17 +2115,6 @@ where
Entry::Occupied(entry) => entry,
};
match entry.get().state {
- TransferState::EntangledRemoved => {
- // If the entangled port has been removed while this one was in-transfer,
- // remove it now.
- if let Some(ipc_sender) = self.message_port_routers.get(&router_id) {
- let _ = ipc_sender.send(MessagePortMsg::RemoveMessagePort(port_id));
- } else {
- warn!("No message-port sender for {:?}", router_id);
- }
- entry.remove_entry();
- continue;
- },
TransferState::CompletionInProgress(expected_router_id) => {
// Here, the transfer was normally completed.
@@ -2106,9 +2138,9 @@ where
fn handle_message_port_transfer_failed(
&mut self,
- ports: HashMap<MessagePortId, VecDeque<PortMessageTask>>,
+ ports: HashMap<MessagePortId, PortTransferInfo>,
) {
- for (port_id, mut previous_buffer) in ports.into_iter() {
+ for (port_id, mut transfer_info) in ports.into_iter() {
let entry = match self.message_ports.remove(&port_id) {
None => {
warn!(
@@ -2120,11 +2152,6 @@ where
Some(entry) => entry,
};
let new_info = match entry.state {
- TransferState::EntangledRemoved => {
- // If the entangled port has been removed while this one was in-transfer,
- // just drop it.
- continue;
- },
TransferState::CompletionFailed(mut current_buffer) => {
// The transfer failed,
// and now the global has returned us the buffer we previously sent.
@@ -2132,7 +2159,7 @@ where
// Tasks in the previous buffer are older,
// hence need to be added to the front of the current one.
- while let Some(task) = previous_buffer.pop_back() {
+ while let Some(task) = transfer_info.port_message_queue.pop_back() {
current_buffer.push_front(task);
}
// Update the state to transfer-in-progress.
@@ -2151,7 +2178,7 @@ where
// Tasks in the previous buffer are older,
// hence need to be added to the front of the current one.
- while let Some(task) = previous_buffer.pop_back() {
+ while let Some(task) = transfer_info.port_message_queue.pop_back() {
current_buffer.push_front(task);
}
// Forward the buffered message-queue to complete the current transfer.
@@ -2159,7 +2186,10 @@ where
if ipc_sender
.send(MessagePortMsg::CompletePendingTransfer(
port_id,
- current_buffer,
+ PortTransferInfo {
+ port_message_queue: current_buffer,
+ disentangled: entry.entangled_with.is_none(),
+ },
))
.is_err()
{
@@ -2206,18 +2236,14 @@ where
Some(entry) => entry,
};
let new_info = match entry.state {
- TransferState::EntangledRemoved => {
- // If the entangled port has been removed while this one was in-transfer,
- // remove it now.
- if let Some(ipc_sender) = self.message_port_routers.get(&router_id) {
- let _ = ipc_sender.send(MessagePortMsg::RemoveMessagePort(port_id));
- } else {
- warn!("No message-port sender for {:?}", router_id);
- }
- continue;
- },
TransferState::TransferInProgress(buffer) => {
- response.insert(port_id, buffer);
+ response.insert(
+ port_id,
+ PortTransferInfo {
+ port_message_queue: buffer,
+ disentangled: entry.entangled_with.is_none(),
+ },
+ );
// If the port was in transfer, and a global is requesting completion,
// we note the start of the completion.
@@ -2296,10 +2322,6 @@ where
TransferState::TransferInProgress(queue) => queue.push_back(task),
TransferState::CompletionFailed(queue) => queue.push_back(task),
TransferState::CompletionRequested(_, queue) => queue.push_back(task),
- TransferState::EntangledRemoved => warn!(
- "Messageport received a message, but entangled has alread been removed {:?}",
- port_id
- ),
}
}
@@ -2365,59 +2387,6 @@ where
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
- fn handle_remove_messageport(&mut self, port_id: MessagePortId) {
- let entangled = match self.message_ports.remove(&port_id) {
- Some(info) => info.entangled_with,
- None => {
- return warn!(
- "Constellation asked to remove unknown messageport {:?}",
- port_id
- );
- },
- };
- let entangled_id = match entangled {
- Some(id) => id,
- None => return,
- };
- let info = match self.message_ports.get_mut(&entangled_id) {
- Some(info) => info,
- None => {
- return warn!(
- "Constellation asked to remove unknown entangled messageport {:?}",
- entangled_id
- );
- },
- };
- let router_id = match info.state {
- TransferState::EntangledRemoved => {
- return warn!(
- "Constellation asked to remove entangled messageport by a port that was already removed {:?}",
- port_id
- );
- },
- TransferState::TransferInProgress(_) |
- TransferState::CompletionInProgress(_) |
- TransferState::CompletionFailed(_) |
- TransferState::CompletionRequested(_, _) => {
- // Note: since the port is in-transer, we don't have a router to send it a message
- // to let it know that its entangled port has been removed.
- // Hence we mark it so that it will be messaged and removed once the transfer completes.
- info.state = TransferState::EntangledRemoved;
- return;
- },
- TransferState::Managed(router_id) => router_id,
- };
- if let Some(sender) = self.message_port_routers.get(&router_id) {
- let _ = sender.send(MessagePortMsg::RemoveMessagePort(entangled_id));
- } else {
- warn!("No message-port sender for {:?}", router_id);
- }
- }
-
- #[cfg_attr(
- feature = "tracing",
- tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
- )]
fn handle_entangle_messageports(&mut self, port1: MessagePortId, port2: MessagePortId) {
if let Some(info) = self.message_ports.get_mut(&port1) {
info.entangled_with = Some(port2);
@@ -2437,6 +2406,57 @@ where
}
}
+ #[cfg_attr(
+ feature = "tracing",
+ tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
+ )]
+ /// <https://html.spec.whatwg.org/multipage/#disentangle>
+ fn handle_disentangle_messageports(
+ &mut self,
+ port1: MessagePortId,
+ port2: Option<MessagePortId>,
+ ) {
+ // Disentangle initiatorPort and otherPort,
+ // so that they are no longer entangled or associated with each other.
+ // Note: If `port2` is some, then this is the first message
+ // and `port1` is the initiatorPort, `port2` is the otherPort.
+ // We can immediately remove the initiator.
+ let _ = self.message_ports.remove(&port1);
+
+ // Note: the none case is when otherPort sent this message
+ // in response to completing its own local disentanglement.
+ let Some(port2) = port2 else {
+ return;
+ };
+
+ // Start disentanglement of the other port.
+ if let Some(info) = self.message_ports.get_mut(&port2) {
+ info.entangled_with = None;
+ match &mut info.state {
+ TransferState::Managed(router_id) |
+ TransferState::CompletionInProgress(router_id) => {
+ // We try to disentangle the other port now,
+ // and if it has been transfered out by the time the message is received,
+ // it will be ignored,
+ // and disentanglement will be completed as part of the transfer.
+ if let Some(ipc_sender) = self.message_port_routers.get(router_id) {
+ let _ = ipc_sender.send(MessagePortMsg::CompleteDisentanglement(port2));
+ } else {
+ warn!("No message-port sender for {:?}", router_id);
+ }
+ },
+ _ => {
+ // Note: the port is in transfer, disentanglement will complete along with it.
+ },
+ }
+ } else {
+ warn!(
+ "Constellation asked to disentangle unknown messageport: {:?}",
+ port2
+ );
+ }
+ }
+
/// <https://w3c.github.io/ServiceWorker/#schedule-job-algorithm>
/// and
/// <https://w3c.github.io/ServiceWorker/#dfn-job-queue>
@@ -4070,6 +4090,7 @@ where
}
new_pipeline.set_throttled(false);
+ self.notify_focus_state(new_pipeline_id);
}
self.update_activity(old_pipeline_id);
@@ -4275,66 +4296,231 @@ where
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
- fn handle_focus_msg(&mut self, pipeline_id: PipelineId) {
- let (browsing_context_id, webview_id) = match self.pipelines.get(&pipeline_id) {
- Some(pipeline) => (pipeline.browsing_context_id, pipeline.webview_id),
+ fn handle_focus_msg(
+ &mut self,
+ pipeline_id: PipelineId,
+ focused_child_browsing_context_id: Option<BrowsingContextId>,
+ sequence: FocusSequenceNumber,
+ ) {
+ let (browsing_context_id, webview_id) = match self.pipelines.get_mut(&pipeline_id) {
+ Some(pipeline) => {
+ pipeline.focus_sequence = sequence;
+ (pipeline.browsing_context_id, pipeline.webview_id)
+ },
None => return warn!("{}: Focus parent after closure", pipeline_id),
};
+ // Ignore if the pipeline isn't fully active.
+ if self.get_activity(pipeline_id) != DocumentActivity::FullyActive {
+ debug!(
+ "Ignoring the focus request because pipeline {} is not \
+ fully active",
+ pipeline_id
+ );
+ return;
+ }
+
// Focus the top-level browsing context.
self.webviews.focus(webview_id);
self.embedder_proxy
.send(EmbedderMsg::WebViewFocused(webview_id));
- // Update the webview’s focused browsing context.
- match self.webviews.get_mut(webview_id) {
- Some(webview) => {
- webview.focused_browsing_context_id = browsing_context_id;
- },
- None => {
- return warn!(
- "{}: Browsing context for focus msg does not exist",
- webview_id
- );
- },
+ // If a container with a non-null nested browsing context is focused,
+ // the nested browsing context's active document becomes the focused
+ // area of the top-level browsing context instead.
+ let focused_browsing_context_id =
+ focused_child_browsing_context_id.unwrap_or(browsing_context_id);
+
+ // Send focus messages to the affected pipelines, except
+ // `pipeline_id`, which has already its local focus state
+ // updated.
+ self.focus_browsing_context(Some(pipeline_id), focused_browsing_context_id);
+ }
+
+ fn handle_focus_remote_document_msg(&mut self, focused_browsing_context_id: BrowsingContextId) {
+ let pipeline_id = match self.browsing_contexts.get(&focused_browsing_context_id) {
+ Some(browsing_context) => browsing_context.pipeline_id,
+ None => return warn!("Browsing context {} not found", focused_browsing_context_id),
};
- // Focus parent iframes recursively
- self.focus_parent_pipeline(browsing_context_id);
+ // Ignore if its active document isn't fully active.
+ if self.get_activity(pipeline_id) != DocumentActivity::FullyActive {
+ debug!(
+ "Ignoring the remote focus request because pipeline {} of \
+ browsing context {} is not fully active",
+ pipeline_id, focused_browsing_context_id,
+ );
+ return;
+ }
+
+ self.focus_browsing_context(None, focused_browsing_context_id);
}
+ /// Perform [the focusing steps][1] for the active document of
+ /// `focused_browsing_context_id`.
+ ///
+ /// If `initiator_pipeline_id` is specified, this method avoids sending
+ /// a message to `initiator_pipeline_id`, assuming its local focus state has
+ /// already been updated. This is necessary for performing the focusing
+ /// steps for an object that is not the document itself but something that
+ /// belongs to the document.
+ ///
+ /// [1]: https://html.spec.whatwg.org/multipage/#focusing-steps
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
- fn focus_parent_pipeline(&mut self, browsing_context_id: BrowsingContextId) {
- let parent_pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
- Some(ctx) => ctx.parent_pipeline_id,
- None => {
- return warn!("{}: Focus parent after closure", browsing_context_id);
- },
+ fn focus_browsing_context(
+ &mut self,
+ initiator_pipeline_id: Option<PipelineId>,
+ focused_browsing_context_id: BrowsingContextId,
+ ) {
+ let webview_id = match self.browsing_contexts.get(&focused_browsing_context_id) {
+ Some(browsing_context) => browsing_context.top_level_id,
+ None => return warn!("Browsing context {} not found", focused_browsing_context_id),
};
- let parent_pipeline_id = match parent_pipeline_id {
- Some(parent_id) => parent_id,
+
+ // Update the webview’s focused browsing context.
+ let old_focused_browsing_context_id = match self.webviews.get_mut(webview_id) {
+ Some(browser) => replace(
+ &mut browser.focused_browsing_context_id,
+ focused_browsing_context_id,
+ ),
None => {
- return debug!("{}: Focus has no parent", browsing_context_id);
+ return warn!(
+ "{}: Browsing context for focus msg does not exist",
+ webview_id
+ );
},
};
- // Send a message to the parent of the provided browsing context (if it
- // exists) telling it to mark the iframe element as focused.
- let msg = ScriptThreadMessage::FocusIFrame(parent_pipeline_id, browsing_context_id);
- let (result, parent_browsing_context_id) = match self.pipelines.get(&parent_pipeline_id) {
- Some(pipeline) => {
- let result = pipeline.event_loop.send(msg);
- (result, pipeline.browsing_context_id)
+ // The following part is similar to [the focus update steps][1] except
+ // that only `Document`s in the given focus chains are considered. It's
+ // ultimately up to the script threads to fire focus events at the
+ // affected objects.
+ //
+ // [1]: https://html.spec.whatwg.org/multipage/#focus-update-steps
+ let mut old_focus_chain_pipelines: Vec<&Pipeline> = self
+ .ancestor_or_self_pipelines_of_browsing_context_iter(old_focused_browsing_context_id)
+ .collect();
+ let mut new_focus_chain_pipelines: Vec<&Pipeline> = self
+ .ancestor_or_self_pipelines_of_browsing_context_iter(focused_browsing_context_id)
+ .collect();
+
+ debug!(
+ "old_focus_chain_pipelines = {:?}",
+ old_focus_chain_pipelines
+ .iter()
+ .map(|p| p.id.to_string())
+ .collect::<Vec<_>>()
+ );
+ debug!(
+ "new_focus_chain_pipelines = {:?}",
+ new_focus_chain_pipelines
+ .iter()
+ .map(|p| p.id.to_string())
+ .collect::<Vec<_>>()
+ );
+
+ // At least the last entries should match. Otherwise something is wrong,
+ // and we don't want to proceed and crash the top-level pipeline by
+ // sending an impossible `Unfocus` message to it.
+ match (
+ &old_focus_chain_pipelines[..],
+ &new_focus_chain_pipelines[..],
+ ) {
+ ([.., p1], [.., p2]) if p1.id == p2.id => {},
+ _ => {
+ warn!("Aborting the focus operation - focus chain sanity check failed");
+ return;
},
- None => return warn!("{}: Focus after closure", parent_pipeline_id),
- };
- if let Err(e) = result {
- self.handle_send_error(parent_pipeline_id, e);
}
- self.focus_parent_pipeline(parent_browsing_context_id);
+
+ // > If the last entry in `old chain` and the last entry in `new chain`
+ // > are the same, pop the last entry from `old chain` and the last
+ // > entry from `new chain` and redo this step.
+ let mut first_common_pipeline_in_chain = None;
+ while let ([.., p1], [.., p2]) = (
+ &old_focus_chain_pipelines[..],
+ &new_focus_chain_pipelines[..],
+ ) {
+ if p1.id != p2.id {
+ break;
+ }
+ old_focus_chain_pipelines.pop();
+ first_common_pipeline_in_chain = new_focus_chain_pipelines.pop();
+ }
+
+ let mut send_errors = Vec::new();
+
+ // > For each entry `entry` in `old chain`, in order, run these
+ // > substeps: [...]
+ for &pipeline in old_focus_chain_pipelines.iter() {
+ if Some(pipeline.id) != initiator_pipeline_id {
+ let msg = ScriptThreadMessage::Unfocus(pipeline.id, pipeline.focus_sequence);
+ trace!("Sending {:?} to {}", msg, pipeline.id);
+ if let Err(e) = pipeline.event_loop.send(msg) {
+ send_errors.push((pipeline.id, e));
+ }
+ } else {
+ trace!(
+ "Not notifying {} - it's the initiator of this focus operation",
+ pipeline.id
+ );
+ }
+ }
+
+ // > For each entry entry in `new chain`, in reverse order, run these
+ // > substeps: [...]
+ let mut child_browsing_context_id = None;
+ for &pipeline in new_focus_chain_pipelines.iter().rev() {
+ // Don't send a message to the browsing context that initiated this
+ // focus operation. It already knows that it has gotten focus.
+ if Some(pipeline.id) != initiator_pipeline_id {
+ let msg = if let Some(child_browsing_context_id) = child_browsing_context_id {
+ // Focus the container element of `child_browsing_context_id`.
+ ScriptThreadMessage::FocusIFrame(
+ pipeline.id,
+ child_browsing_context_id,
+ pipeline.focus_sequence,
+ )
+ } else {
+ // Focus the document.
+ ScriptThreadMessage::FocusDocument(pipeline.id, pipeline.focus_sequence)
+ };
+ trace!("Sending {:?} to {}", msg, pipeline.id);
+ if let Err(e) = pipeline.event_loop.send(msg) {
+ send_errors.push((pipeline.id, e));
+ }
+ } else {
+ trace!(
+ "Not notifying {} - it's the initiator of this focus operation",
+ pipeline.id
+ );
+ }
+ child_browsing_context_id = Some(pipeline.browsing_context_id);
+ }
+
+ if let (Some(pipeline), Some(child_browsing_context_id)) =
+ (first_common_pipeline_in_chain, child_browsing_context_id)
+ {
+ if Some(pipeline.id) != initiator_pipeline_id {
+ // Focus the container element of `child_browsing_context_id`.
+ let msg = ScriptThreadMessage::FocusIFrame(
+ pipeline.id,
+ child_browsing_context_id,
+ pipeline.focus_sequence,
+ );
+ trace!("Sending {:?} to {}", msg, pipeline.id);
+ if let Err(e) = pipeline.event_loop.send(msg) {
+ send_errors.push((pipeline.id, e));
+ }
+ }
+ }
+
+ for (pipeline_id, e) in send_errors {
+ self.handle_send_error(pipeline_id, e);
+ }
}
#[cfg_attr(
@@ -4929,10 +5115,42 @@ where
self.trim_history(top_level_id);
}
+ self.notify_focus_state(change.new_pipeline_id);
+
self.notify_history_changed(change.webview_id);
self.update_webview_in_compositor(change.webview_id);
}
+ /// Update the focus state of the specified pipeline that recently became
+ /// active (thus doesn't have a focused container element) and may have
+ /// out-dated information.
+ fn notify_focus_state(&mut self, pipeline_id: PipelineId) {
+ let pipeline = match self.pipelines.get(&pipeline_id) {
+ Some(pipeline) => pipeline,
+ None => return warn!("Pipeline {} is closed", pipeline_id),
+ };
+
+ let is_focused = match self.webviews.get(pipeline.webview_id) {
+ Some(webview) => webview.focused_browsing_context_id == pipeline.browsing_context_id,
+ None => {
+ return warn!(
+ "Pipeline {}'s top-level browsing context {} is closed",
+ pipeline_id, pipeline.webview_id
+ );
+ },
+ };
+
+ // If the browsing context is focused, focus the document
+ let msg = if is_focused {
+ ScriptThreadMessage::FocusDocument(pipeline_id, pipeline.focus_sequence)
+ } else {
+ ScriptThreadMessage::Unfocus(pipeline_id, pipeline.focus_sequence)
+ };
+ if let Err(e) = pipeline.event_loop.send(msg) {
+ self.handle_send_error(pipeline_id, e);
+ }
+ }
+
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
@@ -5382,7 +5600,29 @@ where
None => {
warn!("{parent_pipeline_id}: Child closed after parent");
},
- Some(parent_pipeline) => parent_pipeline.remove_child(browsing_context_id),
+ Some(parent_pipeline) => {
+ parent_pipeline.remove_child(browsing_context_id);
+
+ // If `browsing_context_id` has focus, focus the parent
+ // browsing context
+ if let Some(webview) = self.webviews.get_mut(browsing_context.top_level_id) {
+ if webview.focused_browsing_context_id == browsing_context_id {
+ trace!(
+ "About-to-be-closed browsing context {} is currently focused, so \
+ focusing its parent {}",
+ browsing_context_id, parent_pipeline.browsing_context_id
+ );
+ webview.focused_browsing_context_id =
+ parent_pipeline.browsing_context_id;
+ }
+ } else {
+ warn!(
+ "Browsing context {} contains a reference to \
+ a non-existent top-level browsing context {}",
+ browsing_context_id, browsing_context.top_level_id
+ );
+ }
+ },
};
}
debug!("{}: Closed", browsing_context_id);
diff --git a/components/constellation/pipeline.rs b/components/constellation/pipeline.rs
index 2e139578ffe..556ef9bd60f 100644
--- a/components/constellation/pipeline.rs
+++ b/components/constellation/pipeline.rs
@@ -25,7 +25,7 @@ use constellation_traits::{LoadData, SWManagerMsg, ScriptToConstellationChan};
use crossbeam_channel::{Sender, unbounded};
use devtools_traits::{DevtoolsControlMsg, ScriptToDevtoolsControlMsg};
use embedder_traits::user_content_manager::UserContentManager;
-use embedder_traits::{AnimationState, ViewportDetails};
+use embedder_traits::{AnimationState, FocusSequenceNumber, ViewportDetails};
use fonts::{SystemFontServiceProxy, SystemFontServiceProxySender};
use ipc_channel::Error;
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
@@ -102,6 +102,8 @@ pub struct Pipeline {
/// The last compositor [`Epoch`] that was laid out in this pipeline if "exit after load" is
/// enabled.
pub layout_epoch: Epoch,
+
+ pub focus_sequence: FocusSequenceNumber,
}
/// Initial setup data needed to construct a pipeline.
@@ -370,6 +372,7 @@ impl Pipeline {
completely_loaded: false,
title: String::new(),
layout_epoch: Epoch(0),
+ focus_sequence: FocusSequenceNumber::default(),
};
pipeline.set_throttled(throttled);
diff --git a/components/constellation/tracing.rs b/components/constellation/tracing.rs
index a939bbafc48..eff7f755c6b 100644
--- a/components/constellation/tracing.rs
+++ b/components/constellation/tracing.rs
@@ -123,8 +123,8 @@ mod from_script {
Self::RemoveMessagePortRouter(..) => target!("RemoveMessagePortRouter"),
Self::RerouteMessagePort(..) => target!("RerouteMessagePort"),
Self::MessagePortShipped(..) => target!("MessagePortShipped"),
- Self::RemoveMessagePort(..) => target!("RemoveMessagePort"),
Self::EntanglePorts(..) => target!("EntanglePorts"),
+ Self::DisentanglePorts(..) => target!("DisentanglePorts"),
Self::NewBroadcastChannelRouter(..) => target!("NewBroadcastChannelRouter"),
Self::RemoveBroadcastChannelRouter(..) => target!("RemoveBroadcastChannelRouter"),
Self::NewBroadcastChannelNameInRouter(..) => {
@@ -138,7 +138,8 @@ mod from_script {
Self::BroadcastStorageEvent(..) => target!("BroadcastStorageEvent"),
Self::ChangeRunningAnimationsState(..) => target!("ChangeRunningAnimationsState"),
Self::CreateCanvasPaintThread(..) => target!("CreateCanvasPaintThread"),
- Self::Focus => target!("Focus"),
+ Self::Focus(..) => target!("Focus"),
+ Self::FocusRemoteDocument(..) => target!("FocusRemoteDocument"),
Self::GetTopForBrowsingContext(..) => target!("GetTopForBrowsingContext"),
Self::GetBrowsingContextInfo(..) => target!("GetBrowsingContextInfo"),
Self::GetChildBrowsingContextId(..) => target!("GetChildBrowsingContextId"),