diff options
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. |