diff options
Diffstat (limited to 'components')
85 files changed, 2587 insertions, 593 deletions
diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 41286a2760a..b1669277ba1 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -26,9 +26,9 @@ use crossbeam_channel::{Receiver, Sender}; use dpi::PhysicalSize; use embedder_traits::{ CompositorHitTestResult, Cursor, InputEvent, MouseButtonEvent, MouseMoveEvent, ShutdownState, - TouchEventType, UntrustedNodeAddress, ViewportDetails, + TouchEventType, UntrustedNodeAddress, ViewportDetails, WheelDelta, WheelEvent, WheelMode, }; -use euclid::{Point2D, Rect, Scale, Size2D, Transform3D}; +use euclid::{Point2D, Rect, Scale, Size2D, Transform3D, Vector2D}; use fnv::FnvHashMap; use ipc_channel::ipc::{self, IpcSharedMemory}; use libc::c_void; @@ -646,6 +646,26 @@ impl IOCompositor { .dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent { point })); }, + CompositorMsg::WebDriverWheelScrollEvent(webview_id, x, y, delta_x, delta_y) => { + let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else { + warn!("Handling input event for unknown webview: {webview_id}"); + return; + }; + let delta = WheelDelta { + x: delta_x, + y: delta_y, + z: 0.0, + mode: WheelMode::DeltaPixel, + }; + let dppx = webview_renderer.device_pixels_per_page_pixel(); + let point = dppx.transform_point(Point2D::new(x, y)); + let scroll_delta = + dppx.transform_vector(Vector2D::new(delta_x as f32, delta_y as f32)); + webview_renderer + .dispatch_input_event(InputEvent::Wheel(WheelEvent { delta, point })); + webview_renderer.on_webdriver_wheel_action(scroll_delta, point); + }, + CompositorMsg::SendInitialTransaction(pipeline) => { let mut txn = Transaction::new(); txn.set_display_list(WebRenderEpoch(0), (pipeline, Default::default())); diff --git a/components/compositing/tracing.rs b/components/compositing/tracing.rs index ae7338106d0..a8bb8b42bb8 100644 --- a/components/compositing/tracing.rs +++ b/components/compositing/tracing.rs @@ -42,6 +42,7 @@ mod from_constellation { Self::LoadComplete(..) => target!("LoadComplete"), Self::WebDriverMouseButtonEvent(..) => target!("WebDriverMouseButtonEvent"), Self::WebDriverMouseMoveEvent(..) => target!("WebDriverMouseMoveEvent"), + Self::WebDriverWheelScrollEvent(..) => target!("WebDriverWheelScrollEvent"), Self::SendInitialTransaction(..) => target!("SendInitialTransaction"), Self::SendScrollNode(..) => target!("SendScrollNode"), Self::SendDisplayList { .. } => target!("SendDisplayList"), diff --git a/components/compositing/webview_renderer.rs b/components/compositing/webview_renderer.rs index 614ef0ff4c3..f76dc68013d 100644 --- a/components/compositing/webview_renderer.rs +++ b/components/compositing/webview_renderer.rs @@ -726,6 +726,22 @@ impl WebViewRenderer { })); } + /// Push scroll pending event when receiving wheel action from webdriver + pub(crate) fn on_webdriver_wheel_action( + &mut self, + scroll_delta: Vector2D<f32, DevicePixel>, + point: Point2D<f32, DevicePixel>, + ) { + if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown { + return; + } + + let scroll_location = + ScrollLocation::Delta(LayoutVector2D::from_untyped(scroll_delta.to_untyped())); + let cursor = DeviceIntPoint::new(point.x as i32, point.y as i32); + self.on_scroll_window_event(scroll_location, cursor) + } + pub(crate) fn process_pending_scroll_events(&mut self, compositor: &mut IOCompositor) { if self.pending_scroll_zoom_events.is_empty() { return; diff --git a/components/config/prefs.rs b/components/config/prefs.rs index 96c40c91360..a9ec112e3eb 100644 --- a/components/config/prefs.rs +++ b/components/config/prefs.rs @@ -236,6 +236,8 @@ pub struct Preferences { /// The user-agent to use for Servo. This can also be set via [`UserAgentPlatform`] in /// order to set the value to the default value for the given platform. pub user_agent: String, + + pub log_filter: String, } impl Preferences { @@ -398,6 +400,7 @@ impl Preferences { threadpools_webrender_workers_max: 4, webgl_testing_context_creation_error: false, user_agent: String::new(), + log_filter: String::new(), } } } diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index f3a15d7708d..e493a97d184 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -4783,6 +4783,12 @@ where self.compositor_proxy .send(CompositorMsg::WebDriverMouseMoveEvent(webview_id, x, y)); }, + WebDriverCommandMsg::WheelScrollAction(webview, x, y, delta_x, delta_y) => { + self.compositor_proxy + .send(CompositorMsg::WebDriverWheelScrollEvent( + webview, x, y, delta_x, delta_y, + )); + }, WebDriverCommandMsg::TakeScreenshot(webview_id, rect, response_sender) => { self.compositor_proxy.send(CompositorMsg::CreatePng( webview_id, diff --git a/components/constellation/sandboxing.rs b/components/constellation/sandboxing.rs index 3738b4f288b..b4c6e7a9a39 100644 --- a/components/constellation/sandboxing.rs +++ b/components/constellation/sandboxing.rs @@ -159,6 +159,7 @@ pub fn spawn_multiprocess(content: UnprivilegedContent) -> Result<Process, Error let mut child_process = process::Command::new(path_to_self); setup_common(&mut child_process, token); + #[allow(clippy::zombie_processes)] let child = child_process .spawn() .expect("Failed to start unsandboxed child process!"); diff --git a/components/devtools/actors/browsing_context.rs b/components/devtools/actors/browsing_context.rs index 5de0855df4a..81a00e82d47 100644 --- a/components/devtools/actors/browsing_context.rs +++ b/components/devtools/actors/browsing_context.rs @@ -149,10 +149,6 @@ impl ResourceAvailable for BrowsingContextActor { fn actor_name(&self) -> String { self.name.clone() } - - fn get_streams(&self) -> &RefCell<HashMap<StreamId, TcpStream>> { - &self.streams - } } impl Actor for BrowsingContextActor { diff --git a/components/devtools/actors/console.rs b/components/devtools/actors/console.rs index 3897ffa0fce..fd0bd184bfa 100644 --- a/components/devtools/actors/console.rs +++ b/components/devtools/actors/console.rs @@ -252,6 +252,7 @@ impl ConsoleActor { page_error: PageError, id: UniqueId, registry: &ActorRegistry, + stream: &mut TcpStream, ) { self.cached_events .borrow_mut() @@ -262,7 +263,11 @@ impl ConsoleActor { if let Root::BrowsingContext(bc) = &self.root { registry .find::<BrowsingContextActor>(bc) - .resource_available(PageErrorWrapper { page_error }, "error-message".into()) + .resource_available( + PageErrorWrapper { page_error }, + "error-message".into(), + stream, + ) }; } } @@ -272,6 +277,7 @@ impl ConsoleActor { console_message: ConsoleMessage, id: UniqueId, registry: &ActorRegistry, + stream: &mut TcpStream, ) { let log_message: ConsoleLog = console_message.into(); self.cached_events @@ -283,7 +289,7 @@ impl ConsoleActor { if let Root::BrowsingContext(bc) = &self.root { registry .find::<BrowsingContextActor>(bc) - .resource_available(log_message, "console-message".into()) + .resource_available(log_message, "console-message".into(), stream) }; } } diff --git a/components/devtools/actors/watcher.rs b/components/devtools/actors/watcher.rs index b0b2c755fd8..061ffc92336 100644 --- a/components/devtools/actors/watcher.rs +++ b/components/devtools/actors/watcher.rs @@ -31,7 +31,7 @@ use crate::actors::watcher::thread_configuration::{ ThreadConfigurationActor, ThreadConfigurationActorMsg, }; use crate::protocol::JsonPacketStream; -use crate::resource::{ResourceAvailable, ResourceAvailableReply}; +use crate::resource::ResourceAvailable; use crate::{EmptyReplyMsg, StreamId, WorkerActor}; pub mod network_parent; @@ -291,28 +291,28 @@ impl Actor for WatcherActor { title: Some(target.title.borrow().clone()), url: Some(target.url.borrow().clone()), }; - target.resource_available(event, "document-event".into()); + target.resource_available(event, "document-event".into(), stream); } }, "source" => { let thread_actor = registry.find::<ThreadActor>(&target.thread); let sources = thread_actor.source_manager.sources(); - target.resources_available(sources.iter().collect(), "source".into()); + target.resources_available( + sources.iter().collect(), + "source".into(), + stream, + ); for worker_name in &root.workers { let worker = registry.find::<WorkerActor>(worker_name); let thread = registry.find::<ThreadActor>(&worker.thread); let worker_sources = thread.source_manager.sources(); - let msg = ResourceAvailableReply { - from: worker.name(), - type_: "resources-available-array".into(), - array: vec![( - "source".to_string(), - worker_sources.iter().cloned().collect(), - )], - }; - let _ = stream.write_json_packet(&msg); + worker.resources_available( + worker_sources.iter().collect(), + "source".into(), + stream, + ); } }, "console-message" | "error-message" => {}, diff --git a/components/devtools/actors/worker.rs b/components/devtools/actors/worker.rs index 68ff56fb3b2..f3ca4f2aed7 100644 --- a/components/devtools/actors/worker.rs +++ b/components/devtools/actors/worker.rs @@ -17,7 +17,7 @@ use servo_url::ServoUrl; use crate::StreamId; use crate::actor::{Actor, ActorMessageStatus, ActorRegistry}; use crate::protocol::JsonPacketStream; -use crate::resource::{ResourceAvailable, ResourceAvailableReply}; +use crate::resource::ResourceAvailable; #[derive(Clone, Copy)] #[allow(dead_code)] @@ -60,10 +60,6 @@ impl ResourceAvailable for WorkerActor { fn actor_name(&self) -> String { self.name.clone() } - - fn get_streams(&self) -> &RefCell<HashMap<StreamId, TcpStream>> { - &self.streams - } } impl Actor for WorkerActor { @@ -133,28 +129,6 @@ impl Actor for WorkerActor { } } -impl WorkerActor { - pub(crate) fn resource_available<T: Serialize>(&self, resource: T, resource_type: String) { - self.resources_available(vec![resource], resource_type); - } - - pub(crate) fn resources_available<T: Serialize>( - &self, - resources: Vec<T>, - resource_type: String, - ) { - let msg = ResourceAvailableReply::<T> { - from: self.name(), - type_: "resources-available-array".into(), - array: vec![(resource_type, resources)], - }; - - for stream in self.streams.borrow_mut().values_mut() { - let _ = stream.write_json_packet(&msg); - } - } -} - #[derive(Serialize)] struct DetachedReply { from: String, diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs index 5fb9485e9d3..d097cb25e9d 100644 --- a/components/devtools/lib.rs +++ b/components/devtools/lib.rs @@ -414,7 +414,7 @@ impl DevtoolsInstance { } fn handle_page_error( - &self, + &mut self, pipeline_id: PipelineId, worker_id: Option<WorkerId>, page_error: PageError, @@ -426,11 +426,13 @@ impl DevtoolsInstance { let actors = self.actors.lock().unwrap(); let console_actor = actors.find::<ConsoleActor>(&console_actor_name); let id = worker_id.map_or(UniqueId::Pipeline(pipeline_id), UniqueId::Worker); - console_actor.handle_page_error(page_error, id, &actors); + for stream in self.connections.values_mut() { + console_actor.handle_page_error(page_error.clone(), id.clone(), &actors, stream); + } } fn handle_console_message( - &self, + &mut self, pipeline_id: PipelineId, worker_id: Option<WorkerId>, console_message: ConsoleMessage, @@ -442,7 +444,9 @@ impl DevtoolsInstance { let actors = self.actors.lock().unwrap(); let console_actor = actors.find::<ConsoleActor>(&console_actor_name); let id = worker_id.map_or(UniqueId::Pipeline(pipeline_id), UniqueId::Worker); - console_actor.handle_console_api(console_message, id, &actors); + for stream in self.connections.values_mut() { + console_actor.handle_console_api(console_message.clone(), id.clone(), &actors, stream); + } } fn find_console_actor( @@ -529,7 +533,10 @@ impl DevtoolsInstance { }; let worker_actor = actors.find::<WorkerActor>(worker_actor_name); - worker_actor.resource_available(source, "source".into()); + + for stream in self.connections.values_mut() { + worker_actor.resource_available(&source, "source".into(), stream); + } } else { let Some(browsing_context_id) = self.pipelines.get(&pipeline_id) else { return; @@ -556,7 +563,10 @@ impl DevtoolsInstance { // Notify browsing context about the new source let browsing_context = actors.find::<BrowsingContextActor>(actor_name); - browsing_context.resource_available(source, "source".into()); + + for stream in self.connections.values_mut() { + browsing_context.resource_available(&source, "source".into(), stream); + } } } } diff --git a/components/devtools/resource.rs b/components/devtools/resource.rs index 7cef8188cc8..4e6aa4042b8 100644 --- a/components/devtools/resource.rs +++ b/components/devtools/resource.rs @@ -2,13 +2,10 @@ * 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::cell::RefCell; -use std::collections::HashMap; use std::net::TcpStream; use serde::Serialize; -use crate::StreamId; use crate::protocol::JsonPacketStream; #[derive(Serialize)] @@ -22,21 +19,27 @@ pub(crate) struct ResourceAvailableReply<T: Serialize> { pub(crate) trait ResourceAvailable { fn actor_name(&self) -> String; - fn get_streams(&self) -> &RefCell<HashMap<StreamId, TcpStream>>; - - fn resource_available<T: Serialize>(&self, resource: T, resource_type: String) { - self.resources_available(vec![resource], resource_type); + fn resource_available<T: Serialize>( + &self, + resource: T, + resource_type: String, + stream: &mut TcpStream, + ) { + self.resources_available(vec![resource], resource_type, stream); } - fn resources_available<T: Serialize>(&self, resources: Vec<T>, resource_type: String) { + fn resources_available<T: Serialize>( + &self, + resources: Vec<T>, + resource_type: String, + stream: &mut TcpStream, + ) { let msg = ResourceAvailableReply::<T> { from: self.actor_name(), type_: "resources-available-array".into(), array: vec![(resource_type, resources)], }; - for stream in self.get_streams().borrow_mut().values_mut() { - let _ = stream.write_json_packet(&msg); - } + let _ = stream.write_json_packet(&msg); } } diff --git a/components/layout/display_list/mod.rs b/components/layout/display_list/mod.rs index 8799dd2da0c..d6cbb50e4a1 100644 --- a/components/layout/display_list/mod.rs +++ b/components/layout/display_list/mod.rs @@ -153,10 +153,6 @@ pub(crate) struct DisplayListBuilder<'a> { /// list building functions. current_clip_chain_id: ClipChainId, - /// The [OpaqueNode] handle to the node used to paint the page background - /// if the background was a canvas. - element_for_canvas_background: OpaqueNode, - /// A [LayoutContext] used to get information about the device pixel ratio /// and get handles to WebRender images. pub context: &'a LayoutContext<'a>, @@ -169,6 +165,11 @@ pub(crate) struct DisplayListBuilder<'a> { /// This data is collected during the traversal of the fragment tree and used /// to paint the highlight at the very end. inspector_highlight: Option<InspectorHighlight>, + + /// Whether or not the `<body>` element should be painted. This is false if the root `<html>` + /// element inherits the `<body>`'s background to paint the page canvas background. + /// See <https://drafts.csswg.org/css-backgrounds/#body-background>. + paint_body_background: bool, } struct InspectorHighlight { @@ -218,12 +219,12 @@ impl DisplayList { current_scroll_node_id: self.compositor_info.root_reference_frame_id, current_reference_frame_scroll_node_id: self.compositor_info.root_reference_frame_id, current_clip_chain_id: ClipChainId::INVALID, - element_for_canvas_background: fragment_tree.canvas_background.from_element, context, display_list: self, inspector_highlight: context .highlighted_dom_node .map(InspectorHighlight::for_node), + paint_body_background: true, }; fragment_tree.build_display_list(&mut builder, root_stacking_context); @@ -999,12 +1000,17 @@ impl<'a> BuilderForBoxFragment<'a> { } fn build_background(&mut self, builder: &mut DisplayListBuilder) { - if self - .fragment - .base - .is_for_node(builder.element_for_canvas_background) + let flags = self.fragment.base.flags; + + // The root element's background is painted separately as it might inherit the `<body>`'s + // background. + if flags.intersects(FragmentFlags::IS_ROOT_ELEMENT) { + return; + } + // If the `<body>` background was inherited by the root element, don't paint it again here. + if !builder.paint_body_background && + flags.intersects(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) { - // This background is already painted for the canvas, don’t paint it again here. return; } @@ -1020,7 +1026,7 @@ impl<'a> BuilderForBoxFragment<'a> { for extra_background in extra_backgrounds { let positioning_area = extra_background.rect; let painter = BackgroundPainter { - style: &extra_background.style, + style: &extra_background.style.borrow_mut().0, painting_area_override: None, positioning_area_override: Some( positioning_area diff --git a/components/layout/display_list/stacking_context.rs b/components/layout/display_list/stacking_context.rs index b044b713260..27fa73a680c 100644 --- a/components/layout/display_list/stacking_context.rs +++ b/components/layout/display_list/stacking_context.rs @@ -582,15 +582,42 @@ impl StackingContext { &self, builder: &mut DisplayListBuilder, fragment_tree: &crate::FragmentTree, - containing_block_rect: &PhysicalRect<Au>, ) { - let style = if let Some(style) = &fragment_tree.canvas_background.style { - style - } else { - // The root element has `display: none`, - // or the canvas background is taken from `<body>` which has `display: none` + let Some(root_fragment) = fragment_tree.root_fragments.iter().find(|fragment| { + fragment + .base() + .is_some_and(|base| base.flags.intersects(FragmentFlags::IS_ROOT_ELEMENT)) + }) else { return; }; + let root_fragment = match root_fragment { + Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment, + _ => return, + } + .borrow(); + + let source_style = { + // > For documents whose root element is an HTML HTML element or an XHTML html element + // > [HTML]: if the computed value of background-image on the root element is none and its + // > background-color is transparent, user agents must instead propagate the computed + // > values of the background properties from that element’s first HTML BODY or XHTML body + // > child element. + if root_fragment.style.background_is_transparent() { + let body_fragment = fragment_tree.body_fragment(); + builder.paint_body_background = body_fragment.is_none(); + body_fragment + .map(|body_fragment| body_fragment.borrow().style.clone()) + .unwrap_or(root_fragment.style.clone()) + } else { + root_fragment.style.clone() + } + }; + + // This can happen if the root fragment does not have a `<body>` child (either because it is + // `display: none` or `display: contents`) or if the `<body>`'s background is transparent. + if source_style.background_is_transparent() { + return; + } // The painting area is theoretically the infinite 2D plane, // but we need a rectangle with finite coordinates. @@ -598,14 +625,15 @@ impl StackingContext { // If the document is smaller than the viewport (and doesn’t scroll), // we still want to paint the rest of the viewport. // If it’s larger, we also want to paint areas reachable after scrolling. - let mut painting_area = fragment_tree + let painting_area = fragment_tree .initial_containing_block .union(&fragment_tree.scrollable_overflow) .to_webrender(); - let background_color = style.resolve_color(&style.get_background().background_color); + let background_color = + source_style.resolve_color(&source_style.get_background().background_color); if background_color.alpha > 0.0 { - let common = builder.common_properties(painting_area, style); + let common = builder.common_properties(painting_area, &source_style); let color = super::rgba(background_color); builder .display_list @@ -613,97 +641,14 @@ impl StackingContext { .push_rect(&common, painting_area, color) } - // `background-color` was comparatively easy, - // but `background-image` needs a positioning area based on the root element. - // Let’s find the corresponding fragment. - - // The fragment generated by the root element is the first one here, unless… - let first_if_any = self.contents.first().or_else(|| { - // There wasn’t any `StackingContextFragment` in the root `StackingContext`, - // because the root element generates a stacking context. Let’s find that one. - self.real_stacking_contexts_and_positioned_stacking_containers - .first() - .and_then(|first_child_stacking_context| { - first_child_stacking_context.contents.first() - }) - }); - - macro_rules! debug_panic { - ($msg: expr) => { - if cfg!(debug_assertions) { - panic!($msg); - } else { - warn!($msg); - return; - } - }; - } - - let first_stacking_context_fragment = if let Some(first) = first_if_any { - first - } else { - // This should only happen if the root element has `display: none` - // TODO(servo#30569) revert to debug_panic!() once underlying bug is fixed - log::warn!( - "debug assertion failed! `CanvasBackground::for_root_element` should have returned `style: None`", - ); - return; - }; - - let StackingContextContent::Fragment { - fragment, - scroll_node_id, - containing_block, - .. - } = first_stacking_context_fragment - else { - debug_panic!("Expected a fragment, not a stacking container"); - }; - let box_fragment = match fragment { - Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment, - _ => debug_panic!("Expected a box-generated fragment"), - }; - let box_fragment = &*box_fragment.borrow(); - - // The `StackingContextFragment` we found is for the root DOM element: - debug_assert_eq!( - fragment.tag().map(|tag| tag.node), - Some(fragment_tree.canvas_background.root_element), - ); - - // The root element may have a CSS transform, and we want the canvas’ - // background image to be transformed. To do so, take its `SpatialId` - // (but not its `ClipId`) - builder.current_scroll_node_id = *scroll_node_id; - - // Now we need express the painting area rectangle in the local coordinate system, - // which differs from the top-level coordinate system based on… - - // Convert the painting area rectangle to the local coordinate system of this `SpatialId` - if let Some(reference_frame_data) = - box_fragment.reference_frame_data_if_necessary(containing_block_rect) - { - painting_area.min -= reference_frame_data.origin.to_webrender().to_vector(); - if let Some(transformed) = reference_frame_data - .transform - .inverse() - .and_then(|inversed| inversed.outer_transformed_rect(&painting_area.to_rect())) - { - painting_area = transformed.to_box2d(); - } else { - // The desired rect cannot be represented, so skip painting this background-image - return; - } - } - let mut fragment_builder = BuilderForBoxFragment::new( - box_fragment, - containing_block, + &root_fragment, + &fragment_tree.initial_containing_block, false, /* is_hit_test_for_scrollable_overflow */ false, /* is_collapsed_table_borders */ ); let painter = super::background::BackgroundPainter { - style, + style: &source_style, painting_area_override: Some(painting_area), positioning_area_override: None, }; diff --git a/components/layout/flow/mod.rs b/components/layout/flow/mod.rs index 0c326c4cc6d..e23193f3904 100644 --- a/components/layout/flow/mod.rs +++ b/components/layout/flow/mod.rs @@ -52,7 +52,7 @@ pub mod inline; mod root; pub(crate) use construct::BlockContainerBuilder; -pub use root::{BoxTree, CanvasBackground}; +pub use root::BoxTree; #[derive(Debug, MallocSizeOf)] pub(crate) struct BlockFormattingContext { diff --git a/components/layout/flow/root.rs b/components/layout/flow/root.rs index e813777b6fe..ec85f3574dc 100644 --- a/components/layout/flow/root.rs +++ b/components/layout/flow/root.rs @@ -12,7 +12,7 @@ use script_layout_interface::wrapper_traits::{ }; use script_layout_interface::{LayoutElementType, LayoutNodeType}; use servo_arc::Arc; -use style::dom::{NodeInfo, OpaqueNode, TNode}; +use style::dom::{NodeInfo, TNode}; use style::properties::ComputedValues; use style::values::computed::Overflow; use style_traits::CSSPixel; @@ -30,7 +30,7 @@ use crate::fragment_tree::FragmentTree; use crate::geom::{LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSize}; use crate::positioned::{AbsolutelyPositionedBox, PositioningContext}; use crate::replaced::ReplacedContents; -use crate::style_ext::{ComputedValuesExt, Display, DisplayGeneratingBox, DisplayInside}; +use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside}; use crate::taffy::{TaffyItemBox, TaffyItemBoxInner}; use crate::{DefiniteContainingBlock, PropagatedBoxTreeData}; @@ -40,9 +40,6 @@ pub struct BoxTree { /// There may be zero if that element has `display: none`. root: BlockFormattingContext, - /// <https://drafts.csswg.org/css-backgrounds/#special-backgrounds> - canvas_background: CanvasBackground, - /// Whether or not the viewport should be sensitive to scrolling input events in two axes viewport_scroll_sensitivity: AxesScrollSensitivity, } @@ -96,7 +93,6 @@ impl BoxTree { contents, contains_floats, }, - canvas_background: CanvasBackground::for_root_element(context, root_element), // From https://www.w3.org/TR/css-overflow-3/#overflow-propagation: // > If visible is applied to the viewport, it must be interpreted as auto. // > If clip is applied to the viewport, it must be interpreted as hidden. @@ -425,69 +421,7 @@ impl BoxTree { root_fragments, scrollable_overflow, physical_containing_block, - self.canvas_background.clone(), self.viewport_scroll_sensitivity, ) } } - -/// <https://drafts.csswg.org/css-backgrounds/#root-background> -#[derive(Clone, MallocSizeOf)] -pub struct CanvasBackground { - /// DOM node for the root element - pub root_element: OpaqueNode, - - /// The element whose style the canvas takes background properties from (see next field). - /// This can be the root element (same as the previous field), or the HTML `<body>` element. - /// See <https://drafts.csswg.org/css-backgrounds/#body-background> - pub from_element: OpaqueNode, - - /// The computed styles to take background properties from. - #[conditional_malloc_size_of] - pub style: Option<Arc<ComputedValues>>, -} - -impl CanvasBackground { - fn for_root_element(context: &LayoutContext, root_element: ServoLayoutNode<'_>) -> Self { - let root_style = root_element.style(context); - - let mut style = root_style; - let mut from_element = root_element; - - // https://drafts.csswg.org/css-backgrounds/#body-background - // “if the computed value of background-image on the root element is none - // and its background-color is transparent” - if style.background_is_transparent() && - // “For documents whose root element is an HTML `HTML` element - // or an XHTML `html` element” - root_element.type_id() == LayoutNodeType::Element(LayoutElementType::HTMLHtmlElement) && - // Don’t try to access styles for an unstyled subtree - !matches!(style.clone_display().into(), Display::None) - { - // “that element’s first HTML `BODY` or XHTML `body` child element” - if let Some(body) = iter_child_nodes(root_element).find(|child| { - child.is_element() && - child.type_id() == - LayoutNodeType::Element(LayoutElementType::HTMLBodyElement) - }) { - style = body.style(context); - from_element = body; - } - } - - Self { - root_element: root_element.opaque(), - from_element: from_element.opaque(), - - // “However, if no boxes are generated for the element - // whose background would be used for the canvas - // (for example, if the root element has display: none), - // then the canvas background is transparent.” - style: if let Display::GeneratingBox(_) = style.clone_display().into() { - Some(style) - } else { - None - }, - } - } -} diff --git a/components/layout/fragment_tree/base_fragment.rs b/components/layout/fragment_tree/base_fragment.rs index 0cf6ee511cb..ff5df44c225 100644 --- a/components/layout/fragment_tree/base_fragment.rs +++ b/components/layout/fragment_tree/base_fragment.rs @@ -32,10 +32,8 @@ impl BaseFragment { } } - /// Returns true if this fragment is non-anonymous and it is for the given - /// OpaqueNode, regardless of the pseudo element. - pub(crate) fn is_for_node(&self, node: OpaqueNode) -> bool { - self.tag.map(|tag| tag.node == node).unwrap_or(false) + pub(crate) fn is_anonymous(&self) -> bool { + self.tag.is_none() } } diff --git a/components/layout/fragment_tree/box_fragment.rs b/components/layout/fragment_tree/box_fragment.rs index 65ad1c4aa93..596556b296c 100644 --- a/components/layout/fragment_tree/box_fragment.rs +++ b/components/layout/fragment_tree/box_fragment.rs @@ -16,7 +16,8 @@ use style::logical_geometry::WritingMode; use style::properties::ComputedValues; use style::values::specified::box_::DisplayOutside; -use super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment}; +use super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment, FragmentFlags}; +use crate::ArcRefCell; use crate::display_list::ToWebRender; use crate::formatting_contexts::Baselines; use crate::geom::{ @@ -40,10 +41,14 @@ pub(crate) enum BackgroundMode { Normal, } +#[derive(Debug, MallocSizeOf)] +pub(crate) struct BackgroundStyle(#[conditional_malloc_size_of] pub ServoArc<ComputedValues>); + +pub(crate) type SharedBackgroundStyle = ArcRefCell<BackgroundStyle>; + #[derive(MallocSizeOf)] pub(crate) struct ExtraBackground { - #[conditional_malloc_size_of] - pub style: ServoArc<ComputedValues>, + pub style: SharedBackgroundStyle, pub rect: PhysicalRect<Au>, } @@ -238,6 +243,16 @@ impl BoxFragment { self.margin + self.border + self.padding } + pub(crate) fn is_root_element(&self) -> bool { + self.base.flags.intersects(FragmentFlags::IS_ROOT_ELEMENT) + } + + pub(crate) fn is_body_element_of_html_element_root(&self) -> bool { + self.base + .flags + .intersects(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) + } + pub fn print(&self, tree: &mut PrintTree) { tree.new_level(format!( "Box\ diff --git a/components/layout/fragment_tree/fragment_tree.rs b/components/layout/fragment_tree/fragment_tree.rs index 1499a50dacf..979bd0090fc 100644 --- a/components/layout/fragment_tree/fragment_tree.rs +++ b/components/layout/fragment_tree/fragment_tree.rs @@ -11,10 +11,10 @@ use malloc_size_of_derive::MallocSizeOf; use style::animation::AnimationSetKey; use webrender_api::units; -use super::{ContainingBlockManager, Fragment}; +use super::{BoxFragment, ContainingBlockManager, Fragment}; +use crate::ArcRefCell; use crate::context::LayoutContext; use crate::display_list::StackingContext; -use crate::flow::CanvasBackground; use crate::geom::PhysicalRect; #[derive(MallocSizeOf)] @@ -36,9 +36,6 @@ pub struct FragmentTree { /// The containing block used in the layout of this fragment tree. pub(crate) initial_containing_block: PhysicalRect<Au>, - /// <https://drafts.csswg.org/css-backgrounds/#special-backgrounds> - pub(crate) canvas_background: CanvasBackground, - /// Whether or not the viewport is sensitive to scroll input events. pub viewport_scroll_sensitivity: AxesScrollSensitivity, } @@ -49,14 +46,12 @@ impl FragmentTree { root_fragments: Vec<Fragment>, scrollable_overflow: PhysicalRect<Au>, initial_containing_block: PhysicalRect<Au>, - canvas_background: CanvasBackground, viewport_scroll_sensitivity: AxesScrollSensitivity, ) -> Self { let fragment_tree = Self { root_fragments, scrollable_overflow, initial_containing_block, - canvas_background, viewport_scroll_sensitivity, }; @@ -102,11 +97,7 @@ impl FragmentTree { root_stacking_context: &StackingContext, ) { // Paint the canvas’ background (if any) before/under everything else - root_stacking_context.build_canvas_background_display_list( - builder, - self, - &self.initial_containing_block, - ); + root_stacking_context.build_canvas_background_display_list(builder, self); root_stacking_context.build_display_list(builder); } @@ -160,4 +151,45 @@ impl FragmentTree { scroll_area } } + + /// Find the `<body>` element's [`Fragment`], if it exists in this [`FragmentTree`]. + pub(crate) fn body_fragment(&self) -> Option<ArcRefCell<BoxFragment>> { + fn find_body(children: &[Fragment]) -> Option<ArcRefCell<BoxFragment>> { + children.iter().find_map(|fragment| { + match fragment { + Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => { + let borrowed_box_fragment = box_fragment.borrow(); + if borrowed_box_fragment.is_body_element_of_html_element_root() { + return Some(box_fragment.clone()); + } + + // The fragment for the `<body>` element is typically a child of the root (though, + // not if it's absolutely positioned), so we need to recurse into the children of + // the root to find it. + // + // Additionally, recurse into any anonymous fragments, as the `<body>` fragment may + // have created anonymous parents (for instance by creating an inline formatting context). + if borrowed_box_fragment.is_root_element() || + borrowed_box_fragment.base.is_anonymous() + { + find_body(&borrowed_box_fragment.children) + } else { + None + } + }, + Fragment::Positioning(positioning_context) + if positioning_context.borrow().base.is_anonymous() => + { + // If the `<body>` element is a `display: inline` then it might be nested inside of a + // `PositioningFragment` for the purposes of putting it on the first line of the implied + // inline formatting context. + find_body(&positioning_context.borrow().children) + }, + _ => None, + } + }) + } + + find_body(&self.root_fragments) + } } diff --git a/components/layout/table/construct.rs b/components/layout/table/construct.rs index 8370e9aeb89..133904db7ae 100644 --- a/components/layout/table/construct.rs +++ b/components/layout/table/construct.rs @@ -29,7 +29,7 @@ use crate::formatting_contexts::{ IndependentFormattingContext, IndependentFormattingContextContents, IndependentNonReplacedContents, }; -use crate::fragment_tree::BaseFragmentInfo; +use crate::fragment_tree::{BackgroundStyle, BaseFragmentInfo, SharedBackgroundStyle}; use crate::layout_box_base::LayoutBoxBase; use crate::style_ext::{DisplayGeneratingBox, DisplayLayoutInternal}; @@ -722,9 +722,10 @@ impl<'style, 'dom> TableBuilderTraversal<'style, 'dom> { let style = anonymous_info.style.clone(); self.push_table_row(ArcRefCell::new(TableTrack { - base: LayoutBoxBase::new((&anonymous_info).into(), style), + base: LayoutBoxBase::new((&anonymous_info).into(), style.clone()), group_index: self.current_row_group_index, is_anonymous: true, + shared_background_style: SharedBackgroundStyle::new(BackgroundStyle(style)), })); } @@ -766,6 +767,9 @@ impl<'dom> TraversalHandler<'dom> for TableBuilderTraversal<'_, 'dom> { base: LayoutBoxBase::new(info.into(), info.style.clone()), group_type: internal.into(), track_range: next_row_index..next_row_index, + shared_background_style: SharedBackgroundStyle::new(BackgroundStyle( + info.style.clone(), + )), }); self.builder.table.row_groups.push(row_group.clone()); @@ -808,6 +812,9 @@ impl<'dom> TraversalHandler<'dom> for TableBuilderTraversal<'_, 'dom> { base: LayoutBoxBase::new(info.into(), info.style.clone()), group_index: self.current_row_group_index, is_anonymous: false, + shared_background_style: SharedBackgroundStyle::new(BackgroundStyle( + info.style.clone(), + )), }); self.push_table_row(row.clone()); box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::Track(row))); @@ -853,6 +860,9 @@ impl<'dom> TraversalHandler<'dom> for TableBuilderTraversal<'_, 'dom> { base: LayoutBoxBase::new(info.into(), info.style.clone()), group_type: internal.into(), track_range: first_column..self.builder.table.columns.len(), + shared_background_style: SharedBackgroundStyle::new(BackgroundStyle( + info.style.clone(), + )), }); self.builder.table.column_groups.push(column_group.clone()); box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::TrackGroup( @@ -1135,6 +1145,9 @@ fn add_column( base: LayoutBoxBase::new(column_info.into(), column_info.style.clone()), group_index, is_anonymous, + shared_background_style: SharedBackgroundStyle::new(BackgroundStyle( + column_info.style.clone(), + )), }); collection.extend(repeat(column.clone()).take(span as usize)); column diff --git a/components/layout/table/layout.rs b/components/layout/table/layout.rs index 2efe339837e..00dac210625 100644 --- a/components/layout/table/layout.rs +++ b/components/layout/table/layout.rs @@ -2063,7 +2063,7 @@ impl<'a> TableLayout<'a> { let column_group = column_group.borrow(); let rect = make_relative_to_row_start(dimensions.get_column_group_rect(&column_group)); fragment.add_extra_background(ExtraBackground { - style: column_group.base.style.clone(), + style: column_group.shared_background_style.clone(), rect, }) } @@ -2072,7 +2072,7 @@ impl<'a> TableLayout<'a> { if !column.is_anonymous { let rect = make_relative_to_row_start(dimensions.get_column_rect(column_index)); fragment.add_extra_background(ExtraBackground { - style: column.base.style.clone(), + style: column.shared_background_style.clone(), rect, }) } @@ -2085,7 +2085,7 @@ impl<'a> TableLayout<'a> { let rect = make_relative_to_row_start(dimensions.get_row_group_rect(&row_group.borrow())); fragment.add_extra_background(ExtraBackground { - style: row_group.borrow().base.style.clone(), + style: row_group.borrow().shared_background_style.clone(), rect, }) } @@ -2093,7 +2093,7 @@ impl<'a> TableLayout<'a> { let row = row.borrow(); let rect = make_relative_to_row_start(row_fragment_layout.rect); fragment.add_extra_background(ExtraBackground { - style: row.base.style.clone(), + style: row.shared_background_style.clone(), rect, }) } diff --git a/components/layout/table/mod.rs b/components/layout/table/mod.rs index fe7f90437b8..8e2783e2919 100644 --- a/components/layout/table/mod.rs +++ b/components/layout/table/mod.rs @@ -85,7 +85,7 @@ use super::flow::BlockFormattingContext; use crate::cell::ArcRefCell; use crate::flow::BlockContainer; use crate::formatting_contexts::IndependentFormattingContext; -use crate::fragment_tree::{BaseFragmentInfo, Fragment}; +use crate::fragment_tree::{BaseFragmentInfo, Fragment, SharedBackgroundStyle}; use crate::geom::PhysicalVec; use crate::layout_box_base::LayoutBoxBase; use crate::style_ext::BorderStyleColor; @@ -288,6 +288,11 @@ pub struct TableTrack { /// Whether or not this [`TableTrack`] was anonymous, for instance created due to /// a `span` attribute set on a parent `<colgroup>`. is_anonymous: bool, + + /// A shared container for this track's style, used to share the style for the purposes + /// of drawing backgrounds in individual cells. This allows updating the style in a + /// single place and having it affect all cell `Fragment`s. + shared_background_style: SharedBackgroundStyle, } #[derive(Debug, MallocSizeOf, PartialEq)] @@ -308,6 +313,11 @@ pub struct TableTrackGroup { /// The range of tracks in this [`TableTrackGroup`]. track_range: Range<usize>, + + /// A shared container for this track's style, used to share the style for the purposes + /// of drawing backgrounds in individual cells. This allows updating the style in a + /// single place and having it affect all cell `Fragment`s. + shared_background_style: SharedBackgroundStyle, } impl TableTrackGroup { diff --git a/components/net/image_cache.rs b/components/net/image_cache.rs index e3d31b11736..46a2a4ea111 100644 --- a/components/net/image_cache.rs +++ b/components/net/image_cache.rs @@ -7,7 +7,7 @@ use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::sync::{Arc, Mutex}; use std::{mem, thread}; -use compositing_traits::{CrossProcessCompositorApi, SerializableImageData}; +use compositing_traits::{CrossProcessCompositorApi, ImageUpdate, SerializableImageData}; use imsz::imsz_from_reader; use ipc_channel::ipc::IpcSharedMemory; use log::{debug, warn}; @@ -675,6 +675,20 @@ impl ImageCache for ImageCacheImpl { } } +impl Drop for ImageCacheStore { + fn drop(&mut self) { + let image_updates = self + .completed_loads + .values() + .filter_map(|load| match &load.image_response { + ImageResponse::Loaded(image, _) => image.id.map(ImageUpdate::DeleteImage), + _ => None, + }) + .collect(); + self.compositor_api.update_images(image_updates); + } +} + impl ImageCacheImpl { /// Require self.store.lock() before calling. fn add_listener_with_store(&self, store: &mut ImageCacheStore, listener: ImageResponder) { diff --git a/components/pixels/lib.rs b/components/pixels/lib.rs index 24386ff830a..c1f57875c6d 100644 --- a/components/pixels/lib.rs +++ b/components/pixels/lib.rs @@ -294,9 +294,10 @@ pub fn generic_transform_inplace< match MULTIPLY { 1 => { let a = rgba[3]; - multiply_u8_color(rgba[0], a); - multiply_u8_color(rgba[1], a); - multiply_u8_color(rgba[2], a); + + rgba[0] = multiply_u8_color(rgba[0], a); + rgba[1] = multiply_u8_color(rgba[1], a); + rgba[2] = multiply_u8_color(rgba[2], a); }, 2 => { let a = rgba[3] as u32; diff --git a/components/script/dom/bindings/buffer_source.rs b/components/script/dom/bindings/buffer_source.rs index dd6984e1eab..14a71532e9d 100644 --- a/components/script/dom/bindings/buffer_source.rs +++ b/components/script/dom/bindings/buffer_source.rs @@ -37,7 +37,7 @@ use js::rust::{ #[cfg(feature = "webgpu")] use js::typedarray::{ArrayBuffer, HeapArrayBuffer}; use js::typedarray::{ - ArrayBufferU8, ArrayBufferView, ArrayBufferViewU8, CreateWith, TypedArray, TypedArrayElement, + ArrayBufferU8, ArrayBufferViewU8, CreateWith, TypedArray, TypedArrayElement, TypedArrayElementCreator, }; @@ -63,36 +63,25 @@ pub(crate) enum BufferSource { ArrayBuffer(Box<Heap<*mut JSObject>>), } -pub(crate) fn new_initialized_heap_buffer_source<T>( - init: HeapTypedArrayInit, +pub(crate) fn create_heap_buffer_source_with_length<T>( + cx: JSContext, + len: u32, can_gc: CanGc, -) -> Result<HeapBufferSource<T>, ()> +) -> Fallible<HeapBufferSource<T>> where T: TypedArrayElement + TypedArrayElementCreator, T::Element: Clone + Copy, { - let heap_buffer_source = match init { - HeapTypedArrayInit::Buffer(buffer_source) => HeapBufferSource { - buffer_source, - phantom: PhantomData, - }, - HeapTypedArrayInit::Info { len, cx } => { - rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>()); - let typed_array_result = - create_buffer_source_with_length::<T>(cx, len as usize, array.handle_mut(), can_gc); - if typed_array_result.is_err() { - return Err(()); - } - - HeapBufferSource::<T>::new(BufferSource::ArrayBufferView(Heap::boxed(*array.handle()))) - }, - }; - Ok(heap_buffer_source) -} + rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>()); + let typed_array_result = + create_buffer_source_with_length::<T>(cx, len as usize, array.handle_mut(), can_gc); + if typed_array_result.is_err() { + return Err(Error::JSFailed); + } -pub(crate) enum HeapTypedArrayInit { - Buffer(BufferSource), - Info { len: u32, cx: JSContext }, + Ok(HeapBufferSource::<T>::new(BufferSource::ArrayBufferView( + Heap::boxed(*array.handle()), + ))) } pub(crate) struct HeapBufferSource<T> { @@ -131,11 +120,11 @@ where } pub(crate) fn from_view( - chunk: CustomAutoRooterGuard<ArrayBufferView>, - ) -> HeapBufferSource<ArrayBufferViewU8> { - HeapBufferSource::<ArrayBufferViewU8>::new(BufferSource::ArrayBufferView(Heap::boxed( - unsafe { *chunk.underlying_object() }, - ))) + chunk: CustomAutoRooterGuard<TypedArray<T, *mut JSObject>>, + ) -> HeapBufferSource<T> { + HeapBufferSource::<T>::new(BufferSource::ArrayBufferView(Heap::boxed(unsafe { + *chunk.underlying_object() + }))) } pub(crate) fn default() -> Self { diff --git a/components/script/dom/clipboard.rs b/components/script/dom/clipboard.rs index 94c8b3c0f19..25878a1b29b 100644 --- a/components/script/dom/clipboard.rs +++ b/components/script/dom/clipboard.rs @@ -3,24 +3,75 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::rc::Rc; +use std::str::FromStr; use constellation_traits::BlobImpl; +use data_url::mime::Mime; use dom_struct::dom_struct; use embedder_traits::EmbedderMsg; +use js::rust::HandleValue as SafeHandleValue; use crate::dom::bindings::codegen::Bindings::ClipboardBinding::{ ClipboardMethods, PresentationStyle, }; +use crate::dom::bindings::error::Error; use crate::dom::bindings::refcounted::TrustedPromise; use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object}; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; use crate::dom::blob::Blob; +use crate::dom::clipboarditem::Representation; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; use crate::dom::promise::Promise; +use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler}; use crate::dom::window::Window; -use crate::script_runtime::CanGc; +use crate::realms::{InRealm, enter_realm}; +use crate::routed_promise::{RoutedPromiseListener, route_promise}; +use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; + +/// The fulfillment handler for the reacting to representationDataPromise part of +/// <https://w3c.github.io/clipboard-apis/#dom-clipboard-readtext>. +#[derive(Clone, JSTraceable, MallocSizeOf)] +struct RepresentationDataPromiseFulfillmentHandler { + #[ignore_malloc_size_of = "Rc are hard"] + promise: Rc<Promise>, +} + +impl Callback for RepresentationDataPromiseFulfillmentHandler { + /// The fulfillment case of Step 3.4.1.1.4.3 of + /// <https://w3c.github.io/clipboard-apis/#dom-clipboard-readtext>. + fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // If v is a DOMString, then follow the below steps: + // Resolve p with v. + // Return p. + self.promise.resolve(cx, v, can_gc); + + // NOTE: Since we ask text from arboard, v can't be a Blob + // If v is a Blob, then follow the below steps: + // Let string be the result of UTF-8 decoding v’s underlying byte sequence. + // Resolve p with string. + // Return p. + } +} + +/// The rejection handler for the reacting to representationDataPromise part of +/// <https://w3c.github.io/clipboard-apis/#dom-clipboard-readtext>. +#[derive(Clone, JSTraceable, MallocSizeOf)] +struct RepresentationDataPromiseRejectionHandler { + #[ignore_malloc_size_of = "Rc are hard"] + promise: Rc<Promise>, +} + +impl Callback for RepresentationDataPromiseRejectionHandler { + /// The rejection case of Step 3.4.1.1.4.3 of + /// <https://w3c.github.io/clipboard-apis/#dom-clipboard-readtext>. + fn callback(&self, _cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Reject p with "NotFoundError" DOMException in realm. + // Return p. + self.promise.reject_error(Error::NotFound, can_gc); + } +} #[dom_struct] pub(crate) struct Clipboard { @@ -40,6 +91,34 @@ impl Clipboard { } impl ClipboardMethods<crate::DomTypeHolder> for Clipboard { + /// <https://w3c.github.io/clipboard-apis/#dom-clipboard-readtext> + fn ReadText(&self, can_gc: CanGc) -> Rc<Promise> { + // Step 1 Let realm be this's relevant realm. + let global = self.global(); + + // Step 2 Let p be a new promise in realm. + let p = Promise::new(&global, can_gc); + + // Step 3 Run the following steps in parallel: + + // TODO Step 3.1 Let r be the result of running check clipboard read permission. + // Step 3.2 If r is false, then: + // Step 3.2.1 Queue a global task on the permission task source, given realm’s global object, + // to reject p with "NotAllowedError" DOMException in realm. + // Step 3.2.2 Abort these steps. + + // Step 3.3 Let data be a copy of the system clipboard data. + let window = global.as_window(); + let sender = route_promise(&p, self, global.task_manager().clipboard_task_source()); + window.send_to_embedder(EmbedderMsg::GetClipboardText(window.webview_id(), sender)); + + // Step 3.4 Queue a global task on the clipboard task source, + // given realm’s global object, to perform the below steps: + // NOTE: We queue the task inside route_promise and perform the steps inside handle_response + + p + } + /// <https://w3c.github.io/clipboard-apis/#dom-clipboard-writetext> fn WriteText(&self, data: DOMString, can_gc: CanGc) -> Rc<Promise> { // Step 1 Let realm be this's relevant realm. @@ -95,6 +174,71 @@ impl ClipboardMethods<crate::DomTypeHolder> for Clipboard { } } +impl RoutedPromiseListener<Result<String, String>> for Clipboard { + fn handle_response( + &self, + response: Result<String, String>, + promise: &Rc<Promise>, + can_gc: CanGc, + ) { + let global = self.global(); + let text = response.unwrap_or_default(); + + // Step 3.4.1 For each systemClipboardItem in data: + // Step 3.4.1.1 For each systemClipboardRepresentation in systemClipboardItem: + // TODO: Arboard provide the first item that has a String representation + + // Step 3.4.1.1.1 Let mimeType be the result of running the + // well-known mime type from os specific format algorithm given systemClipboardRepresentation’s name. + // Note: This is done by arboard, so we just convert the format to a MIME + let mime_type = Mime::from_str("text/plain").unwrap(); + + // Step 3.4.1.1.2 If mimeType is null, continue this loop. + // Note: Since the previous step is infallible, we don't need to handle this case + + // Step 3.4.1.1.3 Let representation be a new representation. + let representation = Representation { + mime_type, + is_custom: false, + data: Promise::new_resolved( + &global, + GlobalScope::get_cx(), + DOMString::from(text), + can_gc, + ), + }; + + // Step 3.4.1.1.4 If representation’s MIME type essence is "text/plain", then: + + // Step 3.4.1.1.4.1 Set representation’s MIME type to mimeType. + // Note: Done when creating a new representation + + // Step 3.4.1.1.4.2 Let representationDataPromise be the representation’s data. + // Step 3.4.1.1.4.3 React to representationDataPromise: + let fulfillment_handler = Box::new(RepresentationDataPromiseFulfillmentHandler { + promise: promise.clone(), + }); + let rejection_handler = Box::new(RepresentationDataPromiseRejectionHandler { + promise: promise.clone(), + }); + let handler = PromiseNativeHandler::new( + &global, + Some(fulfillment_handler), + Some(rejection_handler), + can_gc, + ); + let realm = enter_realm(&*global); + let comp = InRealm::Entered(&realm); + representation + .data + .append_native_handler(&handler, comp, can_gc); + + // Step 3.4.2 Reject p with "NotFoundError" DOMException in realm. + // Step 3.4.3 Return p. + // NOTE: We follow the same behaviour of Gecko by doing nothing if no text is available instead of rejecting p + } +} + /// <https://w3c.github.io/clipboard-apis/#write-blobs-and-option-to-the-clipboard> fn write_blobs_and_option_to_the_clipboard( window: &Window, diff --git a/components/script/dom/clipboarditem.rs b/components/script/dom/clipboarditem.rs index c1c66a403b3..129beb686c1 100644 --- a/components/script/dom/clipboarditem.rs +++ b/components/script/dom/clipboarditem.rs @@ -29,13 +29,13 @@ const CUSTOM_FORMAT_PREFIX: &str = "web "; /// <https://w3c.github.io/clipboard-apis/#representation> #[derive(JSTraceable, MallocSizeOf)] -struct Representation { +pub(super) struct Representation { #[no_trace] #[ignore_malloc_size_of = "Extern type"] - mime_type: Mime, - is_custom: bool, + pub mime_type: Mime, + pub is_custom: bool, #[ignore_malloc_size_of = "Rc is hard"] - data: Rc<Promise>, + pub data: Rc<Promise>, } #[dom_struct] diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index e3590461604..0c71f526a0e 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -4321,7 +4321,7 @@ impl Document { }, }; - self.global().report_csp_violations(violations); + self.global().report_csp_violations(violations, Some(el)); result } @@ -6569,9 +6569,6 @@ impl DocumentMethods<crate::DomTypeHolder> for Document { Ok(()) } - // https://html.spec.whatwg.org/multipage/#documentandelementeventhandlers - document_and_element_event_handlers!(); - // https://fullscreen.spec.whatwg.org/#handler-document-onfullscreenerror event_handler!(fullscreenerror, GetOnfullscreenerror, SetOnfullscreenerror); diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 7f38e55fb14..7770d0c8fa5 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -12,6 +12,7 @@ use std::rc::Rc; use std::str::FromStr; use std::{fmt, mem}; +use content_security_policy as csp; use cssparser::match_ignore_ascii_case; use devtools_traits::AttrInfo; use dom_struct::dom_struct; @@ -2120,6 +2121,59 @@ impl Element { node.owner_doc().element_attr_will_change(self, attr); } + /// <https://html.spec.whatwg.org/multipage/#the-style-attribute> + fn update_style_attribute(&self, attr: &Attr, mutation: AttributeMutation) { + let doc = self.upcast::<Node>().owner_doc(); + // Modifying the `style` attribute might change style. + *self.style_attribute.borrow_mut() = match mutation { + AttributeMutation::Set(..) => { + // This is the fast path we use from + // CSSStyleDeclaration. + // + // Juggle a bit to keep the borrow checker happy + // while avoiding the extra clone. + let is_declaration = matches!(*attr.value(), AttrValue::Declaration(..)); + + let block = if is_declaration { + let mut value = AttrValue::String(String::new()); + attr.swap_value(&mut value); + let (serialization, block) = match value { + AttrValue::Declaration(s, b) => (s, b), + _ => unreachable!(), + }; + let mut value = AttrValue::String(serialization); + attr.swap_value(&mut value); + block + } else { + let win = self.owner_window(); + let source = &**attr.value(); + // However, if the Should element's inline behavior be blocked by + // Content Security Policy? algorithm returns "Blocked" when executed + // upon the attribute's element, "style attribute", and the attribute's value, + // then the style rules defined in the attribute's value must not be applied to the element. [CSP] + if doc.should_elements_inline_type_behavior_be_blocked( + self, + csp::InlineCheckType::StyleAttribute, + source, + ) == csp::CheckResult::Blocked + { + return; + } + Arc::new(doc.style_shared_lock().wrap(parse_style_attribute( + source, + &UrlExtraData(doc.base_url().get_arc()), + win.css_error_reporter(), + doc.quirks_mode(), + CssRuleType::Style, + ))) + }; + + Some(block) + }, + AttributeMutation::Removed => None, + }; + } + // https://dom.spec.whatwg.org/#insert-adjacent pub(crate) fn insert_adjacent( &self, @@ -3800,43 +3854,7 @@ impl VirtualMethods for Element { &local_name!("tabindex") | &local_name!("draggable") | &local_name!("hidden") => { self.update_sequentially_focusable_status(can_gc) }, - &local_name!("style") => { - // Modifying the `style` attribute might change style. - *self.style_attribute.borrow_mut() = match mutation { - AttributeMutation::Set(..) => { - // This is the fast path we use from - // CSSStyleDeclaration. - // - // Juggle a bit to keep the borrow checker happy - // while avoiding the extra clone. - let is_declaration = matches!(*attr.value(), AttrValue::Declaration(..)); - - let block = if is_declaration { - let mut value = AttrValue::String(String::new()); - attr.swap_value(&mut value); - let (serialization, block) = match value { - AttrValue::Declaration(s, b) => (s, b), - _ => unreachable!(), - }; - let mut value = AttrValue::String(serialization); - attr.swap_value(&mut value); - block - } else { - let win = self.owner_window(); - Arc::new(doc.style_shared_lock().wrap(parse_style_attribute( - &attr.value(), - &UrlExtraData(doc.base_url().get_arc()), - win.css_error_reporter(), - doc.quirks_mode(), - CssRuleType::Style, - ))) - }; - - Some(block) - }, - AttributeMutation::Removed => None, - }; - }, + &local_name!("style") => self.update_style_attribute(attr, mutation), &local_name!("id") => { *self.id_attribute.borrow_mut() = mutation.new_value(attr).and_then(|value| { let value = value.as_atom(); diff --git a/components/script/dom/event.rs b/components/script/dom/event.rs index e199e12f655..743ced42a4b 100644 --- a/components/script/dom/event.rs +++ b/components/script/dom/event.rs @@ -1040,14 +1040,39 @@ impl From<bool> for EventCancelable { } impl From<EventCancelable> for bool { - fn from(bubbles: EventCancelable) -> Self { - match bubbles { + fn from(cancelable: EventCancelable) -> Self { + match cancelable { EventCancelable::Cancelable => true, EventCancelable::NotCancelable => false, } } } +#[derive(Clone, Copy, MallocSizeOf, PartialEq)] +pub(crate) enum EventComposed { + Composed, + NotComposed, +} + +impl From<bool> for EventComposed { + fn from(boolean: bool) -> Self { + if boolean { + EventComposed::Composed + } else { + EventComposed::NotComposed + } + } +} + +impl From<EventComposed> for bool { + fn from(composed: EventComposed) -> Self { + match composed { + EventComposed::Composed => true, + EventComposed::NotComposed => false, + } + } +} + #[derive(Clone, Copy, Debug, Eq, JSTraceable, PartialEq)] #[repr(u16)] #[derive(MallocSizeOf)] diff --git a/components/script/dom/eventsource.rs b/components/script/dom/eventsource.rs index 273abda0a02..7cf7bd6106f 100644 --- a/components/script/dom/eventsource.rs +++ b/components/script/dom/eventsource.rs @@ -448,7 +448,7 @@ impl FetchResponseListener for EventSourceContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index 98c4c3ed53d..902d4622db9 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -106,6 +106,7 @@ use crate::dom::crypto::Crypto; use crate::dom::dedicatedworkerglobalscope::{ DedicatedWorkerControlMsg, DedicatedWorkerGlobalScope, }; +use crate::dom::element::Element; use crate::dom::errorevent::ErrorEvent; use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; use crate::dom::eventsource::EventSource; @@ -115,6 +116,7 @@ use crate::dom::htmlscriptelement::{ScriptId, SourceCode}; use crate::dom::imagebitmap::ImageBitmap; use crate::dom::messageevent::MessageEvent; use crate::dom::messageport::MessagePort; +use crate::dom::node::Node; use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope; use crate::dom::performance::Performance; use crate::dom::performanceobserver::VALID_ENTRY_TYPES; @@ -2930,7 +2932,7 @@ impl GlobalScope { let (is_js_evaluation_allowed, violations) = csp_list.is_js_evaluation_allowed(source); - self.report_csp_violations(violations); + self.report_csp_violations(violations, None); is_js_evaluation_allowed == CheckResult::Allowed } @@ -2957,7 +2959,7 @@ impl GlobalScope { let (result, violations) = csp_list.should_navigation_request_be_blocked(&request, NavigationCheckType::Other); - self.report_csp_violations(violations); + self.report_csp_violations(violations, None); result == CheckResult::Blocked } @@ -3444,8 +3446,13 @@ impl GlobalScope { unreachable!(); } + /// <https://www.w3.org/TR/CSP/#report-violation> #[allow(unsafe_code)] - pub(crate) fn report_csp_violations(&self, violations: Vec<Violation>) { + pub(crate) fn report_csp_violations( + &self, + violations: Vec<Violation>, + element: Option<&Element>, + ) { let scripted_caller = unsafe { describe_scripted_caller(*GlobalScope::get_cx()) }.unwrap_or_default(); for violation in violations { @@ -3471,7 +3478,38 @@ impl GlobalScope { .line_number(scripted_caller.line) .column_number(scripted_caller.col + 1) .build(self); - let task = CSPViolationReportTask::new(self, report); + // Step 1: Let global be violation’s global object. + // We use `self` as `global`; + // Step 2: Let target be violation’s element. + let target = element.and_then(|event_target| { + // Step 3.1: If target is not null, and global is a Window, + // and target’s shadow-including root is not global’s associated Document, set target to null. + if let Some(window) = self.downcast::<Window>() { + if !window + .Document() + .upcast::<Node>() + .is_shadow_including_inclusive_ancestor_of(event_target.upcast()) + { + return None; + } + } + Some(event_target) + }); + let target = match target { + // Step 3.2: If target is null: + None => { + // Step 3.2.2: If target is a Window, set target to target’s associated Document. + if let Some(window) = self.downcast::<Window>() { + Trusted::new(window.Document().upcast()) + } else { + // Step 3.2.1: Set target to violation’s global object. + Trusted::new(self.upcast()) + } + }, + Some(event_target) => Trusted::new(event_target.upcast()), + }; + // Step 3: Queue a task to run the following steps: + let task = CSPViolationReportTask::new(Trusted::new(self), target, report); self.task_manager() .dom_manipulation_task_source() .queue(task); diff --git a/components/script/dom/htmlbodyelement.rs b/components/script/dom/htmlbodyelement.rs index b4efba9bed9..19b0ab4efce 100644 --- a/components/script/dom/htmlbodyelement.rs +++ b/components/script/dom/htmlbodyelement.rs @@ -181,26 +181,31 @@ impl VirtualMethods for HTMLBodyElement { (name, AttributeMutation::Set(_)) if name.starts_with("on") => { let window = self.owner_window(); // https://html.spec.whatwg.org/multipage/ - // #event-handlers-on-elements,-document-objects,-and-window-objects:event-handlers-3 + // #event-handlers-on-elements,-document-objects,-and-window-objects:event-handlers-6 match name { - &local_name!("onfocus") | - &local_name!("onload") | - &local_name!("onscroll") | &local_name!("onafterprint") | &local_name!("onbeforeprint") | &local_name!("onbeforeunload") | + &local_name!("onerror") | + &local_name!("onfocus") | &local_name!("onhashchange") | + &local_name!("onload") | &local_name!("onlanguagechange") | &local_name!("onmessage") | + &local_name!("onmessageerror") | &local_name!("onoffline") | &local_name!("ononline") | &local_name!("onpagehide") | + &local_name!("onpagereveal") | &local_name!("onpageshow") | + &local_name!("onpageswap") | &local_name!("onpopstate") | - &local_name!("onstorage") | + &local_name!("onrejectionhandled") | &local_name!("onresize") | - &local_name!("onunload") | - &local_name!("onerror") => { + &local_name!("onscroll") | + &local_name!("onstorage") | + &local_name!("onunhandledrejection") | + &local_name!("onunload") => { let source = &**attr.value(); let evtarget = window.upcast::<EventTarget>(); // forwarded event let source_line = 1; //TODO(#9604) obtain current JS execution line diff --git a/components/script/dom/htmlelement.rs b/components/script/dom/htmlelement.rs index 59b71543d6d..32a979ad138 100644 --- a/components/script/dom/htmlelement.rs +++ b/components/script/dom/htmlelement.rs @@ -191,9 +191,6 @@ impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement { // https://html.spec.whatwg.org/multipage/#globaleventhandlers global_event_handlers!(NoOnload); - // https://html.spec.whatwg.org/multipage/#documentandelementeventhandlers - document_and_element_event_handlers!(); - // https://html.spec.whatwg.org/multipage/#dom-dataset fn Dataset(&self, can_gc: CanGc) -> DomRoot<DOMStringMap> { self.dataset.or_init(|| DOMStringMap::new(self, can_gc)) diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index e0c8e9ef726..a79c7f6e463 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -298,7 +298,7 @@ impl FetchResponseListener for ImageContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs index 51aa6bee286..18bd426acdb 100644 --- a/components/script/dom/htmllinkelement.rs +++ b/components/script/dom/htmllinkelement.rs @@ -773,7 +773,7 @@ impl FetchResponseListener for PrefetchContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index 361c22c1250..391da272ef3 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -2951,7 +2951,7 @@ impl FetchResponseListener for HTMLMediaElementFetchListener { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index 777b30d1e63..4ee1397b4ed 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -546,7 +546,8 @@ impl FetchResponseListener for ClassicContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + let elem = self.elem.root(); + global.report_csp_violations(violations, Some(elem.upcast::<Element>())); } } diff --git a/components/script/dom/htmlvideoelement.rs b/components/script/dom/htmlvideoelement.rs index 6f27c164d02..c5d21c19d9b 100644 --- a/components/script/dom/htmlvideoelement.rs +++ b/components/script/dom/htmlvideoelement.rs @@ -422,7 +422,7 @@ impl FetchResponseListener for PosterFrameFetchContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/imagedata.rs b/components/script/dom/imagedata.rs index bd45a80fce2..a891064952a 100644 --- a/components/script/dom/imagedata.rs +++ b/components/script/dom/imagedata.rs @@ -9,13 +9,13 @@ use std::vec::Vec; use dom_struct::dom_struct; use euclid::default::{Rect, Size2D}; use ipc_channel::ipc::IpcSharedMemory; -use js::jsapi::{Heap, JSObject}; +use js::gc::CustomAutoRooterGuard; +use js::jsapi::JSObject; use js::rust::HandleObject; use js::typedarray::{ClampedU8, CreateWith, Uint8ClampedArray}; -use super::bindings::buffer_source::{ - BufferSource, HeapBufferSource, HeapTypedArrayInit, new_initialized_heap_buffer_source, -}; +use super::bindings::buffer_source::{HeapBufferSource, create_heap_buffer_source_with_length}; +use crate::dom::bindings::buffer_source::create_buffer_source; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::ImageDataMethods; use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto}; @@ -55,31 +55,31 @@ impl ImageData { rooted!(in (*cx) let mut js_object = ptr::null_mut::<JSObject>()); if let Some(ref mut d) = data { d.resize(len as usize, 0); + + let typed_array = + create_buffer_source::<ClampedU8>(cx, &d[..], js_object.handle_mut(), can_gc) + .map_err(|_| Error::JSFailed)?; + let data = CreateWith::Slice(&d[..]); Uint8ClampedArray::create(*cx, data, js_object.handle_mut()).unwrap(); - Self::new_with_jsobject(global, None, width, Some(height), js_object.get(), can_gc) + auto_root!(in(*cx) let data = typed_array); + Self::new_with_data(global, None, width, Some(height), data, can_gc) } else { - Self::new_without_jsobject(global, None, width, height, can_gc) + Self::new_without_data(global, None, width, height, can_gc) } } } #[allow(unsafe_code)] - fn new_with_jsobject( + fn new_with_data( global: &GlobalScope, proto: Option<HandleObject>, width: u32, opt_height: Option<u32>, - jsobject: *mut JSObject, + data: CustomAutoRooterGuard<Uint8ClampedArray>, can_gc: CanGc, ) -> Fallible<DomRoot<ImageData>> { - let heap_typed_array = match new_initialized_heap_buffer_source::<ClampedU8>( - HeapTypedArrayInit::Buffer(BufferSource::ArrayBufferView(Heap::boxed(jsobject))), - can_gc, - ) { - Ok(heap_typed_array) => heap_typed_array, - Err(_) => return Err(Error::JSFailed), - }; + let heap_typed_array = HeapBufferSource::<ClampedU8>::from_view(data); let typed_array = match heap_typed_array.get_typed_array() { Ok(array) => array, @@ -117,13 +117,14 @@ impl ImageData { )) } - fn new_without_jsobject( + fn new_without_data( global: &GlobalScope, proto: Option<HandleObject>, width: u32, height: u32, can_gc: CanGc, ) -> Fallible<DomRoot<ImageData>> { + // If one or both of sw and sh are zero, then throw an "IndexSizeError" DOMException. if width == 0 || height == 0 { return Err(Error::IndexSize); } @@ -139,13 +140,8 @@ impl ImageData { let cx = GlobalScope::get_cx(); - let heap_typed_array = match new_initialized_heap_buffer_source::<ClampedU8>( - HeapTypedArrayInit::Info { len, cx }, - can_gc, - ) { - Ok(heap_typed_array) => heap_typed_array, - Err(_) => return Err(Error::JSFailed), - }; + let heap_typed_array = create_heap_buffer_source_with_length::<ClampedU8>(cx, len, can_gc)?; + let imagedata = Box::new(ImageData { reflector_: Reflector::new(), width, @@ -198,20 +194,19 @@ impl ImageDataMethods<crate::DomTypeHolder> for ImageData { width: u32, height: u32, ) -> Fallible<DomRoot<Self>> { - Self::new_without_jsobject(global, proto, width, height, can_gc) + Self::new_without_data(global, proto, width, height, can_gc) } /// <https://html.spec.whatwg.org/multipage/#dom-imagedata-with-data> fn Constructor_( - _cx: JSContext, global: &GlobalScope, proto: Option<HandleObject>, can_gc: CanGc, - jsobject: *mut JSObject, + data: CustomAutoRooterGuard<Uint8ClampedArray>, width: u32, opt_height: Option<u32>, ) -> Fallible<DomRoot<Self>> { - Self::new_with_jsobject(global, proto, width, opt_height, jsobject, can_gc) + Self::new_with_data(global, proto, width, opt_height, data, can_gc) } /// <https://html.spec.whatwg.org/multipage/#dom-imagedata-width> diff --git a/components/script/dom/intersectionobserver.rs b/components/script/dom/intersectionobserver.rs index ec98116d3a4..6a6f9ce45eb 100644 --- a/components/script/dom/intersectionobserver.rs +++ b/components/script/dom/intersectionobserver.rs @@ -524,11 +524,10 @@ impl IntersectionObserver { // Step 9 // > Let targetArea be targetRect’s area. - let target_area = target_rect.size.width.0 * target_rect.size.height.0; - // Step 10 // > Let intersectionArea be intersectionRect’s area. - let intersection_area = intersection_rect.size.width.0 * intersection_rect.size.height.0; + // These steps are folded in Step 12, rewriting (w1 * h1) / (w2 * h2) as (w1 / w2) * (h1 / h2) + // to avoid multiplication overflows. // Step 11 // > Let isIntersecting be true if targetRect and rootBounds intersect or are edge-adjacent, @@ -545,9 +544,12 @@ impl IntersectionObserver { // Step 12 // > If targetArea is non-zero, let intersectionRatio be intersectionArea divided by targetArea. // > Otherwise, let intersectionRatio be 1 if isIntersecting is true, or 0 if isIntersecting is false. - let intersection_ratio = match target_area { - 0 => is_intersecting.into(), - _ => (intersection_area as f64) / (target_area as f64), + let intersection_ratio = if target_rect.size.width.0 == 0 || target_rect.size.height.0 == 0 + { + is_intersecting.into() + } else { + (intersection_rect.size.width.0 as f64 / target_rect.size.width.0 as f64) * + (intersection_rect.size.height.0 as f64 / target_rect.size.height.0 as f64) }; // Step 13 diff --git a/components/script/dom/macros.rs b/components/script/dom/macros.rs index cc44497d0b9..564fe810db0 100644 --- a/components/script/dom/macros.rs +++ b/components/script/dom/macros.rs @@ -523,21 +523,29 @@ macro_rules! global_event_handlers( ); (NoOnload) => ( event_handler!(abort, GetOnabort, SetOnabort); + event_handler!(auxclick, GetOnauxclick, SetOnauxclick); event_handler!(animationend, GetOnanimationend, SetOnanimationend); event_handler!(animationiteration, GetOnanimationiteration, SetOnanimationiteration); + event_handler!(beforeinput, GetOnbeforeinput, SetOnbeforeinput); + event_handler!(beforematch, GetOnbeforematch, SetOnbeforematch); + event_handler!(beforetoggle, GetOnbeforetoggle, SetOnbeforetoggle); event_handler!(cancel, GetOncancel, SetOncancel); event_handler!(canplay, GetOncanplay, SetOncanplay); event_handler!(canplaythrough, GetOncanplaythrough, SetOncanplaythrough); event_handler!(change, GetOnchange, SetOnchange); event_handler!(click, GetOnclick, SetOnclick); event_handler!(close, GetOnclose, SetOnclose); + event_handler!(command, GetOncommand, SetOncommand); + event_handler!(contextlost, GetOncontextlost, SetOncontextlost); event_handler!(contextmenu, GetOncontextmenu, SetOncontextmenu); + event_handler!(contextrestored, GetOncontextrestored, SetOncontextrestored); + event_handler!(copy, GetOncopy, SetOncopy); event_handler!(cuechange, GetOncuechange, SetOncuechange); + event_handler!(cut, GetOncut, SetOncut); event_handler!(dblclick, GetOndblclick, SetOndblclick); event_handler!(drag, GetOndrag, SetOndrag); event_handler!(dragend, GetOndragend, SetOndragend); event_handler!(dragenter, GetOndragenter, SetOndragenter); - event_handler!(dragexit, GetOndragexit, SetOndragexit); event_handler!(dragleave, GetOndragleave, SetOndragleave); event_handler!(dragover, GetOndragover, SetOndragover); event_handler!(dragstart, GetOndragstart, SetOndragstart); @@ -561,20 +569,21 @@ macro_rules! global_event_handlers( event_handler!(mouseout, GetOnmouseout, SetOnmouseout); event_handler!(mouseover, GetOnmouseover, SetOnmouseover); event_handler!(mouseup, GetOnmouseup, SetOnmouseup); - event_handler!(wheel, GetOnwheel, SetOnwheel); + event_handler!(paste, GetOnpaste, SetOnpaste); event_handler!(pause, GetOnpause, SetOnpause); event_handler!(play, GetOnplay, SetOnplay); event_handler!(playing, GetOnplaying, SetOnplaying); event_handler!(progress, GetOnprogress, SetOnprogress); event_handler!(ratechange, GetOnratechange, SetOnratechange); event_handler!(reset, GetOnreset, SetOnreset); + event_handler!(scrollend, GetOnscrollend, SetOnscrollend); event_handler!(securitypolicyviolation, GetOnsecuritypolicyviolation, SetOnsecuritypolicyviolation); event_handler!(seeked, GetOnseeked, SetOnseeked); event_handler!(seeking, GetOnseeking, SetOnseeking); event_handler!(select, GetOnselect, SetOnselect); event_handler!(selectionchange, GetOnselectionchange, SetOnselectionchange); event_handler!(selectstart, GetOnselectstart, SetOnselectstart); - event_handler!(show, GetOnshow, SetOnshow); + event_handler!(slotchange, GetOnslotchange, SetOnslotchange); event_handler!(stalled, GetOnstalled, SetOnstalled); event_handler!(submit, GetOnsubmit, SetOnsubmit); event_handler!(suspend, GetOnsuspend, SetOnsuspend); @@ -585,6 +594,11 @@ macro_rules! global_event_handlers( event_handler!(transitionrun, GetOntransitionrun, SetOntransitionrun); event_handler!(volumechange, GetOnvolumechange, SetOnvolumechange); event_handler!(waiting, GetOnwaiting, SetOnwaiting); + event_handler!(webkitanimationend, GetOnwebkitanimationend, SetOnwebkitanimationend); + event_handler!(webkitanimationiteration, GetOnwebkitanimationiteration, SetOnwebkitanimationiteration); + event_handler!(webkitanimationstart, GetOnwebkitanimationstart, SetOnwebkitanimationstart); + event_handler!(webkittransitionend, GetOnwebkittransitionend, SetOnwebkittransitionend); + event_handler!(wheel, GetOnwheel, SetOnwheel); ) ); @@ -605,7 +619,9 @@ macro_rules! window_event_handlers( event_handler!(offline, GetOnoffline, SetOnoffline); event_handler!(online, GetOnonline, SetOnonline); event_handler!(pagehide, GetOnpagehide, SetOnpagehide); + event_handler!(pagereveal, GetOnpagereveal, SetOnpagereveal); event_handler!(pageshow, GetOnpageshow, SetOnpageshow); + event_handler!(pageswap, GetOnpageswap, SetOnpageswap); event_handler!(popstate, GetOnpopstate, SetOnpopstate); event_handler!(rejectionhandled, GetOnrejectionhandled, SetOnrejectionhandled); @@ -633,7 +649,9 @@ macro_rules! window_event_handlers( window_owned_event_handler!(offline, GetOnoffline, SetOnoffline); window_owned_event_handler!(online, GetOnonline, SetOnonline); window_owned_event_handler!(pagehide, GetOnpagehide, SetOnpagehide); + window_owned_event_handler!(pagereveal, GetOnpagereveal, SetOnpagereveal); window_owned_event_handler!(pageshow, GetOnpageshow, SetOnpageshow); + window_owned_event_handler!(pageswap, GetOnpageswap, SetOnpageswap); window_owned_event_handler!(popstate, GetOnpopstate, SetOnpopstate); window_owned_event_handler!(rejectionhandled, GetOnrejectionhandled, SetOnrejectionhandled); @@ -646,17 +664,6 @@ macro_rules! window_event_handlers( ); ); -// https://html.spec.whatwg.org/multipage/#documentandelementeventhandlers -// see webidls/EventHandler.webidl -// As more methods get added, just update them here. -macro_rules! document_and_element_event_handlers( - () => ( - event_handler!(cut, GetOncut, SetOncut); - event_handler!(copy, GetOncopy, SetOncopy); - event_handler!(paste, GetOnpaste, SetOnpaste); - ) -); - /// DOM struct implementation for simple interfaces inheriting from PerformanceEntry. macro_rules! impl_performance_entry_struct( ($binding:ident, $struct:ident, $type:expr) => ( diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 4bc272db8dd..1622cf57b79 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -631,6 +631,8 @@ pub(crate) mod webgpu; pub(crate) use self::webgpu::*; #[cfg(not(feature = "webgpu"))] pub(crate) mod gpucanvascontext; +pub(crate) mod transformstream; +pub(crate) mod transformstreamdefaultcontroller; pub(crate) mod wheelevent; #[allow(dead_code)] pub(crate) mod window; diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index e9d36a01426..ca785773b48 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -3110,11 +3110,15 @@ impl NodeMethods<crate::DomTypeHolder> for Node { /// <https://dom.spec.whatwg.org/#dom-node-childnodes> fn ChildNodes(&self, can_gc: CanGc) -> DomRoot<NodeList> { - self.ensure_rare_data().child_list.or_init(|| { - let doc = self.owner_doc(); - let window = doc.window(); - NodeList::new_child_list(window, self, can_gc) - }) + if let Some(list) = self.ensure_rare_data().child_list.get() { + return list; + } + + let doc = self.owner_doc(); + let window = doc.window(); + let list = NodeList::new_child_list(window, self, can_gc); + self.ensure_rare_data().child_list.set(Some(&list)); + list } /// <https://dom.spec.whatwg.org/#dom-node-firstchild> diff --git a/components/script/dom/notification.rs b/components/script/dom/notification.rs index 4dbb97430e5..1aecb785475 100644 --- a/components/script/dom/notification.rs +++ b/components/script/dom/notification.rs @@ -795,7 +795,7 @@ impl FetchResponseListener for ResourceFetchListener { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/readablestreamdefaultcontroller.rs b/components/script/dom/readablestreamdefaultcontroller.rs index c52fb712a03..80c800d1bbe 100644 --- a/components/script/dom/readablestreamdefaultcontroller.rs +++ b/components/script/dom/readablestreamdefaultcontroller.rs @@ -383,7 +383,6 @@ impl ReadableStreamDefaultController { } /// <https://streams.spec.whatwg.org/#set-up-readable-stream-default-controller> - #[allow(unsafe_code)] pub(crate) fn setup( &self, stream: DomRoot<ReadableStream>, @@ -866,7 +865,6 @@ impl ReadableStreamDefaultController { } /// <https://streams.spec.whatwg.org/#rs-default-controller-has-backpressure> - #[allow(unused)] pub(crate) fn has_backpressure(&self) -> bool { // If ! ReadableStreamDefaultControllerShouldCallPull(controller) is true, return false. // Otherwise, return true. diff --git a/components/script/dom/readablestreamgenericreader.rs b/components/script/dom/readablestreamgenericreader.rs index 8ba1149bcb5..d2a109ee692 100644 --- a/components/script/dom/readablestreamgenericreader.rs +++ b/components/script/dom/readablestreamgenericreader.rs @@ -80,7 +80,6 @@ pub(crate) trait ReadableStreamGenericReader { } /// <https://streams.spec.whatwg.org/#readable-stream-reader-generic-release> - #[allow(unsafe_code)] fn generic_release(&self, can_gc: CanGc) -> Fallible<()> { // Let stream be reader.[[stream]]. diff --git a/components/script/dom/securitypolicyviolationevent.rs b/components/script/dom/securitypolicyviolationevent.rs index 3580e525e55..3c528cc5814 100644 --- a/components/script/dom/securitypolicyviolationevent.rs +++ b/components/script/dom/securitypolicyviolationevent.rs @@ -15,7 +15,7 @@ use crate::dom::bindings::codegen::Bindings::SecurityPolicyViolationEventBinding use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::{DOMString, USVString}; -use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed}; use crate::dom::globalscope::GlobalScope; use crate::script_runtime::CanGc; @@ -70,12 +70,14 @@ impl SecurityPolicyViolationEvent { ) } + #[allow(clippy::too_many_arguments)] fn new_with_proto( global: &GlobalScope, proto: Option<HandleObject>, type_: Atom, bubbles: EventBubbles, cancelable: EventCancelable, + composed: EventComposed, init: &SecurityPolicyViolationEventInit, can_gc: CanGc, ) -> DomRoot<Self> { @@ -83,6 +85,7 @@ impl SecurityPolicyViolationEvent { { let event = ev.upcast::<Event>(); event.init_event(type_, bool::from(bubbles), bool::from(cancelable)); + event.set_composed(bool::from(composed)); } ev } @@ -92,10 +95,13 @@ impl SecurityPolicyViolationEvent { type_: Atom, bubbles: EventBubbles, cancelable: EventCancelable, + composed: EventComposed, init: &SecurityPolicyViolationEventInit, can_gc: CanGc, ) -> DomRoot<Self> { - Self::new_with_proto(global, None, type_, bubbles, cancelable, init, can_gc) + Self::new_with_proto( + global, None, type_, bubbles, cancelable, composed, init, can_gc, + ) } } @@ -115,6 +121,7 @@ impl SecurityPolicyViolationEventMethods<crate::DomTypeHolder> for SecurityPolic Atom::from(type_), EventBubbles::from(init.parent.bubbles), EventCancelable::from(init.parent.cancelable), + EventComposed::from(init.parent.composed), init, can_gc, ) diff --git a/components/script/dom/servoparser/html.rs b/components/script/dom/servoparser/html.rs index 7fd0429612a..9dfbeda4030 100644 --- a/components/script/dom/servoparser/html.rs +++ b/components/script/dom/servoparser/html.rs @@ -16,6 +16,7 @@ use html5ever::{QualName, local_name, ns}; use markup5ever::TokenizerResult; use script_bindings::trace::CustomTraceable; use servo_url::ServoUrl; +use style::attr::AttrValue; use style::context::QuirksMode as StyleContextQuirksMode; use xml5ever::LocalName; @@ -116,18 +117,34 @@ impl Tokenizer { } } -fn start_element<S: Serializer>(node: &Element, serializer: &mut S) -> io::Result<()> { - let name = QualName::new(None, node.namespace().clone(), node.local_name().clone()); - let attrs = node - .attrs() - .iter() - .map(|attr| { - let qname = QualName::new(None, attr.namespace().clone(), attr.local_name().clone()); - let value = attr.value().clone(); - (qname, value) - }) - .collect::<Vec<_>>(); - let attr_refs = attrs.iter().map(|(qname, value)| { +/// <https://html.spec.whatwg.org/multipage/#html-fragment-serialisation-algorithm> +fn start_element<S: Serializer>(element: &Element, serializer: &mut S) -> io::Result<()> { + let name = QualName::new( + None, + element.namespace().clone(), + element.local_name().clone(), + ); + + let mut attributes = vec![]; + + // The "is" value of an element is treated as if it was an attribute and it is serialized before all + // other attributes. If the element already has an "is" attribute then the "is" value is ignored. + if !element.has_attribute(&LocalName::from("is")) { + if let Some(is_value) = element.get_is() { + let qualified_name = QualName::new(None, ns!(), LocalName::from("is")); + + attributes.push((qualified_name, AttrValue::String(is_value.to_string()))); + } + } + + // Collect all the "normal" attributes + attributes.extend(element.attrs().iter().map(|attr| { + let qname = QualName::new(None, attr.namespace().clone(), attr.local_name().clone()); + let value = attr.value().clone(); + (qname, value) + })); + + let attr_refs = attributes.iter().map(|(qname, value)| { let ar: AttrRef = (qname, &**value); ar }); diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs index 3a1efdfb291..9e45124522a 100644 --- a/components/script/dom/servoparser/mod.rs +++ b/components/script/dom/servoparser/mod.rs @@ -1075,7 +1075,8 @@ impl FetchResponseListener for ParserContext { }; let document = &parser.document; let global = &document.global(); - global.report_csp_violations(violations); + // TODO(https://github.com/w3c/webappsec-csp/issues/687): Update after spec is resolved + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/transformstream.rs b/components/script/dom/transformstream.rs new file mode 100644 index 00000000000..023fe7ac483 --- /dev/null +++ b/components/script/dom/transformstream.rs @@ -0,0 +1,999 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::cell::Cell; +use std::ptr::{self}; +use std::rc::Rc; + +use dom_struct::dom_struct; +use js::jsapi::{Heap, IsPromiseObject, JSObject}; +use js::jsval::{JSVal, ObjectValue, UndefinedValue}; +use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue, IntoHandle}; +use script_bindings::callback::ExceptionHandling; +use script_bindings::realms::InRealm; + +use super::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategySize; +use super::promisenativehandler::Callback; +use super::types::{TransformStreamDefaultController, WritableStream}; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategy; +use crate::dom::bindings::codegen::Bindings::TransformStreamBinding::TransformStreamMethods; +use crate::dom::bindings::codegen::Bindings::TransformerBinding::Transformer; +use crate::dom::bindings::conversions::ConversionResult; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::countqueuingstrategy::{extract_high_water_mark, extract_size_algorithm}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::dom::readablestream::{ReadableStream, create_readable_stream}; +use crate::dom::types::PromiseNativeHandler; +use crate::dom::underlyingsourcecontainer::UnderlyingSourceType; +use crate::dom::writablestream::create_writable_stream; +use crate::dom::writablestreamdefaultcontroller::UnderlyingSinkType; +use crate::realms::enter_realm; +use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; + +impl js::gc::Rootable for TransformBackPressureChangePromiseFulfillment {} + +/// Reacting to backpressureChangePromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-write-algorithm> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct TransformBackPressureChangePromiseFulfillment { + /// The result of reacting to backpressureChangePromise. + #[ignore_malloc_size_of = "Rc is hard"] + result_promise: Rc<Promise>, + + #[ignore_malloc_size_of = "mozjs"] + chunk: Box<Heap<JSVal>>, + + /// The writable used in the fulfillment steps + writable: Dom<WritableStream>, + + controller: Dom<TransformStreamDefaultController>, +} + +impl Callback for TransformBackPressureChangePromiseFulfillment { + /// Reacting to backpressureChangePromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Let writable be stream.[[writable]]. + // Let state be writable.[[state]]. + // If state is "erroring", throw writable.[[storedError]]. + if self.writable.is_erroring() { + rooted!(in(*cx) let mut error = UndefinedValue()); + self.writable.get_stored_error(error.handle_mut()); + self.result_promise.reject(cx, error.handle(), can_gc); + return; + } + + // Assert: state is "writable". + assert!(self.writable.is_writable()); + + // Return ! TransformStreamDefaultControllerPerformTransform(controller, chunk). + rooted!(in(*cx) let mut chunk = UndefinedValue()); + chunk.set(self.chunk.get()); + let transform_result = self + .controller + .transform_stream_default_controller_perform_transform( + cx, + &self.writable.global(), + chunk.handle(), + can_gc, + ) + .expect("perform transform failed"); + + // PerformTransformFulfillment and PerformTransformRejection do not need + // to be rooted because they only contain an Rc. + let handler = PromiseNativeHandler::new( + &self.writable.global(), + Some(Box::new(PerformTransformFulfillment { + result_promise: self.result_promise.clone(), + })), + Some(Box::new(PerformTransformRejection { + result_promise: self.result_promise.clone(), + })), + can_gc, + ); + + let realm = enter_realm(&*self.writable.global()); + let comp = InRealm::Entered(&realm); + transform_result.append_native_handler(&handler, comp, can_gc); + } +} + +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +/// Reacting to fulfillment of performTransform as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-write-algorithm> +struct PerformTransformFulfillment { + #[ignore_malloc_size_of = "Rc is hard"] + result_promise: Rc<Promise>, +} + +impl Callback for PerformTransformFulfillment { + fn callback(&self, _cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Fulfilled: resolve the outer promise + self.result_promise.resolve_native(&(), can_gc); + } +} + +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +/// Reacting to rejection of performTransform as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-write-algorithm> +struct PerformTransformRejection { + #[ignore_malloc_size_of = "Rc is hard"] + result_promise: Rc<Promise>, +} + +impl Callback for PerformTransformRejection { + fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Stream already errored in perform_transform, just reject result_promise + self.result_promise.reject(cx, v, can_gc); + } +} + +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +/// Reacting to rejection of backpressureChangePromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-write-algorithm> +struct BackpressureChangeRejection { + #[ignore_malloc_size_of = "Rc is hard"] + result_promise: Rc<Promise>, +} + +impl Callback for BackpressureChangeRejection { + fn callback(&self, cx: SafeJSContext, reason: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + self.result_promise.reject(cx, reason, can_gc); + } +} + +impl js::gc::Rootable for CancelPromiseFulfillment {} + +/// Reacting to fulfillment of the cancelpromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-abort-algorithm> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct CancelPromiseFulfillment { + readable: Dom<ReadableStream>, + controller: Dom<TransformStreamDefaultController>, + #[ignore_malloc_size_of = "mozjs"] + reason: Box<Heap<JSVal>>, +} + +impl Callback for CancelPromiseFulfillment { + /// Reacting to backpressureChangePromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // If readable.[[state]] is "errored", reject controller.[[finishPromise]] with readable.[[storedError]]. + if self.readable.is_errored() { + rooted!(in(*cx) let mut error = UndefinedValue()); + self.readable.get_stored_error(error.handle_mut()); + self.controller + .get_finish_promise() + .expect("finish promise is not set") + .reject_native(&error.handle(), can_gc); + } else { + // Otherwise: + // Perform ! ReadableStreamDefaultControllerError(readable.[[controller]], reason). + rooted!(in(*cx) let mut reason = UndefinedValue()); + reason.set(self.reason.get()); + self.readable + .get_default_controller() + .error(reason.handle(), can_gc); + + // Resolve controller.[[finishPromise]] with undefined. + self.controller + .get_finish_promise() + .expect("finish promise is not set") + .resolve_native(&(), can_gc); + } + } +} + +impl js::gc::Rootable for CancelPromiseRejection {} + +/// Reacting to rejection of cancelpromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-abort-algorithm> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct CancelPromiseRejection { + readable: Dom<ReadableStream>, + controller: Dom<TransformStreamDefaultController>, +} + +impl Callback for CancelPromiseRejection { + /// Reacting to backpressureChangePromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Perform ! ReadableStreamDefaultControllerError(readable.[[controller]], r). + self.readable.get_default_controller().error(v, can_gc); + + // Reject controller.[[finishPromise]] with r. + self.controller + .get_finish_promise() + .expect("finish promise is not set") + .reject(cx, v, can_gc); + } +} + +impl js::gc::Rootable for SourceCancelPromiseFulfillment {} + +/// Reacting to fulfillment of the cancelpromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-source-cancel> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct SourceCancelPromiseFulfillment { + writeable: Dom<WritableStream>, + controller: Dom<TransformStreamDefaultController>, + stream: Dom<TransformStream>, + #[ignore_malloc_size_of = "mozjs"] + reason: Box<Heap<JSVal>>, +} + +impl Callback for SourceCancelPromiseFulfillment { + /// Reacting to backpressureChangePromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // If cancelPromise was fulfilled, then: + let finish_promise = self + .controller + .get_finish_promise() + .expect("finish promise is not set"); + + let global = &self.writeable.global(); + // If writable.[[state]] is "errored", reject controller.[[finishPromise]] with writable.[[storedError]]. + if self.writeable.is_errored() { + rooted!(in(*cx) let mut error = UndefinedValue()); + self.writeable.get_stored_error(error.handle_mut()); + finish_promise.reject(cx, error.handle(), can_gc); + } else { + // Otherwise: + // Perform ! WritableStreamDefaultControllerErrorIfNeeded(writable.[[controller]], reason). + rooted!(in(*cx) let mut reason = UndefinedValue()); + reason.set(self.reason.get()); + self.writeable.get_default_controller().error_if_needed( + cx, + reason.handle(), + global, + can_gc, + ); + + // Perform ! TransformStreamUnblockWrite(stream). + self.stream.unblock_write(global, can_gc); + + // Resolve controller.[[finishPromise]] with undefined. + finish_promise.resolve_native(&(), can_gc); + } + } +} + +impl js::gc::Rootable for SourceCancelPromiseRejection {} + +/// Reacting to rejection of cancelpromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-source-cancel> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct SourceCancelPromiseRejection { + writeable: Dom<WritableStream>, + controller: Dom<TransformStreamDefaultController>, + stream: Dom<TransformStream>, +} + +impl Callback for SourceCancelPromiseRejection { + /// Reacting to backpressureChangePromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Perform ! WritableStreamDefaultControllerErrorIfNeeded(writable.[[controller]], r). + let global = &self.writeable.global(); + + self.writeable + .get_default_controller() + .error_if_needed(cx, v, global, can_gc); + + // Perform ! TransformStreamUnblockWrite(stream). + self.stream.unblock_write(global, can_gc); + + // Reject controller.[[finishPromise]] with r. + self.controller + .get_finish_promise() + .expect("finish promise is not set") + .reject(cx, v, can_gc); + } +} + +impl js::gc::Rootable for FlushPromiseFulfillment {} + +/// Reacting to fulfillment of the flushpromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-close-algorithm> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct FlushPromiseFulfillment { + readable: Dom<ReadableStream>, + controller: Dom<TransformStreamDefaultController>, +} + +impl Callback for FlushPromiseFulfillment { + /// Reacting to flushpromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // If flushPromise was fulfilled, then: + let finish_promise = self + .controller + .get_finish_promise() + .expect("finish promise is not set"); + + // If readable.[[state]] is "errored", reject controller.[[finishPromise]] with readable.[[storedError]]. + if self.readable.is_errored() { + rooted!(in(*cx) let mut error = UndefinedValue()); + self.readable.get_stored_error(error.handle_mut()); + finish_promise.reject(cx, error.handle(), can_gc); + } else { + // Otherwise: + // Perform ! ReadableStreamDefaultControllerClose(readable.[[controller]]). + self.readable.get_default_controller().close(can_gc); + + // Resolve controller.[[finishPromise]] with undefined. + finish_promise.resolve_native(&(), can_gc); + } + } +} + +impl js::gc::Rootable for FlushPromiseRejection {} +/// Reacting to rejection of flushpromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-close-algorithm> + +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct FlushPromiseRejection { + readable: Dom<ReadableStream>, + controller: Dom<TransformStreamDefaultController>, +} + +impl Callback for FlushPromiseRejection { + /// Reacting to flushpromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // If flushPromise was rejected with reason r, then: + // Perform ! ReadableStreamDefaultControllerError(readable.[[controller]], r). + self.readable.get_default_controller().error(v, can_gc); + + // Reject controller.[[finishPromise]] with r. + self.controller + .get_finish_promise() + .expect("finish promise is not set") + .reject(cx, v, can_gc); + } +} + +/// <https://streams.spec.whatwg.org/#ts-class> +#[dom_struct] +pub struct TransformStream { + reflector_: Reflector, + + /// <https://streams.spec.whatwg.org/#transformstream-backpressure> + backpressure: Cell<bool>, + + /// <https://streams.spec.whatwg.org/#transformstream-backpressurechangepromise> + #[ignore_malloc_size_of = "Rc is hard"] + backpressure_change_promise: DomRefCell<Option<Rc<Promise>>>, + + /// <https://streams.spec.whatwg.org/#transformstream-controller> + controller: MutNullableDom<TransformStreamDefaultController>, + + /// <https://streams.spec.whatwg.org/#transformstream-detached> + detached: Cell<bool>, + + /// <https://streams.spec.whatwg.org/#transformstream-readable> + readable: MutNullableDom<ReadableStream>, + + /// <https://streams.spec.whatwg.org/#transformstream-writable> + writable: MutNullableDom<WritableStream>, +} + +impl TransformStream { + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + /// <https://streams.spec.whatwg.org/#initialize-transform-stream> + fn new_inherited() -> TransformStream { + TransformStream { + reflector_: Reflector::new(), + backpressure: Default::default(), + backpressure_change_promise: DomRefCell::new(None), + controller: MutNullableDom::new(None), + detached: Cell::new(false), + readable: MutNullableDom::new(None), + writable: MutNullableDom::new(None), + } + } + + pub(crate) fn new_with_proto( + global: &GlobalScope, + proto: Option<SafeHandleObject>, + can_gc: CanGc, + ) -> DomRoot<TransformStream> { + reflect_dom_object_with_proto( + Box::new(TransformStream::new_inherited()), + global, + proto, + can_gc, + ) + } + + pub(crate) fn get_controller(&self) -> DomRoot<TransformStreamDefaultController> { + self.controller.get().expect("controller is not set") + } + + pub(crate) fn get_writable(&self) -> DomRoot<WritableStream> { + self.writable.get().expect("writable stream is not set") + } + + pub(crate) fn get_readable(&self) -> DomRoot<ReadableStream> { + self.readable.get().expect("readable stream is not set") + } + + pub(crate) fn get_backpressure(&self) -> bool { + self.backpressure.get() + } + + /// <https://streams.spec.whatwg.org/#initialize-transform-stream> + #[allow(clippy::too_many_arguments)] + fn initialize( + &self, + cx: SafeJSContext, + global: &GlobalScope, + start_promise: Rc<Promise>, + writable_high_water_mark: f64, + writable_size_algorithm: Rc<QueuingStrategySize>, + readable_high_water_mark: f64, + readable_size_algorithm: Rc<QueuingStrategySize>, + can_gc: CanGc, + ) -> Fallible<()> { + // Let startAlgorithm be an algorithm that returns startPromise. + // Let writeAlgorithm be the following steps, taking a chunk argument: + // Return ! TransformStreamDefaultSinkWriteAlgorithm(stream, chunk). + // Let abortAlgorithm be the following steps, taking a reason argument: + // Return ! TransformStreamDefaultSinkAbortAlgorithm(stream, reason). + // Let closeAlgorithm be the following steps: + // Return ! TransformStreamDefaultSinkCloseAlgorithm(stream). + // Set stream.[[writable]] to ! CreateWritableStream(startAlgorithm, writeAlgorithm, + // closeAlgorithm, abortAlgorithm, writableHighWaterMark, writableSizeAlgorithm). + // Note: Those steps are implemented using UnderlyingSinkType::Transform. + + let writable = create_writable_stream( + cx, + global, + writable_high_water_mark, + writable_size_algorithm, + UnderlyingSinkType::Transform(Dom::from_ref(self), start_promise.clone()), + can_gc, + )?; + self.writable.set(Some(&writable)); + + // Let pullAlgorithm be the following steps: + + // Return ! TransformStreamDefaultSourcePullAlgorithm(stream). + + // Let cancelAlgorithm be the following steps, taking a reason argument: + + // Return ! TransformStreamDefaultSourceCancelAlgorithm(stream, reason). + + // Set stream.[[readable]] to ! CreateReadableStream(startAlgorithm, pullAlgorithm, + // cancelAlgorithm, readableHighWaterMark, readableSizeAlgorithm). + + let readable = create_readable_stream( + global, + UnderlyingSourceType::Transform(Dom::from_ref(self), start_promise.clone()), + Some(readable_size_algorithm), + Some(readable_high_water_mark), + can_gc, + ); + self.readable.set(Some(&readable)); + + // Set stream.[[backpressure]] and stream.[[backpressureChangePromise]] to undefined. + // Note: This is done in the constructor. + + // Perform ! TransformStreamSetBackpressure(stream, true). + self.set_backpressure(global, true, can_gc); + + // Set stream.[[controller]] to undefined. + self.controller.set(None); + + Ok(()) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-set-backpressure> + pub(crate) fn set_backpressure(&self, global: &GlobalScope, backpressure: bool, can_gc: CanGc) { + // Assert: stream.[[backpressure]] is not backpressure. + assert!(self.backpressure.get() != backpressure); + + // If stream.[[backpressureChangePromise]] is not undefined, resolve + // stream.[[backpressureChangePromise]] with undefined. + if let Some(promise) = self.backpressure_change_promise.borrow_mut().take() { + promise.resolve_native(&(), can_gc); + } + + // Set stream.[[backpressureChangePromise]] to a new promise.; + *self.backpressure_change_promise.borrow_mut() = Some(Promise::new(global, can_gc)); + + // Set stream.[[backpressure]] to backpressure. + self.backpressure.set(backpressure); + } + + /// <https://streams.spec.whatwg.org/#set-up-transform-stream-default-controller> + fn set_up_transform_stream_default_controller( + &self, + controller: &TransformStreamDefaultController, + ) { + // Assert: stream implements TransformStream. + // Note: this is checked with type. + + // Assert: stream.[[controller]] is undefined. + assert!(self.controller.get().is_none()); + + // Set controller.[[stream]] to stream. + controller.set_stream(self); + + // Set stream.[[controller]] to controller. + self.controller.set(Some(controller)); + + // Set controller.[[transformAlgorithm]] to transformAlgorithm. + // Set controller.[[flushAlgorithm]] to flushAlgorithm. + // Set controller.[[cancelAlgorithm]] to cancelAlgorithm. + // Note: These are set in the constructor. + } + + /// <https://streams.spec.whatwg.org/#set-up-transform-stream-default-controller-from-transformer> + fn set_up_transform_stream_default_controller_from_transformer( + &self, + global: &GlobalScope, + transformer_obj: SafeHandleObject, + transformer: &Transformer, + can_gc: CanGc, + ) { + // Let controller be a new TransformStreamDefaultController. + let controller = TransformStreamDefaultController::new(global, transformer, can_gc); + + // Let transformAlgorithm be the following steps, taking a chunk argument: + // Let result be TransformStreamDefaultControllerEnqueue(controller, chunk). + // If result is an abrupt completion, return a promise rejected with result.[[Value]]. + // Otherwise, return a promise resolved with undefined. + + // Let flushAlgorithm be an algorithm which returns a promise resolved with undefined. + // Let cancelAlgorithm be an algorithm which returns a promise resolved with undefined. + + // If transformerDict["transform"] exists, set transformAlgorithm to an algorithm which + // takes an argument + // chunk and returns the result of invoking transformerDict["transform"] with argument + // list « chunk, controller » + // and callback this value transformer. + + // If transformerDict["flush"] exists, set flushAlgorithm to an algorithm which returns + // the result + // of invoking transformerDict["flush"] with argument list « controller » and callback + // this value transformer. + + // If transformerDict["cancel"] exists, set cancelAlgorithm to an algorithm which takes an argument + // reason and returns the result of invoking transformerDict["cancel"] with argument list « reason » + // and callback this value transformer. + controller.set_transform_obj(transformer_obj); + + // Perform ! SetUpTransformStreamDefaultController(stream, controller, + // transformAlgorithm, flushAlgorithm, cancelAlgorithm). + self.set_up_transform_stream_default_controller(&controller); + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-sink-write-algorithm> + pub(crate) fn transform_stream_default_sink_write_algorithm( + &self, + cx: SafeJSContext, + global: &GlobalScope, + chunk: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // Assert: stream.[[writable]].[[state]] is "writable". + assert!(self.writable.get().is_some()); + + // Let controller be stream.[[controller]]. + let controller = self.controller.get().expect("controller is not set"); + + // If stream.[[backpressure]] is true, + if self.backpressure.get() { + // Let backpressureChangePromise be stream.[[backpressureChangePromise]]. + let backpressure_change_promise = self.backpressure_change_promise.borrow(); + + // Assert: backpressureChangePromise is not undefined. + assert!(backpressure_change_promise.is_some()); + + // Return the result of reacting to backpressureChangePromise with the following fulfillment steps: + let result_promise = Promise::new(global, can_gc); + rooted!(in(*cx) let mut fulfillment_handler = Some(TransformBackPressureChangePromiseFulfillment { + controller: Dom::from_ref(&controller), + writable: Dom::from_ref(&self.writable.get().expect("writable stream")), + chunk: Heap::boxed(chunk.get()), + result_promise: result_promise.clone(), + })); + + let handler = PromiseNativeHandler::new( + global, + fulfillment_handler.take().map(|h| Box::new(h) as Box<_>), + Some(Box::new(BackpressureChangeRejection { + result_promise: result_promise.clone(), + })), + can_gc, + ); + let realm = enter_realm(global); + let comp = InRealm::Entered(&realm); + backpressure_change_promise + .as_ref() + .expect("Promise must be some by now.") + .append_native_handler(&handler, comp, can_gc); + + return Ok(result_promise); + } + + // Return ! TransformStreamDefaultControllerPerformTransform(controller, chunk). + controller.transform_stream_default_controller_perform_transform(cx, global, chunk, can_gc) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-sink-abort-algorithm> + pub(crate) fn transform_stream_default_sink_abort_algorithm( + &self, + cx: SafeJSContext, + global: &GlobalScope, + reason: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // Let controller be stream.[[controller]]. + let controller = self.controller.get().expect("controller is not set"); + + // If controller.[[finishPromise]] is not undefined, return controller.[[finishPromise]]. + if let Some(finish_promise) = controller.get_finish_promise() { + return Ok(finish_promise); + } + + // Let readable be stream.[[readable]]. + let readable = self.readable.get().expect("readable stream is not set"); + + // Let controller.[[finishPromise]] be a new promise. + controller.set_finish_promise(Promise::new(global, can_gc)); + + // Let cancelPromise be the result of performing controller.[[cancelAlgorithm]], passing reason. + let cancel_promise = controller.perform_cancel(cx, global, reason, can_gc)?; + + // Perform ! TransformStreamDefaultControllerClearAlgorithms(controller). + controller.clear_algorithms(); + + // React to cancelPromise: + let handler = PromiseNativeHandler::new( + global, + Some(Box::new(CancelPromiseFulfillment { + readable: Dom::from_ref(&readable), + controller: Dom::from_ref(&controller), + reason: Heap::boxed(reason.get()), + })), + Some(Box::new(CancelPromiseRejection { + readable: Dom::from_ref(&readable), + controller: Dom::from_ref(&controller), + })), + can_gc, + ); + let realm = enter_realm(global); + let comp = InRealm::Entered(&realm); + cancel_promise.append_native_handler(&handler, comp, can_gc); + + // Return controller.[[finishPromise]]. + let finish_promise = controller + .get_finish_promise() + .expect("finish promise is not set"); + Ok(finish_promise) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-sink-close-algorithm> + pub(crate) fn transform_stream_default_sink_close_algorithm( + &self, + cx: SafeJSContext, + global: &GlobalScope, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // Let controller be stream.[[controller]]. + let controller = self + .controller + .get() + .ok_or(Error::Type("controller is not set".to_string()))?; + + // If controller.[[finishPromise]] is not undefined, return controller.[[finishPromise]]. + if let Some(finish_promise) = controller.get_finish_promise() { + return Ok(finish_promise); + } + + // Let readable be stream.[[readable]]. + let readable = self + .readable + .get() + .ok_or(Error::Type("readable stream is not set".to_string()))?; + + // Let controller.[[finishPromise]] be a new promise. + controller.set_finish_promise(Promise::new(global, can_gc)); + + // Let flushPromise be the result of performing controller.[[flushAlgorithm]]. + let flush_promise = controller.perform_flush(cx, global, can_gc)?; + + // Perform ! TransformStreamDefaultControllerClearAlgorithms(controller). + controller.clear_algorithms(); + + // React to flushPromise: + let handler = PromiseNativeHandler::new( + global, + Some(Box::new(FlushPromiseFulfillment { + readable: Dom::from_ref(&readable), + controller: Dom::from_ref(&controller), + })), + Some(Box::new(FlushPromiseRejection { + readable: Dom::from_ref(&readable), + controller: Dom::from_ref(&controller), + })), + can_gc, + ); + + let realm = enter_realm(global); + let comp = InRealm::Entered(&realm); + flush_promise.append_native_handler(&handler, comp, can_gc); + // Return controller.[[finishPromise]]. + let finish_promise = controller + .get_finish_promise() + .expect("finish promise is not set"); + Ok(finish_promise) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-source-cancel> + pub(crate) fn transform_stream_default_source_cancel( + &self, + cx: SafeJSContext, + global: &GlobalScope, + reason: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // Let controller be stream.[[controller]]. + let controller = self + .controller + .get() + .ok_or(Error::Type("controller is not set".to_string()))?; + + // If controller.[[finishPromise]] is not undefined, return controller.[[finishPromise]]. + if let Some(finish_promise) = controller.get_finish_promise() { + return Ok(finish_promise); + } + + // Let writable be stream.[[writable]]. + let writable = self + .writable + .get() + .ok_or(Error::Type("writable stream is not set".to_string()))?; + + // Let controller.[[finishPromise]] be a new promise. + controller.set_finish_promise(Promise::new(global, can_gc)); + + // Let cancelPromise be the result of performing controller.[[cancelAlgorithm]], passing reason. + let cancel_promise = controller.perform_cancel(cx, global, reason, can_gc)?; + + // Perform ! TransformStreamDefaultControllerClearAlgorithms(controller). + controller.clear_algorithms(); + + // React to cancelPromise: + let handler = PromiseNativeHandler::new( + global, + Some(Box::new(SourceCancelPromiseFulfillment { + writeable: Dom::from_ref(&writable), + controller: Dom::from_ref(&controller), + stream: Dom::from_ref(self), + reason: Heap::boxed(reason.get()), + })), + Some(Box::new(SourceCancelPromiseRejection { + writeable: Dom::from_ref(&writable), + controller: Dom::from_ref(&controller), + stream: Dom::from_ref(self), + })), + can_gc, + ); + + // Return controller.[[finishPromise]]. + let finish_promise = controller + .get_finish_promise() + .expect("finish promise is not set"); + let realm = enter_realm(global); + let comp = InRealm::Entered(&realm); + cancel_promise.append_native_handler(&handler, comp, can_gc); + Ok(finish_promise) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-source-pull> + pub(crate) fn transform_stream_default_source_pull( + &self, + global: &GlobalScope, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // Assert: stream.[[backpressure]] is true. + assert!(self.backpressure.get()); + + // Assert: stream.[[backpressureChangePromise]] is not undefined. + assert!(self.backpressure_change_promise.borrow().is_some()); + + // Perform ! TransformStreamSetBackpressure(stream, false). + self.set_backpressure(global, false, can_gc); + + // Return stream.[[backpressureChangePromise]]. + Ok(self + .backpressure_change_promise + .borrow() + .clone() + .expect("Promise must be some by now.")) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-error-writable-and-unblock-write> + pub(crate) fn error_writable_and_unblock_write( + &self, + cx: SafeJSContext, + global: &GlobalScope, + error: SafeHandleValue, + can_gc: CanGc, + ) { + // Perform ! TransformStreamDefaultControllerClearAlgorithms(stream.[[controller]]). + self.get_controller().clear_algorithms(); + + // Perform ! WritableStreamDefaultControllerErrorIfNeeded(stream.[[writable]].[[controller]], e). + self.get_writable() + .get_default_controller() + .error_if_needed(cx, error, global, can_gc); + + // Perform ! TransformStreamUnblockWrite(stream). + self.unblock_write(global, can_gc) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-unblock-write> + pub(crate) fn unblock_write(&self, global: &GlobalScope, can_gc: CanGc) { + // If stream.[[backpressure]] is true, perform ! TransformStreamSetBackpressure(stream, false). + if self.backpressure.get() { + self.set_backpressure(global, false, can_gc); + } + } + + /// <https://streams.spec.whatwg.org/#transform-stream-error> + pub(crate) fn error( + &self, + cx: SafeJSContext, + global: &GlobalScope, + error: SafeHandleValue, + can_gc: CanGc, + ) { + // Perform ! ReadableStreamDefaultControllerError(stream.[[readable]].[[controller]], e). + self.get_readable() + .get_default_controller() + .error(error, can_gc); + + // Perform ! TransformStreamErrorWritableAndUnblockWrite(stream, e). + self.error_writable_and_unblock_write(cx, global, error, can_gc); + } +} + +impl TransformStreamMethods<crate::DomTypeHolder> for TransformStream { + /// <https://streams.spec.whatwg.org/#ts-constructor> + #[allow(unsafe_code)] + fn Constructor( + cx: SafeJSContext, + global: &GlobalScope, + proto: Option<SafeHandleObject>, + can_gc: CanGc, + transformer: Option<*mut JSObject>, + writable_strategy: &QueuingStrategy, + readable_strategy: &QueuingStrategy, + ) -> Fallible<DomRoot<TransformStream>> { + // If transformer is missing, set it to null. + rooted!(in(*cx) let transformer_obj = transformer.unwrap_or(ptr::null_mut())); + + // Let underlyingSinkDict be underlyingSink, + // converted to an IDL value of type UnderlyingSink. + let transformer_dict = if !transformer_obj.is_null() { + rooted!(in(*cx) let obj_val = ObjectValue(transformer_obj.get())); + match Transformer::new(cx, obj_val.handle()) { + Ok(ConversionResult::Success(val)) => val, + Ok(ConversionResult::Failure(error)) => return Err(Error::Type(error.to_string())), + _ => { + return Err(Error::JSFailed); + }, + } + } else { + Transformer::empty() + }; + + // If transformerDict["readableType"] exists, throw a RangeError exception. + if !transformer_dict.readableType.handle().is_undefined() { + return Err(Error::Range("readableType is set".to_string())); + } + + // If transformerDict["writableType"] exists, throw a RangeError exception. + if !transformer_dict.writableType.handle().is_undefined() { + return Err(Error::Range("writableType is set".to_string())); + } + + // Let readableHighWaterMark be ? ExtractHighWaterMark(readableStrategy, 0). + let readable_high_water_mark = extract_high_water_mark(readable_strategy, 0.0)?; + + // Let readableSizeAlgorithm be ! ExtractSizeAlgorithm(readableStrategy). + let readable_size_algorithm = extract_size_algorithm(readable_strategy, can_gc); + + // Let writableHighWaterMark be ? ExtractHighWaterMark(writableStrategy, 1). + let writable_high_water_mark = extract_high_water_mark(writable_strategy, 1.0)?; + + // Let writableSizeAlgorithm be ! ExtractSizeAlgorithm(writableStrategy). + let writable_size_algorithm = extract_size_algorithm(writable_strategy, can_gc); + + // Let startPromise be a new promise. + let start_promise = Promise::new(global, can_gc); + + // Perform ! InitializeTransformStream(this, startPromise, writableHighWaterMark, + // writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm). + let stream = TransformStream::new_with_proto(global, proto, can_gc); + stream.initialize( + cx, + global, + start_promise.clone(), + writable_high_water_mark, + writable_size_algorithm, + readable_high_water_mark, + readable_size_algorithm, + can_gc, + )?; + + // Perform ? SetUpTransformStreamDefaultControllerFromTransformer(this, transformer, transformerDict). + stream.set_up_transform_stream_default_controller_from_transformer( + global, + transformer_obj.handle(), + &transformer_dict, + can_gc, + ); + + // If transformerDict["start"] exists, then resolve startPromise with the + // result of invoking transformerDict["start"] + // with argument list « this.[[controller]] » and callback this value transformer. + if let Some(start) = &transformer_dict.start { + rooted!(in(*cx) let mut result_object = ptr::null_mut::<JSObject>()); + rooted!(in(*cx) let mut result: JSVal); + rooted!(in(*cx) let this_object = transformer_obj.get()); + start.Call_( + &this_object.handle(), + &stream.get_controller(), + result.handle_mut(), + ExceptionHandling::Rethrow, + can_gc, + )?; + let is_promise = unsafe { + if result.is_object() { + result_object.set(result.to_object()); + IsPromiseObject(result_object.handle().into_handle()) + } else { + false + } + }; + let promise = if is_promise { + let promise = Promise::new_with_js_promise(result_object.handle(), cx); + promise + } else { + Promise::new_resolved(global, cx, result.get(), can_gc) + }; + start_promise.resolve_native(&promise, can_gc); + } else { + // Otherwise, resolve startPromise with undefined. + start_promise.resolve_native(&(), can_gc); + }; + + Ok(stream) + } + + /// <https://streams.spec.whatwg.org/#ts-readable> + fn Readable(&self) -> DomRoot<ReadableStream> { + // Return this.[[readable]]. + self.readable.get().expect("readable stream is not set") + } + + /// <https://streams.spec.whatwg.org/#ts-writable> + fn Writable(&self) -> DomRoot<WritableStream> { + // Return this.[[writable]]. + self.writable.get().expect("writable stream is not set") + } +} diff --git a/components/script/dom/transformstreamdefaultcontroller.rs b/components/script/dom/transformstreamdefaultcontroller.rs new file mode 100644 index 00000000000..41813ef6f96 --- /dev/null +++ b/components/script/dom/transformstreamdefaultcontroller.rs @@ -0,0 +1,420 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::cell::RefCell; +use std::rc::Rc; + +use dom_struct::dom_struct; +use js::jsapi::{ + ExceptionStackBehavior, Heap, JS_IsExceptionPending, JS_SetPendingException, JSObject, +}; +use js::jsval::UndefinedValue; +use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue}; + +use super::bindings::cell::DomRefCell; +use super::bindings::codegen::Bindings::TransformerBinding::{ + Transformer, TransformerCancelCallback, TransformerFlushCallback, TransformerTransformCallback, +}; +use super::types::TransformStream; +use crate::dom::bindings::callback::ExceptionHandling; +use crate::dom::bindings::codegen::Bindings::TransformStreamDefaultControllerBinding::TransformStreamDefaultControllerMethods; +use crate::dom::bindings::error::{Error, ErrorToJsval, Fallible}; +use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler}; +use crate::realms::{InRealm, enter_realm}; +use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; + +impl js::gc::Rootable for TransformTransformPromiseRejection {} + +/// Reacting to transformPromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-controller-perform-transform> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct TransformTransformPromiseRejection { + controller: Dom<TransformStreamDefaultController>, +} + +impl Callback for TransformTransformPromiseRejection { + /// Reacting to transformPromise with the following fulfillment steps: + #[allow(unsafe_code)] + fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Perform ! TransformStreamError(controller.[[stream]], r). + self.controller + .error(cx, &self.controller.global(), v, can_gc); + + // Throw r. + // Note: this is done part of perform_transform(). + } +} + +/// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller> +#[dom_struct] +pub struct TransformStreamDefaultController { + reflector_: Reflector, + + /// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-cancelalgorithm> + #[ignore_malloc_size_of = "Rc is hard"] + cancel: RefCell<Option<Rc<TransformerCancelCallback>>>, + + /// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-flushalgorithm> + #[ignore_malloc_size_of = "Rc is hard"] + flush: RefCell<Option<Rc<TransformerFlushCallback>>>, + + /// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-transformalgorithm> + #[ignore_malloc_size_of = "Rc is hard"] + transform: RefCell<Option<Rc<TransformerTransformCallback>>>, + + /// The JS object used as `this` when invoking sink algorithms. + #[ignore_malloc_size_of = "mozjs"] + transform_obj: Heap<*mut JSObject>, + + /// <https://streams.spec.whatwg.org/#TransformStreamDefaultController-stream> + stream: MutNullableDom<TransformStream>, + + /// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-finishpromise> + #[ignore_malloc_size_of = "Rc is hard"] + finish_promise: DomRefCell<Option<Rc<Promise>>>, +} + +impl TransformStreamDefaultController { + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + fn new_inherited(transformer: &Transformer) -> TransformStreamDefaultController { + TransformStreamDefaultController { + reflector_: Reflector::new(), + cancel: RefCell::new(transformer.cancel.clone()), + flush: RefCell::new(transformer.flush.clone()), + transform: RefCell::new(transformer.transform.clone()), + finish_promise: DomRefCell::new(None), + stream: MutNullableDom::new(None), + transform_obj: Default::default(), + } + } + + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + pub(crate) fn new( + global: &GlobalScope, + transformer: &Transformer, + can_gc: CanGc, + ) -> DomRoot<TransformStreamDefaultController> { + reflect_dom_object( + Box::new(TransformStreamDefaultController::new_inherited(transformer)), + global, + can_gc, + ) + } + + /// Setting the JS object after the heap has settled down. + pub(crate) fn set_transform_obj(&self, this_object: SafeHandleObject) { + self.transform_obj.set(*this_object); + } + + pub(crate) fn set_stream(&self, stream: &TransformStream) { + self.stream.set(Some(stream)); + } + + pub(crate) fn get_finish_promise(&self) -> Option<Rc<Promise>> { + self.finish_promise.borrow().clone() + } + + pub(crate) fn set_finish_promise(&self, promise: Rc<Promise>) { + *self.finish_promise.borrow_mut() = Some(promise); + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-perform-transform> + pub(crate) fn transform_stream_default_controller_perform_transform( + &self, + cx: SafeJSContext, + global: &GlobalScope, + chunk: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // Let transformPromise be the result of performing controller.[[transformAlgorithm]], passing chunk. + let transform_promise = self.perform_transform(cx, global, chunk, can_gc)?; + + // Return the result of reacting to transformPromise with the following rejection steps given the argument r: + rooted!(in(*cx) let mut reject_handler = Some(TransformTransformPromiseRejection { + controller: Dom::from_ref(self), + })); + + let handler = PromiseNativeHandler::new( + global, + None, + reject_handler.take().map(|h| Box::new(h) as Box<_>), + can_gc, + ); + let realm = enter_realm(global); + let comp = InRealm::Entered(&realm); + transform_promise.append_native_handler(&handler, comp, can_gc); + + Ok(transform_promise) + } + + pub(crate) fn perform_transform( + &self, + cx: SafeJSContext, + global: &GlobalScope, + chunk: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // If transformerDict["transform"] exists, set transformAlgorithm to an algorithm which + // takes an argument chunk and returns the result of invoking transformerDict["transform"] with argument list + // « chunk, controller » and callback this value transformer. + let algo = self.transform.borrow().clone(); + let result = if let Some(transform) = algo { + rooted!(in(*cx) let this_object = self.transform_obj.get()); + let call_result = transform.Call_( + &this_object.handle(), + chunk, + self, + ExceptionHandling::Rethrow, + can_gc, + ); + match call_result { + Ok(p) => p, + Err(e) => { + let p = Promise::new(global, can_gc); + p.reject_error(e, can_gc); + p + }, + } + } else { + // Let transformAlgorithm be the following steps, taking a chunk argument: + // Let result be TransformStreamDefaultControllerEnqueue(controller, chunk). + // If result is an abrupt completion, return a promise rejected with result.[[Value]]. + let promise = if let Err(error) = self.enqueue(cx, global, chunk, can_gc) { + rooted!(in(*cx) let mut error_val = UndefinedValue()); + error.to_jsval(cx, global, error_val.handle_mut(), can_gc); + Promise::new_rejected(global, cx, error_val.handle(), can_gc) + } else { + // Otherwise, return a promise resolved with undefined. + Promise::new_resolved(global, cx, (), can_gc) + }; + + promise + }; + + Ok(result) + } + + pub(crate) fn perform_cancel( + &self, + cx: SafeJSContext, + global: &GlobalScope, + chunk: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // If transformerDict["cancel"] exists, set cancelAlgorithm to an algorithm which takes an argument + // reason and returns the result of invoking transformerDict["cancel"] with argument list « reason » + // and callback this value transformer. + let algo = self.cancel.borrow().clone(); + let result = if let Some(cancel) = algo { + rooted!(in(*cx) let this_object = self.transform_obj.get()); + let call_result = cancel.Call_( + &this_object.handle(), + chunk, + ExceptionHandling::Rethrow, + can_gc, + ); + match call_result { + Ok(p) => p, + Err(e) => { + let p = Promise::new(global, can_gc); + p.reject_error(e, can_gc); + p + }, + } + } else { + // Let cancelAlgorithm be an algorithm which returns a promise resolved with undefined. + Promise::new_resolved(global, cx, (), can_gc) + }; + + Ok(result) + } + + pub(crate) fn perform_flush( + &self, + cx: SafeJSContext, + global: &GlobalScope, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // If transformerDict["flush"] exists, set flushAlgorithm to an algorithm which returns the result of + // invoking transformerDict["flush"] with argument list « controller » and callback this value transformer. + let algo = self.flush.borrow().clone(); + let result = if let Some(flush) = algo { + rooted!(in(*cx) let this_object = self.transform_obj.get()); + let call_result = flush.Call_( + &this_object.handle(), + self, + ExceptionHandling::Rethrow, + can_gc, + ); + match call_result { + Ok(p) => p, + Err(e) => { + let p = Promise::new(global, can_gc); + p.reject_error(e, can_gc); + p + }, + } + } else { + // Let flushAlgorithm be an algorithm which returns a promise resolved with undefined. + Promise::new_resolved(global, cx, (), can_gc) + }; + + Ok(result) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-enqueue> + #[allow(unsafe_code)] + pub(crate) fn enqueue( + &self, + cx: SafeJSContext, + global: &GlobalScope, + chunk: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<()> { + // Let stream be controller.[[stream]]. + let stream = self.stream.get().expect("stream is null"); + + // Let readableController be stream.[[readable]].[[controller]]. + let readable = stream.get_readable(); + let readable_controller = readable.get_default_controller(); + + // If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(readableController) + // is false, throw a TypeError exception. + if !readable_controller.can_close_or_enqueue() { + return Err(Error::Type( + "ReadableStreamDefaultControllerCanCloseOrEnqueue is false".to_owned(), + )); + } + + // Let enqueueResult be ReadableStreamDefaultControllerEnqueue(readableController, chunk). + // If enqueueResult is an abrupt completion, + if let Err(error) = readable_controller.enqueue(cx, chunk, can_gc) { + // Perform ! TransformStreamErrorWritableAndUnblockWrite(stream, enqueueResult.[[Value]]). + rooted!(in(*cx) let mut rooted_error = UndefinedValue()); + error + .clone() + .to_jsval(cx, global, rooted_error.handle_mut(), can_gc); + stream.error_writable_and_unblock_write(cx, global, rooted_error.handle(), can_gc); + + // Throw stream.[[readable]].[[storedError]]. + unsafe { + if !JS_IsExceptionPending(*cx) { + rooted!(in(*cx) let mut stored_error = UndefinedValue()); + readable.get_stored_error(stored_error.handle_mut()); + + JS_SetPendingException( + *cx, + stored_error.handle().into(), + ExceptionStackBehavior::Capture, + ); + } + } + return Err(error); + } + + // Let backpressure be ! ReadableStreamDefaultControllerHasBackpressure(readableController). + let backpressure = readable_controller.has_backpressure(); + + // If backpressure is not stream.[[backpressure]], + if backpressure != stream.get_backpressure() { + // Assert: backpressure is true. + assert!(backpressure); + + // Perform ! TransformStreamSetBackpressure(stream, true). + stream.set_backpressure(global, true, can_gc); + } + Ok(()) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-error> + pub(crate) fn error( + &self, + cx: SafeJSContext, + global: &GlobalScope, + reason: SafeHandleValue, + can_gc: CanGc, + ) { + // Perform ! TransformStreamError(controller.[[stream]], e). + self.stream + .get() + .expect("stream is undefined") + .error(cx, global, reason, can_gc); + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-clear-algorithms> + pub(crate) fn clear_algorithms(&self) { + // Set controller.[[transformAlgorithm]] to undefined. + self.transform.replace(None); + + // Set controller.[[flushAlgorithm]] to undefined. + self.flush.replace(None); + + // Set controller.[[cancelAlgorithm]] to undefined. + self.cancel.replace(None); + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-terminate> + pub(crate) fn terminate(&self, cx: SafeJSContext, global: &GlobalScope, can_gc: CanGc) { + // Let stream be controller.[[stream]]. + let stream = self.stream.get().expect("stream is null"); + + // Let readableController be stream.[[readable]].[[controller]]. + let readable = stream.get_readable(); + let readable_controller = readable.get_default_controller(); + + // Perform ! ReadableStreamDefaultControllerClose(readableController). + readable_controller.close(can_gc); + + // Let error be a TypeError exception indicating that the stream has been terminated. + let error = Error::Type("stream has been terminated".to_owned()); + + // Perform ! TransformStreamErrorWritableAndUnblockWrite(stream, error). + rooted!(in(*cx) let mut rooted_error = UndefinedValue()); + error.to_jsval(cx, global, rooted_error.handle_mut(), can_gc); + stream.error_writable_and_unblock_write(cx, global, rooted_error.handle(), can_gc); + } +} + +impl TransformStreamDefaultControllerMethods<crate::DomTypeHolder> + for TransformStreamDefaultController +{ + /// <https://streams.spec.whatwg.org/#ts-default-controller-desired-size> + fn GetDesiredSize(&self) -> Option<f64> { + // Let readableController be this.[[stream]].[[readable]].[[controller]]. + let readable_controller = self + .stream + .get() + .expect("stream is null") + .get_readable() + .get_default_controller(); + + // Return ! ReadableStreamDefaultControllerGetDesiredSize(readableController). + readable_controller.get_desired_size() + } + + /// <https://streams.spec.whatwg.org/#ts-default-controller-enqueue> + fn Enqueue(&self, cx: SafeJSContext, chunk: SafeHandleValue, can_gc: CanGc) -> Fallible<()> { + // Perform ? TransformStreamDefaultControllerEnqueue(this, chunk). + self.enqueue(cx, &self.global(), chunk, can_gc) + } + + /// <https://streams.spec.whatwg.org/#ts-default-controller-error> + fn Error(&self, cx: SafeJSContext, reason: SafeHandleValue, can_gc: CanGc) -> Fallible<()> { + // Perform ? TransformStreamDefaultControllerError(this, e). + self.error(cx, &self.global(), reason, can_gc); + Ok(()) + } + + /// <https://streams.spec.whatwg.org/#ts-default-controller-terminate> + fn Terminate(&self, can_gc: CanGc) -> Fallible<()> { + // Perform ? TransformStreamDefaultControllerTerminate(this). + self.terminate(GlobalScope::get_cx(), &self.global(), can_gc); + Ok(()) + } +} diff --git a/components/script/dom/trustedtypepolicyfactory.rs b/components/script/dom/trustedtypepolicyfactory.rs index 0927446b904..284fa7045eb 100644 --- a/components/script/dom/trustedtypepolicyfactory.rs +++ b/components/script/dom/trustedtypepolicyfactory.rs @@ -66,7 +66,7 @@ impl TrustedTypePolicyFactory { (CheckResult::Allowed, Vec::new()) }; - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); // Step 2: If allowedByCSP is "Blocked", throw a TypeError and abort further steps. if allowed_by_csp == CheckResult::Blocked { @@ -230,7 +230,7 @@ impl TrustedTypePolicyFactory { .should_sink_type_mismatch_violation_be_blocked_by_csp( sink, sink_group, &input, ); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); // Step 6.2: If disposition is “Allowed”, return stringified input and abort further steps. if disposition == CheckResult::Allowed { Ok(input) diff --git a/components/script/dom/underlyingsourcecontainer.rs b/components/script/dom/underlyingsourcecontainer.rs index 4acb58bafef..dd4b867df45 100644 --- a/components/script/dom/underlyingsourcecontainer.rs +++ b/components/script/dom/underlyingsourcecontainer.rs @@ -20,6 +20,7 @@ use crate::dom::defaultteeunderlyingsource::DefaultTeeUnderlyingSource; use crate::dom::globalscope::GlobalScope; use crate::dom::messageport::MessagePort; use crate::dom::promise::Promise; +use crate::dom::transformstream::TransformStream; use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; /// <https://streams.spec.whatwg.org/#underlying-source-api> @@ -46,8 +47,7 @@ pub(crate) enum UnderlyingSourceType { /// A struct representing a JS object as underlying source, /// and the actual JS object for use as `thisArg` in callbacks. /// This is used for the `TransformStream` API. - #[allow(unused)] - Transform(/* Dom<TransformStream>, Rc<Promise>*/), + Transform(Dom<TransformStream>, Rc<Promise>), } impl UnderlyingSourceType { @@ -139,9 +139,9 @@ impl UnderlyingSourceContainer { // Call the cancel algorithm for the appropriate branch. tee_underlying_source.cancel_algorithm(cx, global, reason, can_gc) }, - UnderlyingSourceType::Transform() => { + UnderlyingSourceType::Transform(stream, _) => { // Return ! TransformStreamDefaultSourceCancelAlgorithm(stream, reason). - todo!(); + Some(stream.transform_stream_default_source_cancel(cx, global, reason, can_gc)) }, UnderlyingSourceType::Transfer(port) => { // Let cancelAlgorithm be the following steps, taking a reason argument: @@ -213,9 +213,9 @@ impl UnderlyingSourceContainer { Some(Ok(promise)) }, // Note: other source type have no pull steps for now. - UnderlyingSourceType::Transform() => { + UnderlyingSourceType::Transform(stream, _) => { // Return ! TransformStreamDefaultSourcePullAlgorithm(stream). - todo!(); + Some(stream.transform_stream_default_source_pull(&self.global(), can_gc)) }, _ => None, } @@ -280,9 +280,9 @@ impl UnderlyingSourceContainer { // from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable None }, - UnderlyingSourceType::Transform() => { - // Some(transform_underlying_source.start_algorithm()) - todo!(); + UnderlyingSourceType::Transform(_, start_promise) => { + // Let startAlgorithm be an algorithm that returns startPromise. + Some(Ok(start_promise.clone())) }, _ => None, } diff --git a/components/script/dom/websocket.rs b/components/script/dom/websocket.rs index b9b1b50c901..bbb637dfe28 100644 --- a/components/script/dom/websocket.rs +++ b/components/script/dom/websocket.rs @@ -471,7 +471,7 @@ struct ReportCSPViolationTask { impl TaskOnce for ReportCSPViolationTask { fn run_once(self) { let global = self.websocket.root().global(); - global.report_csp_violations(self.violations); + global.report_csp_violations(self.violations, None); } } diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index d888cc8d917..24e694b4f06 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -65,6 +65,7 @@ use profile_traits::time::ProfilerChan as TimeProfilerChan; use script_bindings::codegen::GenericBindings::NavigatorBinding::NavigatorMethods; use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods; use script_bindings::interfaces::WindowHelpers; +use script_bindings::root::Root; use script_layout_interface::{ FragmentType, Layout, PendingImageState, QueryMsg, Reflow, ReflowGoal, ReflowRequest, TrustedNodeAddress, combine_id_with_fragment_type, @@ -146,6 +147,7 @@ use crate::dom::performance::Performance; use crate::dom::promise::Promise; use crate::dom::screen::Screen; use crate::dom::selection::Selection; +use crate::dom::shadowroot::ShadowRoot; use crate::dom::storage::Storage; #[cfg(feature = "bluetooth")] use crate::dom::testrunner::TestRunner; @@ -165,7 +167,7 @@ use crate::script_runtime::{CanGc, JSContext, Runtime}; use crate::script_thread::ScriptThread; use crate::timers::{IsInterval, TimerCallback}; use crate::unminify::unminified_path; -use crate::webdriver_handlers::jsval_to_webdriver; +use crate::webdriver_handlers::{find_node_by_unique_id_in_document, jsval_to_webdriver}; use crate::{fetch, window_named_properties}; /// A callback to call when a response comes back from the `ImageCache`. @@ -1394,6 +1396,30 @@ impl WindowMethods<crate::DomTypeHolder> for Window { } } + fn WebdriverElement(&self, id: DOMString) -> Option<DomRoot<Element>> { + find_node_by_unique_id_in_document(&self.Document(), id.into()) + .ok() + .and_then(Root::downcast) + } + + fn WebdriverFrame(&self, id: DOMString) -> Option<DomRoot<Element>> { + find_node_by_unique_id_in_document(&self.Document(), id.into()) + .ok() + .and_then(Root::downcast::<HTMLIFrameElement>) + .map(Root::upcast::<Element>) + } + + fn WebdriverWindow(&self, _id: DOMString) -> Option<DomRoot<Window>> { + warn!("Window references are not supported in webdriver yet"); + None + } + + fn WebdriverShadowRoot(&self, id: DOMString) -> Option<DomRoot<ShadowRoot>> { + find_node_by_unique_id_in_document(&self.Document(), id.into()) + .ok() + .and_then(Root::downcast) + } + // https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle fn GetComputedStyle( &self, diff --git a/components/script/dom/writablestream.rs b/components/script/dom/writablestream.rs index 1b029f592de..f528f4fbde2 100644 --- a/components/script/dom/writablestream.rs +++ b/components/script/dom/writablestream.rs @@ -962,14 +962,13 @@ impl WritableStream { /// <https://streams.spec.whatwg.org/#create-writable-stream> #[cfg_attr(crown, allow(crown::unrooted_must_root))] -#[allow(unused)] pub(crate) fn create_writable_stream( cx: SafeJSContext, global: &GlobalScope, - can_gc: CanGc, writable_high_water_mark: f64, writable_size_algorithm: Rc<QueuingStrategySize>, underlying_sink_type: UnderlyingSinkType, + can_gc: CanGc, ) -> Fallible<DomRoot<WritableStream>> { // Assert: ! IsNonNegativeNumber(highWaterMark) is true. assert!(writable_high_water_mark >= 0.0); diff --git a/components/script/dom/writablestreamdefaultcontroller.rs b/components/script/dom/writablestreamdefaultcontroller.rs index 084165a6892..135ee6faa59 100644 --- a/components/script/dom/writablestreamdefaultcontroller.rs +++ b/components/script/dom/writablestreamdefaultcontroller.rs @@ -12,6 +12,7 @@ use js::jsval::{JSVal, UndefinedValue}; use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue, IntoHandle}; use super::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategySize; +use super::types::TransformStream; use crate::dom::bindings::callback::ExceptionHandling; use crate::dom::bindings::codegen::Bindings::UnderlyingSinkBinding::{ UnderlyingSinkAbortCallback, UnderlyingSinkCloseCallback, UnderlyingSinkStartCallback, @@ -290,8 +291,7 @@ pub enum UnderlyingSinkType { port: Dom<MessagePort>, }, /// Algorithms supporting transform streams are implemented in Rust. - #[allow(unused)] - Transform(/*Dom<TransformStream>, Rc<Promise>*/), + Transform(Dom<TransformStream>, Rc<Promise>), } impl UnderlyingSinkType { @@ -413,7 +413,7 @@ impl WritableStreamDefaultController { } => { backpressure_promise.borrow_mut().take(); }, - UnderlyingSinkType::Transform() => { + UnderlyingSinkType::Transform(_, _) => { return; }, } @@ -423,7 +423,6 @@ impl WritableStreamDefaultController { } /// <https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller> - #[allow(unsafe_code)] pub(crate) fn setup( &self, cx: SafeJSContext, @@ -560,9 +559,9 @@ impl WritableStreamDefaultController { // Let startAlgorithm be an algorithm that returns undefined. Ok(Promise::new_resolved(global, cx, (), can_gc)) }, - UnderlyingSinkType::Transform() => { + UnderlyingSinkType::Transform(_, start_promise) => { // Let startAlgorithm be an algorithm that returns startPromise. - todo!() + Ok(start_promise.clone()) }, } } @@ -622,9 +621,11 @@ impl WritableStreamDefaultController { } promise }, - UnderlyingSinkType::Transform() => { + UnderlyingSinkType::Transform(stream, _) => { // Return ! TransformStreamDefaultSinkAbortAlgorithm(stream, reason). - todo!() + stream + .transform_stream_default_sink_abort_algorithm(cx, global, reason, can_gc) + .expect("Transform stream default sink abort algorithm should not fail.") }, }; @@ -707,9 +708,11 @@ impl WritableStreamDefaultController { .append_native_handler(&handler, comp, can_gc); result_promise }, - UnderlyingSinkType::Transform() => { + UnderlyingSinkType::Transform(stream, _) => { // Return ! TransformStreamDefaultSinkWriteAlgorithm(stream, chunk). - todo!() + stream + .transform_stream_default_sink_write_algorithm(cx, global, chunk, can_gc) + .expect("Transform stream default sink write algorithm should not fail.") }, } } @@ -757,9 +760,11 @@ impl WritableStreamDefaultController { // Return a promise resolved with undefined. Promise::new_resolved(global, cx, (), can_gc) }, - UnderlyingSinkType::Transform() => { + UnderlyingSinkType::Transform(stream, _) => { // Return ! TransformStreamDefaultSinkCloseAlgorithm(stream). - todo!() + stream + .transform_stream_default_sink_close_algorithm(cx, global, can_gc) + .expect("Transform stream default sink close algorithm should not fail.") }, } } @@ -1038,7 +1043,7 @@ impl WritableStreamDefaultController { } /// <https://streams.spec.whatwg.org/#writable-stream-default-controller-error> - fn error( + pub(crate) fn error( &self, stream: &WritableStream, cx: SafeJSContext, diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index 9cef58acc9a..ca5bb72a1dc 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -148,7 +148,7 @@ impl FetchResponseListener for XHRContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/fetch.rs b/components/script/fetch.rs index 9192a030b66..989cdba862a 100644 --- a/components/script/fetch.rs +++ b/components/script/fetch.rs @@ -313,7 +313,7 @@ impl FetchResponseListener for FetchContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/layout_image.rs b/components/script/layout_image.rs index df542b4b759..546e758e38c 100644 --- a/components/script/layout_image.rs +++ b/components/script/layout_image.rs @@ -81,7 +81,7 @@ impl FetchResponseListener for LayoutImageContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/script_module.rs b/components/script/script_module.rs index 0aa35a2eda8..449f17901ed 100644 --- a/components/script/script_module.rs +++ b/components/script/script_module.rs @@ -1277,7 +1277,7 @@ impl FetchResponseListener for ModuleContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/script_runtime.rs b/components/script/script_runtime.rs index 1f05c15d74e..c407f9cfc73 100644 --- a/components/script/script_runtime.rs +++ b/components/script/script_runtime.rs @@ -390,7 +390,7 @@ unsafe extern "C" fn content_security_policy_allows( RuntimeCode::WASM => csp_list.is_wasm_evaluation_allowed(), }; - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); allowed = is_evaluation_allowed == CheckResult::Allowed; }); allowed diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 54cf89a213f..d6ab18be49b 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -3606,7 +3606,8 @@ impl ScriptThread { fn handle_csp_violations(&self, id: PipelineId, _: RequestId, violations: Vec<csp::Violation>) { if let Some(global) = self.documents.borrow().find_global(id) { - global.report_csp_violations(violations); + // TODO(https://github.com/w3c/webappsec-csp/issues/687): Update after spec is resolved + global.report_csp_violations(violations, None); } } diff --git a/components/script/security_manager.rs b/components/script/security_manager.rs index ee320206de2..ee062594eb8 100644 --- a/components/script/security_manager.rs +++ b/components/script/security_manager.rs @@ -14,8 +14,7 @@ use crate::dom::bindings::codegen::Bindings::SecurityPolicyViolationEventBinding }; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::refcounted::Trusted; -use crate::dom::bindings::reflector::DomGlobal; -use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed}; use crate::dom::eventtarget::EventTarget; use crate::dom::securitypolicyviolationevent::SecurityPolicyViolationEvent; use crate::dom::types::GlobalScope; @@ -23,6 +22,7 @@ use crate::script_runtime::CanGc; use crate::task::TaskOnce; pub(crate) struct CSPViolationReportTask { + global: Trusted<GlobalScope>, event_target: Trusted<EventTarget>, violation_report: SecurityPolicyViolationReport, } @@ -159,28 +159,31 @@ impl CSPViolationReportBuilder { impl CSPViolationReportTask { pub fn new( - global: &GlobalScope, - report: SecurityPolicyViolationReport, + global: Trusted<GlobalScope>, + event_target: Trusted<EventTarget>, + violation_report: SecurityPolicyViolationReport, ) -> CSPViolationReportTask { CSPViolationReportTask { - violation_report: report, - event_target: Trusted::new(global.upcast::<EventTarget>()), + global, + event_target, + violation_report, } } fn fire_violation_event(self, can_gc: CanGc) { - let target = self.event_target.root(); - let global = &target.global(); let event = SecurityPolicyViolationEvent::new( - global, + &self.global.root(), Atom::from("securitypolicyviolation"), EventBubbles::Bubbles, EventCancelable::Cancelable, + EventComposed::Composed, &self.violation_report.convert(), can_gc, ); - event.upcast::<Event>().fire(&target, can_gc); + event + .upcast::<Event>() + .fire(&self.event_target.root(), can_gc); } } diff --git a/components/script/stylesheet_loader.rs b/components/script/stylesheet_loader.rs index 67e186c7f6a..a18d63e323b 100644 --- a/components/script/stylesheet_loader.rs +++ b/components/script/stylesheet_loader.rs @@ -286,7 +286,7 @@ impl FetchResponseListener for StylesheetContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/webdriver_handlers.rs b/components/script/webdriver_handlers.rs index 781ac53f415..330ae4f0788 100644 --- a/components/script/webdriver_handlers.rs +++ b/components/script/webdriver_handlers.rs @@ -53,6 +53,7 @@ use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::{DomGlobal, DomObject}; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; +use crate::dom::document::Document; use crate::dom::element::Element; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; @@ -77,12 +78,27 @@ fn find_node_by_unique_id( pipeline: PipelineId, node_id: String, ) -> Result<DomRoot<Node>, ErrorStatus> { - match documents.find_document(pipeline).and_then(|document| { - document - .upcast::<Node>() - .traverse_preorder(ShadowIncluding::Yes) - .find(|node| node.unique_id() == node_id) - }) { + match documents.find_document(pipeline) { + Some(doc) => find_node_by_unique_id_in_document(&doc, node_id), + None => { + if ScriptThread::has_node_id(&node_id) { + Err(ErrorStatus::StaleElementReference) + } else { + Err(ErrorStatus::NoSuchElement) + } + }, + } +} + +pub(crate) fn find_node_by_unique_id_in_document( + document: &Document, + node_id: String, +) -> Result<DomRoot<Node>, ErrorStatus> { + match document + .upcast::<Node>() + .traverse_preorder(ShadowIncluding::Yes) + .find(|node| node.unique_id() == node_id) + { Some(node) => Ok(node), None => { if ScriptThread::has_node_id(&node_id) { @@ -183,7 +199,7 @@ unsafe fn is_arguments_object(cx: *mut JSContext, value: HandleValue) -> bool { jsstring_to_str(cx, class_name) == "[object Arguments]" } -#[derive(Eq, Hash, PartialEq)] +#[derive(Clone, Eq, Hash, PartialEq)] struct HashableJSVal(u64); impl From<HandleValue<'_>> for HashableJSVal { @@ -193,6 +209,7 @@ impl From<HandleValue<'_>> for HashableJSVal { } #[allow(unsafe_code)] +/// <https://w3c.github.io/webdriver/#dfn-json-deserialize> pub(crate) fn jsval_to_webdriver( cx: SafeJSContext, global_scope: &GlobalScope, @@ -215,12 +232,6 @@ unsafe fn jsval_to_webdriver_inner( val: HandleValue, seen: &mut HashSet<HashableJSVal>, ) -> WebDriverJSResult { - let hashable = val.into(); - if seen.contains(&hashable) { - return Err(WebDriverJSError::JSError); - } - seen.insert(hashable); - let _ac = enter_realm(global_scope); if val.get().is_undefined() { Ok(WebDriverJSValue::Undefined) @@ -252,14 +263,26 @@ unsafe fn jsval_to_webdriver_inner( _ => unreachable!(), }; Ok(WebDriverJSValue::String(String::from(string))) - } else if val.get().is_object() { + } + // https://w3c.github.io/webdriver/#dfn-clone-an-object + else if val.get().is_object() { + let hashable = val.into(); + // Step 1. If value is in `seen`, return error with error code javascript error. + if seen.contains(&hashable) { + return Err(WebDriverJSError::JSError); + } + //Step 2. Append value to `seen`. + seen.insert(hashable.clone()); + rooted!(in(cx) let object = match FromJSValConvertible::from_jsval(cx, val, ()).unwrap() { ConversionResult::Success(object) => object, _ => unreachable!(), }); let _ac = JSAutoRealm::new(cx, *object); - if is_array_like::<crate::DomTypeHolder>(cx, val) || is_arguments_object(cx, val) { + let return_val = if is_array_like::<crate::DomTypeHolder>(cx, val) || + is_arguments_object(cx, val) + { let mut result: Vec<WebDriverJSValue> = Vec::new(); let length = match get_property::<u32>( @@ -282,7 +305,7 @@ unsafe fn jsval_to_webdriver_inner( return Err(WebDriverJSError::JSError); }, }; - + // Step 4. For each enumerable property in value, run the following substeps: for i in 0..length { rooted!(in(cx) let mut item = UndefinedValue()); match get_property_jsval(cx, object.handle(), &i.to_string(), item.handle_mut()) { @@ -303,7 +326,6 @@ unsafe fn jsval_to_webdriver_inner( }, } } - Ok(WebDriverJSValue::ArrayLike(result)) } else if let Ok(element) = root_from_object::<Element>(*object, cx) { Ok(WebDriverJSValue::Element(WebElement( @@ -312,7 +334,7 @@ unsafe fn jsval_to_webdriver_inner( } else if let Ok(window) = root_from_object::<Window>(*object, cx) { let window_proxy = window.window_proxy(); if window_proxy.is_browsing_context_discarded() { - Err(WebDriverJSError::StaleElementReference) + return Err(WebDriverJSError::StaleElementReference); } else if window_proxy.browsing_context_id() == window_proxy.webview_id() { Ok(WebDriverJSValue::Window(WebWindow( window.Document().upcast::<Node>().unique_id(), @@ -332,7 +354,12 @@ unsafe fn jsval_to_webdriver_inner( &HandleValueArray::empty(), value.handle_mut(), ) { - jsval_to_webdriver_inner(cx, global_scope, value.handle(), seen) + Ok(jsval_to_webdriver_inner( + cx, + global_scope, + value.handle(), + seen, + )?) } else { throw_dom_exception( SafeJSContext::from_ptr(cx), @@ -340,7 +367,7 @@ unsafe fn jsval_to_webdriver_inner( Error::JSFailed, CanGc::note(), ); - Err(WebDriverJSError::JSError) + return Err(WebDriverJSError::JSError); } } else { let mut result = HashMap::new(); @@ -392,9 +419,12 @@ unsafe fn jsval_to_webdriver_inner( } } } - Ok(WebDriverJSValue::Object(result)) - } + }; + // Step 5. Remove the last element of `seen`. + seen.remove(&hashable); + // Step 6. Return success with data `result`. + return_val } else { Err(WebDriverJSError::UnknownType) } diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index cb33188804f..2a9874a386f 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -88,7 +88,7 @@ DOMInterfaces = { }, 'Clipboard': { - 'canGc': ['WriteText'] + 'canGc': ['ReadText', 'WriteText'] }, 'ClipboardItem': { diff --git a/components/script_bindings/conversions.rs b/components/script_bindings/conversions.rs index 4d8e7aa595c..953dca889a8 100644 --- a/components/script_bindings/conversions.rs +++ b/components/script_bindings/conversions.rs @@ -532,7 +532,7 @@ where /// Returns whether `value` is an array-like object (Array, FileList, /// HTMLCollection, HTMLFormControlsCollection, HTMLOptionsCollection, -/// NodeList). +/// NodeList, DOMTokenList). /// /// # Safety /// `cx` must point to a valid, non-null JSContext. @@ -548,6 +548,10 @@ pub unsafe fn is_array_like<D: crate::DomTypes>(cx: *mut JSContext, value: Handl _ => return false, }; + // TODO: HTMLAllCollection + if root_from_object::<D::DOMTokenList>(object, cx).is_ok() { + return true; + } if root_from_object::<D::FileList>(object, cx).is_ok() { return true; } diff --git a/components/script_bindings/webidls/CanvasRenderingContext2D.webidl b/components/script_bindings/webidls/CanvasRenderingContext2D.webidl index 0c4960fe6ad..46d91683577 100644 --- a/components/script_bindings/webidls/CanvasRenderingContext2D.webidl +++ b/components/script_bindings/webidls/CanvasRenderingContext2D.webidl @@ -254,7 +254,7 @@ interface CanvasPattern { Serializable] interface ImageData { [Throws] constructor(unsigned long sw, unsigned long sh/*, optional ImageDataSettings settings = {}*/); - [Throws] constructor(/* Uint8ClampedArray */ object data, unsigned long sw, optional unsigned long sh + [Throws] constructor(Uint8ClampedArray data, unsigned long sw, optional unsigned long sh /*, optional ImageDataSettings settings = {}*/); readonly attribute unsigned long width; diff --git a/components/script_bindings/webidls/Clipboard.webidl b/components/script_bindings/webidls/Clipboard.webidl index 7562adbfa38..b77e975917e 100644 --- a/components/script_bindings/webidls/Clipboard.webidl +++ b/components/script_bindings/webidls/Clipboard.webidl @@ -9,7 +9,7 @@ typedef sequence<ClipboardItem> ClipboardItems; [SecureContext, Exposed=Window, Pref="dom_async_clipboard_enabled"] interface Clipboard : EventTarget { // Promise<ClipboardItems> read(); - // Promise<DOMString> readText(); + Promise<DOMString> readText(); // Promise<undefined> write(ClipboardItems data); Promise<undefined> writeText(DOMString data); }; diff --git a/components/script_bindings/webidls/Document.webidl b/components/script_bindings/webidls/Document.webidl index 737e74d3bf2..e878b1642e4 100644 --- a/components/script_bindings/webidls/Document.webidl +++ b/components/script_bindings/webidls/Document.webidl @@ -154,7 +154,6 @@ partial /*sealed*/ interface Document { // also has obsolete members }; Document includes GlobalEventHandlers; -Document includes DocumentAndElementEventHandlers; // https://html.spec.whatwg.org/multipage/#Document-partial partial interface Document { diff --git a/components/script_bindings/webidls/EventHandler.webidl b/components/script_bindings/webidls/EventHandler.webidl index f597ce237d3..d32302f4b37 100644 --- a/components/script_bindings/webidls/EventHandler.webidl +++ b/components/script_bindings/webidls/EventHandler.webidl @@ -28,6 +28,10 @@ typedef OnBeforeUnloadEventHandlerNonNull? OnBeforeUnloadEventHandler; [Exposed=Window] interface mixin GlobalEventHandlers { attribute EventHandler onabort; + attribute EventHandler onauxclick; + attribute EventHandler onbeforeinput; + attribute EventHandler onbeforematch; + attribute EventHandler onbeforetoggle; attribute EventHandler onblur; attribute EventHandler oncancel; attribute EventHandler oncanplay; @@ -35,13 +39,17 @@ interface mixin GlobalEventHandlers { attribute EventHandler onchange; attribute EventHandler onclick; attribute EventHandler onclose; + attribute EventHandler oncommand; + attribute EventHandler oncontextlost; attribute EventHandler oncontextmenu; + attribute EventHandler oncontextrestored; + attribute EventHandler oncopy; attribute EventHandler oncuechange; + attribute EventHandler oncut; attribute EventHandler ondblclick; attribute EventHandler ondrag; attribute EventHandler ondragend; attribute EventHandler ondragenter; - attribute EventHandler ondragexit; attribute EventHandler ondragleave; attribute EventHandler ondragover; attribute EventHandler ondragstart; @@ -68,7 +76,7 @@ interface mixin GlobalEventHandlers { attribute EventHandler onmouseout; attribute EventHandler onmouseover; attribute EventHandler onmouseup; - attribute EventHandler onwheel; + attribute EventHandler onpaste; attribute EventHandler onpause; attribute EventHandler onplay; attribute EventHandler onplaying; @@ -77,11 +85,12 @@ interface mixin GlobalEventHandlers { attribute EventHandler onreset; attribute EventHandler onresize; attribute EventHandler onscroll; + attribute EventHandler onscrollend; attribute EventHandler onsecuritypolicyviolation; attribute EventHandler onseeked; attribute EventHandler onseeking; attribute EventHandler onselect; - attribute EventHandler onshow; + attribute EventHandler onslotchange; attribute EventHandler onstalled; attribute EventHandler onsubmit; attribute EventHandler onsuspend; @@ -89,6 +98,11 @@ interface mixin GlobalEventHandlers { attribute EventHandler ontoggle; attribute EventHandler onvolumechange; attribute EventHandler onwaiting; + attribute EventHandler onwebkitanimationend; + attribute EventHandler onwebkitanimationiteration; + attribute EventHandler onwebkitanimationstart; + attribute EventHandler onwebkittransitionend; + attribute EventHandler onwheel; }; // https://drafts.csswg.org/css-animations/#interface-globaleventhandlers-idl @@ -123,18 +137,12 @@ interface mixin WindowEventHandlers { attribute EventHandler onoffline; attribute EventHandler ononline; attribute EventHandler onpagehide; + attribute EventHandler onpagereveal; attribute EventHandler onpageshow; + attribute EventHandler onpageswap; attribute EventHandler onpopstate; attribute EventHandler onrejectionhandled; attribute EventHandler onstorage; attribute EventHandler onunhandledrejection; attribute EventHandler onunload; }; - -// https://html.spec.whatwg.org/multipage/#documentandelementeventhandlers -[Exposed=Window] -interface mixin DocumentAndElementEventHandlers { - attribute EventHandler oncopy; - attribute EventHandler oncut; - attribute EventHandler onpaste; -}; diff --git a/components/script_bindings/webidls/HTMLElement.webidl b/components/script_bindings/webidls/HTMLElement.webidl index 76bfada1b94..19a4b515d11 100644 --- a/components/script_bindings/webidls/HTMLElement.webidl +++ b/components/script_bindings/webidls/HTMLElement.webidl @@ -73,7 +73,6 @@ partial interface HTMLElement { }; HTMLElement includes GlobalEventHandlers; -HTMLElement includes DocumentAndElementEventHandlers; HTMLElement includes ElementContentEditable; HTMLElement includes ElementCSSInlineStyle; HTMLElement includes HTMLOrSVGElement; diff --git a/components/script_bindings/webidls/TransformStream.webidl b/components/script_bindings/webidls/TransformStream.webidl new file mode 100644 index 00000000000..c36a49b114a --- /dev/null +++ b/components/script_bindings/webidls/TransformStream.webidl @@ -0,0 +1,18 @@ +/* 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 http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://streams.spec.whatwg.org/#ts-class-definition + */ + +[Exposed=*] // [Transferable] - See Bug 1562065 +interface TransformStream { + [Throws] + constructor(optional object transformer, + optional QueuingStrategy writableStrategy = {}, + optional QueuingStrategy readableStrategy = {}); + + readonly attribute ReadableStream readable; + readonly attribute WritableStream writable; +}; diff --git a/components/script_bindings/webidls/TransformStreamDefaultController.webidl b/components/script_bindings/webidls/TransformStreamDefaultController.webidl new file mode 100644 index 00000000000..5f5511ed4b6 --- /dev/null +++ b/components/script_bindings/webidls/TransformStreamDefaultController.webidl @@ -0,0 +1,15 @@ +/* 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 http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://streams.spec.whatwg.org/#ts-default-controller-class-definition + */ + +[Exposed=*] +interface TransformStreamDefaultController { + readonly attribute unrestricted double? desiredSize; + [Throws] undefined enqueue(optional any chunk); + [Throws] undefined error(optional any reason); + [Throws] undefined terminate(); +}; diff --git a/components/script_bindings/webidls/Transformer.webidl b/components/script_bindings/webidls/Transformer.webidl new file mode 100644 index 00000000000..652511450a4 --- /dev/null +++ b/components/script_bindings/webidls/Transformer.webidl @@ -0,0 +1,22 @@ +/* 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 http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://streams.spec.whatwg.org/#transformer-api + */ + +[GenerateInit] +dictionary Transformer { + TransformerStartCallback start; + TransformerTransformCallback transform; + TransformerFlushCallback flush; + TransformerCancelCallback cancel; + any readableType; + any writableType; +}; + +callback TransformerStartCallback = any (TransformStreamDefaultController controller); +callback TransformerFlushCallback = Promise<undefined> (TransformStreamDefaultController controller); +callback TransformerTransformCallback = Promise<undefined> (any chunk, TransformStreamDefaultController controller); +callback TransformerCancelCallback = Promise<undefined> (any reason); diff --git a/components/script_bindings/webidls/Window.webidl b/components/script_bindings/webidls/Window.webidl index eb7c3e1d03d..929279f7951 100644 --- a/components/script_bindings/webidls/Window.webidl +++ b/components/script_bindings/webidls/Window.webidl @@ -150,6 +150,10 @@ partial interface Window { undefined webdriverCallback(optional any result); undefined webdriverException(optional any result); undefined webdriverTimeout(); + Element? webdriverElement(DOMString id); + Element? webdriverFrame(DOMString id); + Window? webdriverWindow(DOMString id); + ShadowRoot? webdriverShadowRoot(DOMString id); }; // https://html.spec.whatwg.org/multipage/#dom-sessionstorage diff --git a/components/shared/compositing/lib.rs b/components/shared/compositing/lib.rs index 2bc2cc74d50..a6701ca2b52 100644 --- a/components/shared/compositing/lib.rs +++ b/components/shared/compositing/lib.rs @@ -104,6 +104,8 @@ pub enum CompositorMsg { WebDriverMouseButtonEvent(WebViewId, MouseButtonAction, MouseButton, f32, f32), /// WebDriver mouse move event WebDriverMouseMoveEvent(WebViewId, f32, f32), + // Webdriver wheel scroll event + WebDriverWheelScrollEvent(WebViewId, f32, f32, f64, f64), /// Inform WebRender of the existence of this pipeline. SendInitialTransaction(WebRenderPipelineId), diff --git a/components/shared/embedder/input_events.rs b/components/shared/embedder/input_events.rs index 0268be6dd9c..acaa9afb3ff 100644 --- a/components/shared/embedder/input_events.rs +++ b/components/shared/embedder/input_events.rs @@ -61,15 +61,16 @@ pub enum MouseButton { Other(u16), } -impl From<u16> for MouseButton { - fn from(value: u16) -> Self { +impl<T: Into<u64>> From<T> for MouseButton { + fn from(value: T) -> Self { + let value = value.into(); match value { 0 => MouseButton::Left, 1 => MouseButton::Middle, 2 => MouseButton::Right, 3 => MouseButton::Back, 4 => MouseButton::Forward, - _ => MouseButton::Other(value), + _ => MouseButton::Other(value as u16), } } } diff --git a/components/shared/embedder/webdriver.rs b/components/shared/embedder/webdriver.rs index 9577163411e..3716a29951a 100644 --- a/components/shared/embedder/webdriver.rs +++ b/components/shared/embedder/webdriver.rs @@ -44,6 +44,8 @@ pub enum WebDriverCommandMsg { MouseButtonAction(WebViewId, MouseButtonAction, MouseButton, f32, f32), /// Act as if the mouse was moved in the browsing context with the given ID. MouseMoveAction(WebViewId, f32, f32), + /// Act as if the mouse wheel is scrolled in the browsing context given the given ID. + WheelScrollAction(WebViewId, f32, f32, f64, f64), /// Set the window size. SetWindowSize(WebViewId, DeviceIntSize, IpcSender<Size2D<f32, CSSPixel>>), /// Take a screenshot of the window. diff --git a/components/webdriver_server/actions.rs b/components/webdriver_server/actions.rs index fbede5b5887..9136e091472 100644 --- a/components/webdriver_server/actions.rs +++ b/components/webdriver_server/actions.rs @@ -13,20 +13,23 @@ use keyboard_types::webdriver::KeyInputState; use webdriver::actions::{ ActionSequence, ActionsType, GeneralAction, KeyAction, KeyActionItem, KeyDownAction, KeyUpAction, NullActionItem, PointerAction, PointerActionItem, PointerActionParameters, - PointerDownAction, PointerMoveAction, PointerOrigin, PointerType, PointerUpAction, + PointerDownAction, PointerMoveAction, PointerOrigin, PointerType, PointerUpAction, WheelAction, + WheelActionItem, WheelScrollAction, }; -use webdriver::error::ErrorStatus; +use webdriver::error::{ErrorStatus, WebDriverError}; -use crate::Handler; +use crate::{Handler, wait_for_script_response}; -// Interval between pointerMove increments in ms, based on common vsync +// Interval between wheelScroll and pointerMove increments in ms, based on common vsync static POINTERMOVE_INTERVAL: u64 = 17; +static WHEELSCROLL_INTERVAL: u64 = 17; // https://w3c.github.io/webdriver/#dfn-input-source-state pub(crate) enum InputSourceState { Null, Key(KeyInputState), Pointer(PointerInputState), + Wheel, } // https://w3c.github.io/webdriver/#dfn-pointer-input-source @@ -76,7 +79,15 @@ fn compute_tick_duration(tick_actions: &ActionSequence) -> u64 { } }, ActionsType::Key { actions: _ } => (), - ActionsType::Wheel { .. } => log::error!("not implemented"), + ActionsType::Wheel { actions } => { + for action in actions.iter() { + let action_duration = match action { + WheelActionItem::General(GeneralAction::Pause(action)) => action.duration, + WheelActionItem::Wheel(WheelAction::Scroll(action)) => action.duration, + }; + duration = cmp::max(duration, action_duration.unwrap_or(0)); + } + }, } duration } @@ -176,9 +187,26 @@ impl Handler { } } }, - ActionsType::Wheel { .. } => { - log::error!("not yet implemented"); - return Err(ErrorStatus::UnsupportedOperation); + ActionsType::Wheel { actions } => { + for action in actions.iter() { + match action { + WheelActionItem::General(_action) => { + self.dispatch_general_action(source_id) + }, + WheelActionItem::Wheel(action) => { + self.session_mut() + .unwrap() + .input_state_table + .entry(source_id.to_string()) + .or_insert(InputSourceState::Wheel); + match action { + WheelAction::Scroll(action) => { + self.dispatch_scroll_action(action, tick_duration)? + }, + } + }, + } + } }, } @@ -191,9 +219,8 @@ impl Handler { let raw_key = action.value.chars().next().unwrap(); let key_input_state = match session.input_state_table.get_mut(source_id).unwrap() { - InputSourceState::Null => unreachable!(), InputSourceState::Key(key_input_state) => key_input_state, - InputSourceState::Pointer(_) => unreachable!(), + _ => unreachable!(), }; session.input_cancel_list.push(ActionSequence { @@ -219,9 +246,8 @@ impl Handler { let raw_key = action.value.chars().next().unwrap(); let key_input_state = match session.input_state_table.get_mut(source_id).unwrap() { - InputSourceState::Null => unreachable!(), InputSourceState::Key(key_input_state) => key_input_state, - InputSourceState::Pointer(_) => unreachable!(), + _ => unreachable!(), }; session.input_cancel_list.push(ActionSequence { @@ -251,9 +277,8 @@ impl Handler { let session = self.session.as_mut().unwrap(); let pointer_input_state = match session.input_state_table.get_mut(source_id).unwrap() { - InputSourceState::Null => unreachable!(), - InputSourceState::Key(_) => unreachable!(), InputSourceState::Pointer(pointer_input_state) => pointer_input_state, + _ => unreachable!(), }; if pointer_input_state.pressed.contains(&action.button) { @@ -280,11 +305,10 @@ impl Handler { }, }); - let button = (action.button as u16).into(); let cmd_msg = WebDriverCommandMsg::MouseButtonAction( session.webview_id, MouseButtonAction::Down, - button, + action.button.into(), pointer_input_state.x as f32, pointer_input_state.y as f32, ); @@ -298,9 +322,8 @@ impl Handler { let session = self.session.as_mut().unwrap(); let pointer_input_state = match session.input_state_table.get_mut(source_id).unwrap() { - InputSourceState::Null => unreachable!(), - InputSourceState::Key(_) => unreachable!(), InputSourceState::Pointer(pointer_input_state) => pointer_input_state, + _ => unreachable!(), }; if !pointer_input_state.pressed.contains(&action.button) { @@ -327,11 +350,10 @@ impl Handler { }, }); - let button = (action.button as u16).into(); let cmd_msg = WebDriverCommandMsg::MouseButtonAction( session.webview_id, MouseButtonAction::Up, - button, + action.button.into(), pointer_input_state.x as f32, pointer_input_state.y as f32, ); @@ -362,14 +384,12 @@ impl Handler { .get(source_id) .unwrap() { - InputSourceState::Null => unreachable!(), - InputSourceState::Key(_) => unreachable!(), InputSourceState::Pointer(pointer_input_state) => { (pointer_input_state.x, pointer_input_state.y) }, + _ => unreachable!(), }; - // Step 5 - 6 let (x, y) = match action.origin { PointerOrigin::Viewport => (x_offset, y_offset), PointerOrigin::Pointer => (start_x + x_offset, start_y + y_offset), @@ -379,26 +399,19 @@ impl Handler { WebDriverScriptCommand::GetElementInViewCenterPoint(x.to_string(), sender), ) .unwrap(); - - let Some(point) = receiver.recv().unwrap()? else { + let response = match wait_for_script_response(receiver) { + Ok(response) => response, + Err(WebDriverError { error, .. }) => return Err(error), + }; + let Ok(Some(point)) = response else { return Err(ErrorStatus::UnknownError); }; point }, }; - let (sender, receiver) = ipc::channel().unwrap(); - let cmd_msg = - WebDriverCommandMsg::GetWindowSize(self.session.as_ref().unwrap().webview_id, sender); - self.constellation_chan - .send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg)) - .unwrap(); - - // Steps 7 - 8 - let viewport_size = receiver.recv().unwrap(); - if x < 0 || x as f32 > viewport_size.width || y < 0 || y as f32 > viewport_size.height { - return Err(ErrorStatus::MoveTargetOutOfBounds); - } + // Step 5 - 6 + self.check_viewport_bound(x, y)?; // Step 9 let duration = match action.duration { @@ -432,9 +445,8 @@ impl Handler { ) { let session = self.session.as_mut().unwrap(); let pointer_input_state = match session.input_state_table.get_mut(source_id).unwrap() { - InputSourceState::Null => unreachable!(), - InputSourceState::Key(_) => unreachable!(), InputSourceState::Pointer(pointer_input_state) => pointer_input_state, + _ => unreachable!(), }; loop { @@ -487,4 +499,163 @@ impl Handler { thread::sleep(Duration::from_millis(POINTERMOVE_INTERVAL)); } } + + /// <https://w3c.github.io/webdriver/#dfn-dispatch-a-scroll-action> + fn dispatch_scroll_action( + &mut self, + action: &WheelScrollAction, + tick_duration: u64, + ) -> Result<(), ErrorStatus> { + // Note: We have not implemented `extract an action sequence` which will calls + // `process a wheel action` that validate many of the variable used here. + // Hence, we do all the checking here until those functions is properly + // implemented. + // <https://w3c.github.io/webdriver/#dfn-process-a-wheel-action> + + let tick_start = Instant::now(); + + // Step 1 + let Some(x_offset) = action.x else { + return Err(ErrorStatus::InvalidArgument); + }; + + // Step 2 + let Some(y_offset) = action.y else { + return Err(ErrorStatus::InvalidArgument); + }; + + // Step 3 - 4 + // Get coordinates relative to an origin. Origin must be viewport. + let (x, y) = match action.origin { + PointerOrigin::Viewport => (x_offset, y_offset), + _ => return Err(ErrorStatus::InvalidArgument), + }; + + // Step 5 - 6 + self.check_viewport_bound(x, y)?; + + // Step 7 - 8 + let Some(delta_x) = action.deltaX else { + return Err(ErrorStatus::InvalidArgument); + }; + + let Some(delta_y) = action.deltaY else { + return Err(ErrorStatus::InvalidArgument); + }; + + // Step 9 + let duration = match action.duration { + Some(duration) => duration, + None => tick_duration, + }; + + // Step 10 + if duration > 0 { + thread::sleep(Duration::from_millis(WHEELSCROLL_INTERVAL)); + } + + // Step 11 + self.perform_scroll(duration, x, y, delta_x, delta_y, 0, 0, tick_start); + + // Step 12 + Ok(()) + } + + /// <https://w3c.github.io/webdriver/#dfn-perform-a-scroll> + #[allow(clippy::too_many_arguments)] + fn perform_scroll( + &mut self, + duration: u64, + x: i64, + y: i64, + target_delta_x: i64, + target_delta_y: i64, + mut curr_delta_x: i64, + mut curr_delta_y: i64, + tick_start: Instant, + ) { + let session = self.session.as_mut().unwrap(); + + // Step 1 + let time_delta = tick_start.elapsed().as_millis(); + + // Step 2 + let duration_ratio = if duration > 0 { + time_delta as f64 / duration as f64 + } else { + 1.0 + }; + + // Step 3 + let last = 1.0 - duration_ratio < 0.001; + + // Step 4 + let (delta_x, delta_y) = if last { + (target_delta_x - curr_delta_x, target_delta_y - curr_delta_y) + } else { + ( + (duration_ratio * target_delta_x as f64) as i64 - curr_delta_x, + (duration_ratio * target_delta_y as f64) as i64 - curr_delta_y, + ) + }; + + // Step 5 + if delta_x != 0 || delta_y != 0 { + // Perform implementation-specific action dispatch steps + let cmd_msg = WebDriverCommandMsg::WheelScrollAction( + session.webview_id, + x as f32, + y as f32, + delta_x as f64, + delta_y as f64, + ); + self.constellation_chan + .send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg)) + .unwrap(); + + curr_delta_x += delta_x; + curr_delta_y += delta_y; + } + + // Step 6 + if last { + return; + } + + // Step 7 + // TODO: The two steps should be done in parallel + // 7.1. Asynchronously wait for an implementation defined amount of time to pass. + thread::sleep(Duration::from_millis(WHEELSCROLL_INTERVAL)); + // 7.2. Perform a scroll with arguments duration, x, y, target delta x, + // target delta y, current delta x, current delta y. + self.perform_scroll( + duration, + x, + y, + target_delta_x, + target_delta_y, + curr_delta_x, + curr_delta_y, + tick_start, + ); + } + + fn check_viewport_bound(&self, x: i64, y: i64) -> Result<(), ErrorStatus> { + let (sender, receiver) = ipc::channel().unwrap(); + let cmd_msg = + WebDriverCommandMsg::GetWindowSize(self.session.as_ref().unwrap().webview_id, sender); + self.constellation_chan + .send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg)) + .unwrap(); + + let viewport_size = match wait_for_script_response(receiver) { + Ok(response) => response, + Err(WebDriverError { error, .. }) => return Err(error), + }; + if x < 0 || x as f32 > viewport_size.width || y < 0 || y as f32 > viewport_size.height { + Err(ErrorStatus::MoveTargetOutOfBounds) + } else { + Ok(()) + } + } } diff --git a/components/webdriver_server/lib.rs b/components/webdriver_server/lib.rs index d003ebf8adb..ad5b4e736a9 100644 --- a/components/webdriver_server/lib.rs +++ b/components/webdriver_server/lib.rs @@ -23,7 +23,7 @@ use constellation_traits::{EmbedderToConstellationMessage, TraversalDirection}; use cookie::{CookieBuilder, Expiration}; use crossbeam_channel::{Receiver, Sender, after, select, unbounded}; use embedder_traits::{ - WebDriverCommandMsg, WebDriverCookieError, WebDriverFrameId, WebDriverJSError, + MouseButton, WebDriverCommandMsg, WebDriverCookieError, WebDriverFrameId, WebDriverJSError, WebDriverJSResult, WebDriverJSValue, WebDriverLoadStatus, WebDriverScriptCommand, }; use euclid::{Rect, Size2D}; @@ -1619,7 +1619,10 @@ impl Handler { InputSourceState::Pointer(PointerInputState::new(&PointerType::Mouse)), ); - // Steps 8.3 - 8.6 + // Step 8.7. Construct a pointer move action. + // Step 8.8. Set a property x to 0 on pointer move action. + // Step 8.9. Set a property y to 0 on pointer move action. + // Step 8.10. Set a property origin to element on pointer move action. let pointer_move_action = PointerMoveAction { duration: None, origin: PointerOrigin::Element(WebElement(element_id)), @@ -1628,32 +1631,32 @@ impl Handler { ..Default::default() }; - // Steps 8.7 - 8.8 + // Step 8.11. Construct pointer down action. + // Step 8.12. Set a property button to 0 on pointer down action. let pointer_down_action = PointerDownAction { - button: 1, + button: i16::from(MouseButton::Left) as u64, ..Default::default() }; - // Steps 8.9 - 8.10 + // Step 8.13. Construct pointer up action. + // Step 8.14. Set a property button to 0 on pointer up action. let pointer_up_action = PointerUpAction { - button: 1, + button: i16::from(MouseButton::Left) as u64, ..Default::default() }; - // Step 8.11 + // Step 8.16 Dispatch a list of actions with input state, + // actions, session's current browsing context, and actions options. if let Err(error) = self.dispatch_pointermove_action(&id, &pointer_move_action, 0) { return Err(WebDriverError::new(error, "")); } - // Steps 8.12 self.dispatch_pointerdown_action(&id, &pointer_down_action); - - // Steps 8.13 self.dispatch_pointerup_action(&id, &pointer_up_action); - // Step 8.14 + // Step 8.17 Remove an input source with input state and input id. self.session_mut()?.input_state_table.remove(&id); // Step 13 @@ -1915,6 +1918,15 @@ impl WebDriverHandler<ServoExtensionRoute> for Handler { } } +/// <https://w3c.github.io/webdriver/#dfn-web-element-identifier> +const ELEMENT_IDENTIFIER: &str = "element-6066-11e4-a52e-4f735466cecf"; +/// <https://w3c.github.io/webdriver/#dfn-web-frame-identifier> +const FRAME_IDENTIFIER: &str = "frame-075b-4da1-b6ba-e579c2d3230a"; +/// <https://w3c.github.io/webdriver/#dfn-web-window-identifier> +const WINDOW_IDENTIFIER: &str = "window-fcc6-11e5-b4f8-330a88ab9d7f"; +/// <https://w3c.github.io/webdriver/#dfn-shadow-root-identifier> +const SHADOW_ROOT_IDENTIFIER: &str = "shadow-6066-11e4-a52e-4f735466cecf"; + fn webdriver_value_to_js_argument(v: &Value) -> String { match v { Value::String(s) => format!("\"{}\"", s), @@ -1929,6 +1941,22 @@ fn webdriver_value_to_js_argument(v: &Value) -> String { format!("[{}]", elems.join(", ")) }, Value::Object(map) => { + let key = map.keys().next().map(String::as_str); + match (key, map.values().next()) { + (Some(ELEMENT_IDENTIFIER), Some(id)) => { + return format!("window.webdriverElement({})", id); + }, + (Some(FRAME_IDENTIFIER), Some(id)) => { + return format!("window.webdriverFrame({})", id); + }, + (Some(WINDOW_IDENTIFIER), Some(id)) => { + return format!("window.webdriverWindow({})", id); + }, + (Some(SHADOW_ROOT_IDENTIFIER), Some(id)) => { + return format!("window.webdriverShadowRoot({})", id); + }, + _ => {}, + } let elems = map .iter() .map(|(k, v)| format!("{}: {}", k, webdriver_value_to_js_argument(v))) diff --git a/components/webgl/Cargo.toml b/components/webgl/Cargo.toml index b0c1c0ceb29..542a3cb4fae 100644 --- a/components/webgl/Cargo.toml +++ b/components/webgl/Cargo.toml @@ -26,6 +26,7 @@ fnv = { workspace = true } glow = { workspace = true } half = "2" ipc-channel = { workspace = true } +itertools = { workspace = true } log = { workspace = true } pixels = { path = "../pixels" } snapshot = { workspace = true } diff --git a/components/webgl/webgl_thread.rs b/components/webgl/webgl_thread.rs index b1ac2b2d3c4..9562c4cb4e0 100644 --- a/components/webgl/webgl_thread.rs +++ b/components/webgl/webgl_thread.rs @@ -32,6 +32,7 @@ use glow::{ }; use half::f16; use ipc_channel::ipc::IpcSharedMemory; +use itertools::Itertools; use log::{debug, error, trace, warn}; use pixels::{self, PixelFormat, unmultiply_inplace}; use surfman::chains::{PreserveBuffer, SwapChains, SwapChainsAPI}; @@ -2570,24 +2571,10 @@ impl WebGLImpl { chan.send((range_min, range_max, precision)).unwrap(); } - fn get_extensions(gl: &Gl, chan: &WebGLSender<String>) { - let mut ext_count = [0]; - unsafe { - gl.get_parameter_i32_slice(gl::NUM_EXTENSIONS, &mut ext_count); - } - // Fall back to the depricated extensions API if that fails - if unsafe { gl.get_error() } != gl::NO_ERROR { - chan.send(unsafe { gl.get_parameter_string(gl::EXTENSIONS) }) - .unwrap(); - return; - } - let ext_count = ext_count[0] as usize; - let mut extensions = Vec::with_capacity(ext_count); - for idx in 0..ext_count { - extensions.push(unsafe { gl.get_parameter_indexed_string(gl::EXTENSIONS, idx as u32) }) - } - let extensions = extensions.join(" "); - chan.send(extensions).unwrap(); + /// This is an implementation of `getSupportedExtensions()` from + /// <https://registry.khronos.org/webgl/specs/latest/1.0/#5.14> + fn get_extensions(gl: &Gl, result_sender: &WebGLSender<String>) { + let _ = result_sender.send(gl.supported_extensions().iter().join(" ")); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6 |