aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
Diffstat (limited to 'components')
-rw-r--r--components/canvas/Cargo.toml13
-rw-r--r--components/canvas/canvas_data.rs9
-rw-r--r--components/canvas/lib.rs7
-rw-r--r--components/constellation/constellation.rs524
-rw-r--r--components/constellation/pipeline.rs5
-rw-r--r--components/constellation/tracing.rs5
-rw-r--r--components/devtools/actors/watcher.rs69
-rw-r--r--components/devtools/actors/worker.rs29
-rw-r--r--components/devtools/lib.rs71
-rw-r--r--components/layout/display_list/mod.rs9
-rw-r--r--components/layout/display_list/stacking_context.rs2
-rw-r--r--components/layout/flexbox/layout.rs9
-rw-r--r--components/layout/flow/float.rs3
-rw-r--r--components/layout/flow/inline/line.rs3
-rw-r--r--components/layout/flow/inline/mod.rs3
-rw-r--r--components/layout/flow/mod.rs4
-rw-r--r--components/layout/flow/root.rs2
-rw-r--r--components/layout/fragment_tree/box_fragment.rs106
-rw-r--r--components/layout/fragment_tree/fragment.rs17
-rw-r--r--components/layout/fragment_tree/fragment_tree.rs21
-rw-r--r--components/layout/fragment_tree/positioning_fragment.rs2
-rw-r--r--components/layout/positioned.rs36
-rw-r--r--components/layout/style_ext.rs36
-rw-r--r--components/layout/table/layout.rs6
-rw-r--r--components/layout/taffy/layout.rs5
-rw-r--r--components/net/async_runtime.rs40
-rw-r--r--components/net/connector.rs2
-rw-r--r--components/net/http_loader.rs4
-rw-r--r--components/net/resource_thread.rs2
-rw-r--r--components/net/websocket_loader.rs33
-rw-r--r--components/script/canvas_state.rs87
-rw-r--r--components/script/dom/canvasrenderingcontext2d.rs36
-rw-r--r--components/script/dom/dissimilaroriginwindow.rs7
-rw-r--r--components/script/dom/document.rs445
-rw-r--r--components/script/dom/globalscope.rs199
-rw-r--r--components/script/dom/htmldetailselement.rs4
-rw-r--r--components/script/dom/htmlelement.rs9
-rw-r--r--components/script/dom/htmlformelement.rs10
-rw-r--r--components/script/dom/htmlscriptelement.rs2
-rw-r--r--components/script/dom/messageport.rs38
-rw-r--r--components/script/dom/node.rs50
-rw-r--r--components/script/dom/offscreencanvasrenderingcontext2d.rs2
-rw-r--r--components/script/dom/readablestream.rs8
-rw-r--r--components/script/dom/underlyingsourcecontainer.rs2
-rw-r--r--components/script/dom/window.rs26
-rw-r--r--components/script/dom/windowproxy.rs17
-rw-r--r--components/script/dom/worker.rs12
-rw-r--r--components/script/dom/writablestream.rs4
-rw-r--r--components/script/dom/writablestreamdefaultcontroller.rs10
-rw-r--r--components/script/messaging.rs2
-rw-r--r--components/script/script_thread.rs81
-rw-r--r--components/script_bindings/codegen/Bindings.conf2
-rw-r--r--components/script_bindings/webidls/MessagePort.webidl1
-rw-r--r--components/script_bindings/webidls/Window.webidl4
-rw-r--r--components/servo/Cargo.toml7
-rw-r--r--components/servo/lib.rs2
-rw-r--r--components/shared/constellation/from_script_message.rs36
-rw-r--r--components/shared/constellation/lib.rs19
-rw-r--r--components/shared/constellation/structured_data/transferable.rs7
-rw-r--r--components/shared/devtools/lib.rs1
-rw-r--r--components/shared/embedder/lib.rs75
-rw-r--r--components/shared/script/lib.rs14
-rw-r--r--components/webgl/Cargo.toml36
-rw-r--r--components/webgl/lib.rs13
-rw-r--r--components/webgl/webgl_limits.rs (renamed from components/canvas/webgl_limits.rs)0
-rw-r--r--components/webgl/webgl_mode/inprocess.rs (renamed from components/canvas/webgl_mode/inprocess.rs)0
-rw-r--r--components/webgl/webgl_mode/mod.rs (renamed from components/canvas/webgl_mode/mod.rs)0
-rw-r--r--components/webgl/webgl_thread.rs (renamed from components/canvas/webgl_thread.rs)0
-rw-r--r--components/webgl/webxr.rs (renamed from components/canvas/webxr.rs)0
69 files changed, 1758 insertions, 587 deletions
diff --git a/components/canvas/Cargo.toml b/components/canvas/Cargo.toml
index 7e7b00efe11..6084fc6e434 100644
--- a/components/canvas/Cargo.toml
+++ b/components/canvas/Cargo.toml
@@ -11,24 +11,15 @@ rust-version.workspace = true
name = "canvas"
path = "lib.rs"
-[features]
-webgl_backtrace = ["canvas_traits/webgl_backtrace"]
-webxr = ["dep:webxr", "dep:webxr-api"]
-
[dependencies]
app_units = { workspace = true }
-bitflags = { workspace = true }
-byteorder = { workspace = true }
canvas_traits = { workspace = true }
compositing_traits = { workspace = true }
crossbeam-channel = { workspace = true }
cssparser = { workspace = true }
euclid = { workspace = true }
-fnv = { workspace = true }
font-kit = "0.14"
fonts = { path = "../fonts" }
-glow = { workspace = true }
-half = "2"
ipc-channel = { workspace = true }
log = { workspace = true }
lyon_geom = "1.0.4"
@@ -40,9 +31,5 @@ raqote = "0.8.5"
servo_arc = { workspace = true }
snapshot = { workspace = true }
stylo = { workspace = true }
-surfman = { workspace = true }
unicode-script = { workspace = true }
-webrender = { workspace = true }
webrender_api = { workspace = true }
-webxr = { path = "../webxr", features = ["ipc"], optional = true }
-webxr-api = { workspace = true, features = ["ipc"], optional = true }
diff --git a/components/canvas/canvas_data.rs b/components/canvas/canvas_data.rs
index 99d6273813e..2667b7f6b44 100644
--- a/components/canvas/canvas_data.rs
+++ b/components/canvas/canvas_data.rs
@@ -28,6 +28,10 @@ use webrender_api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat, ImageKey
use crate::raqote_backend::Repetition;
+// Asserts on WR texture cache update for zero sized image with raw data.
+// https://github.com/servo/webrender/blob/main/webrender/src/texture_cache.rs#L1475
+const MIN_WR_IMAGE_SIZE: Size2D<u64> = Size2D::new(1, 1);
+
fn to_path(path: &[PathSegment], mut builder: Box<dyn GenericPathBuilder>) -> Path {
let mut build_ref = PathBuilderRef {
builder: &mut builder,
@@ -595,6 +599,7 @@ impl<'a> CanvasData<'a> {
compositor_api: CrossProcessCompositorApi,
font_context: Arc<FontContext>,
) -> CanvasData<'a> {
+ let size = size.max(MIN_WR_IMAGE_SIZE);
let backend = create_backend();
let draw_target = backend.create_drawtarget(size);
let image_key = compositor_api.generate_image_key().unwrap();
@@ -1402,7 +1407,9 @@ impl<'a> CanvasData<'a> {
}
pub fn recreate(&mut self, size: Option<Size2D<u64>>) {
- let size = size.unwrap_or_else(|| self.drawtarget.get_size().to_u64());
+ let size = size
+ .unwrap_or_else(|| self.drawtarget.get_size().to_u64())
+ .max(MIN_WR_IMAGE_SIZE);
self.drawtarget = self
.backend
.create_drawtarget(Size2D::new(size.width, size.height));
diff --git a/components/canvas/lib.rs b/components/canvas/lib.rs
index d2c62c1d8b6..86c291fdc87 100644
--- a/components/canvas/lib.rs
+++ b/components/canvas/lib.rs
@@ -6,12 +6,5 @@
mod raqote_backend;
-pub use webgl_mode::WebGLComm;
-
pub mod canvas_data;
pub mod canvas_paint_thread;
-mod webgl_limits;
-mod webgl_mode;
-pub mod webgl_thread;
-#[cfg(feature = "webxr")]
-mod webxr;
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"),
diff --git a/components/devtools/actors/watcher.rs b/components/devtools/actors/watcher.rs
index 6a84499b6dd..b0b2c755fd8 100644
--- a/components/devtools/actors/watcher.rs
+++ b/components/devtools/actors/watcher.rs
@@ -20,8 +20,10 @@ use serde_json::{Map, Value};
use self::network_parent::{NetworkParentActor, NetworkParentActorMsg};
use super::thread::ThreadActor;
+use super::worker::WorkerMsg;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actors::browsing_context::{BrowsingContextActor, BrowsingContextActorMsg};
+use crate::actors::root::RootActor;
use crate::actors::watcher::target_configuration::{
TargetConfigurationActor, TargetConfigurationActorMsg,
};
@@ -29,8 +31,8 @@ use crate::actors::watcher::thread_configuration::{
ThreadConfigurationActor, ThreadConfigurationActorMsg,
};
use crate::protocol::JsonPacketStream;
-use crate::resource::ResourceAvailable;
-use crate::{EmptyReplyMsg, StreamId};
+use crate::resource::{ResourceAvailable, ResourceAvailableReply};
+use crate::{EmptyReplyMsg, StreamId, WorkerActor};
pub mod network_parent;
pub mod target_configuration;
@@ -55,7 +57,7 @@ impl SessionContext {
supported_targets: HashMap::from([
("frame", true),
("process", false),
- ("worker", false),
+ ("worker", true),
("service_worker", false),
("shared_worker", false),
]),
@@ -103,11 +105,18 @@ pub enum SessionContextType {
}
#[derive(Serialize)]
+#[serde(untagged)]
+enum TargetActorMsg {
+ BrowsingContext(BrowsingContextActorMsg),
+ Worker(WorkerMsg),
+}
+
+#[derive(Serialize)]
struct WatchTargetsReply {
from: String,
#[serde(rename = "type")]
type_: String,
- target: BrowsingContextActorMsg,
+ target: TargetActorMsg,
}
#[derive(Serialize)]
@@ -212,16 +221,38 @@ impl Actor for WatcherActor {
_id: StreamId,
) -> Result<ActorMessageStatus, ()> {
let target = registry.find::<BrowsingContextActor>(&self.browsing_context_actor);
+ let root = registry.find::<RootActor>("root");
Ok(match msg_type {
"watchTargets" => {
- let msg = WatchTargetsReply {
- from: self.name(),
- type_: "target-available-form".into(),
- target: target.encodable(),
- };
- let _ = stream.write_json_packet(&msg);
+ // As per logs we either get targetType as "frame" or "worker"
+ let target_type = msg
+ .get("targetType")
+ .and_then(Value::as_str)
+ .unwrap_or("frame"); // default to "frame"
+
+ if target_type == "frame" {
+ let msg = WatchTargetsReply {
+ from: self.name(),
+ type_: "target-available-form".into(),
+ target: TargetActorMsg::BrowsingContext(target.encodable()),
+ };
+ let _ = stream.write_json_packet(&msg);
- target.frame_update(stream);
+ target.frame_update(stream);
+ } else if target_type == "worker" {
+ for worker_name in &root.workers {
+ let worker = registry.find::<WorkerActor>(worker_name);
+ let worker_msg = WatchTargetsReply {
+ from: self.name(),
+ type_: "target-available-form".into(),
+ target: TargetActorMsg::Worker(worker.encodable()),
+ };
+ let _ = stream.write_json_packet(&worker_msg);
+ }
+ } else {
+ warn!("Unexpected target_type: {}", target_type);
+ return Ok(ActorMessageStatus::Ignored);
+ }
// Messages that contain a `type` field are used to send event callbacks, but they
// don't count as a reply. Since every message needs to be responded, we send an
@@ -267,6 +298,22 @@ impl Actor for WatcherActor {
let thread_actor = registry.find::<ThreadActor>(&target.thread);
let sources = thread_actor.source_manager.sources();
target.resources_available(sources.iter().collect(), "source".into());
+
+ for worker_name in &root.workers {
+ let worker = registry.find::<WorkerActor>(worker_name);
+ let thread = registry.find::<ThreadActor>(&worker.thread);
+ let worker_sources = thread.source_manager.sources();
+
+ let msg = ResourceAvailableReply {
+ from: worker.name(),
+ type_: "resources-available-array".into(),
+ array: vec![(
+ "source".to_string(),
+ worker_sources.iter().cloned().collect(),
+ )],
+ };
+ let _ = stream.write_json_packet(&msg);
+ }
},
"console-message" | "error-message" => {},
_ => warn!("resource {} not handled yet", resource),
diff --git a/components/devtools/actors/worker.rs b/components/devtools/actors/worker.rs
index 42c9d9a9c28..68ff56fb3b2 100644
--- a/components/devtools/actors/worker.rs
+++ b/components/devtools/actors/worker.rs
@@ -17,7 +17,7 @@ use servo_url::ServoUrl;
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
-use crate::resource::ResourceAvailable;
+use crate::resource::{ResourceAvailable, ResourceAvailableReply};
#[derive(Clone, Copy)]
#[allow(dead_code)]
@@ -48,8 +48,10 @@ impl WorkerActor {
url: self.url.to_string(),
traits: WorkerTraits {
is_parent_intercept_enabled: false,
+ supports_top_level_target_flag: false,
},
type_: self.type_ as u32,
+ target_type: "worker".to_string(),
}
}
}
@@ -131,6 +133,28 @@ impl Actor for WorkerActor {
}
}
+impl WorkerActor {
+ pub(crate) fn resource_available<T: Serialize>(&self, resource: T, resource_type: String) {
+ self.resources_available(vec![resource], resource_type);
+ }
+
+ pub(crate) fn resources_available<T: Serialize>(
+ &self,
+ resources: Vec<T>,
+ resource_type: String,
+ ) {
+ let msg = ResourceAvailableReply::<T> {
+ from: self.name(),
+ type_: "resources-available-array".into(),
+ array: vec![(resource_type, resources)],
+ };
+
+ for stream in self.streams.borrow_mut().values_mut() {
+ let _ = stream.write_json_packet(&msg);
+ }
+ }
+}
+
#[derive(Serialize)]
struct DetachedReply {
from: String,
@@ -160,6 +184,7 @@ struct ConnectReply {
#[serde(rename_all = "camelCase")]
struct WorkerTraits {
is_parent_intercept_enabled: bool,
+ supports_top_level_target_flag: bool,
}
#[derive(Serialize)]
@@ -173,4 +198,6 @@ pub(crate) struct WorkerMsg {
traits: WorkerTraits,
#[serde(rename = "type")]
type_: u32,
+ #[serde(rename = "targetType")]
+ target_type: String,
}
diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs
index 4d1e0222177..5fb9485e9d3 100644
--- a/components/devtools/lib.rs
+++ b/components/devtools/lib.rs
@@ -510,35 +510,54 @@ impl DevtoolsInstance {
fn handle_script_source_info(&mut self, pipeline_id: PipelineId, source_info: SourceInfo) {
let mut actors = self.actors.lock().unwrap();
- let browsing_context_id = match self.pipelines.get(&pipeline_id) {
- Some(id) => id,
- None => return,
- };
+ if let Some(worker_id) = source_info.worker_id {
+ let Some(worker_actor_name) = self.actor_workers.get(&worker_id) else {
+ return;
+ };
- let actor_name = match self.browsing_contexts.get(browsing_context_id) {
- Some(name) => name,
- None => return,
- };
+ let thread_actor_name = actors.find::<WorkerActor>(worker_actor_name).thread.clone();
- let thread_actor_name = actors
- .find::<BrowsingContextActor>(actor_name)
- .thread
- .clone();
-
- let thread_actor = actors.find_mut::<ThreadActor>(&thread_actor_name);
- thread_actor
- .source_manager
- .add_source(source_info.url.clone());
-
- let source = SourceData {
- actor: thread_actor_name.clone(),
- url: source_info.url.to_string(),
- is_black_boxed: false,
- };
+ let thread_actor = actors.find_mut::<ThreadActor>(&thread_actor_name);
+ thread_actor
+ .source_manager
+ .add_source(source_info.url.clone());
+
+ let source = SourceData {
+ actor: thread_actor_name.clone(),
+ url: source_info.url.to_string(),
+ is_black_boxed: false,
+ };
- // Notify browsing context about the new source
- let browsing_context = actors.find::<BrowsingContextActor>(actor_name);
- browsing_context.resource_available(source, "source".into());
+ let worker_actor = actors.find::<WorkerActor>(worker_actor_name);
+ worker_actor.resource_available(source, "source".into());
+ } else {
+ let Some(browsing_context_id) = self.pipelines.get(&pipeline_id) else {
+ return;
+ };
+ let Some(actor_name) = self.browsing_contexts.get(browsing_context_id) else {
+ return;
+ };
+
+ let thread_actor_name = actors
+ .find::<BrowsingContextActor>(actor_name)
+ .thread
+ .clone();
+
+ let thread_actor = actors.find_mut::<ThreadActor>(&thread_actor_name);
+ thread_actor
+ .source_manager
+ .add_source(source_info.url.clone());
+
+ let source = SourceData {
+ actor: thread_actor_name.clone(),
+ url: source_info.url.to_string(),
+ is_black_boxed: false,
+ };
+
+ // Notify browsing context about the new source
+ let browsing_context = actors.find::<BrowsingContextActor>(actor_name);
+ browsing_context.resource_available(source, "source".into());
+ }
}
}
diff --git a/components/layout/display_list/mod.rs b/components/layout/display_list/mod.rs
index 912c69366bd..3908da69ce1 100644
--- a/components/layout/display_list/mod.rs
+++ b/components/layout/display_list/mod.rs
@@ -39,7 +39,7 @@ use webrender_api::{
use wr::units::LayoutVector2D;
use crate::context::{LayoutContext, ResolvedImage};
-use crate::display_list::conversions::ToWebRender;
+pub use crate::display_list::conversions::ToWebRender;
use crate::display_list::stacking_context::StackingContextSection;
use crate::fragment_tree::{
BackgroundMode, BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo, Tag,
@@ -711,7 +711,12 @@ impl<'a> BuilderForBoxFragment<'a> {
fn build(&mut self, builder: &mut DisplayListBuilder, section: StackingContextSection) {
if self.is_hit_test_for_scrollable_overflow {
- self.build_hit_test(builder, self.fragment.scrollable_overflow().to_webrender());
+ self.build_hit_test(
+ builder,
+ self.fragment
+ .reachable_scrollable_overflow_region()
+ .to_webrender(),
+ );
return;
}
diff --git a/components/layout/display_list/stacking_context.rs b/components/layout/display_list/stacking_context.rs
index 9809e428d6e..b044b713260 100644
--- a/components/layout/display_list/stacking_context.rs
+++ b/components/layout/display_list/stacking_context.rs
@@ -1477,7 +1477,7 @@ impl BoxFragment {
y: overflow.y.into(),
};
- let content_rect = self.scrollable_overflow().to_webrender();
+ let content_rect = self.reachable_scrollable_overflow_region().to_webrender();
let scroll_tree_node_id = display_list.define_scroll_frame(
parent_scroll_node_id,
diff --git a/components/layout/flexbox/layout.rs b/components/layout/flexbox/layout.rs
index 77069236787..a5540123681 100644
--- a/components/layout/flexbox/layout.rs
+++ b/components/layout/flexbox/layout.rs
@@ -1774,7 +1774,9 @@ impl FlexItem<'_> {
non_stretch_layout_result: Option<&mut FlexItemLayoutResult>,
) -> Option<FlexItemLayoutResult> {
let containing_block = flex_context.containing_block;
- let mut positioning_context = PositioningContext::new_for_style(self.box_.style())
+ let independent_formatting_context = &self.box_.independent_formatting_context;
+ let mut positioning_context = independent_formatting_context
+ .new_positioning_context()
.unwrap_or_else(|| {
PositioningContext::new_for_subtree(
flex_context
@@ -1783,7 +1785,6 @@ impl FlexItem<'_> {
)
});
- let independent_formatting_context = &self.box_.independent_formatting_context;
let item_writing_mode = independent_formatting_context.style().writing_mode;
let item_is_horizontal = item_writing_mode.is_horizontal();
let flex_axis = flex_context.config.flex_axis;
@@ -2616,7 +2617,9 @@ impl FlexItemBox {
cross_size_stretches_to_container_size: bool,
intrinsic_sizing_mode: IntrinsicSizingMode,
) -> Au {
- let mut positioning_context = PositioningContext::new_for_style(self.style())
+ let mut positioning_context = self
+ .independent_formatting_context
+ .new_positioning_context()
.unwrap_or_else(|| {
PositioningContext::new_for_subtree(
flex_context
diff --git a/components/layout/flow/float.rs b/components/layout/flow/float.rs
index 0570ce0d0f4..dbc50c07603 100644
--- a/components/layout/flow/float.rs
+++ b/components/layout/flow/float.rs
@@ -913,11 +913,10 @@ impl FloatBox {
positioning_context: &mut PositioningContext,
containing_block: &ContainingBlock,
) -> BoxFragment {
- let style = self.contents.style().clone();
positioning_context.layout_maybe_position_relative_fragment(
layout_context,
containing_block,
- &style,
+ &self.contents.base,
|positioning_context| {
self.contents
.layout_float_or_atomic_inline(
diff --git a/components/layout/flow/inline/line.rs b/components/layout/flow/inline/line.rs
index c42f32c9242..e65eaed2367 100644
--- a/components/layout/flow/inline/line.rs
+++ b/components/layout/flow/inline/line.rs
@@ -326,13 +326,12 @@ impl LineItemLayout<'_, '_> {
let inline_box = self.layout.ifc.inline_boxes.get(identifier);
let inline_box = &*(inline_box.borrow());
- let style = &inline_box.base.style;
let space_above_baseline = inline_box_state.calculate_space_above_baseline();
let block_start_offset =
self.calculate_inline_box_block_start(inline_box_state, space_above_baseline);
let positioning_context_or_start_offset_in_parent =
- match PositioningContext::new_for_style(style) {
+ match inline_box.base.new_positioning_context() {
Some(positioning_context) => Either::Left(positioning_context),
None => Either::Right(self.current_positioning_context_mut().len()),
};
diff --git a/components/layout/flow/inline/mod.rs b/components/layout/flow/inline/mod.rs
index dabb9773410..25fbaa324b1 100644
--- a/components/layout/flow/inline/mod.rs
+++ b/components/layout/flow/inline/mod.rs
@@ -2004,7 +2004,8 @@ impl IndependentFormattingContext {
bidi_level: Level,
) {
// We need to know the inline size of the atomic before deciding whether to do the line break.
- let mut child_positioning_context = PositioningContext::new_for_style(self.style())
+ let mut child_positioning_context = self
+ .new_positioning_context()
.unwrap_or_else(|| PositioningContext::new_for_subtree(true));
let IndependentFloatOrAtomicLayoutResult {
mut fragment,
diff --git a/components/layout/flow/mod.rs b/components/layout/flow/mod.rs
index d983e8592c4..983282dc389 100644
--- a/components/layout/flow/mod.rs
+++ b/components/layout/flow/mod.rs
@@ -779,7 +779,7 @@ impl BlockLevelBox {
ArcRefCell::new(positioning_context.layout_maybe_position_relative_fragment(
layout_context,
containing_block,
- &base.style,
+ base,
|positioning_context| {
layout_in_flow_non_replaced_block_level_same_formatting_context(
layout_context,
@@ -798,7 +798,7 @@ impl BlockLevelBox {
positioning_context.layout_maybe_position_relative_fragment(
layout_context,
containing_block,
- independent.style(),
+ &independent.base,
|positioning_context| {
independent.layout_in_flow_block_level(
layout_context,
diff --git a/components/layout/flow/root.rs b/components/layout/flow/root.rs
index 187726595f8..c6498eeed63 100644
--- a/components/layout/flow/root.rs
+++ b/components/layout/flow/root.rs
@@ -411,7 +411,7 @@ impl BoxTree {
let scrollable_overflow = root_fragments
.iter()
.fold(PhysicalRect::zero(), |acc, child| {
- let child_overflow = child.scrollable_overflow();
+ let child_overflow = child.scrollable_overflow_for_parent();
// https://drafts.csswg.org/css-overflow/#scrolling-direction
// We want to clip scrollable overflow on box-start and inline-start
diff --git a/components/layout/fragment_tree/box_fragment.rs b/components/layout/fragment_tree/box_fragment.rs
index e87826ec3ca..65ad1c4aa93 100644
--- a/components/layout/fragment_tree/box_fragment.rs
+++ b/components/layout/fragment_tree/box_fragment.rs
@@ -2,11 +2,12 @@
* 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 app_units::Au;
+use app_units::{Au, MAX_AU, MIN_AU};
use atomic_refcell::AtomicRefCell;
use base::print_tree::PrintTree;
use malloc_size_of_derive::MallocSizeOf;
use servo_arc::Arc as ServoArc;
+use servo_geometry::f32_rect_to_au_rect;
use style::Zero;
use style::computed_values::border_collapse::T as BorderCollapse;
use style::computed_values::overflow_x::T as ComputedOverflow;
@@ -16,6 +17,7 @@ use style::properties::ComputedValues;
use style::values::specified::box_::DisplayOutside;
use super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment};
+use crate::display_list::ToWebRender;
use crate::formatting_contexts::Baselines;
use crate::geom::{
AuOrAuto, LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides, ToLogical,
@@ -116,7 +118,7 @@ impl BoxFragment {
) -> BoxFragment {
let scrollable_overflow_from_children =
children.iter().fold(PhysicalRect::zero(), |acc, child| {
- acc.union(&child.scrollable_overflow())
+ acc.union(&child.scrollable_overflow_for_parent())
});
BoxFragment {
@@ -267,30 +269,96 @@ impl BoxFragment {
pub fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
let mut overflow = self.border_rect();
- if self.style.establishes_scroll_container(self.base.flags) {
- return overflow;
+ if !self.style.establishes_scroll_container(self.base.flags) {
+ // https://www.w3.org/TR/css-overflow-3/#scrollable
+ // Only include the scrollable overflow of a child box if it has overflow: visible.
+ let scrollable_overflow = self.scrollable_overflow();
+ let bottom_right = PhysicalPoint::new(
+ overflow.max_x().max(scrollable_overflow.max_x()),
+ overflow.max_y().max(scrollable_overflow.max_y()),
+ );
+
+ let overflow_style = self.style.effective_overflow(self.base.flags);
+ if overflow_style.y == ComputedOverflow::Visible {
+ overflow.origin.y = overflow.origin.y.min(scrollable_overflow.origin.y);
+ overflow.size.height = bottom_right.y - overflow.origin.y;
+ }
+
+ if overflow_style.x == ComputedOverflow::Visible {
+ overflow.origin.x = overflow.origin.x.min(scrollable_overflow.origin.x);
+ overflow.size.width = bottom_right.x - overflow.origin.x;
+ }
}
- // https://www.w3.org/TR/css-overflow-3/#scrollable
- // Only include the scrollable overflow of a child box if it has overflow: visible.
- let scrollable_overflow = self.scrollable_overflow();
- let bottom_right = PhysicalPoint::new(
- overflow.max_x().max(scrollable_overflow.max_x()),
- overflow.max_y().max(scrollable_overflow.max_y()),
- );
+ // <https://drafts.csswg.org/css-overflow-3/#scrollable-overflow-region>
+ // > ...accounting for transforms by projecting each box onto the plane of
+ // > the element that establishes its 3D rendering context. [CSS3-TRANSFORMS]
+ // Both boxes and its scrollable overflow (if it is included) should be transformed accordingly.
+ //
+ // TODO(stevennovaryo): We are supposed to handle perspective transform and 3d context, but it is yet to happen.
+ if self
+ .style
+ .has_effective_transform_or_perspective(self.base.flags)
+ {
+ if let Some(transform) =
+ self.calculate_transform_matrix(&self.border_rect().to_untyped())
+ {
+ if let Some(transformed_overflow_box) =
+ transform.outer_transformed_rect(&overflow.to_webrender().to_rect())
+ {
+ overflow =
+ f32_rect_to_au_rect(transformed_overflow_box.to_untyped()).cast_unit();
+ }
+ }
+ }
+
+ overflow
+ }
- let overflow_style = self.style.effective_overflow(self.base.flags);
- if overflow_style.y == ComputedOverflow::Visible {
- overflow.origin.y = overflow.origin.y.min(scrollable_overflow.origin.y);
- overflow.size.height = bottom_right.y - overflow.origin.y;
+ /// <https://drafts.csswg.org/css-overflow/#unreachable-scrollable-overflow-region>
+ /// > area beyond the scroll origin in either axis is considered the unreachable scrollable overflow region
+ ///
+ /// Return the clipped the scrollable overflow based on its scroll origin, determined by overflow direction.
+ /// For an element, the clip rect is the padding rect and for viewport, it is the initial containing block.
+ pub fn clip_unreachable_scrollable_overflow_region(
+ &self,
+ scrollable_overflow: PhysicalRect<Au>,
+ clipping_rect: PhysicalRect<Au>,
+ ) -> PhysicalRect<Au> {
+ let scrolling_direction = self.style.overflow_direction();
+ let mut scrollable_overflow_box = scrollable_overflow.to_box2d();
+ let mut clipping_box = clipping_rect.to_box2d();
+
+ if scrolling_direction.rightward {
+ clipping_box.max.x = MAX_AU;
+ } else {
+ clipping_box.min.x = MIN_AU;
}
- if overflow_style.x == ComputedOverflow::Visible {
- overflow.origin.x = overflow.origin.x.min(scrollable_overflow.origin.x);
- overflow.size.width = bottom_right.x - overflow.origin.x;
+ if scrolling_direction.downward {
+ clipping_box.max.y = MAX_AU;
+ } else {
+ clipping_box.min.y = MIN_AU;
}
- overflow
+ scrollable_overflow_box = scrollable_overflow_box.intersection_unchecked(&clipping_box);
+
+ match scrollable_overflow_box.is_negative() {
+ true => PhysicalRect::zero(),
+ false => scrollable_overflow_box.to_rect(),
+ }
+ }
+
+ /// <https://drafts.csswg.org/css-overflow/#unreachable-scrollable-overflow-region>
+ /// > area beyond the scroll origin in either axis is considered the unreachable scrollable overflow region
+ ///
+ /// Return the clipped the scrollable overflow based on its scroll origin, determined by overflow direction.
+ /// This will coincides with the scrollport if the fragment is a scroll container.
+ pub fn reachable_scrollable_overflow_region(&self) -> PhysicalRect<Au> {
+ self.clip_unreachable_scrollable_overflow_region(
+ self.scrollable_overflow(),
+ self.padding_rect(),
+ )
}
pub(crate) fn calculate_resolved_insets_if_positioned(&self) -> PhysicalSides<AuOrAuto> {
diff --git a/components/layout/fragment_tree/fragment.rs b/components/layout/fragment_tree/fragment.rs
index 7708b0893ee..1c5324fa1c4 100644
--- a/components/layout/fragment_tree/fragment.rs
+++ b/components/layout/fragment_tree/fragment.rs
@@ -170,17 +170,28 @@ impl Fragment {
}
}
- pub fn scrolling_area(&self) -> PhysicalRect<Au> {
+ pub fn unclipped_scrolling_area(&self) -> PhysicalRect<Au> {
match self {
Fragment::Box(fragment) | Fragment::Float(fragment) => {
let fragment = fragment.borrow();
fragment.offset_by_containing_block(&fragment.scrollable_overflow())
},
- _ => self.scrollable_overflow(),
+ _ => self.scrollable_overflow_for_parent(),
+ }
+ }
+
+ pub fn scrolling_area(&self) -> PhysicalRect<Au> {
+ match self {
+ Fragment::Box(fragment) | Fragment::Float(fragment) => {
+ let fragment = fragment.borrow();
+ fragment
+ .offset_by_containing_block(&fragment.reachable_scrollable_overflow_region())
+ },
+ _ => self.scrollable_overflow_for_parent(),
}
}
- pub fn scrollable_overflow(&self) -> PhysicalRect<Au> {
+ pub fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
match self {
Fragment::Box(fragment) | Fragment::Float(fragment) => {
fragment.borrow().scrollable_overflow_for_parent()
diff --git a/components/layout/fragment_tree/fragment_tree.rs b/components/layout/fragment_tree/fragment_tree.rs
index 3a082c99389..1499a50dacf 100644
--- a/components/layout/fragment_tree/fragment_tree.rs
+++ b/components/layout/fragment_tree/fragment_tree.rs
@@ -138,11 +138,26 @@ impl FragmentTree {
.find_map(|child| child.find(&info, 0, &mut process_func))
}
+ /// <https://drafts.csswg.org/cssom-view/#scrolling-area>
+ ///
+ /// Scrolling area for a viewport that is clipped according to overflow direction of root element.
pub fn get_scrolling_area_for_viewport(&self) -> PhysicalRect<Au> {
let mut scroll_area = self.initial_containing_block;
- for fragment in self.root_fragments.iter() {
- scroll_area = fragment.scrolling_area().union(&scroll_area);
+ if let Some(root_fragment) = self.root_fragments.first() {
+ for fragment in self.root_fragments.iter() {
+ scroll_area = fragment.unclipped_scrolling_area().union(&scroll_area);
+ }
+ match root_fragment {
+ Fragment::Box(fragment) | Fragment::Float(fragment) => fragment
+ .borrow()
+ .clip_unreachable_scrollable_overflow_region(
+ scroll_area,
+ self.initial_containing_block,
+ ),
+ _ => scroll_area,
+ }
+ } else {
+ scroll_area
}
- scroll_area
}
}
diff --git a/components/layout/fragment_tree/positioning_fragment.rs b/components/layout/fragment_tree/positioning_fragment.rs
index 1fe968eb484..0cf525a3479 100644
--- a/components/layout/fragment_tree/positioning_fragment.rs
+++ b/components/layout/fragment_tree/positioning_fragment.rs
@@ -56,7 +56,7 @@ impl PositioningFragment {
let scrollable_overflow = children.iter().fold(PhysicalRect::zero(), |acc, child| {
acc.union(
&child
- .scrollable_overflow()
+ .scrollable_overflow_for_parent()
.translate(content_origin.to_vector()),
)
});
diff --git a/components/layout/positioned.rs b/components/layout/positioned.rs
index 5f08e4e86c5..6bfe2af87ef 100644
--- a/components/layout/positioned.rs
+++ b/components/layout/positioned.rs
@@ -29,6 +29,7 @@ use crate::geom::{
PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, PhysicalVec, Size, Sizes, ToLogical,
ToLogicalWithContainingBlock,
};
+use crate::layout_box_base::LayoutBoxBase;
use crate::sizing::ContentSizes;
use crate::style_ext::{Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, DisplayInside};
use crate::{
@@ -103,6 +104,20 @@ impl AbsolutelyPositionedBox {
}
}
+impl IndependentFormattingContext {
+ #[inline]
+ pub(crate) fn new_positioning_context(&self) -> Option<PositioningContext> {
+ self.base.new_positioning_context()
+ }
+}
+
+impl LayoutBoxBase {
+ #[inline]
+ pub(crate) fn new_positioning_context(&self) -> Option<PositioningContext> {
+ PositioningContext::new_for_style(&self.style, &self.base_fragment_info.flags)
+ }
+}
+
impl PositioningContext {
pub(crate) fn new_for_containing_block_for_all_descendants() -> Self {
Self {
@@ -130,14 +145,10 @@ impl PositioningContext {
self.for_nearest_positioned_ancestor.is_some()
}
- pub(crate) fn new_for_style(style: &ComputedValues) -> Option<Self> {
- // NB: We never make PositioningContexts for replaced elements, which is why we always
- // pass false here.
- if style.establishes_containing_block_for_all_descendants(FragmentFlags::empty()) {
+ fn new_for_style(style: &ComputedValues, flags: &FragmentFlags) -> Option<Self> {
+ if style.establishes_containing_block_for_all_descendants(*flags) {
Some(Self::new_for_containing_block_for_all_descendants())
- } else if style
- .establishes_containing_block_for_absolute_descendants(FragmentFlags::empty())
- {
+ } else if style.establishes_containing_block_for_absolute_descendants(*flags) {
Some(Self {
for_nearest_positioned_ancestor: Some(Vec::new()),
for_nearest_containing_block_for_all_descendants: Vec::new(),
@@ -213,12 +224,12 @@ impl PositioningContext {
&mut self,
layout_context: &LayoutContext,
containing_block: &ContainingBlock,
- style: &ComputedValues,
+ base: &LayoutBoxBase,
fragment_layout_fn: impl FnOnce(&mut Self) -> BoxFragment,
) -> BoxFragment {
// Try to create a context, but if one isn't necessary, simply create the fragment
// using the given closure and the current `PositioningContext`.
- let mut new_context = match Self::new_for_style(style) {
+ let mut new_context = match base.new_positioning_context() {
Some(new_context) => new_context,
None => return fragment_layout_fn(self),
};
@@ -229,9 +240,8 @@ impl PositioningContext {
// If the new context has any hoisted boxes for the nearest containing block for
// pass them up the tree.
self.append(new_context);
-
- if style.clone_position() == Position::Relative {
- new_fragment.content_rect.origin += relative_adjustement(style, containing_block)
+ if base.style.clone_position() == Position::Relative {
+ new_fragment.content_rect.origin += relative_adjustement(&base.style, containing_block)
.to_physical_vector(containing_block.style.writing_mode)
}
@@ -586,7 +596,7 @@ impl HoistedAbsolutelyPositionedBox {
.sizes
}));
- let mut positioning_context = PositioningContext::new_for_style(&style).unwrap();
+ let mut positioning_context = context.new_positioning_context().unwrap();
let mut new_fragment = {
let content_size: LogicalVec2<Au>;
let fragments;
diff --git a/components/layout/style_ext.rs b/components/layout/style_ext.rs
index b157be914e0..023db6b07f1 100644
--- a/components/layout/style_ext.rs
+++ b/components/layout/style_ext.rs
@@ -12,7 +12,7 @@ use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode;
use style::computed_values::position::T as ComputedPosition;
use style::computed_values::transform_style::T as ComputedTransformStyle;
use style::computed_values::unicode_bidi::T as UnicodeBidi;
-use style::logical_geometry::{Direction as AxisDirection, WritingMode};
+use style::logical_geometry::{Direction as AxisDirection, PhysicalSide, WritingMode};
use style::properties::ComputedValues;
use style::properties::longhands::backface_visibility::computed_value::T as BackfaceVisiblity;
use style::properties::longhands::box_sizing::computed_value::T as BoxSizing;
@@ -280,6 +280,16 @@ impl Default for BorderStyleColor {
}
}
+/// <https://drafts.csswg.org/cssom-view/#overflow-directions>
+/// > A scrolling box of a viewport or element has two overflow directions,
+/// > which are the block-end and inline-end directions for that viewport or element.
+pub(crate) struct OverflowDirection {
+ /// Whether block-end or inline-end direction is [PhysicalSide::Right].
+ pub rightward: bool,
+ /// Whether block-end or inline-end direction is [PhysicalSide::Bottom].
+ pub downward: bool,
+}
+
pub(crate) trait ComputedValuesExt {
fn physical_box_offsets(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>>;
fn box_offsets(&self, writing_mode: WritingMode) -> LogicalSides<LengthPercentageOrAuto<'_>>;
@@ -354,6 +364,7 @@ pub(crate) trait ComputedValuesExt {
writing_mode: WritingMode,
) -> bool;
fn is_inline_box(&self, fragment_flags: FragmentFlags) -> bool;
+ fn overflow_direction(&self) -> OverflowDirection;
}
impl ComputedValuesExt for ComputedValues {
@@ -834,9 +845,9 @@ impl ComputedValuesExt for ComputedValues {
// > A value other than none for the filter property results in the creation of a containing
// > block for absolute and fixed positioned descendants unless the element it applies to is
// > a document root element in the current browsing context.
- // FIXME(#35391): Need to check if this is the root element.
- if !self.get_effects().filter.0.is_empty() ||
- will_change_bits.intersects(WillChangeBits::FIXPOS_CB_NON_SVG)
+ if !fragment_flags.contains(FragmentFlags::IS_ROOT_ELEMENT) &&
+ (!self.get_effects().filter.0.is_empty() ||
+ will_change_bits.intersects(WillChangeBits::FIXPOS_CB_NON_SVG))
{
return true;
}
@@ -980,6 +991,23 @@ impl ComputedValuesExt for ComputedValues {
};
has_percentage(box_offsets.block_start) || has_percentage(box_offsets.block_end)
}
+
+ // <https://drafts.csswg.org/cssom-view/#overflow-directions>
+ fn overflow_direction(&self) -> OverflowDirection {
+ let inline_end_direction = self.writing_mode.inline_end_physical_side();
+ let block_end_direction = self.writing_mode.block_end_physical_side();
+
+ let rightward = inline_end_direction == PhysicalSide::Right ||
+ block_end_direction == PhysicalSide::Right;
+ let downward = inline_end_direction == PhysicalSide::Bottom ||
+ block_end_direction == PhysicalSide::Bottom;
+
+ // TODO(stevennovaryo): We should consider the flex-container's CSS (e.g. flow-direction: column-reverse).
+ OverflowDirection {
+ rightward,
+ downward,
+ }
+ }
}
pub(crate) enum LayoutStyle<'a> {
diff --git a/components/layout/table/layout.rs b/components/layout/table/layout.rs
index 2261f7d165c..0cbe3e9ca76 100644
--- a/components/layout/table/layout.rs
+++ b/components/layout/table/layout.rs
@@ -1503,7 +1503,7 @@ impl<'a> TableLayout<'a> {
layout_context: &LayoutContext,
parent_positioning_context: &mut PositioningContext,
) -> BoxFragment {
- let mut positioning_context = PositioningContext::new_for_style(caption.context.style());
+ let mut positioning_context = caption.context.new_positioning_context();
let containing_block = &ContainingBlock {
size: ContainingBlockSize {
inline: self.table_width + self.pbm.padding_border_sums.inline,
@@ -2325,7 +2325,7 @@ impl<'a> RowFragmentLayout<'a> {
Self {
row: table_row,
rect,
- positioning_context: PositioningContext::new_for_style(&table_row.base.style),
+ positioning_context: table_row.base.new_positioning_context(),
containing_block,
fragments: Vec::new(),
}
@@ -2410,7 +2410,7 @@ impl RowGroupFragmentLayout {
let row_group = row_group.borrow();
(
dimensions.get_row_group_rect(&row_group),
- PositioningContext::new_for_style(&row_group.base.style),
+ row_group.base.new_positioning_context(),
)
};
Self {
diff --git a/components/layout/taffy/layout.rs b/components/layout/taffy/layout.rs
index a7581136bf2..3777c902053 100644
--- a/components/layout/taffy/layout.rs
+++ b/components/layout/taffy/layout.rs
@@ -251,8 +251,9 @@ impl taffy::LayoutPartialTree for TaffyContainerContext<'_> {
style,
};
let layout = {
- let mut child_positioning_context =
- PositioningContext::new_for_style(style).unwrap_or_else(|| {
+ let mut child_positioning_context = independent_context
+ .new_positioning_context()
+ .unwrap_or_else(|| {
PositioningContext::new_for_subtree(
self.positioning_context
.collects_for_nearest_positioned_ancestor(),
diff --git a/components/net/async_runtime.rs b/components/net/async_runtime.rs
index c99068b1076..909bdef8fb0 100644
--- a/components/net/async_runtime.rs
+++ b/components/net/async_runtime.rs
@@ -2,31 +2,27 @@
* 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::cmp::Ord;
+use std::sync::LazyLock;
use std::sync::atomic::{AtomicUsize, Ordering};
-use std::sync::{LazyLock, Mutex};
use std::thread;
use tokio::runtime::{Builder, Runtime};
-pub static HANDLE: LazyLock<Mutex<Option<Runtime>>> = LazyLock::new(|| {
- Mutex::new(Some(
- Builder::new_multi_thread()
- .thread_name_fn(|| {
- static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0);
- let id = ATOMIC_ID.fetch_add(1, Ordering::Relaxed);
- format!("tokio-runtime-{}", id)
- })
- .worker_threads(
- thread::available_parallelism()
- .map(|i| i.get())
- .unwrap_or(servo_config::pref!(threadpools_fallback_worker_num) as usize)
- .min(
- servo_config::pref!(threadpools_async_runtime_workers_max).max(1) as usize,
- ),
- )
- .enable_io()
- .enable_time()
- .build()
- .unwrap(),
- ))
+pub static HANDLE: LazyLock<Runtime> = LazyLock::new(|| {
+ Builder::new_multi_thread()
+ .thread_name_fn(|| {
+ static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0);
+ let id = ATOMIC_ID.fetch_add(1, Ordering::Relaxed);
+ format!("tokio-runtime-{}", id)
+ })
+ .worker_threads(
+ thread::available_parallelism()
+ .map(|i| i.get())
+ .unwrap_or(servo_config::pref!(threadpools_fallback_worker_num) as usize)
+ .min(servo_config::pref!(threadpools_async_runtime_workers_max).max(1) as usize),
+ )
+ .enable_io()
+ .enable_time()
+ .build()
+ .expect("Unable to build tokio-runtime runtime")
});
diff --git a/components/net/connector.rs b/components/net/connector.rs
index 12d0638d84d..e02ff8971e3 100644
--- a/components/net/connector.rs
+++ b/components/net/connector.rs
@@ -165,7 +165,7 @@ where
F: Future<Output = ()> + 'static + std::marker::Send,
{
fn execute(&self, fut: F) {
- HANDLE.lock().unwrap().as_ref().unwrap().spawn(fut);
+ HANDLE.spawn(fut);
}
}
diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs
index 35624bb8645..e0867b8d07f 100644
--- a/components/net/http_loader.rs
+++ b/components/net/http_loader.rs
@@ -493,7 +493,7 @@ impl BodySink {
match self {
BodySink::Chunked(sender) => {
let sender = sender.clone();
- HANDLE.lock().unwrap().as_mut().unwrap().spawn(async move {
+ HANDLE.spawn(async move {
let _ = sender.send(Ok(Frame::data(bytes.into()))).await;
});
},
@@ -2016,7 +2016,7 @@ async fn http_network_fetch(
let url1 = request.url();
let url2 = url1.clone();
- HANDLE.lock().unwrap().as_ref().unwrap().spawn(
+ HANDLE.spawn(
res.into_body()
.map_err(|e| {
warn!("Error streaming response body: {:?}", e);
diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs
index b6f885f29b7..5d1ede28c32 100644
--- a/components/net/resource_thread.rs
+++ b/components/net/resource_thread.rs
@@ -771,7 +771,7 @@ impl CoreResourceManager {
_ => (FileTokenCheck::NotRequired, None),
};
- HANDLE.lock().unwrap().as_ref().unwrap().spawn(async move {
+ HANDLE.spawn(async move {
// XXXManishearth: Check origin against pipeline id (also ensure that the mode is allowed)
// todo load context / mimesniff in fetch
// todo referrer policy?
diff --git a/components/net/websocket_loader.rs b/components/net/websocket_loader.rs
index 95f66558482..128436ac47c 100644
--- a/components/net/websocket_loader.rs
+++ b/components/net/websocket_loader.rs
@@ -418,24 +418,21 @@ fn connect(
tls_config.alpn_protocols = vec!["http/1.1".to_string().into()];
let resource_event_sender2 = resource_event_sender.clone();
- match HANDLE.lock().unwrap().as_mut() {
- Some(handle) => handle.spawn(
- start_websocket(
- http_state,
- req_url.clone(),
- resource_event_sender,
- protocols,
- client,
- tls_config,
- dom_action_receiver,
- )
- .map_err(move |e| {
- warn!("Failed to establish a WebSocket connection: {:?}", e);
- let _ = resource_event_sender2.send(WebSocketNetworkEvent::Fail);
- }),
- ),
- None => return Err("No runtime available".to_string()),
- };
+ HANDLE.spawn(
+ start_websocket(
+ http_state,
+ req_url.clone(),
+ resource_event_sender,
+ protocols,
+ client,
+ tls_config,
+ dom_action_receiver,
+ )
+ .map_err(move |e| {
+ warn!("Failed to establish a WebSocket connection: {:?}", e);
+ let _ = resource_event_sender2.send(WebSocketNetworkEvent::Fail);
+ }),
+ );
Ok(())
}
diff --git a/components/script/canvas_state.rs b/components/script/canvas_state.rs
index 408c94c124a..e9892818e92 100644
--- a/components/script/canvas_state.rs
+++ b/components/script/canvas_state.rs
@@ -152,6 +152,8 @@ pub(crate) struct CanvasState {
canvas_id: CanvasId,
#[no_trace]
image_key: ImageKey,
+ #[no_trace]
+ size: Cell<Size2D<u64>>,
state: DomRefCell<CanvasContextState>,
origin_clean: Cell<bool>,
#[ignore_malloc_size_of = "Arc"]
@@ -176,6 +178,7 @@ impl CanvasState {
profiled_ipc::channel(global.time_profiler_chan().clone()).unwrap();
let script_to_constellation_chan = global.script_to_constellation_chan();
debug!("Asking constellation to create new canvas thread.");
+ let size = adjust_canvas_size(size);
script_to_constellation_chan
.send(ScriptToConstellationMessage::CreateCanvasPaintThread(
size, sender,
@@ -194,6 +197,7 @@ impl CanvasState {
CanvasState {
ipc_renderer,
canvas_id,
+ size: Cell::new(size),
state: DomRefCell::new(CanvasContextState::new()),
origin_clean: Cell::new(true),
image_cache: global.image_cache(),
@@ -221,7 +225,15 @@ impl CanvasState {
self.canvas_id
}
+ pub(crate) fn is_paintable(&self) -> bool {
+ !self.size.get().is_empty()
+ }
+
pub(crate) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) {
+ if !self.is_paintable() {
+ return;
+ }
+
self.ipc_renderer
.send(CanvasMsg::Canvas2d(msg, self.get_canvas_id()))
.unwrap()
@@ -229,6 +241,10 @@ impl CanvasState {
/// Updates WR image and blocks on completion
pub(crate) fn update_rendering(&self) {
+ if !self.is_paintable() {
+ return;
+ }
+
let (sender, receiver) = ipc::channel().unwrap();
self.ipc_renderer
.send(CanvasMsg::Canvas2d(
@@ -239,16 +255,27 @@ impl CanvasState {
receiver.recv().unwrap();
}
- // https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions
+ /// <https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions>
pub(crate) fn set_bitmap_dimensions(&self, size: Size2D<u64>) {
self.reset_to_initial_state();
+
+ self.size.replace(adjust_canvas_size(size));
+
self.ipc_renderer
- .send(CanvasMsg::Recreate(Some(size), self.get_canvas_id()))
+ .send(CanvasMsg::Recreate(
+ Some(self.size.get()),
+ self.get_canvas_id(),
+ ))
.unwrap();
}
pub(crate) fn reset(&self) {
self.reset_to_initial_state();
+
+ if !self.is_paintable() {
+ return;
+ }
+
self.ipc_renderer
.send(CanvasMsg::Recreate(None, self.get_canvas_id()))
.unwrap();
@@ -347,7 +374,6 @@ impl CanvasState {
pub(crate) fn get_rect(&self, canvas_size: Size2D<u64>, rect: Rect<u64>) -> Vec<u8> {
assert!(self.origin_is_clean());
-
assert!(Rect::from_size(canvas_size).contains_rect(&rect));
let (sender, receiver) = ipc::channel().unwrap();
@@ -398,18 +424,22 @@ impl CanvasState {
dw: Option<f64>,
dh: Option<f64>,
) -> ErrorResult {
+ if !self.is_paintable() {
+ return Ok(());
+ }
+
let result = match image {
CanvasImageSource::HTMLCanvasElement(ref canvas) => {
- // https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument
- if !canvas.is_valid() {
+ // <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
+ if canvas.get_size().is_empty() {
return Err(Error::InvalidState);
}
self.draw_html_canvas_element(canvas, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh)
},
CanvasImageSource::OffscreenCanvas(ref canvas) => {
- // https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument
- if !canvas.is_valid() {
+ // <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
+ if canvas.get_size().is_empty() {
return Err(Error::InvalidState);
}
@@ -528,11 +558,6 @@ impl CanvasState {
dw: Option<f64>,
dh: Option<f64>,
) -> ErrorResult {
- // 1. Check the usability of the image argument
- if !canvas.is_valid() {
- return Err(Error::InvalidState);
- }
-
let canvas_size = canvas.get_size();
let dw = dw.unwrap_or(canvas_size.width as f64);
let dh = dh.unwrap_or(canvas_size.height as f64);
@@ -1403,13 +1428,13 @@ impl CanvasState {
},
};
- ImageData::new(
- global,
- size.width,
- size.height,
- Some(self.get_rect(canvas_size, read_rect)),
- can_gc,
- )
+ let data = if self.is_paintable() {
+ Some(self.get_rect(canvas_size, read_rect))
+ } else {
+ None
+ };
+
+ ImageData::new(global, size.width, size.height, data, can_gc)
}
// https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata
@@ -1445,6 +1470,10 @@ impl CanvasState {
dirty_width: i32,
dirty_height: i32,
) {
+ if !self.is_paintable() {
+ return;
+ }
+
// FIXME(nox): There are many arithmetic operations here that can
// overflow or underflow, this should probably be audited.
@@ -2013,3 +2042,23 @@ where
style.font_family.to_css_string()
)
}
+
+fn adjust_canvas_size(size: Size2D<u64>) -> Size2D<u64> {
+ // Firefox limits width/height to 32767 pixels and Chromium to 65535 pixels,
+ // but slows down dramatically before it reaches that limit.
+ // We limit by area instead, giving us larger maximum dimensions,
+ // in exchange for a smaller maximum canvas size.
+ const MAX_CANVAS_AREA: u64 = 32768 * 8192;
+ // Max width/height to 65535 in CSS pixels.
+ const MAX_CANVAS_SIZE: u64 = 65535;
+
+ if !size.is_empty() &&
+ size.greater_than(Size2D::new(MAX_CANVAS_SIZE, MAX_CANVAS_SIZE))
+ .none() &&
+ size.area() < MAX_CANVAS_AREA
+ {
+ size
+ } else {
+ Size2D::zero()
+ }
+}
diff --git a/components/script/dom/canvasrenderingcontext2d.rs b/components/script/dom/canvasrenderingcontext2d.rs
index 73052e6906e..38bd38ad511 100644
--- a/components/script/dom/canvasrenderingcontext2d.rs
+++ b/components/script/dom/canvasrenderingcontext2d.rs
@@ -4,7 +4,7 @@
use canvas_traits::canvas::{Canvas2dMsg, CanvasId, CanvasMsg, FromScriptMsg};
use dom_struct::dom_struct;
-use euclid::default::{Point2D, Rect, Size2D};
+use euclid::default::Size2D;
use profile_traits::ipc;
use script_bindings::inheritance::Castable;
use script_layout_interface::HTMLCanvasDataSource;
@@ -74,23 +74,12 @@ impl CanvasRenderingContext2D {
reflect_dom_object(boxed, global, can_gc)
}
- // https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions
- pub(crate) fn set_bitmap_dimensions(&self, size: Size2D<u32>) {
- self.reset_to_initial_state();
- self.canvas_state
- .get_ipc_renderer()
- .send(CanvasMsg::Recreate(
- Some(size.to_u64()),
- self.canvas_state.get_canvas_id(),
- ))
- .unwrap();
- }
-
// https://html.spec.whatwg.org/multipage/#reset-the-rendering-context-to-its-default-state
fn reset_to_initial_state(&self) {
self.canvas_state.reset_to_initial_state();
}
+ /// <https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions>
pub(crate) fn set_canvas_bitmap_dimensions(&self, size: Size2D<u64>) {
self.canvas_state.set_bitmap_dimensions(size);
}
@@ -106,20 +95,17 @@ impl CanvasRenderingContext2D {
pub(crate) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) {
self.canvas_state.send_canvas_2d_msg(msg)
}
-
- pub(crate) fn get_rect(&self, rect: Rect<u32>) -> Vec<u8> {
- let rect = Rect::new(
- Point2D::new(rect.origin.x as u64, rect.origin.y as u64),
- Size2D::new(rect.size.width as u64, rect.size.height as u64),
- );
- self.canvas_state.get_rect(self.canvas.size(), rect)
- }
}
impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, CanvasRenderingContext2D> {
fn canvas_data_source(self) -> HTMLCanvasDataSource {
let canvas_state = &self.unsafe_get().canvas_state;
- HTMLCanvasDataSource::Image(canvas_state.image_key())
+
+ if canvas_state.is_paintable() {
+ HTMLCanvasDataSource::Image(canvas_state.image_key())
+ } else {
+ HTMLCanvasDataSource::Empty
+ }
}
}
@@ -139,13 +125,11 @@ impl CanvasContext for CanvasRenderingContext2D {
}
fn resize(&self) {
- self.set_bitmap_dimensions(self.size().cast())
+ self.set_canvas_bitmap_dimensions(self.size().cast())
}
fn get_image_data(&self) -> Option<Snapshot> {
- let size = self.size();
-
- if size.is_empty() {
+ if !self.canvas_state.is_paintable() {
return None;
}
diff --git a/components/script/dom/dissimilaroriginwindow.rs b/components/script/dom/dissimilaroriginwindow.rs
index b7fbe0855fe..70c384db822 100644
--- a/components/script/dom/dissimilaroriginwindow.rs
+++ b/components/script/dom/dissimilaroriginwindow.rs
@@ -181,12 +181,13 @@ impl DissimilarOriginWindowMethods<crate::DomTypeHolder> for DissimilarOriginWin
// https://html.spec.whatwg.org/multipage/#dom-window-blur
fn Blur(&self) {
- // TODO: Implement x-origin blur
+ // > User agents are encouraged to ignore calls to this `blur()` method
+ // > entirely.
}
- // https://html.spec.whatwg.org/multipage/#dom-focus
+ // https://html.spec.whatwg.org/multipage/#dom-window-focus
fn Focus(&self) {
- // TODO: Implement x-origin focus
+ self.window_proxy().focus();
}
// https://html.spec.whatwg.org/multipage/#dom-location
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs
index 1e2a3747751..2baab15e1b8 100644
--- a/components/script/dom/document.rs
+++ b/components/script/dom/document.rs
@@ -30,8 +30,8 @@ use devtools_traits::ScriptToDevtoolsControlMsg;
use dom_struct::dom_struct;
use embedder_traits::{
AllowOrDeny, AnimationState, CompositorHitTestResult, ContextMenuResult, EditingActionEvent,
- EmbedderMsg, ImeEvent, InputEvent, LoadStatus, MouseButton, MouseButtonAction,
- MouseButtonEvent, TouchEvent, TouchEventType, TouchId, WheelEvent,
+ EmbedderMsg, FocusSequenceNumber, ImeEvent, InputEvent, LoadStatus, MouseButton,
+ MouseButtonAction, MouseButtonEvent, TouchEvent, TouchEventType, TouchId, WheelEvent,
};
use encoding_rs::{Encoding, UTF_8};
use euclid::default::{Point2D, Rect, Size2D};
@@ -270,12 +270,11 @@ pub(crate) enum IsHTMLDocument {
#[derive(JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
-enum FocusTransaction {
- /// No focus operation is in effect.
- NotInTransaction,
- /// A focus operation is in effect.
- /// Contains the element that has most recently requested focus for itself.
- InTransaction(Option<Dom<Element>>),
+struct FocusTransaction {
+ /// The focused element of this document.
+ element: Option<Dom<Element>>,
+ /// See [`Document::has_focus`].
+ has_focus: bool,
}
/// Information about a declarative refresh
@@ -341,9 +340,16 @@ pub(crate) struct Document {
/// Whether the DOMContentLoaded event has already been dispatched.
domcontentloaded_dispatched: Cell<bool>,
/// The state of this document's focus transaction.
- focus_transaction: DomRefCell<FocusTransaction>,
+ focus_transaction: DomRefCell<Option<FocusTransaction>>,
/// The element that currently has the document focus context.
focused: MutNullableDom<Element>,
+ /// The last sequence number sent to the constellation.
+ #[no_trace]
+ focus_sequence: Cell<FocusSequenceNumber>,
+ /// Indicates whether the container is included in the top-level browsing
+ /// context's focus chain (not considering system focus). Permanently `true`
+ /// for a top-level document.
+ has_focus: Cell<bool>,
/// The script element that is currently executing.
current_script: MutNullableDom<HTMLScriptElement>,
/// <https://html.spec.whatwg.org/multipage/#pending-parsing-blocking-script>
@@ -1120,122 +1126,318 @@ impl Document {
self.focused.get()
}
+ /// Get the last sequence number sent to the constellation.
+ ///
+ /// Received focus-related messages with sequence numbers less than the one
+ /// returned by this method must be discarded.
+ pub fn get_focus_sequence(&self) -> FocusSequenceNumber {
+ self.focus_sequence.get()
+ }
+
+ /// Generate the next sequence number for focus-related messages.
+ fn increment_fetch_focus_sequence(&self) -> FocusSequenceNumber {
+ self.focus_sequence.set(FocusSequenceNumber(
+ self.focus_sequence
+ .get()
+ .0
+ .checked_add(1)
+ .expect("too many focus messages have been sent"),
+ ));
+ self.focus_sequence.get()
+ }
+
/// Initiate a new round of checking for elements requesting focus. The last element to call
/// `request_focus` before `commit_focus_transaction` is called will receive focus.
fn begin_focus_transaction(&self) {
- *self.focus_transaction.borrow_mut() = FocusTransaction::InTransaction(Default::default());
+ // Initialize it with the current state
+ *self.focus_transaction.borrow_mut() = Some(FocusTransaction {
+ element: self.focused.get().as_deref().map(Dom::from_ref),
+ has_focus: self.has_focus.get(),
+ });
}
/// <https://html.spec.whatwg.org/multipage/#focus-fixup-rule>
pub(crate) fn perform_focus_fixup_rule(&self, not_focusable: &Element, can_gc: CanGc) {
+ // Return if `not_focusable` is not the designated focused area of the
+ // `Document`.
if Some(not_focusable) != self.focused.get().as_deref() {
return;
}
- self.request_focus(
- self.GetBody().as_ref().map(|e| e.upcast()),
- FocusType::Element,
- can_gc,
- )
+
+ let implicit_transaction = self.focus_transaction.borrow().is_none();
+
+ if implicit_transaction {
+ self.begin_focus_transaction();
+ }
+
+ // Designate the viewport as the new focused area of the `Document`, but
+ // do not run the focusing steps.
+ {
+ let mut focus_transaction = self.focus_transaction.borrow_mut();
+ focus_transaction.as_mut().unwrap().element = None;
+ }
+
+ if implicit_transaction {
+ self.commit_focus_transaction(FocusInitiator::Local, can_gc);
+ }
}
- /// Request that the given element receive focus once the current transaction is complete.
- /// If None is passed, then whatever element is currently focused will no longer be focused
- /// once the transaction is complete.
+ /// Request that the given element receive focus once the current
+ /// transaction is complete. `None` specifies to focus the document.
+ ///
+ /// If there's no ongoing transaction, this method automatically starts and
+ /// commits an implicit transaction.
pub(crate) fn request_focus(
&self,
elem: Option<&Element>,
- focus_type: FocusType,
+ focus_initiator: FocusInitiator,
can_gc: CanGc,
) {
- let implicit_transaction = matches!(
- *self.focus_transaction.borrow(),
- FocusTransaction::NotInTransaction
- );
+ // If an element is specified, and it's non-focusable, ignore the
+ // request.
+ if elem.is_some_and(|e| !e.is_focusable_area()) {
+ return;
+ }
+
+ let implicit_transaction = self.focus_transaction.borrow().is_none();
+
if implicit_transaction {
self.begin_focus_transaction();
}
- if elem.is_none_or(|e| e.is_focusable_area()) {
- *self.focus_transaction.borrow_mut() =
- FocusTransaction::InTransaction(elem.map(Dom::from_ref));
+
+ {
+ let mut focus_transaction = self.focus_transaction.borrow_mut();
+ let focus_transaction = focus_transaction.as_mut().unwrap();
+ focus_transaction.element = elem.map(Dom::from_ref);
+ focus_transaction.has_focus = true;
}
+
if implicit_transaction {
- self.commit_focus_transaction(focus_type, can_gc);
+ self.commit_focus_transaction(focus_initiator, can_gc);
+ }
+ }
+
+ /// Update the local focus state accordingly after being notified that the
+ /// document's container is removed from the top-level browsing context's
+ /// focus chain (not considering system focus).
+ pub(crate) fn handle_container_unfocus(&self, can_gc: CanGc) {
+ assert!(
+ self.window().parent_info().is_some(),
+ "top-level document cannot be unfocused",
+ );
+
+ // Since this method is called from an event loop, there mustn't be
+ // an in-progress focus transaction
+ assert!(
+ self.focus_transaction.borrow().is_none(),
+ "there mustn't be an in-progress focus transaction at this point"
+ );
+
+ // Start an implicit focus transaction
+ self.begin_focus_transaction();
+
+ // Update the transaction
+ {
+ let mut focus_transaction = self.focus_transaction.borrow_mut();
+ focus_transaction.as_mut().unwrap().has_focus = false;
}
+
+ // Commit the implicit focus transaction
+ self.commit_focus_transaction(FocusInitiator::Remote, can_gc);
}
/// Reassign the focus context to the element that last requested focus during this
- /// transaction, or none if no elements requested it.
- fn commit_focus_transaction(&self, focus_type: FocusType, can_gc: CanGc) {
- let possibly_focused = match *self.focus_transaction.borrow() {
- FocusTransaction::NotInTransaction => unreachable!(),
- FocusTransaction::InTransaction(ref elem) => {
- elem.as_ref().map(|e| DomRoot::from_ref(&**e))
- },
+ /// transaction, or the document if no elements requested it.
+ fn commit_focus_transaction(&self, focus_initiator: FocusInitiator, can_gc: CanGc) {
+ let (mut new_focused, new_focus_state) = {
+ let focus_transaction = self.focus_transaction.borrow();
+ let focus_transaction = focus_transaction
+ .as_ref()
+ .expect("no focus transaction in progress");
+ (
+ focus_transaction
+ .element
+ .as_ref()
+ .map(|e| DomRoot::from_ref(&**e)),
+ focus_transaction.has_focus,
+ )
};
- *self.focus_transaction.borrow_mut() = FocusTransaction::NotInTransaction;
- if self.focused == possibly_focused.as_deref() {
- return;
+ *self.focus_transaction.borrow_mut() = None;
+
+ if !new_focus_state {
+ // In many browsers, a document forgets its focused area when the
+ // document is removed from the top-level BC's focus chain
+ if new_focused.take().is_some() {
+ trace!(
+ "Forgetting the document's focused area because the \
+ document's container was removed from the top-level BC's \
+ focus chain"
+ );
+ }
}
- if let Some(ref elem) = self.focused.get() {
- let node = elem.upcast::<Node>();
- elem.set_focus_state(false);
- // FIXME: pass appropriate relatedTarget
- self.fire_focus_event(FocusEventType::Blur, node, None, can_gc);
- // Notify the embedder to hide the input method.
- if elem.input_method_type().is_some() {
- self.send_to_embedder(EmbedderMsg::HideIME(self.webview_id()));
+ let old_focused = self.focused.get();
+ let old_focus_state = self.has_focus.get();
+
+ debug!(
+ "Committing focus transaction: {:?} → {:?}",
+ (&old_focused, old_focus_state),
+ (&new_focused, new_focus_state),
+ );
+
+ // `*_focused_filtered` indicates the local element (if any) included in
+ // the top-level BC's focus chain.
+ let old_focused_filtered = old_focused.as_ref().filter(|_| old_focus_state);
+ let new_focused_filtered = new_focused.as_ref().filter(|_| new_focus_state);
+
+ let trace_focus_chain = |name, element, doc| {
+ trace!(
+ "{} local focus chain: {}",
+ name,
+ match (element, doc) {
+ (Some(e), _) => format!("[{:?}, document]", e),
+ (None, true) => "[document]".to_owned(),
+ (None, false) => "[]".to_owned(),
+ }
+ );
+ };
+
+ trace_focus_chain("Old", old_focused_filtered, old_focus_state);
+ trace_focus_chain("New", new_focused_filtered, new_focus_state);
+
+ if old_focused_filtered != new_focused_filtered {
+ if let Some(elem) = &old_focused_filtered {
+ let node = elem.upcast::<Node>();
+ elem.set_focus_state(false);
+ // FIXME: pass appropriate relatedTarget
+ if node.is_connected() {
+ self.fire_focus_event(FocusEventType::Blur, node.upcast(), None, can_gc);
+ }
+
+ // Notify the embedder to hide the input method.
+ if elem.input_method_type().is_some() {
+ self.send_to_embedder(EmbedderMsg::HideIME(self.webview_id()));
+ }
}
}
- self.focused.set(possibly_focused.as_deref());
+ if old_focus_state != new_focus_state && !new_focus_state {
+ self.fire_focus_event(FocusEventType::Blur, self.global().upcast(), None, can_gc);
+ }
- if let Some(ref elem) = self.focused.get() {
- elem.set_focus_state(true);
- let node = elem.upcast::<Node>();
- // FIXME: pass appropriate relatedTarget
- self.fire_focus_event(FocusEventType::Focus, node, None, can_gc);
- // Update the focus state for all elements in the focus chain.
- // https://html.spec.whatwg.org/multipage/#focus-chain
- if focus_type == FocusType::Element {
- self.window()
- .send_to_constellation(ScriptToConstellationMessage::Focus);
+ self.focused.set(new_focused.as_deref());
+ self.has_focus.set(new_focus_state);
+
+ if old_focus_state != new_focus_state && new_focus_state {
+ self.fire_focus_event(FocusEventType::Focus, self.global().upcast(), None, can_gc);
+ }
+
+ if old_focused_filtered != new_focused_filtered {
+ if let Some(elem) = &new_focused_filtered {
+ elem.set_focus_state(true);
+ let node = elem.upcast::<Node>();
+ // FIXME: pass appropriate relatedTarget
+ self.fire_focus_event(FocusEventType::Focus, node.upcast(), None, can_gc);
+
+ // Notify the embedder to display an input method.
+ if let Some(kind) = elem.input_method_type() {
+ let rect = elem.upcast::<Node>().bounding_content_box_or_zero(can_gc);
+ let rect = Rect::new(
+ Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()),
+ Size2D::new(rect.size.width.to_px(), rect.size.height.to_px()),
+ );
+ let (text, multiline) = if let Some(input) = elem.downcast::<HTMLInputElement>()
+ {
+ (
+ Some((
+ (input.Value()).to_string(),
+ input.GetSelectionEnd().unwrap_or(0) as i32,
+ )),
+ false,
+ )
+ } else if let Some(textarea) = elem.downcast::<HTMLTextAreaElement>() {
+ (
+ Some((
+ (textarea.Value()).to_string(),
+ textarea.GetSelectionEnd().unwrap_or(0) as i32,
+ )),
+ true,
+ )
+ } else {
+ (None, false)
+ };
+ self.send_to_embedder(EmbedderMsg::ShowIME(
+ self.webview_id(),
+ kind,
+ text,
+ multiline,
+ DeviceIntRect::from_untyped(&rect.to_box2d()),
+ ));
+ }
}
+ }
+
+ if focus_initiator != FocusInitiator::Local {
+ return;
+ }
- // Notify the embedder to display an input method.
- if let Some(kind) = elem.input_method_type() {
- let rect = elem.upcast::<Node>().bounding_content_box_or_zero(can_gc);
- let rect = Rect::new(
- Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()),
- Size2D::new(rect.size.width.to_px(), rect.size.height.to_px()),
+ // We are the initiator of the focus operation, so we must broadcast
+ // the change we intend to make.
+ match (old_focus_state, new_focus_state) {
+ (_, true) => {
+ // Advertise the change in the focus chain.
+ // <https://html.spec.whatwg.org/multipage/#focus-chain>
+ // <https://html.spec.whatwg.org/multipage/#focusing-steps>
+ //
+ // If the top-level BC doesn't have system focus, this won't
+ // have an immediate effect, but it will when we gain system
+ // focus again. Therefore we still have to send `ScriptMsg::
+ // Focus`.
+ //
+ // When a container with a non-null nested browsing context is
+ // focused, its active document becomes the focused area of the
+ // top-level browsing context instead. Therefore we need to let
+ // the constellation know if such a container is focused.
+ //
+ // > The focusing steps for an object `new focus target` [...]
+ // >
+ // > 3. If `new focus target` is a browsing context container
+ // > with non-null nested browsing context, then set
+ // > `new focus target` to the nested browsing context's
+ // > active document.
+ let child_browsing_context_id = new_focused
+ .as_ref()
+ .and_then(|elem| elem.downcast::<HTMLIFrameElement>())
+ .and_then(|iframe| iframe.browsing_context_id());
+
+ let sequence = self.increment_fetch_focus_sequence();
+
+ debug!(
+ "Advertising the focus request to the constellation \
+ with sequence number {} and child BC ID {}",
+ sequence,
+ child_browsing_context_id
+ .as_ref()
+ .map(|id| id as &dyn std::fmt::Display)
+ .unwrap_or(&"(none)"),
);
- let (text, multiline) = if let Some(input) = elem.downcast::<HTMLInputElement>() {
- (
- Some((
- input.Value().to_string(),
- input.GetSelectionEnd().unwrap_or(0) as i32,
- )),
- false,
- )
- } else if let Some(textarea) = elem.downcast::<HTMLTextAreaElement>() {
- (
- Some((
- textarea.Value().to_string(),
- textarea.GetSelectionEnd().unwrap_or(0) as i32,
- )),
- true,
- )
- } else {
- (None, false)
- };
- self.send_to_embedder(EmbedderMsg::ShowIME(
- self.webview_id(),
- kind,
- text,
- multiline,
- DeviceIntRect::from_untyped(&rect.to_box2d()),
- ));
- }
+
+ self.window()
+ .send_to_constellation(ScriptToConstellationMessage::Focus(
+ child_browsing_context_id,
+ sequence,
+ ));
+ },
+ (false, false) => {
+ // Our `Document` doesn't have focus, and we intend to keep it
+ // this way.
+ },
+ (true, false) => {
+ unreachable!(
+ "Can't lose the document's focus without specifying \
+ another one to focus"
+ );
+ },
}
}
@@ -1350,7 +1552,10 @@ impl Document {
}
self.begin_focus_transaction();
- self.request_focus(Some(&*el), FocusType::Element, can_gc);
+ // Try to focus `el`. If it's not focusable, focus the document
+ // instead.
+ self.request_focus(None, FocusInitiator::Local, can_gc);
+ self.request_focus(Some(&*el), FocusInitiator::Local, can_gc);
}
let dom_event = DomRoot::upcast::<Event>(MouseEvent::for_platform_mouse_event(
@@ -1388,7 +1593,9 @@ impl Document {
}
if let MouseButtonAction::Click = event.action {
- self.commit_focus_transaction(FocusType::Element, can_gc);
+ if self.focus_transaction.borrow().is_some() {
+ self.commit_focus_transaction(FocusInitiator::Local, can_gc);
+ }
self.maybe_fire_dblclick(
hit_test_result.point_in_viewport,
node,
@@ -2215,7 +2422,7 @@ impl Document {
ImeEvent::Dismissed => {
self.request_focus(
self.GetBody().as_ref().map(|e| e.upcast()),
- FocusType::Element,
+ FocusInitiator::Local,
can_gc,
);
return;
@@ -3194,7 +3401,7 @@ impl Document {
fn fire_focus_event(
&self,
focus_event_type: FocusEventType,
- node: &Node,
+ event_target: &EventTarget,
related_target: Option<&EventTarget>,
can_gc: CanGc,
) {
@@ -3214,8 +3421,7 @@ impl Document {
);
let event = event.upcast::<Event>();
event.set_trusted(true);
- let target = node.upcast();
- event.fire(target, can_gc);
+ event.fire(event_target, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#cookie-averse-document-object>
@@ -3795,6 +4001,8 @@ impl Document {
.and_then(|charset| Encoding::for_label(charset.as_bytes()))
.unwrap_or(UTF_8);
+ let has_focus = window.parent_info().is_none();
+
let has_browsing_context = has_browsing_context == HasBrowsingContext::Yes;
Document {
@@ -3842,8 +4050,10 @@ impl Document {
stylesheet_list: MutNullableDom::new(None),
ready_state: Cell::new(ready_state),
domcontentloaded_dispatched: Cell::new(domcontentloaded_dispatched),
- focus_transaction: DomRefCell::new(FocusTransaction::NotInTransaction),
+ focus_transaction: DomRefCell::new(None),
focused: Default::default(),
+ focus_sequence: Cell::new(FocusSequenceNumber::default()),
+ has_focus: Cell::new(has_focus),
current_script: Default::default(),
pending_parsing_blocking_script: Default::default(),
script_blocking_stylesheets_count: Cell::new(0u32),
@@ -4989,12 +5199,34 @@ impl DocumentMethods<crate::DomTypeHolder> for Document {
// https://html.spec.whatwg.org/multipage/#dom-document-hasfocus
fn HasFocus(&self) -> bool {
- // Step 1-2.
- if self.window().parent_info().is_none() && self.is_fully_active() {
- return true;
+ // <https://html.spec.whatwg.org/multipage/#has-focus-steps>
+ //
+ // > The has focus steps, given a `Document` object `target`, are as
+ // > follows:
+ // >
+ // > 1. If `target`'s browsing context's top-level browsing context does
+ // > not have system focus, then return false.
+
+ // > 2. Let `candidate` be `target`'s browsing context's top-level
+ // > browsing context's active document.
+ // >
+ // > 3. While true:
+ // >
+ // > 3.1. If `candidate` is target, then return true.
+ // >
+ // > 3.2. If the focused area of `candidate` is a browsing context
+ // > container with a non-null nested browsing context, then set
+ // > `candidate` to the active document of that browsing context
+ // > container's nested browsing context.
+ // >
+ // > 3.3. Otherwise, return false.
+ if self.window().parent_info().is_none() {
+ // 2 → 3 → (3.1 || ⋯ → 3.3)
+ self.is_fully_active()
+ } else {
+ // 2 → 3 → 3.2 → (⋯ → 3.1 || ⋯ → 3.3)
+ self.is_fully_active() && self.has_focus.get()
}
- // TODO Step 3.
- false
}
// https://html.spec.whatwg.org/multipage/#dom-document-domain
@@ -6397,6 +6629,17 @@ pub(crate) enum FocusType {
Parent, // Focusing a parent element (an iframe)
}
+/// Specifies the initiator of a focus operation.
+#[derive(Clone, Copy, PartialEq)]
+pub enum FocusInitiator {
+ /// The operation is initiated by this document and to be broadcasted
+ /// through the constellation.
+ Local,
+ /// The operation is initiated somewhere else, and we are updating our
+ /// internal state accordingly.
+ Remote,
+}
+
/// Focus events
pub(crate) enum FocusEventType {
Focus, // Element gained focus. Doesn't bubble.
diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs
index b3345b90fc0..efa9a9a97ab 100644
--- a/components/script/dom/globalscope.rs
+++ b/components/script/dom/globalscope.rs
@@ -457,8 +457,9 @@ pub(crate) struct ManagedMessagePort {
/// and only add them, and ask the constellation to complete the transfer,
/// in a subsequent task if the port hasn't been re-transfered.
pending: bool,
- /// Has the port been closed? If closed, it can be dropped and later GC'ed.
- closed: bool,
+ /// Whether the port has been closed by script in this global,
+ /// so it can be removed.
+ explicitly_closed: bool,
/// Note: it may seem strange to use a pair of options, versus for example an enum.
/// But it looks like tranform streams will require both of those in their transfer.
/// This will be resolved when we reach that point of the implementation.
@@ -546,12 +547,17 @@ impl MessageListener {
let mut succeeded = vec![];
let mut failed = HashMap::new();
- for (id, buffer) in ports.into_iter() {
+ for (id, info) in ports.into_iter() {
if global.is_managing_port(&id) {
succeeded.push(id);
- global.complete_port_transfer(id, buffer);
+ global.complete_port_transfer(
+ id,
+ info.port_message_queue,
+ info.disentangled,
+ CanGc::note()
+ );
} else {
- failed.insert(id, buffer);
+ failed.insert(id, info);
}
}
let _ = global.script_to_constellation_chan().send(
@@ -560,13 +566,21 @@ impl MessageListener {
})
);
},
- MessagePortMsg::CompletePendingTransfer(port_id, buffer) => {
+ MessagePortMsg::CompletePendingTransfer(port_id, info) => {
let context = self.context.clone();
self.task_source.queue(task!(complete_pending: move || {
let global = context.root();
- global.complete_port_transfer(port_id, buffer);
+ global.complete_port_transfer(port_id, info.port_message_queue, info.disentangled, CanGc::note());
}));
},
+ MessagePortMsg::CompleteDisentanglement(port_id) => {
+ let context = self.context.clone();
+ self.task_source
+ .queue(task!(try_complete_disentanglement: move || {
+ let global = context.root();
+ global.try_complete_disentanglement(port_id, CanGc::note());
+ }));
+ },
MessagePortMsg::NewTask(port_id, task) => {
let context = self.context.clone();
self.task_source.queue(task!(process_new_task: move || {
@@ -574,14 +588,6 @@ impl MessageListener {
global.route_task_to_port(port_id, task, CanGc::note());
}));
},
- MessagePortMsg::RemoveMessagePort(port_id) => {
- let context = self.context.clone();
- self.task_source
- .queue(task!(process_remove_message_port: move || {
- let global = context.root();
- global.note_entangled_port_removed(&port_id);
- }));
- },
}
}
}
@@ -871,7 +877,13 @@ impl GlobalScope {
}
/// Complete the transfer of a message-port.
- fn complete_port_transfer(&self, port_id: MessagePortId, tasks: VecDeque<PortMessageTask>) {
+ fn complete_port_transfer(
+ &self,
+ port_id: MessagePortId,
+ tasks: VecDeque<PortMessageTask>,
+ disentangled: bool,
+ can_gc: CanGc,
+ ) {
let should_start = if let MessagePortState::Managed(_id, message_ports) =
&mut *self.message_port_state.borrow_mut()
{
@@ -885,6 +897,10 @@ impl GlobalScope {
}
if let Some(port_impl) = managed_port.port_impl.as_mut() {
port_impl.complete_transfer(tasks);
+ if disentangled {
+ port_impl.disentangle();
+ managed_port.dom_port.disentangle();
+ }
port_impl.enabled()
} else {
panic!("managed-port has no port-impl.");
@@ -895,7 +911,45 @@ impl GlobalScope {
panic!("complete_port_transfer called for an unknown port.");
};
if should_start {
- self.start_message_port(&port_id);
+ self.start_message_port(&port_id, can_gc);
+ }
+ }
+
+ /// The closing of `otherPort`, if it is in a different global.
+ /// <https://html.spec.whatwg.org/multipage/#disentangle>
+ fn try_complete_disentanglement(&self, port_id: MessagePortId, can_gc: CanGc) {
+ let dom_port = if let MessagePortState::Managed(_id, message_ports) =
+ &mut *self.message_port_state.borrow_mut()
+ {
+ let dom_port = if let Some(managed_port) = message_ports.get_mut(&port_id) {
+ if managed_port.pending {
+ unreachable!("CompleteDisentanglement msg received for a pending port.");
+ }
+ let port_impl = managed_port
+ .port_impl
+ .as_mut()
+ .expect("managed-port has no port-impl.");
+ port_impl.disentangle();
+ managed_port.dom_port.as_rooted()
+ } else {
+ // Note: this, and the other return below,
+ // can happen if the port has already been transferred out of this global,
+ // in which case the disentanglement will complete along with the transfer.
+ return;
+ };
+ dom_port
+ } else {
+ return;
+ };
+
+ // Fire an event named close at otherPort.
+ dom_port.upcast().fire_event(atom!("close"), can_gc);
+
+ let res = self.script_to_constellation_chan().send(
+ ScriptToConstellationMessage::DisentanglePorts(port_id, None),
+ );
+ if res.is_err() {
+ warn!("Sending DisentanglePorts failed");
}
}
@@ -951,8 +1005,64 @@ impl GlobalScope {
}
/// <https://html.spec.whatwg.org/multipage/#disentangle>
- pub(crate) fn disentangle_port(&self, _port: &MessagePort) {
- // TODO: #36465
+ pub(crate) fn disentangle_port(&self, port: &MessagePort, can_gc: CanGc) {
+ let initiator_port = port.message_port_id();
+ // Let otherPort be the MessagePort which initiatorPort was entangled with.
+ let Some(other_port) = port.disentangle() else {
+ // Assert: otherPort exists.
+ // Note: ignoring the assert,
+ // because the streams spec seems to disentangle ports that are disentangled already.
+ return;
+ };
+
+ // Disentangle initiatorPort and otherPort, so that they are no longer entangled or associated with each other.
+ // Note: this is done in part here, and in part at the constellation(if otherPort is in another global).
+ let dom_port = if let MessagePortState::Managed(_id, message_ports) =
+ &mut *self.message_port_state.borrow_mut()
+ {
+ let mut dom_port = None;
+ for port_id in &[initiator_port, &other_port] {
+ match message_ports.get_mut(port_id) {
+ None => {
+ continue;
+ },
+ Some(managed_port) => {
+ let port_impl = managed_port
+ .port_impl
+ .as_mut()
+ .expect("managed-port has no port-impl.");
+ managed_port.dom_port.disentangle();
+ port_impl.disentangle();
+
+ if **port_id == other_port {
+ dom_port = Some(managed_port.dom_port.as_rooted())
+ }
+ },
+ }
+ }
+ dom_port
+ } else {
+ panic!("disentangle_port called on a global not managing any ports.");
+ };
+
+ // Fire an event named close at `otherPort`.
+ // Note: done here if the port is managed by the same global as `initialPort`.
+ if let Some(dom_port) = dom_port {
+ dom_port.upcast().fire_event(atom!("close"), can_gc);
+ }
+
+ let chan = self.script_to_constellation_chan().clone();
+ let initiator_port = *initiator_port;
+ self.task_manager()
+ .port_message_queue()
+ .queue(task!(post_message: move || {
+ // Note: we do this in a task to ensure it doesn't affect messages that are still to be routed,
+ // see the task queueing in `post_messageport_msg`.
+ let res = chan.send(ScriptToConstellationMessage::DisentanglePorts(initiator_port, Some(other_port)));
+ if res.is_err() {
+ warn!("Sending DisentanglePorts failed");
+ }
+ }));
}
/// <https://html.spec.whatwg.org/multipage/#entangle>
@@ -984,18 +1094,6 @@ impl GlobalScope {
.send(ScriptToConstellationMessage::EntanglePorts(port1, port2));
}
- /// Note that the entangled port of `port_id` has been removed in another global.
- pub(crate) fn note_entangled_port_removed(&self, port_id: &MessagePortId) {
- // Note: currently this is a no-op,
- // as we only use the `close` method to manage the local lifecyle of a port.
- // This could be used as part of lifecyle management to determine a port can be GC'ed.
- // See https://github.com/servo/servo/issues/25772
- warn!(
- "Entangled port of {:?} has been removed in another global",
- port_id
- );
- }
-
/// Handle the transfer of a port in the current task.
pub(crate) fn mark_port_as_transferred(&self, port_id: &MessagePortId) -> MessagePortImpl {
if let MessagePortState::Managed(_id, message_ports) =
@@ -1021,20 +1119,21 @@ impl GlobalScope {
}
/// <https://html.spec.whatwg.org/multipage/#dom-messageport-start>
- pub(crate) fn start_message_port(&self, port_id: &MessagePortId) {
- let message_buffer = if let MessagePortState::Managed(_id, message_ports) =
+ pub(crate) fn start_message_port(&self, port_id: &MessagePortId, can_gc: CanGc) {
+ let (message_buffer, dom_port) = if let MessagePortState::Managed(_id, message_ports) =
&mut *self.message_port_state.borrow_mut()
{
- match message_ports.get_mut(port_id) {
+ let (message_buffer, dom_port) = match message_ports.get_mut(port_id) {
None => panic!("start_message_port called on a unknown port."),
Some(managed_port) => {
if let Some(port_impl) = managed_port.port_impl.as_mut() {
- port_impl.start()
+ (port_impl.start(), managed_port.dom_port.as_rooted())
} else {
panic!("managed-port has no port-impl.");
}
},
- }
+ };
+ (message_buffer, dom_port)
} else {
return warn!("start_message_port called on a global not managing any ports.");
};
@@ -1042,6 +1141,18 @@ impl GlobalScope {
for task in message_buffer {
self.route_task_to_port(*port_id, task, CanGc::note());
}
+ if dom_port.disentangled() {
+ // <https://html.spec.whatwg.org/multipage/#disentangle>
+ // Fire an event named close at otherPort.
+ dom_port.upcast().fire_event(atom!("close"), can_gc);
+
+ let res = self.script_to_constellation_chan().send(
+ ScriptToConstellationMessage::DisentanglePorts(*port_id, None),
+ );
+ if res.is_err() {
+ warn!("Sending DisentanglePorts failed");
+ }
+ }
}
}
@@ -1055,7 +1166,7 @@ impl GlobalScope {
Some(managed_port) => {
if let Some(port_impl) = managed_port.port_impl.as_mut() {
port_impl.close();
- managed_port.closed = true;
+ managed_port.explicitly_closed = true;
} else {
panic!("managed-port has no port-impl.");
}
@@ -1436,12 +1547,7 @@ impl GlobalScope {
let to_be_removed: Vec<MessagePortId> = message_ports
.iter()
.filter_map(|(id, managed_port)| {
- if managed_port.closed {
- // Let the constellation know to drop this port and the one it is entangled with,
- // and to forward this message to the script-process where the entangled is found.
- let _ = self
- .script_to_constellation_chan()
- .send(ScriptToConstellationMessage::RemoveMessagePort(*id));
+ if managed_port.explicitly_closed {
Some(*id)
} else {
None
@@ -1451,6 +1557,9 @@ impl GlobalScope {
for id in to_be_removed {
message_ports.remove(&id);
}
+ // Note: ports are only removed throught explicit closure by script in this global.
+ // TODO: #25772
+ // TODO: remove ports when we can be sure their port message queue is empty(via the constellation).
message_ports.is_empty()
} else {
false
@@ -1581,7 +1690,7 @@ impl GlobalScope {
port_impl: Some(port_impl),
dom_port: Dom::from_ref(dom_port),
pending: true,
- closed: false,
+ explicitly_closed: false,
cross_realm_transform_readable: None,
cross_realm_transform_writable: None,
},
@@ -1605,7 +1714,7 @@ impl GlobalScope {
port_impl: Some(port_impl),
dom_port: Dom::from_ref(dom_port),
pending: false,
- closed: false,
+ explicitly_closed: false,
cross_realm_transform_readable: None,
cross_realm_transform_writable: None,
},
diff --git a/components/script/dom/htmldetailselement.rs b/components/script/dom/htmldetailselement.rs
index a3e2a05af32..1d48b8e7a97 100644
--- a/components/script/dom/htmldetailselement.rs
+++ b/components/script/dom/htmldetailselement.rs
@@ -178,8 +178,6 @@ impl HTMLDetailsElement {
}
}
shadow_tree.descendants.Assign(slottable_children);
-
- self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
}
fn update_shadow_tree_styles(&self, can_gc: CanGc) {
@@ -214,8 +212,6 @@ impl HTMLDetailsElement {
.implicit_summary
.upcast::<Element>()
.set_string_attribute(&local_name!("style"), implicit_summary_style.into(), can_gc);
-
- self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
}
}
diff --git a/components/script/dom/htmlelement.rs b/components/script/dom/htmlelement.rs
index 9505d5182c7..e7efbde9b1d 100644
--- a/components/script/dom/htmlelement.rs
+++ b/components/script/dom/htmlelement.rs
@@ -32,7 +32,7 @@ use crate::dom::bindings::str::DOMString;
use crate::dom::characterdata::CharacterData;
use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner};
use crate::dom::customelementregistry::CallbackReaction;
-use crate::dom::document::{Document, FocusType};
+use crate::dom::document::{Document, FocusInitiator};
use crate::dom::documentfragment::DocumentFragment;
use crate::dom::domstringmap::DOMStringMap;
use crate::dom::element::{AttributeMutation, Element};
@@ -415,18 +415,19 @@ impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
// TODO: Mark the element as locked for focus and run the focusing steps.
// https://html.spec.whatwg.org/multipage/#focusing-steps
let document = self.owner_document();
- document.request_focus(Some(self.upcast()), FocusType::Element, can_gc);
+ document.request_focus(Some(self.upcast()), FocusInitiator::Local, can_gc);
}
// https://html.spec.whatwg.org/multipage/#dom-blur
fn Blur(&self, can_gc: CanGc) {
- // TODO: Run the unfocusing steps.
+ // TODO: Run the unfocusing steps. Focus the top-level document, not
+ // the current document.
if !self.as_element().focus_state() {
return;
}
// https://html.spec.whatwg.org/multipage/#unfocusing-steps
let document = self.owner_document();
- document.request_focus(None, FocusType::Element, can_gc);
+ document.request_focus(None, FocusInitiator::Local, can_gc);
}
// https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetparent
diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs
index ce6dcca66f3..2421b683bf7 100644
--- a/components/script/dom/htmlformelement.rs
+++ b/components/script/dom/htmlformelement.rs
@@ -1270,8 +1270,14 @@ impl HTMLFormElement {
return;
}
- let controls = self.controls.borrow();
- for child in controls.iter() {
+ let controls: Vec<_> = self
+ .controls
+ .borrow()
+ .iter()
+ .map(|c| c.as_rooted())
+ .collect();
+
+ for child in controls {
let child = child.upcast::<Node>();
match child.type_id() {
diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs
index 9452dcb17a6..58853f600d2 100644
--- a/components/script/dom/htmlscriptelement.rs
+++ b/components/script/dom/htmlscriptelement.rs
@@ -1008,12 +1008,12 @@ impl HTMLScriptElement {
Ok(script) => script,
};
- // TODO: we need to handle this for worker
if let Some(chan) = self.global().devtools_chan() {
let pipeline_id = self.global().pipeline_id();
let source_info = SourceInfo {
url: script.url.clone(),
external: script.external,
+ worker_id: None,
};
let _ = chan.send(ScriptToDevtoolsControlMsg::ScriptSourceLoaded(
pipeline_id,
diff --git a/components/script/dom/messageport.rs b/components/script/dom/messageport.rs
index 85d94c1aa7a..d70d3139b96 100644
--- a/components/script/dom/messageport.rs
+++ b/components/script/dom/messageport.rs
@@ -83,6 +83,20 @@ impl MessagePort {
*self.entangled_port.borrow_mut() = Some(other_id);
}
+ /// <https://html.spec.whatwg.org/multipage/#disentangle>
+ pub(crate) fn disentangle(&self) -> Option<MessagePortId> {
+ // Disentangle initiatorPort and otherPort, so that they are no longer entangled or associated with each other.
+ // Note: called from `disentangle_port` in the global, where the rest happens.
+ self.entangled_port.borrow_mut().take()
+ }
+
+ /// Has the port been disentangled?
+ /// Used when starting the port to fire the `close` event,
+ /// to cover the case of a disentanglement while in transfer.
+ pub(crate) fn disentangled(&self) -> bool {
+ self.entangled_port.borrow().is_none()
+ }
+
pub(crate) fn message_port_id(&self) -> &MessagePortId {
&self.message_port_id
}
@@ -314,20 +328,28 @@ impl MessagePortMethods<crate::DomTypeHolder> for MessagePort {
}
/// <https://html.spec.whatwg.org/multipage/#dom-messageport-start>
- fn Start(&self) {
+ fn Start(&self, can_gc: CanGc) {
if self.detached.get() {
return;
}
- self.global().start_message_port(self.message_port_id());
+ self.global()
+ .start_message_port(self.message_port_id(), can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#dom-messageport-close>
- fn Close(&self) {
+ fn Close(&self, can_gc: CanGc) {
if self.detached.get() {
return;
}
+
+ // Set this's [[Detached]] internal slot value to true.
self.detached.set(true);
- self.global().close_message_port(self.message_port_id());
+
+ let global = self.global();
+ global.close_message_port(self.message_port_id());
+
+ // If this is entangled, disentangle it.
+ global.disentangle_port(self, can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessage>
@@ -340,15 +362,19 @@ impl MessagePortMethods<crate::DomTypeHolder> for MessagePort {
}
/// <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessage>
- fn SetOnmessage(&self, listener: Option<Rc<EventHandlerNonNull>>) {
+ fn SetOnmessage(&self, listener: Option<Rc<EventHandlerNonNull>>, can_gc: CanGc) {
if self.detached.get() {
return;
}
self.set_onmessage(listener);
// Note: we cannot use the event_handler macro, due to the need to start the port.
- self.global().start_message_port(self.message_port_id());
+ self.global()
+ .start_message_port(self.message_port_id(), can_gc);
}
// <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessageerror>
event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror);
+
+ // <https://html.spec.whatwg.org/multipage/#handler-messageport-onclose>
+ event_handler!(close, GetOnclose, SetOnclose);
}
diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs
index b56126076da..d312fe88fc5 100644
--- a/components/script/dom/node.rs
+++ b/components/script/dom/node.rs
@@ -1007,24 +1007,25 @@ impl Node {
/// <https://dom.spec.whatwg.org/#dom-childnode-replacewith>
pub(crate) fn replace_with(&self, nodes: Vec<NodeOrString>, can_gc: CanGc) -> ErrorResult {
- // Step 1.
- let parent = if let Some(parent) = self.GetParentNode() {
- parent
- } else {
- // Step 2.
+ // Step 1. Let parent be this’s parent.
+ let Some(parent) = self.GetParentNode() else {
+ // Step 2. If parent is null, then return.
return Ok(());
};
- // Step 3.
+
+ // Step 3. Let viableNextSibling be this’s first following sibling not in nodes; otherwise null.
let viable_next_sibling = first_node_not_in(self.following_siblings(), &nodes);
- // Step 4.
+
+ // Step 4. Let node be the result of converting nodes into a node, given nodes and this’s node document.
let node = self
.owner_doc()
.node_from_nodes_and_strings(nodes, can_gc)?;
+
if self.parent_node == Some(&*parent) {
- // Step 5.
+ // Step 5. If this’s parent is parent, replace this with node within parent.
parent.ReplaceChild(&node, self, can_gc)?;
} else {
- // Step 6.
+ // Step 6. Otherwise, pre-insert node into parent before viableNextSibling.
Node::pre_insert(&node, &parent, viable_next_sibling.as_deref(), can_gc)?;
}
Ok(())
@@ -3172,24 +3173,29 @@ impl NodeMethods<crate::DomTypeHolder> for Node {
/// <https://dom.spec.whatwg.org/#concept-node-replace>
fn ReplaceChild(&self, node: &Node, child: &Node, can_gc: CanGc) -> Fallible<DomRoot<Node>> {
- // Step 1.
+ // Step 1. If parent is not a Document, DocumentFragment, or Element node,
+ // then throw a "HierarchyRequestError" DOMException.
match self.type_id() {
NodeTypeId::Document(_) | NodeTypeId::DocumentFragment(_) | NodeTypeId::Element(..) => {
},
_ => return Err(Error::HierarchyRequest),
}
- // Step 2.
+ // Step 2. If node is a host-including inclusive ancestor of parent,
+ // then throw a "HierarchyRequestError" DOMException.
if node.is_inclusive_ancestor_of(self) {
return Err(Error::HierarchyRequest);
}
- // Step 3.
+ // Step 3. If child’s parent is not parent, then throw a "NotFoundError" DOMException.
if !self.is_parent_of(child) {
return Err(Error::NotFound);
}
- // Step 4-5.
+ // Step 4. If node is not a DocumentFragment, DocumentType, Element, or CharacterData node,
+ // then throw a "HierarchyRequestError" DOMException.
+ // Step 5. If either node is a Text node and parent is a document,
+ // or node is a doctype and parent is not a document, then throw a "HierarchyRequestError" DOMException.
match node.type_id() {
NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) if self.is::<Document>() => {
return Err(Error::HierarchyRequest);
@@ -3201,7 +3207,8 @@ impl NodeMethods<crate::DomTypeHolder> for Node {
_ => (),
}
- // Step 6.
+ // Step 6. If parent is a document, and any of the statements below, switched on the interface node implements,
+ // are true, then throw a "HierarchyRequestError" DOMException.
if self.is::<Document>() {
match node.type_id() {
// Step 6.1
@@ -3255,7 +3262,8 @@ impl NodeMethods<crate::DomTypeHolder> for Node {
}
}
- // Step 7-8.
+ // Step 7. Let referenceChild be child’s next sibling.
+ // Step 8. If referenceChild is node, then set referenceChild to node’s next sibling.
let child_next_sibling = child.GetNextSibling();
let node_next_sibling = node.GetNextSibling();
let reference_child = if child_next_sibling.as_deref() == Some(node) {
@@ -3264,7 +3272,7 @@ impl NodeMethods<crate::DomTypeHolder> for Node {
child_next_sibling.as_deref()
};
- // Step 9.
+ // Step 9. Let previousSibling be child’s previous sibling.
let previous_sibling = child.GetPreviousSibling();
// NOTE: All existing browsers assume that adoption is performed here, which does not follow the DOM spec.
@@ -3285,7 +3293,7 @@ impl NodeMethods<crate::DomTypeHolder> for Node {
None
};
- // Step 12.
+ // Step 12. Let nodes be node’s children if node is a DocumentFragment node; otherwise « node ».
rooted_vec!(let mut nodes);
let nodes = if node.type_id() ==
NodeTypeId::DocumentFragment(DocumentFragmentTypeId::DocumentFragment) ||
@@ -3297,7 +3305,7 @@ impl NodeMethods<crate::DomTypeHolder> for Node {
from_ref(&node)
};
- // Step 13.
+ // Step 13. Insert node into parent before referenceChild with the suppress observers flag set.
Node::insert(
node,
self,
@@ -3306,13 +3314,15 @@ impl NodeMethods<crate::DomTypeHolder> for Node {
can_gc,
);
- // Step 14.
vtable_for(self).children_changed(&ChildrenMutation::replace(
previous_sibling.as_deref(),
&removed_child,
nodes,
reference_child,
));
+
+ // Step 14. Queue a tree mutation record for parent with nodes, removedNodes,
+ // previousSibling, and referenceChild.
let removed = removed_child.map(|r| [r]);
let mutation = LazyCell::new(|| Mutation::ChildList {
added: Some(nodes),
@@ -3323,7 +3333,7 @@ impl NodeMethods<crate::DomTypeHolder> for Node {
MutationObserver::queue_a_mutation_record(self, mutation);
- // Step 15.
+ // Step 15. Return child.
Ok(DomRoot::from_ref(child))
}
diff --git a/components/script/dom/offscreencanvasrenderingcontext2d.rs b/components/script/dom/offscreencanvasrenderingcontext2d.rs
index 2f9b52640e6..b2d0f3201ca 100644
--- a/components/script/dom/offscreencanvasrenderingcontext2d.rs
+++ b/components/script/dom/offscreencanvasrenderingcontext2d.rs
@@ -65,7 +65,7 @@ impl OffscreenCanvasRenderingContext2D {
}
pub(crate) fn set_canvas_bitmap_dimensions(&self, size: Size2D<u64>) {
- self.context.set_bitmap_dimensions(size.cast());
+ self.context.set_canvas_bitmap_dimensions(size.cast());
}
pub(crate) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) {
diff --git a/components/script/dom/readablestream.rs b/components/script/dom/readablestream.rs
index 51393ab33ae..4982bfa32e3 100644
--- a/components/script/dom/readablestream.rs
+++ b/components/script/dom/readablestream.rs
@@ -1825,7 +1825,7 @@ impl ReadableStream {
global.note_cross_realm_transform_readable(&cross_realm_transform_readable, port_id);
// Enable port’s port message queue.
- port.Start();
+ port.Start(can_gc);
// Perform ! SetUpReadableStreamDefaultController
controller
@@ -2093,7 +2093,7 @@ impl CrossRealmTransformReadable {
self.controller.close(can_gc);
// Disentangle port.
- global.disentangle_port(port);
+ global.disentangle_port(port, can_gc);
}
// Otherwise, if type is "error",
@@ -2102,7 +2102,7 @@ impl CrossRealmTransformReadable {
self.controller.error(value.handle(), can_gc);
// Disentangle port.
- global.disentangle_port(port);
+ global.disentangle_port(port, can_gc);
}
}
@@ -2129,7 +2129,7 @@ impl CrossRealmTransformReadable {
self.controller.error(rooted_error.handle(), can_gc);
// Disentangle port.
- global.disentangle_port(port);
+ global.disentangle_port(port, can_gc);
}
}
diff --git a/components/script/dom/underlyingsourcecontainer.rs b/components/script/dom/underlyingsourcecontainer.rs
index 541a831693a..4acb58bafef 100644
--- a/components/script/dom/underlyingsourcecontainer.rs
+++ b/components/script/dom/underlyingsourcecontainer.rs
@@ -151,7 +151,7 @@ impl UnderlyingSourceContainer {
let result = port.pack_and_post_message_handling_error("error", reason, can_gc);
// Disentangle port.
- self.global().disentangle_port(port);
+ self.global().disentangle_port(port, can_gc);
let promise = Promise::new(&self.global(), can_gc);
diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs
index e210476a5df..a685bbb25f2 100644
--- a/components/script/dom/window.rs
+++ b/components/script/dom/window.rs
@@ -787,6 +787,32 @@ impl WindowMethods<crate::DomTypeHolder> for Window {
doc.abort(can_gc);
}
+ /// <https://html.spec.whatwg.org/multipage/#dom-window-focus>
+ fn Focus(&self) {
+ // > 1. Let `current` be this `Window` object's browsing context.
+ // >
+ // > 2. If `current` is null, then return.
+ let current = match self.undiscarded_window_proxy() {
+ Some(proxy) => proxy,
+ None => return,
+ };
+
+ // > 3. Run the focusing steps with `current`.
+ current.focus();
+
+ // > 4. If current is a top-level browsing context, user agents are
+ // > encouraged to trigger some sort of notification to indicate to
+ // > the user that the page is attempting to gain focus.
+ //
+ // TODO: Step 4
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-window-blur
+ fn Blur(&self) {
+ // > User agents are encouraged to ignore calls to this `blur()` method
+ // > entirely.
+ }
+
// https://html.spec.whatwg.org/multipage/#dom-open
fn Open(
&self,
diff --git a/components/script/dom/windowproxy.rs b/components/script/dom/windowproxy.rs
index e3fc81bf7ec..dc02f9feb49 100644
--- a/components/script/dom/windowproxy.rs
+++ b/components/script/dom/windowproxy.rs
@@ -620,6 +620,23 @@ impl WindowProxy {
result
}
+ /// Run [the focusing steps] with this browsing context.
+ ///
+ /// [the focusing steps]: https://html.spec.whatwg.org/multipage/#focusing-steps
+ pub fn focus(&self) {
+ debug!(
+ "Requesting the constellation to initiate a focus operation for \
+ browsing context {}",
+ self.browsing_context_id()
+ );
+ self.global()
+ .script_to_constellation_chan()
+ .send(ScriptToConstellationMessage::FocusRemoteDocument(
+ self.browsing_context_id(),
+ ))
+ .unwrap();
+ }
+
#[allow(unsafe_code)]
/// Change the Window that this WindowProxy resolves to.
// TODO: support setting the window proxy to a dummy value,
diff --git a/components/script/dom/worker.rs b/components/script/dom/worker.rs
index 429234c7a8e..6ad56026db6 100644
--- a/components/script/dom/worker.rs
+++ b/components/script/dom/worker.rs
@@ -8,7 +8,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use constellation_traits::{StructuredSerializedData, WorkerScriptLoadOrigin};
use crossbeam_channel::{Sender, unbounded};
-use devtools_traits::{DevtoolsPageInfo, ScriptToDevtoolsControlMsg, WorkerId};
+use devtools_traits::{DevtoolsPageInfo, ScriptToDevtoolsControlMsg, SourceInfo, WorkerId};
use dom_struct::dom_struct;
use ipc_channel::ipc;
use js::jsapi::{Heap, JSObject};
@@ -213,6 +213,16 @@ impl WorkerMethods<crate::DomTypeHolder> for Worker {
devtools_sender.clone(),
page_info,
));
+
+ let source_info = SourceInfo {
+ url: worker_url.clone(),
+ external: true, // Worker scripts are always external.
+ worker_id: Some(worker_id),
+ };
+ let _ = chan.send(ScriptToDevtoolsControlMsg::ScriptSourceLoaded(
+ pipeline_id,
+ source_info,
+ ));
}
}
diff --git a/components/script/dom/writablestream.rs b/components/script/dom/writablestream.rs
index 8c2b2434cd2..1b029f592de 100644
--- a/components/script/dom/writablestream.rs
+++ b/components/script/dom/writablestream.rs
@@ -893,7 +893,7 @@ impl WritableStream {
global.note_cross_realm_transform_writable(&cross_realm_transform_writable, port_id);
// Enable port’s port message queue.
- port.Start();
+ port.Start(can_gc);
// Perform ! SetUpWritableStreamDefaultController
controller
@@ -1202,7 +1202,7 @@ impl CrossRealmTransformWritable {
.error_if_needed(cx, rooted_error.handle(), global, can_gc);
// Disentangle port.
- global.disentangle_port(port);
+ global.disentangle_port(port, can_gc);
}
}
diff --git a/components/script/dom/writablestreamdefaultcontroller.rs b/components/script/dom/writablestreamdefaultcontroller.rs
index 301404ffdb2..084165a6892 100644
--- a/components/script/dom/writablestreamdefaultcontroller.rs
+++ b/components/script/dom/writablestreamdefaultcontroller.rs
@@ -173,11 +173,11 @@ impl Callback for TransferBackPressurePromiseReaction {
self.port
.pack_and_post_message_handling_error("chunk", chunk.handle(), can_gc);
- // Disentangle port.
- global.disentangle_port(&self.port);
-
// If result is an abrupt completion,
if let Err(error) = result {
+ // Disentangle port.
+ global.disentangle_port(&self.port, can_gc);
+
// Return a promise rejected with result.[[Value]].
self.result_promise.reject_error(error, can_gc);
} else {
@@ -609,7 +609,7 @@ impl WritableStreamDefaultController {
let result = port.pack_and_post_message_handling_error("error", reason, can_gc);
// Disentangle port.
- global.disentangle_port(port);
+ global.disentangle_port(port, can_gc);
let promise = Promise::new(global, can_gc);
@@ -752,7 +752,7 @@ impl WritableStreamDefaultController {
.expect("Sending close should not fail.");
// Disentangle port.
- global.disentangle_port(port);
+ global.disentangle_port(port, can_gc);
// Return a promise resolved with undefined.
Promise::new_resolved(global, cx, (), can_gc)
diff --git a/components/script/messaging.rs b/components/script/messaging.rs
index 7d0b7aabe05..e0ea9e30af2 100644
--- a/components/script/messaging.rs
+++ b/components/script/messaging.rs
@@ -72,6 +72,8 @@ impl MixedMessage {
ScriptThreadMessage::UpdateHistoryState(id, ..) => Some(*id),
ScriptThreadMessage::RemoveHistoryStates(id, ..) => Some(*id),
ScriptThreadMessage::FocusIFrame(id, ..) => Some(*id),
+ ScriptThreadMessage::FocusDocument(id, ..) => Some(*id),
+ ScriptThreadMessage::Unfocus(id, ..) => Some(*id),
ScriptThreadMessage::WebDriverScriptCommand(id, ..) => Some(*id),
ScriptThreadMessage::TickAllAnimations(..) => None,
ScriptThreadMessage::WebFontLoaded(id, ..) => Some(*id),
diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs
index f78b5bf281b..2129979ad42 100644
--- a/components/script/script_thread.rs
+++ b/components/script/script_thread.rs
@@ -50,8 +50,9 @@ use devtools_traits::{
};
use embedder_traits::user_content_manager::UserContentManager;
use embedder_traits::{
- CompositorHitTestResult, EmbedderMsg, InputEvent, MediaSessionActionType, MouseButton,
- MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverScriptCommand,
+ CompositorHitTestResult, EmbedderMsg, FocusSequenceNumber, InputEvent, MediaSessionActionType,
+ MouseButton, MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails,
+ WebDriverScriptCommand,
};
use euclid::Point2D;
use euclid::default::Rect;
@@ -124,7 +125,7 @@ use crate::dom::customelementregistry::{
CallbackReaction, CustomElementDefinition, CustomElementReactionStack,
};
use crate::dom::document::{
- Document, DocumentSource, FocusType, HasBrowsingContext, IsHTMLDocument, TouchEventResult,
+ Document, DocumentSource, FocusInitiator, HasBrowsingContext, IsHTMLDocument, TouchEventResult,
};
use crate::dom::element::Element;
use crate::dom::globalscope::GlobalScope;
@@ -331,8 +332,7 @@ pub struct ScriptThread {
#[no_trace]
layout_factory: Arc<dyn LayoutFactory>,
- // Mouse down point.
- // In future, this shall be mouse_down_point for primary button
+ /// The screen coordinates where the primary mouse button was pressed.
#[no_trace]
relative_mouse_down_point: Cell<Point2D<f32, DevicePixel>>,
}
@@ -1804,8 +1804,14 @@ impl ScriptThread {
ScriptThreadMessage::RemoveHistoryStates(pipeline_id, history_states) => {
self.handle_remove_history_states(pipeline_id, history_states)
},
- ScriptThreadMessage::FocusIFrame(parent_pipeline_id, frame_id) => {
- self.handle_focus_iframe_msg(parent_pipeline_id, frame_id, can_gc)
+ ScriptThreadMessage::FocusIFrame(parent_pipeline_id, frame_id, sequence) => {
+ self.handle_focus_iframe_msg(parent_pipeline_id, frame_id, sequence, can_gc)
+ },
+ ScriptThreadMessage::FocusDocument(pipeline_id, sequence) => {
+ self.handle_focus_document_msg(pipeline_id, sequence, can_gc)
+ },
+ ScriptThreadMessage::Unfocus(pipeline_id, sequence) => {
+ self.handle_unfocus_msg(pipeline_id, sequence, can_gc)
},
ScriptThreadMessage::WebDriverScriptCommand(pipeline_id, msg) => {
self.handle_webdriver_msg(pipeline_id, msg, can_gc)
@@ -2514,6 +2520,7 @@ impl ScriptThread {
&self,
parent_pipeline_id: PipelineId,
browsing_context_id: BrowsingContextId,
+ sequence: FocusSequenceNumber,
can_gc: CanGc,
) {
let document = self
@@ -2533,7 +2540,65 @@ impl ScriptThread {
return;
};
- document.request_focus(Some(&iframe_element_root), FocusType::Parent, can_gc);
+ if document.get_focus_sequence() > sequence {
+ debug!(
+ "Disregarding the FocusIFrame message because the contained sequence number is \
+ too old ({:?} < {:?})",
+ sequence,
+ document.get_focus_sequence()
+ );
+ return;
+ }
+
+ document.request_focus(Some(&iframe_element_root), FocusInitiator::Remote, can_gc);
+ }
+
+ fn handle_focus_document_msg(
+ &self,
+ pipeline_id: PipelineId,
+ sequence: FocusSequenceNumber,
+ can_gc: CanGc,
+ ) {
+ if let Some(doc) = self.documents.borrow().find_document(pipeline_id) {
+ if doc.get_focus_sequence() > sequence {
+ debug!(
+ "Disregarding the FocusDocument message because the contained sequence number is \
+ too old ({:?} < {:?})",
+ sequence,
+ doc.get_focus_sequence()
+ );
+ return;
+ }
+ doc.request_focus(None, FocusInitiator::Remote, can_gc);
+ } else {
+ warn!(
+ "Couldn't find document by pipleline_id:{pipeline_id:?} when handle_focus_document_msg."
+ );
+ }
+ }
+
+ fn handle_unfocus_msg(
+ &self,
+ pipeline_id: PipelineId,
+ sequence: FocusSequenceNumber,
+ can_gc: CanGc,
+ ) {
+ if let Some(doc) = self.documents.borrow().find_document(pipeline_id) {
+ if doc.get_focus_sequence() > sequence {
+ debug!(
+ "Disregarding the Unfocus message because the contained sequence number is \
+ too old ({:?} < {:?})",
+ sequence,
+ doc.get_focus_sequence()
+ );
+ return;
+ }
+ doc.handle_container_unfocus(can_gc);
+ } else {
+ warn!(
+ "Couldn't find document by pipleline_id:{pipeline_id:?} when handle_unfocus_msg."
+ );
+ }
}
fn handle_post_message_msg(
diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf
index c50bc31a7f5..4ab0b21cabe 100644
--- a/components/script_bindings/codegen/Bindings.conf
+++ b/components/script_bindings/codegen/Bindings.conf
@@ -474,7 +474,7 @@ DOMInterfaces = {
'MessagePort': {
'weakReferenceable': True,
- 'canGc': ['GetOnmessage'],
+ 'canGc': ['Close', 'GetOnmessage', 'SetOnmessage', 'Start'],
},
'MessageEvent': {
diff --git a/components/script_bindings/webidls/MessagePort.webidl b/components/script_bindings/webidls/MessagePort.webidl
index 6fc1f432b38..b7082fc7fc3 100644
--- a/components/script_bindings/webidls/MessagePort.webidl
+++ b/components/script_bindings/webidls/MessagePort.webidl
@@ -16,6 +16,7 @@ interface MessagePort : EventTarget {
// event handlers
attribute EventHandler onmessage;
attribute EventHandler onmessageerror;
+ attribute EventHandler onclose;
};
dictionary StructuredSerializeOptions {
diff --git a/components/script_bindings/webidls/Window.webidl b/components/script_bindings/webidls/Window.webidl
index 81c442b119f..eb7c3e1d03d 100644
--- a/components/script_bindings/webidls/Window.webidl
+++ b/components/script_bindings/webidls/Window.webidl
@@ -27,8 +27,8 @@
[CrossOriginCallable] undefined close();
[CrossOriginReadable] readonly attribute boolean closed;
undefined stop();
- //[CrossOriginCallable] void focus();
- //[CrossOriginCallable] void blur();
+ [CrossOriginCallable] undefined focus();
+ [CrossOriginCallable] undefined blur();
// other browsing contexts
[Replaceable, CrossOriginReadable] readonly attribute WindowProxy frames;
diff --git a/components/servo/Cargo.toml b/components/servo/Cargo.toml
index 498d170492d..b49f60e742a 100644
--- a/components/servo/Cargo.toml
+++ b/components/servo/Cargo.toml
@@ -46,14 +46,14 @@ tracing = [
webdriver = ["webdriver_server"]
webgl_backtrace = [
"script/webgl_backtrace",
- "canvas/webgl_backtrace",
+ "webgl/webgl_backtrace",
"canvas_traits/webgl_backtrace",
]
webxr = [
"dep:webxr",
"dep:webxr-api",
"compositing/webxr",
- "canvas/webxr",
+ "webgl/webxr",
"script/webxr",
]
webgpu = [
@@ -68,7 +68,8 @@ base = { workspace = true }
bincode = { workspace = true }
bluetooth = { path = "../bluetooth", optional = true }
bluetooth_traits = { workspace = true, optional = true }
-canvas = { path = "../canvas", default-features = false }
+canvas = { path = "../canvas" }
+webgl = { path = "../webgl", default-features = false }
canvas_traits = { workspace = true }
cfg-if = { workspace = true }
compositing = { path = "../compositing" }
diff --git a/components/servo/lib.rs b/components/servo/lib.rs
index 7fb990527ec..6f39e773e4e 100644
--- a/components/servo/lib.rs
+++ b/components/servo/lib.rs
@@ -38,7 +38,6 @@ use base::id::{PipelineNamespace, PipelineNamespaceId};
use bluetooth::BluetoothThreadFactory;
#[cfg(feature = "bluetooth")]
use bluetooth_traits::BluetoothRequest;
-use canvas::WebGLComm;
use canvas::canvas_paint_thread::CanvasPaintThread;
use canvas_traits::webgl::{GlType, WebGLThreads};
use clipboard_delegate::StringRequest;
@@ -99,6 +98,7 @@ use servo_delegate::DefaultServoDelegate;
use servo_media::ServoMedia;
use servo_media::player::context::GlContext;
use servo_url::ServoUrl;
+use webgl::WebGLComm;
#[cfg(feature = "webgpu")]
pub use webgpu;
#[cfg(feature = "webgpu")]
diff --git a/components/shared/constellation/from_script_message.rs b/components/shared/constellation/from_script_message.rs
index 8346551fd15..ddc9f788617 100644
--- a/components/shared/constellation/from_script_message.rs
+++ b/components/shared/constellation/from_script_message.rs
@@ -4,7 +4,7 @@
//! Messages send from the ScriptThread to the Constellation.
-use std::collections::{HashMap, VecDeque};
+use std::collections::HashMap;
use std::fmt;
use base::Epoch;
@@ -15,7 +15,8 @@ use base::id::{
use canvas_traits::canvas::{CanvasId, CanvasMsg};
use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, WorkerId};
use embedder_traits::{
- AnimationState, EmbedderMsg, MediaSessionEvent, TouchEventResult, ViewportDetails,
+ AnimationState, EmbedderMsg, FocusSequenceNumber, MediaSessionEvent, TouchEventResult,
+ ViewportDetails,
};
use euclid::default::Size2D as UntypedSize2D;
use http::{HeaderMap, Method};
@@ -34,7 +35,9 @@ use webgpu_traits::{WebGPU, WebGPUAdapterResponse};
use webrender_api::ImageKey;
use crate::structured_data::{BroadcastMsg, StructuredSerializedData};
-use crate::{LogEntry, MessagePortMsg, PortMessageTask, TraversalDirection, WindowSizeType};
+use crate::{
+ LogEntry, MessagePortMsg, PortMessageTask, PortTransferInfo, TraversalDirection, WindowSizeType,
+};
/// A Script to Constellation channel.
#[derive(Clone, Debug, Deserialize, Serialize)]
@@ -469,7 +472,7 @@ pub enum ScriptToConstellationMessage {
/* The ids of ports transferred successfully */
Vec<MessagePortId>,
/* The ids, and buffers, of ports whose transfer failed */
- HashMap<MessagePortId, VecDeque<PortMessageTask>>,
+ HashMap<MessagePortId, PortTransferInfo>,
),
/// A new message-port was created or transferred, with corresponding control-sender.
NewMessagePort(MessagePortRouterId, MessagePortId),
@@ -481,10 +484,14 @@ pub enum ScriptToConstellationMessage {
RerouteMessagePort(MessagePortId, PortMessageTask),
/// A message-port was shipped, let the entangled port know.
MessagePortShipped(MessagePortId),
- /// A message-port has been discarded by script.
- RemoveMessagePort(MessagePortId),
/// Entangle two message-ports.
EntanglePorts(MessagePortId, MessagePortId),
+ /// Disentangle two message-ports.
+ /// The first is the initiator, the second the other port,
+ /// unless the message is sent to complete a disentanglement,
+ /// in which case the first one is the other port,
+ /// and the second is none.
+ DisentanglePorts(MessagePortId, Option<MessagePortId>),
/// A global has started managing broadcast-channels.
NewBroadcastChannelRouter(
BroadcastChannelRouterId,
@@ -519,8 +526,21 @@ pub enum ScriptToConstellationMessage {
UntypedSize2D<u64>,
IpcSender<(IpcSender<CanvasMsg>, CanvasId, ImageKey)>,
),
- /// Notifies the constellation that this frame has received focus.
- Focus,
+ /// Notifies the constellation that this pipeline is requesting focus.
+ ///
+ /// When this message is sent, the sender pipeline has already its local
+ /// focus state updated. The constellation, after receiving this message,
+ /// will broadcast messages to other pipelines that are affected by this
+ /// focus operation.
+ ///
+ /// The first field contains the browsing context ID of the container
+ /// element if one was focused.
+ ///
+ /// The second field is a sequence number that the constellation should use
+ /// when sending a focus-related message to the sender pipeline next time.
+ Focus(Option<BrowsingContextId>, FocusSequenceNumber),
+ /// Requests the constellation to focus the specified browsing context.
+ FocusRemoteDocument(BrowsingContextId),
/// Get the top-level browsing context info for a given browsing context.
GetTopForBrowsingContext(BrowsingContextId, IpcSender<Option<WebViewId>>),
/// Get the browsing context id of the browsing context in which pipeline is
diff --git a/components/shared/constellation/lib.rs b/components/shared/constellation/lib.rs
index b3d4fe525a1..559bc2dd2d1 100644
--- a/components/shared/constellation/lib.rs
+++ b/components/shared/constellation/lib.rs
@@ -157,18 +157,29 @@ pub struct PortMessageTask {
pub data: StructuredSerializedData,
}
+/// The information needed by a global to process the transfer of a port.
+#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
+pub struct PortTransferInfo {
+ /// <https://html.spec.whatwg.org/multipage/#port-message-queue>
+ pub port_message_queue: VecDeque<PortMessageTask>,
+ /// A boolean indicating whether the port has been disentangled while in transfer,
+ /// if so, the disentanglement should be completed along with the transfer.
+ /// <https://html.spec.whatwg.org/multipage/#disentangle>
+ pub disentangled: bool,
+}
+
/// Messages for communication between the constellation and a global managing ports.
#[derive(Debug, Deserialize, Serialize)]
#[allow(clippy::large_enum_variant)]
pub enum MessagePortMsg {
/// Complete the transfer for a batch of ports.
- CompleteTransfer(HashMap<MessagePortId, VecDeque<PortMessageTask>>),
+ CompleteTransfer(HashMap<MessagePortId, PortTransferInfo>),
/// Complete the transfer of a single port,
/// whose transfer was pending because it had been requested
/// while a previous failed transfer was being rolled-back.
- CompletePendingTransfer(MessagePortId, VecDeque<PortMessageTask>),
- /// Remove a port, the entangled one doesn't exists anymore.
- RemoveMessagePort(MessagePortId),
+ CompletePendingTransfer(MessagePortId, PortTransferInfo),
+ /// <https://html.spec.whatwg.org/multipage/#disentangle>
+ CompleteDisentanglement(MessagePortId),
/// Handle a new port-message-task.
NewTask(MessagePortId, PortMessageTask),
}
diff --git a/components/shared/constellation/structured_data/transferable.rs b/components/shared/constellation/structured_data/transferable.rs
index cd6388f5f34..7e4fe0e6d2d 100644
--- a/components/shared/constellation/structured_data/transferable.rs
+++ b/components/shared/constellation/structured_data/transferable.rs
@@ -77,7 +77,12 @@ impl MessagePortImpl {
self.entangled_port
}
- /// Entanged this port with another.
+ /// <https://html.spec.whatwg.org/multipage/#disentangle>
+ pub fn disentangle(&mut self) -> Option<MessagePortId> {
+ self.entangled_port.take()
+ }
+
+ /// <https://html.spec.whatwg.org/multipage/#entangle>
pub fn entangle(&mut self, other_id: MessagePortId) {
self.entangled_port = Some(other_id);
}
diff --git a/components/shared/devtools/lib.rs b/components/shared/devtools/lib.rs
index 59857dc0d00..0cf99d22658 100644
--- a/components/shared/devtools/lib.rs
+++ b/components/shared/devtools/lib.rs
@@ -551,4 +551,5 @@ impl fmt::Display for ShadowRootMode {
pub struct SourceInfo {
pub url: ServoUrl,
pub external: bool,
+ pub worker_id: Option<WorkerId>,
}
diff --git a/components/shared/embedder/lib.rs b/components/shared/embedder/lib.rs
index 5f1171859dc..c87fa9019ef 100644
--- a/components/shared/embedder/lib.rs
+++ b/components/shared/embedder/lib.rs
@@ -14,7 +14,7 @@ pub mod user_content_manager;
mod webdriver;
use std::ffi::c_void;
-use std::fmt::{Debug, Error, Formatter};
+use std::fmt::{Debug, Display, Error, Formatter};
use std::path::PathBuf;
use std::sync::Arc;
@@ -784,3 +784,76 @@ pub enum AnimationState {
/// No animations are active but callbacks are queued
NoAnimationCallbacksPresent,
}
+
+/// A sequence number generated by a script thread for its pipelines. The
+/// constellation attaches the target pipeline's last seen `FocusSequenceNumber`
+/// to every focus-related message it sends.
+///
+/// This is used to resolve the inconsistency that occurs due to bidirectional
+/// focus state synchronization and provide eventual consistency. Example:
+///
+/// ```text
+/// script constellation
+/// -----------------------------------------------------------------------
+/// send ActivateDocument ----------> receive ActivateDocument
+/// ,---- send FocusDocument
+/// |
+/// focus an iframe |
+/// send Focus -----------------|---> receive Focus
+/// | focus the iframe's content document
+/// receive FocusDocument <-----' send FocusDocument to the content pipeline --> ...
+/// unfocus the iframe
+/// focus the document
+///
+/// Final state: Final state:
+/// the iframe is not focused the iframe is focused
+/// ```
+///
+/// When the above sequence completes, from the script thread's point of view,
+/// the iframe is unfocused, but from the constellation's point of view, the
+/// iframe is still focused.
+///
+/// This inconsistency can be resolved by associating a sequence number to each
+/// message. Whenever a script thread initiates a focus operation, it generates
+/// and sends a brand new sequence number. The constellation attaches the
+/// last-received sequence number to each message it sends. This way, the script
+/// thread can discard out-dated incoming focus messages, and eventually, all
+/// actors converge to the consistent state which is determined based on the
+/// last focus message received by the constellation.
+///
+/// ```text
+/// script constellation
+/// -----------------------------------------------------------------------
+/// send ActivateDocument ----------> receive ActivateDocument
+/// ,---- send FocusDocument (0)
+/// |
+/// seq_number += 1 |
+/// focus an iframe |
+/// send Focus (1) -------------|---> receive Focus (1)
+/// | focus the iframe's content document
+/// receive FocusDocument (0) <-' send FocusDocument to the content pipeline --> ...
+/// ignore it because 0 < 1
+///
+/// Final state: Final state:
+/// the iframe is focused the iframe is focused
+/// ```
+#[derive(
+ Clone,
+ Copy,
+ Debug,
+ Default,
+ Deserialize,
+ Eq,
+ Hash,
+ MallocSizeOf,
+ PartialEq,
+ Serialize,
+ PartialOrd,
+)]
+pub struct FocusSequenceNumber(pub u64);
+
+impl Display for FocusSequenceNumber {
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
+ Display::fmt(&self.0, f)
+ }
+}
diff --git a/components/shared/script/lib.rs b/components/shared/script/lib.rs
index 7323907cba3..748c42400a8 100644
--- a/components/shared/script/lib.rs
+++ b/components/shared/script/lib.rs
@@ -27,8 +27,8 @@ use crossbeam_channel::{RecvTimeoutError, Sender};
use devtools_traits::ScriptToDevtoolsControlMsg;
use embedder_traits::user_content_manager::UserContentManager;
use embedder_traits::{
- CompositorHitTestResult, InputEvent, MediaSessionActionType, Theme, ViewportDetails,
- WebDriverScriptCommand,
+ CompositorHitTestResult, FocusSequenceNumber, InputEvent, MediaSessionActionType, Theme,
+ ViewportDetails, WebDriverScriptCommand,
};
use euclid::{Rect, Scale, Size2D, UnknownUnit};
use ipc_channel::ipc::{IpcReceiver, IpcSender};
@@ -191,7 +191,15 @@ pub enum ScriptThreadMessage {
RemoveHistoryStates(PipelineId, Vec<HistoryStateId>),
/// Set an iframe to be focused. Used when an element in an iframe gains focus.
/// PipelineId is for the parent, BrowsingContextId is for the nested browsing context
- FocusIFrame(PipelineId, BrowsingContextId),
+ FocusIFrame(PipelineId, BrowsingContextId, FocusSequenceNumber),
+ /// Focus the document. Used when the container gains focus.
+ FocusDocument(PipelineId, FocusSequenceNumber),
+ /// Notifies that the document's container (e.g., an iframe) is not included
+ /// in the top-level browsing context's focus chain (not considering system
+ /// focus) anymore.
+ ///
+ /// Obviously, this message is invalid for a top-level document.
+ Unfocus(PipelineId, FocusSequenceNumber),
/// Passes a webdriver command to the script thread for execution
WebDriverScriptCommand(PipelineId, WebDriverScriptCommand),
/// Notifies script thread that all animations are done
diff --git a/components/webgl/Cargo.toml b/components/webgl/Cargo.toml
new file mode 100644
index 00000000000..b0c1c0ceb29
--- /dev/null
+++ b/components/webgl/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+name = "webgl"
+version.workspace = true
+authors.workspace = true
+license.workspace = true
+edition.workspace = true
+publish.workspace = true
+rust-version.workspace = true
+
+[lib]
+name = "webgl"
+path = "lib.rs"
+
+[features]
+webgl_backtrace = ["canvas_traits/webgl_backtrace"]
+webxr = ["dep:webxr", "dep:webxr-api"]
+
+[dependencies]
+bitflags = { workspace = true }
+byteorder = { workspace = true }
+canvas_traits = { workspace = true }
+compositing_traits = { workspace = true }
+crossbeam-channel = { workspace = true }
+euclid = { workspace = true }
+fnv = { workspace = true }
+glow = { workspace = true }
+half = "2"
+ipc-channel = { workspace = true }
+log = { workspace = true }
+pixels = { path = "../pixels" }
+snapshot = { workspace = true }
+surfman = { workspace = true }
+webrender = { workspace = true }
+webrender_api = { workspace = true }
+webxr = { path = "../webxr", features = ["ipc"], optional = true }
+webxr-api = { workspace = true, features = ["ipc"], optional = true }
diff --git a/components/webgl/lib.rs b/components/webgl/lib.rs
new file mode 100644
index 00000000000..923e7faad24
--- /dev/null
+++ b/components/webgl/lib.rs
@@ -0,0 +1,13 @@
+/* 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/. */
+
+#![deny(unsafe_code)]
+
+pub use webgl_mode::WebGLComm;
+
+mod webgl_limits;
+mod webgl_mode;
+pub mod webgl_thread;
+#[cfg(feature = "webxr")]
+mod webxr;
diff --git a/components/canvas/webgl_limits.rs b/components/webgl/webgl_limits.rs
index f683b6efff6..f683b6efff6 100644
--- a/components/canvas/webgl_limits.rs
+++ b/components/webgl/webgl_limits.rs
diff --git a/components/canvas/webgl_mode/inprocess.rs b/components/webgl/webgl_mode/inprocess.rs
index 566da2c58c8..566da2c58c8 100644
--- a/components/canvas/webgl_mode/inprocess.rs
+++ b/components/webgl/webgl_mode/inprocess.rs
diff --git a/components/canvas/webgl_mode/mod.rs b/components/webgl/webgl_mode/mod.rs
index 8bc74f6e244..8bc74f6e244 100644
--- a/components/canvas/webgl_mode/mod.rs
+++ b/components/webgl/webgl_mode/mod.rs
diff --git a/components/canvas/webgl_thread.rs b/components/webgl/webgl_thread.rs
index b1ac2b2d3c4..b1ac2b2d3c4 100644
--- a/components/canvas/webgl_thread.rs
+++ b/components/webgl/webgl_thread.rs
diff --git a/components/canvas/webxr.rs b/components/webgl/webxr.rs
index d43303e7393..d43303e7393 100644
--- a/components/canvas/webxr.rs
+++ b/components/webgl/webxr.rs