aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock34
-rw-r--r--components/canvas/Cargo.toml13
-rw-r--r--components/canvas/lib.rs7
-rw-r--r--components/constellation/constellation.rs171
-rw-r--r--components/constellation/tracing.rs2
-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/dom/globalscope.rs199
-rw-r--r--components/script/dom/htmldetailselement.rs4
-rw-r--r--components/script/dom/messageport.rs38
-rw-r--r--components/script/dom/node.rs50
-rw-r--r--components/script/dom/readablestream.rs8
-rw-r--r--components/script/dom/underlyingsourcecontainer.rs2
-rw-r--r--components/script/dom/writablestream.rs4
-rw-r--r--components/script/dom/writablestreamdefaultcontroller.rs10
-rw-r--r--components/script_bindings/codegen/Bindings.conf2
-rw-r--r--components/script_bindings/webidls/MessagePort.webidl1
-rw-r--r--components/servo/Cargo.toml7
-rw-r--r--components/servo/lib.rs2
-rw-r--r--components/shared/constellation/from_script_message.rs16
-rw-r--r--components/shared/constellation/lib.rs19
-rw-r--r--components/shared/constellation/structured_data/transferable.rs7
-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
-rw-r--r--tests/wpt/meta/MANIFEST.json4
-rw-r--r--tests/wpt/meta/html/dom/idlharness.any.js.ini3
-rw-r--r--tests/wpt/meta/html/dom/idlharness.https.html.ini6
-rw-r--r--tests/wpt/meta/webmessaging/message-channels/close-event/explicitly-closed.tentative.window.js.ini7
-rw-r--r--tests/wpt/tests/webmessaging/message-channels/close-event/explicitly-closed.tentative.window.js10
-rw-r--r--tests/wpt/tests/webmessaging/message-channels/close-event/resources/helper.js38
38 files changed, 502 insertions, 292 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 6ba344522cd..d2bdb6fdf48 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -784,18 +784,13 @@ name = "canvas"
version = "0.0.1"
dependencies = [
"app_units",
- "bitflags 2.9.0",
- "byteorder",
"canvas_traits",
"compositing_traits",
"crossbeam-channel",
"cssparser",
"euclid",
- "fnv",
"font-kit",
"fonts",
- "glow",
- "half",
"ipc-channel",
"log",
"lyon_geom",
@@ -807,12 +802,8 @@ dependencies = [
"servo_arc",
"snapshot",
"stylo",
- "surfman",
"unicode-script",
- "webrender",
"webrender_api",
- "webxr",
- "webxr-api",
]
[[package]]
@@ -4334,6 +4325,7 @@ dependencies = [
"tracing",
"url",
"webdriver_server",
+ "webgl",
"webgpu",
"webrender",
"webrender_api",
@@ -8490,6 +8482,30 @@ dependencies = [
]
[[package]]
+name = "webgl"
+version = "0.0.1"
+dependencies = [
+ "bitflags 2.9.0",
+ "byteorder",
+ "canvas_traits",
+ "compositing_traits",
+ "crossbeam-channel",
+ "euclid",
+ "fnv",
+ "glow",
+ "half",
+ "ipc-channel",
+ "log",
+ "pixels",
+ "snapshot",
+ "surfman",
+ "webrender",
+ "webrender_api",
+ "webxr",
+ "webxr-api",
+]
+
+[[package]]
name = "webgpu"
version = "0.0.1"
dependencies = [
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/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 2f9345c416f..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::{
@@ -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)]
@@ -1524,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,
@@ -2117,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.
@@ -2151,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!(
@@ -2165,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.
@@ -2177,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.
@@ -2196,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.
@@ -2204,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()
{
@@ -2251,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.
@@ -2341,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
- ),
}
}
@@ -2410,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);
@@ -2482,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>
diff --git a/components/constellation/tracing.rs b/components/constellation/tracing.rs
index 5c9a09e1f13..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(..) => {
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/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/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/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/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_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/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 625d642033e..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;
@@ -35,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)]
@@ -470,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),
@@ -482,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,
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/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
diff --git a/tests/wpt/meta/MANIFEST.json b/tests/wpt/meta/MANIFEST.json
index 750266dc59c..72c930afccf 100644
--- a/tests/wpt/meta/MANIFEST.json
+++ b/tests/wpt/meta/MANIFEST.json
@@ -518802,7 +518802,7 @@
"close-event": {
"resources": {
"helper.js": [
- "cb9ea9fe981e95374b836255c752a42de788fc7b",
+ "48744ac1c5b530ef8d46c3d9a0378c698353a5bc",
[]
]
}
@@ -848537,7 +848537,7 @@
]
],
"explicitly-closed.tentative.window.js": [
- "612003d58eaea908ad93294a7bbf777184356a28",
+ "12bfa0bd73e9278e39b825d4fa81437f943cbd02",
[
"webmessaging/message-channels/close-event/explicitly-closed.tentative.window.html",
{
diff --git a/tests/wpt/meta/html/dom/idlharness.any.js.ini b/tests/wpt/meta/html/dom/idlharness.any.js.ini
index f17466adc7f..ad5e57e0759 100644
--- a/tests/wpt/meta/html/dom/idlharness.any.js.ini
+++ b/tests/wpt/meta/html/dom/idlharness.any.js.ini
@@ -95,9 +95,6 @@
[History interface: existence and properties of interface object]
expected: FAIL
- [MessagePort interface: attribute onclose]
- expected: FAIL
-
[WorkerGlobalScope interface: attribute onlanguagechange]
expected: FAIL
diff --git a/tests/wpt/meta/html/dom/idlharness.https.html.ini b/tests/wpt/meta/html/dom/idlharness.https.html.ini
index 64533ac1838..ac7504347d7 100644
--- a/tests/wpt/meta/html/dom/idlharness.https.html.ini
+++ b/tests/wpt/meta/html/dom/idlharness.https.html.ini
@@ -1517,9 +1517,6 @@
[SVGSVGElement interface: attribute onpagereveal]
expected: FAIL
- [MessagePort interface: attribute onclose]
- expected: FAIL
-
[NotRestoredReasonDetails interface: existence and properties of interface object]
expected: FAIL
@@ -5363,9 +5360,6 @@
[Navigator interface: window.navigator must inherit property "pdfViewerEnabled" with the proper type]
expected: FAIL
- [MessagePort interface: attribute onclose]
- expected: FAIL
-
[SharedWorker interface: existence and properties of interface object]
expected: FAIL
diff --git a/tests/wpt/meta/webmessaging/message-channels/close-event/explicitly-closed.tentative.window.js.ini b/tests/wpt/meta/webmessaging/message-channels/close-event/explicitly-closed.tentative.window.js.ini
deleted file mode 100644
index c625c16f713..00000000000
--- a/tests/wpt/meta/webmessaging/message-channels/close-event/explicitly-closed.tentative.window.js.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[explicitly-closed.tentative.window.html]
- expected: TIMEOUT
- [Close event on port2 is fired when port1 is explicitly closed]
- expected: TIMEOUT
-
- [Close event on port2 is fired when port1, which is in a different window, is explicitly closed.]
- expected: TIMEOUT
diff --git a/tests/wpt/tests/webmessaging/message-channels/close-event/explicitly-closed.tentative.window.js b/tests/wpt/tests/webmessaging/message-channels/close-event/explicitly-closed.tentative.window.js
index 612003d58ea..12bfa0bd73e 100644
--- a/tests/wpt/tests/webmessaging/message-channels/close-event/explicitly-closed.tentative.window.js
+++ b/tests/wpt/tests/webmessaging/message-channels/close-event/explicitly-closed.tentative.window.js
@@ -33,3 +33,13 @@ promise_test(async t => {
});
await closeEventPromise;
}, 'Close event on port2 is fired when port1, which is in a different window, is explicitly closed.')
+
+promise_test(async t => {
+ const rc = await addWindow();
+ const waitForPort = expectMessagePortFromWindowWithoutStartingIt(window);
+ await createMessageChannelAndSendPortFollowedByClose(rc);
+ const port = await waitForPort;
+ const closeEventPromise = createCloseEventPromise(port);
+ port.start();
+ await closeEventPromise;
+}, 'Close event on port2 is fired when port1, in a different window, is closed during the transfer of port2.')
diff --git a/tests/wpt/tests/webmessaging/message-channels/close-event/resources/helper.js b/tests/wpt/tests/webmessaging/message-channels/close-event/resources/helper.js
index cb9ea9fe981..48744ac1c5b 100644
--- a/tests/wpt/tests/webmessaging/message-channels/close-event/resources/helper.js
+++ b/tests/wpt/tests/webmessaging/message-channels/close-event/resources/helper.js
@@ -22,6 +22,44 @@ function expectMessagePortFromWindow(window) {
}
/**
+ * Create a new promise that resolves when the window receives
+ * the MessagePort and does not start it.
+ *
+ * @param {Window} window - The window to wait for the MessagePort.
+ * @returns {Promise<MessagePort>} A promise you should await to ensure the
+ * window
+ * receives the MessagePort.
+ */
+function expectMessagePortFromWindowWithoutStartingIt(window) {
+ return new Promise(resolve => {
+ window.onmessage = e => {
+ try {
+ assert_true(e.ports[0] instanceof window.MessagePort);
+ resolve(e.ports[0]);
+ } catch (e) {
+ reject(e);
+ }
+ };
+ });
+}
+
+/**
+ * Create a new MessageChannel and transfers one of the ports to
+ * the window which opened the window with a remote context provided
+ * as an argument, and immediately closes the entangled port.
+ *
+ * @param {RemoteContextWrapper} remoteContextWrapper
+ */
+async function createMessageChannelAndSendPortFollowedByClose(remoteContextWrapper) {
+ await remoteContextWrapper.executeScript(() => {
+ const {port1, port2} = new MessageChannel();
+ port1.start();
+ window.opener.postMessage({}, '*', [port2]);
+ port1.close();
+ });
+}
+
+/**
* Create a new MessageChannel and transfers one of the ports to
* the window which opened the window with a remote context provided
* as an argument.