diff options
174 files changed, 2978 insertions, 694 deletions
diff --git a/.gitignore b/.gitignore index 73c2fce6cda..630eb63b9bb 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,9 @@ webrender-captures/ Session.vim Sessionx.vim +# Zed +/.zed + /unminified-js /unminified-css diff --git a/Cargo.lock b/Cargo.lock index d18aa44ad35..90bb55247c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -442,9 +442,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -1065,7 +1065,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -2194,6 +2194,7 @@ dependencies = [ "net_traits", "num-traits", "parking_lot", + "profile_traits", "range", "serde", "servo_allocator", @@ -4252,14 +4253,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] name = "libm" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25169bd5913a4b437588a7e3d127cd6e90127b60e0ffbd834a38f1599e016b8" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" @@ -6167,9 +6168,12 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] [[package]] name = "rustls-webpki" @@ -8478,9 +8482,9 @@ dependencies = [ [[package]] name = "web_atoms" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bcbdcad8fb2e316072ba6bbe09419afdb550285668ac2534f4230a6f2da0ee" +checksum = "0b9c5f0bc545ea3b20b423e33b9b457764de0b3730cd957f6c6aa6c301785f6e" dependencies = [ "phf", "phf_codegen", @@ -8553,6 +8557,7 @@ dependencies = [ "glow", "half", "ipc-channel", + "itertools 0.14.0", "log", "pixels", "snapshot", @@ -8859,7 +8864,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -9259,9 +9264,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index b7ba61f2145..52f84878e14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,7 +115,7 @@ rayon = "1" regex = "1.11" rustls = { version = "0.23", default-features = false, features = ["logging", "std", "tls12"] } rustls-pemfile = "2.0" -rustls-pki-types = "1.11" +rustls-pki-types = "1.12" script_layout_interface = { path = "components/shared/script_layout" } script_traits = { path = "components/shared/script" } selectors = { git = "https://github.com/servo/stylo", branch = "2025-05-01" } 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/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/fonts/Cargo.toml b/components/fonts/Cargo.toml index 2323cb1b240..ce51a9f9112 100644 --- a/components/fonts/Cargo.toml +++ b/components/fonts/Cargo.toml @@ -38,6 +38,7 @@ memmap2 = { workspace = true } net_traits = { workspace = true } num-traits = { workspace = true } parking_lot = { workspace = true } +profile_traits = { workspace = true } range = { path = "../range" } serde = { workspace = true } servo_arc = { workspace = true } diff --git a/components/fonts/font_store.rs b/components/fonts/font_store.rs index 826be947672..0099c56c266 100644 --- a/components/fonts/font_store.rs +++ b/components/fonts/font_store.rs @@ -5,8 +5,8 @@ use std::collections::HashMap; use std::sync::Arc; -use atomic_refcell::AtomicRefCell; use log::warn; +use malloc_size_of_derive::MallocSizeOf; use parking_lot::RwLock; use style::stylesheets::DocumentStyleSheet; use style::values::computed::{FontStyle, FontWeight}; @@ -15,7 +15,7 @@ use crate::font::FontDescriptor; use crate::font_template::{FontTemplate, FontTemplateRef, FontTemplateRefMethods, IsOblique}; use crate::system_font_service::{FontIdentifier, LowercaseFontFamilyName}; -#[derive(Default)] +#[derive(Default, MallocSizeOf)] pub struct FontStore { pub(crate) families: HashMap<LowercaseFontFamilyName, FontTemplates>, web_fonts_loading_for_stylesheets: Vec<(DocumentStyleSheet, usize)>, @@ -134,7 +134,7 @@ impl FontStore { /// /// This optimization is taken from: /// <https://searchfox.org/mozilla-central/source/gfx/thebes/gfxFontEntry.cpp>. -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, MallocSizeOf)] struct SimpleFamily { regular: Option<FontTemplateRef>, bold: Option<FontTemplateRef>, @@ -190,7 +190,7 @@ impl SimpleFamily { } } /// A list of font templates that make up a given font family. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, MallocSizeOf)] pub struct FontTemplates { pub(crate) templates: Vec<FontTemplateRef>, simple_family: Option<SimpleFamily>, @@ -263,7 +263,7 @@ impl FontTemplates { } } - let new_template = Arc::new(AtomicRefCell::new(new_template)); + let new_template = FontTemplateRef::new(new_template); self.templates.push(new_template.clone()); self.update_simple_family(new_template); } diff --git a/components/fonts/font_template.rs b/components/fonts/font_template.rs index eca1017d14e..b8173ee0317 100644 --- a/components/fonts/font_template.rs +++ b/components/fonts/font_template.rs @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::fmt::{Debug, Error, Formatter}; -use std::ops::RangeInclusive; +use std::ops::{Deref, RangeInclusive}; use std::sync::Arc; use atomic_refcell::AtomicRefCell; @@ -20,7 +20,21 @@ use crate::system_font_service::{ }; /// A reference to a [`FontTemplate`] with shared ownership and mutability. -pub type FontTemplateRef = Arc<AtomicRefCell<FontTemplate>>; +#[derive(Clone, Debug, MallocSizeOf)] +pub struct FontTemplateRef(#[conditional_malloc_size_of] Arc<AtomicRefCell<FontTemplate>>); + +impl FontTemplateRef { + pub fn new(template: FontTemplate) -> Self { + Self(Arc::new(AtomicRefCell::new(template))) + } +} + +impl Deref for FontTemplateRef { + type Target = Arc<AtomicRefCell<FontTemplate>>; + fn deref(&self) -> &Self::Target { + &self.0 + } +} /// Describes how to select a font from a given family. This is very basic at the moment and needs /// to be expanded or refactored when we support more of the font styling parameters. diff --git a/components/fonts/system_font_service.rs b/components/fonts/system_font_service.rs index 91b2d810eff..f799affa7c8 100644 --- a/components/fonts/system_font_service.rs +++ b/components/fonts/system_font_service.rs @@ -6,16 +6,19 @@ use std::borrow::ToOwned; use std::cell::OnceCell; use std::collections::HashMap; use std::ops::{Deref, RangeInclusive}; -use std::sync::Arc; use std::{fmt, thread}; use app_units::Au; -use atomic_refcell::AtomicRefCell; use compositing_traits::CrossProcessCompositorApi; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use log::debug; +use malloc_size_of::MallocSizeOf as MallocSizeOfTrait; use malloc_size_of_derive::MallocSizeOf; use parking_lot::{Mutex, RwLock}; +use profile_traits::mem::{ + ProcessReports, ProfilerChan, Report, ReportKind, ReportsChan, perform_memory_report, +}; +use profile_traits::path; use serde::{Deserialize, Serialize}; use servo_config::pref; use servo_url::ServoUrl; @@ -66,11 +69,12 @@ pub enum SystemFontServiceMessage { ), GetFontKey(IpcSender<FontKey>), GetFontInstanceKey(IpcSender<FontInstanceKey>), + CollectMemoryReport(ReportsChan), Exit(IpcSender<()>), Ping, } -#[derive(Default)] +#[derive(Default, MallocSizeOf)] struct ResolvedGenericFontFamilies { default: OnceCell<LowercaseFontFamilyName>, serif: OnceCell<LowercaseFontFamilyName>, @@ -84,6 +88,7 @@ struct ResolvedGenericFontFamilies { /// The system font service. There is one of these for every Servo instance. This is a thread, /// responsible for reading the list of system fonts, handling requests to match against /// them, and ensuring that only one copy of system font data is loaded at a time. +#[derive(MallocSizeOf)] pub struct SystemFontService { port: IpcReceiver<SystemFontServiceMessage>, local_families: FontStore, @@ -118,8 +123,12 @@ impl SystemFontServiceProxySender { } impl SystemFontService { - pub fn spawn(compositor_api: CrossProcessCompositorApi) -> SystemFontServiceProxySender { + pub fn spawn( + compositor_api: CrossProcessCompositorApi, + memory_profiler_sender: ProfilerChan, + ) -> SystemFontServiceProxySender { let (sender, receiver) = ipc::channel().unwrap(); + let memory_reporter_sender = sender.clone(); thread::Builder::new() .name("SystemFontService".to_owned()) @@ -138,7 +147,13 @@ impl SystemFontService { cache.fetch_new_keys(); cache.refresh_local_families(); - cache.run(); + + memory_profiler_sender.run_with_memory_reporting( + || cache.run(), + "system-fonts".to_owned(), + memory_reporter_sender, + SystemFontServiceMessage::CollectMemoryReport, + ); }) .expect("Thread spawning failed"); @@ -172,6 +187,9 @@ impl SystemFontService { self.fetch_new_keys(); let _ = result_sender.send(self.free_font_instance_keys.pop().unwrap()); }, + SystemFontServiceMessage::CollectMemoryReport(report_sender) => { + self.collect_memory_report(report_sender); + }, SystemFontServiceMessage::Ping => (), SystemFontServiceMessage::Exit(result) => { let _ = result.send(()); @@ -181,6 +199,17 @@ impl SystemFontService { } } + fn collect_memory_report(&self, report_sender: ReportsChan) { + perform_memory_report(|ops| { + let reports = vec![Report { + path: path!["system-fonts"], + kind: ReportKind::ExplicitSystemHeapSize, + size: self.size_of(ops), + }]; + report_sender.send(ProcessReports::new(reports)); + }); + } + #[cfg_attr( feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") @@ -528,11 +557,7 @@ impl SystemFontServiceProxy { panic!("SystemFontService has already exited."); }; - let templates: Vec<_> = templates - .into_iter() - .map(AtomicRefCell::new) - .map(Arc::new) - .collect(); + let templates: Vec<_> = templates.into_iter().map(FontTemplateRef::new).collect(); self.templates.write().insert(cache_key, templates.clone()); templates diff --git a/components/fonts/tests/font.rs b/components/fonts/tests/font.rs index 78c507e7b93..a473be9222b 100644 --- a/components/fonts/tests/font.rs +++ b/components/fonts/tests/font.rs @@ -5,14 +5,13 @@ use std::fs::File; use std::io::Read; use std::path::PathBuf; -use std::sync::Arc; use app_units::Au; use euclid::num::Zero; use fonts::platform::font::PlatformFont; use fonts::{ - Font, FontData, FontDescriptor, FontIdentifier, FontTemplate, PlatformFontMethods, - ShapingFlags, ShapingOptions, + Font, FontData, FontDescriptor, FontIdentifier, FontTemplate, FontTemplateRef, + PlatformFontMethods, ShapingFlags, ShapingOptions, }; use servo_url::ServoUrl; use style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps; @@ -42,13 +41,7 @@ fn make_font(path: PathBuf) -> Font { variant: FontVariantCaps::Normal, pt_size: Au::from_px(24), }; - Font::new( - Arc::new(atomic_refcell::AtomicRefCell::new(template)), - descriptor, - Some(data), - None, - ) - .unwrap() + Font::new(FontTemplateRef::new(template), descriptor, Some(data), None).unwrap() } #[test] diff --git a/components/fonts/tests/font_context.rs b/components/fonts/tests/font_context.rs index aeafa02bcc1..0793c1e4ce1 100644 --- a/components/fonts/tests/font_context.rs +++ b/components/fonts/tests/font_context.rs @@ -137,6 +137,7 @@ mod font_context { break; }, SystemFontServiceMessage::Ping => {}, + SystemFontServiceMessage::CollectMemoryReport(..) => {}, } } } diff --git a/components/layout/display_list/mod.rs b/components/layout/display_list/mod.rs index 8799dd2da0c..186272cda36 100644 --- a/components/layout/display_list/mod.rs +++ b/components/layout/display_list/mod.rs @@ -1020,7 +1020,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/fragment_tree/box_fragment.rs b/components/layout/fragment_tree/box_fragment.rs index 65ad1c4aa93..4cd8f278498 100644 --- a/components/layout/fragment_tree/box_fragment.rs +++ b/components/layout/fragment_tree/box_fragment.rs @@ -17,6 +17,7 @@ use style::properties::ComputedValues; use style::values::specified::box_::DisplayOutside; use super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment}; +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>, } diff --git a/components/layout/layout_impl.rs b/components/layout/layout_impl.rs index b8d91c38027..cdf76d3fed0 100644 --- a/components/layout/layout_impl.rs +++ b/components/layout/layout_impl.rs @@ -4,9 +4,8 @@ #![allow(unsafe_code)] -use std::cell::{Cell, LazyCell, RefCell}; -use std::collections::{HashMap, HashSet}; -use std::ffi::c_void; +use std::cell::{Cell, RefCell}; +use std::collections::HashMap; use std::fmt::Debug; use std::process; use std::sync::{Arc, LazyLock}; @@ -95,10 +94,6 @@ use crate::{BoxTree, FragmentTree}; static STYLE_THREAD_POOL: Mutex<&style::global_style_data::STYLE_THREAD_POOL> = Mutex::new(&style::global_style_data::STYLE_THREAD_POOL); -thread_local!(static SEEN_POINTERS: LazyCell<RefCell<HashSet<*const c_void>>> = const { - LazyCell::new(|| RefCell::new(HashSet::new())) -}); - /// A CSS file to style the user agent stylesheet. static USER_AGENT_CSS: &[u8] = include_bytes!("./stylesheets/user-agent.css"); 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/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs index accd0aaf243..2bdeedf986d 100644 --- a/components/malloc_size_of/lib.rs +++ b/components/malloc_size_of/lib.rs @@ -50,6 +50,7 @@ use std::cell::OnceCell; use std::collections::BinaryHeap; use std::hash::{BuildHasher, Hash}; use std::ops::Range; +use std::rc::Rc; use std::sync::Arc; use style::values::generics::length::GenericLengthPercentageOrAuto; @@ -577,6 +578,28 @@ impl<T: MallocSizeOf> MallocConditionalSizeOf for Arc<T> { } } +impl<T> MallocUnconditionalShallowSizeOf for Rc<T> { + fn unconditional_shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + unsafe { ops.malloc_size_of(Rc::as_ptr(self)) } + } +} + +impl<T: MallocSizeOf> MallocUnconditionalSizeOf for Rc<T> { + fn unconditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.unconditional_shallow_size_of(ops) + (**self).size_of(ops) + } +} + +impl<T: MallocSizeOf> MallocConditionalSizeOf for Rc<T> { + fn conditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + if ops.have_seen_ptr(Rc::as_ptr(self)) { + 0 + } else { + self.unconditional_size_of(ops) + } + } +} + /// If a mutex is stored directly as a member of a data type that is being measured, /// it is the unique owner of its contents and deserves to be measured. /// @@ -709,6 +732,12 @@ impl<T> MallocSizeOf for ipc_channel::ipc::IpcSender<T> { } } +impl<T> MallocSizeOf for ipc_channel::ipc::IpcReceiver<T> { + fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { + 0 + } +} + impl MallocSizeOf for ipc_channel::ipc::IpcSharedMemory { fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { self.len() @@ -749,7 +778,10 @@ malloc_size_of_is_0!(std::time::SystemTime); malloc_size_of_is_0!(style::data::ElementData); malloc_size_of_is_0!(style::font_face::SourceList); malloc_size_of_is_0!(style::properties::ComputedValues); +malloc_size_of_is_0!(style::properties::declaration_block::PropertyDeclarationBlock); malloc_size_of_is_0!(style::queries::values::PrefersColorScheme); +malloc_size_of_is_0!(style::stylesheets::Stylesheet); +malloc_size_of_is_0!(style::values::specified::source_size_list::SourceSizeList); malloc_size_of_is_0!(taffy::Layout); malloc_size_of_is_0!(unicode_bidi::Level); malloc_size_of_is_0!(unicode_script::Script); @@ -773,6 +805,7 @@ malloc_size_of_is_webrender_malloc_size_of!(webrender_api::BorderStyle); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::BoxShadowClipMode); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::ColorF); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::ExtendMode); +malloc_size_of_is_webrender_malloc_size_of!(webrender_api::FontKey); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::FontInstanceKey); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::GlyphInstance); malloc_size_of_is_webrender_malloc_size_of!(webrender_api::GradientStop); @@ -817,6 +850,14 @@ where } } +impl<T> MallocSizeOf for style::shared_lock::Locked<T> { + fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { + // TODO: fix this implementation when Locked derives MallocSizeOf. + 0 + //<style::shared_lock::Locked<T> as stylo_malloc_size_of::MallocSizeOf>::size_of(self, ops) + } +} + impl<T: MallocSizeOf> MallocSizeOf for atomic_refcell::AtomicRefCell<T> { fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { self.borrow().size_of(ops) 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/cell.rs b/components/script/dom/bindings/cell.rs index 6c987270911..7e7e752bc0c 100644 --- a/components/script/dom/bindings/cell.rs +++ b/components/script/dom/bindings/cell.rs @@ -10,6 +10,7 @@ pub(crate) use std::cell::{Ref, RefCell, RefMut}; #[cfg(feature = "refcell_backtrace")] pub(crate) use accountable_refcell::{Ref, RefCell, RefMut, ref_filter_map}; +use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOfOps}; #[cfg(not(feature = "refcell_backtrace"))] pub(crate) use ref_filter_map::ref_filter_map; @@ -24,6 +25,12 @@ pub(crate) struct DomRefCell<T> { value: RefCell<T>, } +impl<T: MallocConditionalSizeOf> MallocConditionalSizeOf for DomRefCell<T> { + fn conditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.value.borrow().conditional_size_of(ops) + } +} + // Functionality specific to Servo's `DomRefCell` type // =================================================== 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 ae48fa1fb2f..a4b05e7f445 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -4307,21 +4307,21 @@ impl Document { type_: csp::InlineCheckType, source: &str, ) -> csp::CheckResult { - let element = csp::Element { - nonce: el - .get_attribute(&ns!(), &local_name!("nonce")) - .map(|attr| Cow::Owned(attr.value().to_string())), - }; let (result, violations) = match self.get_csp_list() { None => { return csp::CheckResult::Allowed; }, Some(csp_list) => { + let element = csp::Element { + nonce: el + .get_attribute(&ns!(), &local_name!("nonce")) + .map(|attr| Cow::Owned(attr.value().to_string())), + }; csp_list.should_elements_inline_type_behavior_be_blocked(&element, type_, source) }, }; - self.global().report_csp_violations(violations); + self.global().report_csp_violations(violations, Some(el)); result } diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index b2168846fad..7f38e55fb14 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -179,7 +179,7 @@ pub struct Element { /// <https://dom.spec.whatwg.org/#concept-element-is-value> #[no_trace] is: DomRefCell<Option<LocalName>>, - #[ignore_malloc_size_of = "Arc"] + #[conditional_malloc_size_of] #[no_trace] style_attribute: DomRefCell<Option<Arc<Locked<PropertyDeclarationBlock>>>>, attr_list: MutNullableDom<NamedNodeMap>, 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/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index adff445ae1c..a79c7f6e463 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -97,6 +97,7 @@ enum ParseState { AfterDescriptor, } +#[derive(MallocSizeOf)] pub(crate) struct SourceSet { image_sources: Vec<ImageSource>, source_size: SourceSizeList, @@ -111,13 +112,13 @@ impl SourceSet { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, MallocSizeOf, PartialEq)] pub struct ImageSource { pub url: String, pub descriptor: Descriptor, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, MallocSizeOf, PartialEq)] pub struct Descriptor { pub width: Option<u32>, pub density: Option<f64>, @@ -145,7 +146,7 @@ struct ImageRequest { parsed_url: Option<ServoUrl>, source_url: Option<USVString>, blocker: DomRefCell<Option<LoadBlocker>>, - #[ignore_malloc_size_of = "Arc"] + #[conditional_malloc_size_of] #[no_trace] image: Option<Arc<Image>>, #[no_trace] @@ -162,7 +163,6 @@ pub(crate) struct HTMLImageElement { pending_request: DomRefCell<ImageRequest>, form_owner: MutNullableDom<HTMLFormElement>, generation: Cell<u32>, - #[ignore_malloc_size_of = "SourceSet"] source_set: DomRefCell<SourceSet>, last_selected_source: DomRefCell<Option<USVString>>, #[ignore_malloc_size_of = "promises are hard"] @@ -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 db5c14af450..18bd426acdb 100644 --- a/components/script/dom/htmllinkelement.rs +++ b/components/script/dom/htmllinkelement.rs @@ -98,7 +98,7 @@ pub(crate) struct HTMLLinkElement { #[no_trace] relations: Cell<LinkRelations>, - #[ignore_malloc_size_of = "Arc"] + #[conditional_malloc_size_of] #[no_trace] stylesheet: DomRefCell<Option<Arc<Stylesheet>>>, cssom_stylesheet: MutNullableDom<CSSStyleSheet>, @@ -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 9d0ca807748..4ee1397b4ed 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -281,19 +281,18 @@ pub(crate) enum ScriptType { pub(crate) struct CompiledSourceCode { #[ignore_malloc_size_of = "SM handles JS values"] pub(crate) source_code: Stencil, - #[ignore_malloc_size_of = "Rc is hard"] + #[conditional_malloc_size_of = "Rc is hard"] pub(crate) original_text: Rc<DOMString>, } -#[derive(JSTraceable)] +#[derive(JSTraceable, MallocSizeOf)] pub(crate) enum SourceCode { - Text(Rc<DOMString>), + Text(#[conditional_malloc_size_of] Rc<DOMString>), Compiled(CompiledSourceCode), } #[derive(JSTraceable, MallocSizeOf)] pub(crate) struct ScriptOrigin { - #[ignore_malloc_size_of = "Rc is hard"] code: SourceCode, #[no_trace] url: ServoUrl, @@ -547,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/htmlstyleelement.rs b/components/script/dom/htmlstyleelement.rs index 0deb507f283..aed08b7bcf6 100644 --- a/components/script/dom/htmlstyleelement.rs +++ b/components/script/dom/htmlstyleelement.rs @@ -4,6 +4,7 @@ use std::cell::Cell; +use content_security_policy as csp; use dom_struct::dom_struct; use html5ever::{LocalName, Prefix}; use js::rust::HandleObject; @@ -33,7 +34,7 @@ use crate::stylesheet_loader::{StylesheetLoader, StylesheetOwner}; #[dom_struct] pub(crate) struct HTMLStyleElement { htmlelement: HTMLElement, - #[ignore_malloc_size_of = "Arc"] + #[conditional_malloc_size_of] #[no_trace] stylesheet: DomRefCell<Option<Arc<Stylesheet>>>, cssom_stylesheet: MutNullableDom<CSSStyleSheet>, @@ -97,8 +98,21 @@ impl HTMLStyleElement { return; } - let window = node.owner_window(); let doc = self.owner_document(); + + // Step 5: If the Should element's inline behavior be blocked by Content Security Policy? algorithm + // returns "Blocked" when executed upon the style element, "style", + // and the style element's child text content, then return. [CSP] + if doc.should_elements_inline_type_behavior_be_blocked( + self.upcast(), + csp::InlineCheckType::Style, + &node.child_text_content(), + ) == csp::CheckResult::Blocked + { + return; + } + + let window = node.owner_window(); let data = node .GetTextContent() .expect("Element.textContent must be a string"); 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/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/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/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/servo/lib.rs b/components/servo/lib.rs index 366685e1123..b8210450cd8 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -1044,7 +1044,11 @@ fn create_constellation( ); let system_font_service = Arc::new( - SystemFontService::spawn(compositor_proxy.cross_process_compositor_api.clone()).to_proxy(), + SystemFontService::spawn( + compositor_proxy.cross_process_compositor_api.clone(), + mem_profiler_chan.clone(), + ) + .to_proxy(), ); let (canvas_create_sender, canvas_ipc_sender) = CanvasPaintThread::start( diff --git a/components/shared/compositing/lib.rs b/components/shared/compositing/lib.rs index 31371f87529..a6701ca2b52 100644 --- a/components/shared/compositing/lib.rs +++ b/components/shared/compositing/lib.rs @@ -14,6 +14,7 @@ use embedder_traits::{ use euclid::Rect; use ipc_channel::ipc::IpcSender; use log::warn; +use malloc_size_of_derive::MallocSizeOf; use pixels::Image; use strum_macros::IntoStaticStr; use style_traits::CSSPixel; @@ -103,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), @@ -188,7 +191,7 @@ pub struct CompositionPipeline { } /// A mechanism to send messages from ScriptThread to the parent process' WebRender instance. -#[derive(Clone, Deserialize, Serialize)] +#[derive(Clone, Deserialize, MallocSizeOf, Serialize)] pub struct CrossProcessCompositorApi(pub IpcSender<CompositorMsg>); impl CrossProcessCompositorApi { 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/shared/profile/mem.rs b/components/shared/profile/mem.rs index 1be4eb5abc4..b626facd042 100644 --- a/components/shared/profile/mem.rs +++ b/components/shared/profile/mem.rs @@ -279,7 +279,6 @@ thread_local!(static SEEN_POINTERS: LazyCell<RefCell<HashSet<*const c_void>>> = /// The function is expected to call all the desired [MallocSizeOf::size_of] /// for allocations reachable from the current thread. pub fn perform_memory_report<F: FnOnce(&mut MallocSizeOfOps)>(f: F) { - SEEN_POINTERS.with(|pointers| pointers.borrow_mut().clear()); let seen_pointer = move |ptr| SEEN_POINTERS.with(|pointers| !pointers.borrow_mut().insert(ptr)); let mut ops = MallocSizeOfOps::new( servo_allocator::usable_size, @@ -287,4 +286,9 @@ pub fn perform_memory_report<F: FnOnce(&mut MallocSizeOfOps)>(f: F) { Some(Box::new(seen_pointer)), ); f(&mut ops); + SEEN_POINTERS.with(|pointers| { + let mut pointers = pointers.borrow_mut(); + pointers.clear(); + pointers.shrink_to_fit(); + }); } diff --git a/components/url/lib.rs b/components/url/lib.rs index 8aa04fd5ae4..f1ae1083c8e 100644 --- a/components/url/lib.rs +++ b/components/url/lib.rs @@ -35,7 +35,7 @@ pub enum UrlError { } #[derive(Clone, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize)] -pub struct ServoUrl(#[ignore_malloc_size_of = "Arc"] Arc<Url>); +pub struct ServoUrl(#[conditional_malloc_size_of] Arc<Url>); impl ServoUrl { pub fn from_url(url: Url) -> Self { diff --git a/components/webdriver_server/actions.rs b/components/webdriver_server/actions.rs index fbede5b5887..b18a6eaaf2e 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 crate::Handler; -// 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), @@ -387,18 +407,8 @@ impl Handler { }, }; - 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 +442,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 +496,168 @@ 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(); + + match receiver.recv() { + Ok(viewport_size) => { + if x < 0 || + x as f32 > viewport_size.width || + y < 0 || + y as f32 > viewport_size.height + { + Err(ErrorStatus::MoveTargetOutOfBounds) + } else { + Ok(()) + } + }, + Err(_) => Err(ErrorStatus::UnknownError), + } + } } 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 diff --git a/python/requirements.txt b/python/requirements.txt index 20d42065dde..d026ce3ee5f 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -37,3 +37,6 @@ types-requests # For mach package on macOS. Mako == 1.2.2 + +# For devtools tests. +geckordp == 1.0.3 diff --git a/python/servo/devtools_tests.py b/python/servo/devtools_tests.py new file mode 100644 index 00000000000..c50fffb0ac1 --- /dev/null +++ b/python/servo/devtools_tests.py @@ -0,0 +1,150 @@ +# Copyright 2013 The Servo Project Developers. See the COPYRIGHT +# file at the top-level directory of this distribution. +# +# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +# option. This file may not be copied, modified, or distributed +# except according to those terms. + +from concurrent.futures import Future +import logging +from geckordp.actors.root import RootActor +from geckordp.actors.descriptors.tab import TabActor +from geckordp.actors.watcher import WatcherActor +from geckordp.actors.resources import Resources +from geckordp.actors.events import Events +from geckordp.rdp_client import RDPClient +import http.server +import os.path +import socketserver +import subprocess +import time +from threading import Thread +from typing import Optional +import unittest + + +class DevtoolsTests(unittest.IsolatedAsyncioTestCase): + # /path/to/servo/python/servo + script_path = None + + def __init__(self, methodName="runTest"): + super().__init__(methodName) + self.servoshell = None + self.base_url = None + self.web_server = None + self.web_server_thread = None + + def test_sources_list(self): + self.run_servoshell(test_dir=os.path.join(DevtoolsTests.script_path, "devtools_tests/sources")) + self.assert_sources_list([f"{self.base_url}/classic.js", f"{self.base_url}/test.html", "https://servo.org/js/load-table.js"]) + + def test_sources_list_with_data_no_scripts(self): + self.run_servoshell(url="data:text/html,") + self.assert_sources_list([]) + + def test_sources_list_with_data_empty_inline_script(self): + self.run_servoshell(url="data:text/html,<script></script>") + self.assert_sources_list([]) + + def test_sources_list_with_data_inline_script(self): + self.run_servoshell(url="data:text/html,<script>;</script>") + self.assert_sources_list(["data:text/html,<script>;</script>"]) + + def run_servoshell(self, *, test_dir=None, url=None): + if test_dir is None: + test_dir = os.path.join(DevtoolsTests.script_path, "devtools_tests") + base_url = Future() + + class Handler(http.server.SimpleHTTPRequestHandler): + def __init__(self, *args, **kwargs): + super().__init__(*args, directory=test_dir, **kwargs) + + def log_message(self, format, *args): + # Uncomment this to log requests. + # return super().log_message(format, *args) + pass + + def server_thread(): + self.web_server = socketserver.TCPServer(("0.0.0.0", 0), Handler) + base_url.set_result(f"http://127.0.0.1:{self.web_server.server_address[1]}") + self.web_server.serve_forever() + + # Start a web server for the test. + self.web_server_thread = Thread(target=server_thread) + self.web_server_thread.start() + self.base_url = base_url.result(1) + + # Change this setting if you want to debug Servo. + os.environ["RUST_LOG"] = "error,devtools=warn" + + # Run servoshell. + if url is None: + url = f"{self.base_url}/test.html" + self.servoshell = subprocess.Popen(["target/release/servo", "--devtools=6080", url]) + + # FIXME: Don’t do this + time.sleep(1) + + def tearDown(self): + # Terminate servoshell. + self.servoshell.terminate() + + # Stop the web server. + self.web_server.shutdown() + self.web_server_thread.join() + + def assert_sources_list(self, expected_urls): + client = RDPClient() + client.connect("127.0.0.1", 6080) + root = RootActor(client) + tabs = root.list_tabs() + tab_dict = tabs[0] + tab = TabActor(client, tab_dict["actor"]) + watcher = tab.get_watcher() + watcher = WatcherActor(client, watcher["actor"]) + + target = Future() + + def on_target(data): + if data["target"]["browsingContextID"] == tab_dict["browsingContextID"]: + target.set_result(data["target"]) + + client.add_event_listener( + watcher.actor_id, Events.Watcher.TARGET_AVAILABLE_FORM, on_target, + ) + watcher.watch_targets(WatcherActor.Targets.FRAME) + + done = Future() + target = target.result(1) + + def on_source_resource(data): + for [resource_type, sources] in data["array"]: + try: + self.assertEqual(resource_type, "source") + self.assertEqual([source["url"] for source in sources], expected_urls) + done.set_result(None) + except Exception as e: + # Raising here does nothing, for some reason. + # Send the exception back so it can be raised. + done.set_result(e) + + client.add_event_listener( + target["actor"], + Events.Watcher.RESOURCES_AVAILABLE_ARRAY, + on_source_resource, + ) + watcher.watch_resources([Resources.SOURCE]) + + result: Optional[Exception] = done.result(1) + if result: + raise result + client.disconnect() + + +def run_tests(script_path): + DevtoolsTests.script_path = script_path + verbosity = 1 if logging.getLogger().level >= logging.WARN else 2 + suite = unittest.TestLoader().loadTestsFromTestCase(DevtoolsTests) + return unittest.TextTestRunner(verbosity=verbosity).run(suite).wasSuccessful() diff --git a/python/servo/devtools_tests/sources/classic.js b/python/servo/devtools_tests/sources/classic.js new file mode 100644 index 00000000000..84fd1671805 --- /dev/null +++ b/python/servo/devtools_tests/sources/classic.js @@ -0,0 +1 @@ +console.log("external classic"); diff --git a/python/servo/devtools_tests/sources/module.js b/python/servo/devtools_tests/sources/module.js new file mode 100644 index 00000000000..a1d0f1f37cf --- /dev/null +++ b/python/servo/devtools_tests/sources/module.js @@ -0,0 +1,2 @@ +export default 1; +console.log("external module"); diff --git a/python/servo/devtools_tests/sources/test.html b/python/servo/devtools_tests/sources/test.html new file mode 100644 index 00000000000..b8e1aa0e334 --- /dev/null +++ b/python/servo/devtools_tests/sources/test.html @@ -0,0 +1,11 @@ +<!doctype html><meta charset=utf-8> +<script src="classic.js"></script> +<script> + console.log("inline classic"); + new Worker("worker.js"); +</script> +<script type="module"> + import module from "./module.js"; + console.log("inline module"); +</script> +<script src="https://servo.org/js/load-table.js"></script> diff --git a/python/servo/devtools_tests/sources/worker.js b/python/servo/devtools_tests/sources/worker.js new file mode 100644 index 00000000000..a7993a8b5fb --- /dev/null +++ b/python/servo/devtools_tests/sources/worker.js @@ -0,0 +1 @@ +console.log("external classic worker"); diff --git a/python/servo/testing_commands.py b/python/servo/testing_commands.py index 860217d062a..5e5a8c2e8d2 100644 --- a/python/servo/testing_commands.py +++ b/python/servo/testing_commands.py @@ -19,6 +19,7 @@ import subprocess import textwrap import json +import servo.devtools_tests from servo.post_build_commands import PostBuildCommands import wpt import wpt.manifestupdate @@ -326,6 +327,14 @@ class MachCommands(CommandBase): return 0 if passed else 1 + @Command('test-devtools', + description='Run tests for devtools.', + category='testing') + def test_devtools(self): + print("Running devtools tests...") + passed = servo.devtools_tests.run_tests(SCRIPT_PATH) + return 0 if passed else 1 + @Command('test-wpt-failure', description='Run the tests harness that verifies that the test failures are reported correctly', category='testing', diff --git a/tests/wpt/include.ini b/tests/wpt/include.ini index ba81ab64b83..8ea1e50b8d0 100644 --- a/tests/wpt/include.ini +++ b/tests/wpt/include.ini @@ -256,6 +256,8 @@ skip: true skip: true [writable-streams] skip: false + [transform-streams] + skip: false [subresource-integrity] skip: false [touch-events] diff --git a/tests/wpt/meta/MANIFEST.json b/tests/wpt/meta/MANIFEST.json index df2beac06bd..893b07e9e3f 100644 --- a/tests/wpt/meta/MANIFEST.json +++ b/tests/wpt/meta/MANIFEST.json @@ -569742,7 +569742,7 @@ ] ], "icon-blocked.sub.html": [ - "cc882347a1ac7b595275c2263ef266826e6f07bd", + "4c39e5dec735c10635a603356367610d3aad3fa2", [ null, {} @@ -569797,6 +569797,13 @@ {} ] ], + "img-src-targeting.html": [ + "3b4fe7c690b0b980a9626de0deb02c8950f5d4a0", + [ + null, + {} + ] + ], "img-src-wildcard-allowed.html": [ "050a4d14100bb1ef719d6700bfbd37a97424af59", [ diff --git a/tests/wpt/meta/clipboard-apis/idlharness.https.window.js.ini b/tests/wpt/meta/clipboard-apis/idlharness.https.window.js.ini index 98bb3d8b303..e648b62a314 100644 --- a/tests/wpt/meta/clipboard-apis/idlharness.https.window.js.ini +++ b/tests/wpt/meta/clipboard-apis/idlharness.https.window.js.ini @@ -8,9 +8,6 @@ [Clipboard interface: operation read(optional ClipboardUnsanitizedFormats)] expected: FAIL - [Clipboard interface: operation readText()] - expected: FAIL - [Clipboard interface: operation write(ClipboardItems)] expected: FAIL @@ -20,9 +17,6 @@ [Clipboard interface: calling read(optional ClipboardUnsanitizedFormats) on navigator.clipboard with too few arguments must throw TypeError] expected: FAIL - [Clipboard interface: navigator.clipboard must inherit property "readText()" with the proper type] - expected: FAIL - [Clipboard interface: navigator.clipboard must inherit property "write(ClipboardItems)" with the proper type] expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.http.html.ini b/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.http.html.ini index e29f4dd5d4e..5891a18681e 100644 --- a/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.http.html.ini +++ b/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.http.html.ini @@ -1,13 +1,4 @@ [script-tag.http.html] - [Content Security Policy: Expects blocked for script-tag to cross-http origin and keep-origin redirection from http context.: securitypolicyviolation] - expected: FAIL - - [Content Security Policy: Expects blocked for script-tag to cross-http origin and no-redirect redirection from http context.: securitypolicyviolation] - expected: FAIL - - [Content Security Policy: Expects blocked for script-tag to cross-http origin and swap-origin redirection from http context.: securitypolicyviolation] - expected: FAIL - [Content Security Policy: Expects blocked for script-tag to same-http origin and swap-origin redirection from http context.] expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.https.html.ini b/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.https.html.ini index bbe30519d0b..699a0dd6238 100644 --- a/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.https.html.ini +++ b/tests/wpt/meta/content-security-policy/gen/top.http-rp/script-src-self/script-tag.https.html.ini @@ -1,13 +1,4 @@ [script-tag.https.html] - [Content Security Policy: Expects blocked for script-tag to cross-https origin and keep-origin redirection from https context.: securitypolicyviolation] - expected: FAIL - - [Content Security Policy: Expects blocked for script-tag to cross-https origin and no-redirect redirection from https context.: securitypolicyviolation] - expected: FAIL - - [Content Security Policy: Expects blocked for script-tag to cross-https origin and swap-origin redirection from https context.: securitypolicyviolation] - expected: FAIL - [Content Security Policy: Expects blocked for script-tag to same-https origin and swap-origin redirection from https context.] expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.http.html.ini b/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.http.html.ini index e29f4dd5d4e..5891a18681e 100644 --- a/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.http.html.ini +++ b/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.http.html.ini @@ -1,13 +1,4 @@ [script-tag.http.html] - [Content Security Policy: Expects blocked for script-tag to cross-http origin and keep-origin redirection from http context.: securitypolicyviolation] - expected: FAIL - - [Content Security Policy: Expects blocked for script-tag to cross-http origin and no-redirect redirection from http context.: securitypolicyviolation] - expected: FAIL - - [Content Security Policy: Expects blocked for script-tag to cross-http origin and swap-origin redirection from http context.: securitypolicyviolation] - expected: FAIL - [Content Security Policy: Expects blocked for script-tag to same-http origin and swap-origin redirection from http context.] expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.https.html.ini b/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.https.html.ini index bbe30519d0b..699a0dd6238 100644 --- a/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.https.html.ini +++ b/tests/wpt/meta/content-security-policy/gen/top.meta/script-src-self/script-tag.https.html.ini @@ -1,13 +1,4 @@ [script-tag.https.html] - [Content Security Policy: Expects blocked for script-tag to cross-https origin and keep-origin redirection from https context.: securitypolicyviolation] - expected: FAIL - - [Content Security Policy: Expects blocked for script-tag to cross-https origin and no-redirect redirection from https context.: securitypolicyviolation] - expected: FAIL - - [Content Security Policy: Expects blocked for script-tag to cross-https origin and swap-origin redirection from https context.: securitypolicyviolation] - expected: FAIL - [Content Security Policy: Expects blocked for script-tag to same-https origin and swap-origin redirection from https context.] expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/navigation/to-javascript-url-script-src.html.ini b/tests/wpt/meta/content-security-policy/navigation/to-javascript-url-script-src.html.ini deleted file mode 100644 index 213ed2d4692..00000000000 --- a/tests/wpt/meta/content-security-policy/navigation/to-javascript-url-script-src.html.ini +++ /dev/null @@ -1,13 +0,0 @@ -[to-javascript-url-script-src.html] - expected: TIMEOUT - [<iframe src='javascript:'> blocked without 'unsafe-inline'.] - expected: TIMEOUT - - [<iframe> navigated to 'javascript:' blocked without 'unsafe-inline'.] - expected: NOTRUN - - [<iframe src='...'> with 'unsafe-inline' navigated to 'javascript:' blocked in this document] - expected: NOTRUN - - [<iframe src='...'> without 'unsafe-inline' navigated to 'javascript:' blocked in this document.] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/reporting/report-original-url.sub.html.ini b/tests/wpt/meta/content-security-policy/reporting/report-original-url.sub.html.ini index 9d64a7e6bc4..66a2ee93f3b 100644 --- a/tests/wpt/meta/content-security-policy/reporting/report-original-url.sub.html.ini +++ b/tests/wpt/meta/content-security-policy/reporting/report-original-url.sub.html.ini @@ -1,11 +1,5 @@ [report-original-url.sub.html] expected: TIMEOUT - [Direct block, same-origin = full URL in report] - expected: TIMEOUT - - [Direct block, cross-origin = full URL in report] - expected: TIMEOUT - [Block after redirect, same-origin = original URL in report] expected: TIMEOUT diff --git a/tests/wpt/meta/content-security-policy/script-src/script-src-report-only-policy-works-with-hash-policy.html.ini b/tests/wpt/meta/content-security-policy/script-src/script-src-report-only-policy-works-with-hash-policy.html.ini deleted file mode 100644 index 31bfeae4a12..00000000000 --- a/tests/wpt/meta/content-security-policy/script-src/script-src-report-only-policy-works-with-hash-policy.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[script-src-report-only-policy-works-with-hash-policy.html] - expected: TIMEOUT - [Test that the securitypolicyviolation event is fired] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-eval.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-eval.html.ini deleted file mode 100644 index bebd42f2743..00000000000 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-eval.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[blockeduri-eval.html] - expected: TIMEOUT - [Eval violations have a blockedURI of 'eval'] - expected: TIMEOUT diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-inline.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-inline.html.ini index 9c191e43078..3d0febce2b5 100644 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-inline.html.ini +++ b/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-inline.html.ini @@ -1,4 +1,3 @@ [blockeduri-inline.html] - expected: TIMEOUT [Inline violations have a blockedURI of 'inline'] - expected: TIMEOUT + expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-ws-wss-scheme.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-ws-wss-scheme.html.ini index 6ebb357445f..14fa5353a94 100644 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-ws-wss-scheme.html.ini +++ b/tests/wpt/meta/content-security-policy/securitypolicyviolation/blockeduri-ws-wss-scheme.html.ini @@ -1,13 +1,3 @@ [blockeduri-ws-wss-scheme.html] - expected: TIMEOUT - [ws] - expected: FAIL - - [wss] - expected: FAIL - - [cross-origin] - expected: FAIL - [redirect] - expected: TIMEOUT + expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/linenumber.tentative.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/linenumber.tentative.html.ini index fed78a0aa49..e8114229ab9 100644 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/linenumber.tentative.html.ini +++ b/tests/wpt/meta/content-security-policy/securitypolicyviolation/linenumber.tentative.html.ini @@ -1,4 +1,3 @@ [linenumber.tentative.html] - expected: TIMEOUT [linenumber] - expected: NOTRUN + expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/script-sample-no-opt-in.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/script-sample-no-opt-in.html.ini index b12f81377d1..409022079e0 100644 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/script-sample-no-opt-in.html.ini +++ b/tests/wpt/meta/content-security-policy/securitypolicyviolation/script-sample-no-opt-in.html.ini @@ -1,13 +1,7 @@ [script-sample-no-opt-in.html] expected: TIMEOUT - [Inline script should not have a sample.] - expected: TIMEOUT - - [Inline event handlers should not have a sample.] - expected: TIMEOUT - [JavaScript URLs in iframes should not have a sample.] expected: TIMEOUT - [eval()-alikes should not have a sample.] + [Inline event handlers should not have a sample.] expected: TIMEOUT diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/script-sample.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/script-sample.html.ini index f4c315396f6..8723775f27e 100644 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/script-sample.html.ini +++ b/tests/wpt/meta/content-security-policy/securitypolicyviolation/script-sample.html.ini @@ -1,19 +1,7 @@ [script-sample.html] expected: TIMEOUT - [Inline script should have a sample.] - expected: TIMEOUT - - [Inline event handlers should have a sample.] - expected: TIMEOUT - [JavaScript URLs in iframes should have a sample.] expected: TIMEOUT - [eval() should have a sample.] - expected: TIMEOUT - - [setInterval() should have a sample.] - expected: TIMEOUT - - [setTimeout() should have a sample.] + [Inline event handlers should have a sample.] expected: TIMEOUT diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/source-file-blob-scheme.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/source-file-blob-scheme.html.ini index 03d164a4050..514f91961e3 100644 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/source-file-blob-scheme.html.ini +++ b/tests/wpt/meta/content-security-policy/securitypolicyviolation/source-file-blob-scheme.html.ini @@ -1,4 +1,3 @@ [source-file-blob-scheme.html] - expected: TIMEOUT [Violations from data:-URL scripts have a sourceFile of 'blob'] - expected: TIMEOUT + expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/source-file-data-scheme.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/source-file-data-scheme.html.ini index 387a7e2ff98..ed4dba7d3c3 100644 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/source-file-data-scheme.html.ini +++ b/tests/wpt/meta/content-security-policy/securitypolicyviolation/source-file-data-scheme.html.ini @@ -1,4 +1,3 @@ [source-file-data-scheme.html] - expected: TIMEOUT [Violations from data:-URL scripts have a sourceFile of 'data'] - expected: TIMEOUT + expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/style-sample-no-opt-in.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/style-sample-no-opt-in.html.ini index eb10ad61b2c..ef89e7cb1a9 100644 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/style-sample-no-opt-in.html.ini +++ b/tests/wpt/meta/content-security-policy/securitypolicyviolation/style-sample-no-opt-in.html.ini @@ -1,7 +1,4 @@ [style-sample-no-opt-in.html] expected: TIMEOUT - [Inline style blocks should not have a sample.] - expected: TIMEOUT - [Inline style attributes should not have a sample.] expected: TIMEOUT diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/style-sample.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/style-sample.html.ini index 460e21bd6cd..63e278182d8 100644 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/style-sample.html.ini +++ b/tests/wpt/meta/content-security-policy/securitypolicyviolation/style-sample.html.ini @@ -1,7 +1,4 @@ [style-sample.html] expected: TIMEOUT - [Inline style blocks should have a sample.] - expected: TIMEOUT - [Inline style attributes should have a sample.] expected: TIMEOUT diff --git a/tests/wpt/meta/content-security-policy/securitypolicyviolation/targeting.html.ini b/tests/wpt/meta/content-security-policy/securitypolicyviolation/targeting.html.ini index 88da5e48238..952c5185dd8 100644 --- a/tests/wpt/meta/content-security-policy/securitypolicyviolation/targeting.html.ini +++ b/tests/wpt/meta/content-security-policy/securitypolicyviolation/targeting.html.ini @@ -4,13 +4,10 @@ expected: NOTRUN [Inline violations target the right element.] - expected: TIMEOUT + expected: FAIL [Correct targeting inside shadow tree (inline handler).] expected: TIMEOUT - [Correct targeting inside shadow tree (style).] - expected: TIMEOUT - [Elements created in this document, but pushed into a same-origin frame trigger on that frame's document, not on this frame's document.] expected: TIMEOUT diff --git a/tests/wpt/meta/content-security-policy/style-src-attr-elem/style-src-elem-blocked-attr-allowed.html.ini b/tests/wpt/meta/content-security-policy/style-src-attr-elem/style-src-elem-blocked-attr-allowed.html.ini deleted file mode 100644 index c67fec5245c..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src-attr-elem/style-src-elem-blocked-attr-allowed.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[style-src-elem-blocked-attr-allowed.html] - expected: TIMEOUT - [Should fire a security policy violation for the inline block] - expected: NOTRUN - - [The inline style should not be applied and the attribute style should be applied] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/style-src-attr-elem/style-src-elem-blocked-src-allowed.html.ini b/tests/wpt/meta/content-security-policy/style-src-attr-elem/style-src-elem-blocked-src-allowed.html.ini deleted file mode 100644 index 06439a8cc0a..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src-attr-elem/style-src-elem-blocked-src-allowed.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[style-src-elem-blocked-src-allowed.html] - expected: TIMEOUT - [Should fire a security policy violation event] - expected: NOTRUN - - [The inline style should not be applied] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/style-src/injected-inline-style-blocked.sub.html.ini b/tests/wpt/meta/content-security-policy/style-src/injected-inline-style-blocked.sub.html.ini deleted file mode 100644 index 06132f67bfc..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/injected-inline-style-blocked.sub.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[injected-inline-style-blocked.sub.html] - [Expecting logs: ["violated-directive=style-src-elem","violated-directive=style-src-elem","PASS"\]] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/style-src/inline-style-blocked.sub.html.ini b/tests/wpt/meta/content-security-policy/style-src/inline-style-blocked.sub.html.ini deleted file mode 100644 index 62bbd2f0e13..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/inline-style-blocked.sub.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[inline-style-blocked.sub.html] - expected: TIMEOUT - [Triggers securitypolicyviolation.] - expected: TIMEOUT - - [Inline style element is blocked by CSP.] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/style-src/style-src-hash-blocked.html.ini b/tests/wpt/meta/content-security-policy/style-src/style-src-hash-blocked.html.ini deleted file mode 100644 index a79f011aec3..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/style-src-hash-blocked.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[style-src-hash-blocked.html] - expected: TIMEOUT - [Should not load style that does not match hash] - expected: FAIL - - [Should fire a securitypolicyviolation event] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/style-src/style-src-hash-case-insensitive.html.ini b/tests/wpt/meta/content-security-policy/style-src/style-src-hash-case-insensitive.html.ini new file mode 100644 index 00000000000..fe35d3ea411 --- /dev/null +++ b/tests/wpt/meta/content-security-policy/style-src/style-src-hash-case-insensitive.html.ini @@ -0,0 +1,3 @@ +[style-src-hash-case-insensitive.html] + [All style elements should load because they have proper hashes] + expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/style-src/style-src-imported-style-blocked.html.ini b/tests/wpt/meta/content-security-policy/style-src/style-src-imported-style-blocked.html.ini deleted file mode 100644 index fba57c0f24f..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/style-src-imported-style-blocked.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[style-src-imported-style-blocked.html] - expected: TIMEOUT - [Should fire a securitypolicyviolation event] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/style-src/style-src-injected-inline-style-blocked.html.ini b/tests/wpt/meta/content-security-policy/style-src/style-src-injected-inline-style-blocked.html.ini deleted file mode 100644 index bb5c48df1b0..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/style-src-injected-inline-style-blocked.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[style-src-injected-inline-style-blocked.html] - expected: TIMEOUT - [Injected style attributes should not be applied] - expected: FAIL - - [Should fire a securitypolicyviolation event] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/style-src/style-src-injected-stylesheet-blocked.sub.html.ini b/tests/wpt/meta/content-security-policy/style-src/style-src-injected-stylesheet-blocked.sub.html.ini deleted file mode 100644 index 0b431bab548..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/style-src-injected-stylesheet-blocked.sub.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[style-src-injected-stylesheet-blocked.sub.html] - expected: TIMEOUT - [Should fire a securitypolicyviolation event] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-blocked.html.ini b/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-blocked.html.ini deleted file mode 100644 index 33ee0df35af..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-blocked.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[style-src-inline-style-blocked.html] - expected: TIMEOUT - [Inline style element should not load without 'unsafe-inline'] - expected: FAIL - - [Should fire a securitypolicyviolation event] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-nonce-blocked-error-event.html.ini b/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-nonce-blocked-error-event.html.ini index 63a1c9b6240..eb2f1c46fbb 100644 --- a/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-nonce-blocked-error-event.html.ini +++ b/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-nonce-blocked-error-event.html.ini @@ -1,7 +1,4 @@ [style-src-inline-style-nonce-blocked-error-event.html] expected: TIMEOUT - [Should fire a securitypolicyviolation event] - expected: NOTRUN - [Test that paragraph remains unmodified and error events received.] expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-nonce-blocked.html.ini b/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-nonce-blocked.html.ini deleted file mode 100644 index df0fb590691..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/style-src-inline-style-nonce-blocked.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[style-src-inline-style-nonce-blocked.html] - expected: TIMEOUT - [Should not load inline style element with invalid nonce] - expected: FAIL - - [Should fire a securitypolicyviolation event] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/style-src/style-src-none-blocked.html.ini b/tests/wpt/meta/content-security-policy/style-src/style-src-none-blocked.html.ini deleted file mode 100644 index c3014b37c31..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/style-src-none-blocked.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[style-src-none-blocked.html] - expected: TIMEOUT - [Should fire a securitypolicyviolation event] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/style-src/style-src-stylesheet-nonce-blocked.html.ini b/tests/wpt/meta/content-security-policy/style-src/style-src-stylesheet-nonce-blocked.html.ini deleted file mode 100644 index b8645f13da7..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/style-src-stylesheet-nonce-blocked.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[style-src-stylesheet-nonce-blocked.html] - expected: TIMEOUT - [Should fire a securitypolicyviolation event] - expected: NOTRUN diff --git a/tests/wpt/meta/content-security-policy/style-src/stylehash-basic-blocked.sub.html.ini b/tests/wpt/meta/content-security-policy/style-src/stylehash-basic-blocked.sub.html.ini deleted file mode 100644 index 10375077b42..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/stylehash-basic-blocked.sub.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[stylehash-basic-blocked.sub.html] - [Expecting alerts: ["PASS: The 'p' element's text is green, which means the style was correctly applied.", "violated-directive=style-src-elem"\]] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/style-src/stylenonce-allowed.sub.html.ini b/tests/wpt/meta/content-security-policy/style-src/stylenonce-allowed.sub.html.ini deleted file mode 100644 index ae2f83c41d8..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/stylenonce-allowed.sub.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[stylenonce-allowed.sub.html] - expected: TIMEOUT - [Should fire securitypolicyviolation] - expected: NOTRUN - - [stylenonce-allowed] - expected: FAIL diff --git a/tests/wpt/meta/content-security-policy/style-src/stylenonce-blocked.sub.html.ini b/tests/wpt/meta/content-security-policy/style-src/stylenonce-blocked.sub.html.ini deleted file mode 100644 index 97a86a69eea..00000000000 --- a/tests/wpt/meta/content-security-policy/style-src/stylenonce-blocked.sub.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[stylenonce-blocked.sub.html] - expected: TIMEOUT - [Should fire securitypolicyviolation] - expected: NOTRUN - - [stylenonce-blocked] - expected: FAIL diff --git a/tests/wpt/meta/custom-elements/parser/serializing-html-fragments-customized-builtins.html.ini b/tests/wpt/meta/custom-elements/parser/serializing-html-fragments-customized-builtins.html.ini deleted file mode 100644 index 15f02f181e4..00000000000 --- a/tests/wpt/meta/custom-elements/parser/serializing-html-fragments-customized-builtins.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[serializing-html-fragments-customized-builtins.html] - ["is" value should be serialized if the custom element has no "is" content attribute] - expected: FAIL - - ["is" value should be serialized even for an undefined element] - expected: FAIL diff --git a/tests/wpt/meta/fetch/metadata/report.https.sub.html.ini b/tests/wpt/meta/fetch/metadata/report.https.sub.html.ini index 058151d81ce..0348be9f384 100644 --- a/tests/wpt/meta/fetch/metadata/report.https.sub.html.ini +++ b/tests/wpt/meta/fetch/metadata/report.https.sub.html.ini @@ -1,2 +1,10 @@ [report.https.sub.html] - expected: TIMEOUT + expected: ERROR + [same-origin report] + expected: TIMEOUT + + [same-site report] + expected: TIMEOUT + + [cross-site report] + expected: TIMEOUT diff --git a/tests/wpt/meta/streams/transferable/transfer-with-messageport.window.js.ini b/tests/wpt/meta/streams/transferable/transfer-with-messageport.window.js.ini index fc885c5a594..a2502281be7 100644 --- a/tests/wpt/meta/streams/transferable/transfer-with-messageport.window.js.ini +++ b/tests/wpt/meta/streams/transferable/transfer-with-messageport.window.js.ini @@ -7,6 +7,3 @@ [Transferring a MessagePort with multiple streams should set `.ports`] expected: FAIL - - [TransformStream must not be serializable] - expected: FAIL diff --git a/tests/wpt/meta/streams/transferable/transform-stream-members.any.js.ini b/tests/wpt/meta/streams/transferable/transform-stream-members.any.js.ini index c5d5f43d1a4..4414054a1cf 100644 --- a/tests/wpt/meta/streams/transferable/transform-stream-members.any.js.ini +++ b/tests/wpt/meta/streams/transferable/transform-stream-members.any.js.ini @@ -10,14 +10,10 @@ [transform-stream-members.any.shadowrealm-in-window.html] expected: ERROR -[transform-stream-members.any.html] - expected: ERROR [transform-stream-members.https.any.shadowrealm-in-serviceworker.html] expected: ERROR -[transform-stream-members.any.worker.html] - expected: ERROR [transform-stream-members.any.shadowrealm-in-dedicatedworker.html] expected: ERROR diff --git a/tests/wpt/meta/streams/transferable/transform-stream.html.ini b/tests/wpt/meta/streams/transferable/transform-stream.html.ini index dc6fe8a6c75..af9a1d42ae7 100644 --- a/tests/wpt/meta/streams/transferable/transform-stream.html.ini +++ b/tests/wpt/meta/streams/transferable/transform-stream.html.ini @@ -2,14 +2,5 @@ [window.postMessage should be able to transfer a TransformStream] expected: FAIL - [a TransformStream with a locked writable should not be transferable] - expected: FAIL - - [a TransformStream with a locked readable should not be transferable] - expected: FAIL - - [a TransformStream with both sides locked should not be transferable] - expected: FAIL - [piping through transferred transforms should work] expected: FAIL diff --git a/tests/wpt/meta/streams/transferable/writable-stream.html.ini b/tests/wpt/meta/streams/transferable/writable-stream.html.ini deleted file mode 100644 index 47326208f88..00000000000 --- a/tests/wpt/meta/streams/transferable/writable-stream.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[writable-stream.html] - [window.postMessage should be able to transfer a {readable, writable} pair] - expected: FAIL diff --git a/tests/wpt/meta/streams/transform-streams/backpressure.any.js.ini b/tests/wpt/meta/streams/transform-streams/backpressure.any.js.ini new file mode 100644 index 00000000000..099d3a6f2e0 --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/backpressure.any.js.ini @@ -0,0 +1,23 @@ +[backpressure.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[backpressure.any.shadowrealm-in-window.html] + expected: ERROR + +[backpressure.any.serviceworker.html] + expected: ERROR + +[backpressure.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[backpressure.any.sharedworker.html] + expected: ERROR + +[backpressure.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[backpressure.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[backpressure.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR
\ No newline at end of file diff --git a/tests/wpt/meta/streams/transform-streams/cancel.any.js.ini b/tests/wpt/meta/streams/transform-streams/cancel.any.js.ini new file mode 100644 index 00000000000..7e5a1b9af50 --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/cancel.any.js.ini @@ -0,0 +1,32 @@ +[cancel.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[cancel.any.serviceworker.html] + expected: ERROR + +[cancel.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[cancel.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[cancel.any.sharedworker.html] + expected: ERROR + +[cancel.any.shadowrealm-in-window.html] + expected: ERROR + +[cancel.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[cancel.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[cancel.any.worker.html] + [readable.cancel() and a parallel writable.close() should reject if a transformer.cancel() calls controller.error()] + expected: FAIL + + +[cancel.any.html] + [readable.cancel() and a parallel writable.close() should reject if a transformer.cancel() calls controller.error()] + expected: FAIL diff --git a/tests/wpt/meta/streams/transform-streams/errors.any.js.ini b/tests/wpt/meta/streams/transform-streams/errors.any.js.ini new file mode 100644 index 00000000000..02eaa76ca8e --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/errors.any.js.ini @@ -0,0 +1,32 @@ +[errors.any.sharedworker.html] + expected: ERROR + +[errors.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[errors.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[errors.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[errors.any.serviceworker.html] + expected: ERROR + +[errors.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[errors.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[errors.any.shadowrealm-in-window.html] + expected: ERROR + +[errors.any.html] + [abort should set the close reason for the writable when it happens before cancel during start, and cancel should reject] + expected: FAIL + + +[errors.any.worker.html] + [abort should set the close reason for the writable when it happens before cancel during start, and cancel should reject] + expected: FAIL diff --git a/tests/wpt/meta/streams/transform-streams/flush.any.js.ini b/tests/wpt/meta/streams/transform-streams/flush.any.js.ini new file mode 100644 index 00000000000..a2ee0101993 --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/flush.any.js.ini @@ -0,0 +1,23 @@ +[flush.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[flush.any.shadowrealm-in-window.html] + expected: ERROR + +[flush.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[flush.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[flush.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[flush.any.sharedworker.html] + expected: ERROR + +[flush.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[flush.any.serviceworker.html] + expected: ERROR
\ No newline at end of file diff --git a/tests/wpt/meta/streams/transform-streams/general.any.js.ini b/tests/wpt/meta/streams/transform-streams/general.any.js.ini new file mode 100644 index 00000000000..11dfc7d4ece --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/general.any.js.ini @@ -0,0 +1,23 @@ +[general.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[general.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[general.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[general.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[general.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[general.any.serviceworker.html] + expected: ERROR + +[general.any.shadowrealm-in-window.html] + expected: ERROR + +[general.any.sharedworker.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/transform-streams/lipfuzz.any.js.ini b/tests/wpt/meta/streams/transform-streams/lipfuzz.any.js.ini new file mode 100644 index 00000000000..5af2be4a1ae --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/lipfuzz.any.js.ini @@ -0,0 +1,23 @@ +[lipfuzz.any.shadowrealm-in-window.html] + expected: ERROR + +[lipfuzz.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[lipfuzz.any.serviceworker.html] + expected: ERROR + +[lipfuzz.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[lipfuzz.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[lipfuzz.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[lipfuzz.any.sharedworker.html] + expected: ERROR + +[lipfuzz.any.shadowrealm-in-sharedworker.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/transform-streams/patched-global.any.js.ini b/tests/wpt/meta/streams/transform-streams/patched-global.any.js.ini new file mode 100644 index 00000000000..06a324cb060 --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/patched-global.any.js.ini @@ -0,0 +1,23 @@ +[patched-global.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[patched-global.any.shadowrealm-in-window.html] + expected: ERROR + +[patched-global.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[patched-global.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[patched-global.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[patched-global.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[patched-global.any.sharedworker.html] + expected: ERROR + +[patched-global.any.serviceworker.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/transform-streams/properties.any.js.ini b/tests/wpt/meta/streams/transform-streams/properties.any.js.ini new file mode 100644 index 00000000000..f5573ee57e4 --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/properties.any.js.ini @@ -0,0 +1,23 @@ +[properties.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[properties.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[properties.any.shadowrealm-in-window.html] + expected: ERROR + +[properties.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[properties.any.sharedworker.html] + expected: ERROR + +[properties.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[properties.any.serviceworker.html] + expected: ERROR + +[properties.any.shadowrealm-in-sharedworker.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/transform-streams/reentrant-strategies.any.js.ini b/tests/wpt/meta/streams/transform-streams/reentrant-strategies.any.js.ini new file mode 100644 index 00000000000..1c6b9fd51da --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/reentrant-strategies.any.js.ini @@ -0,0 +1,23 @@ +[reentrant-strategies.any.shadowrealm-in-window.html] + expected: ERROR + +[reentrant-strategies.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[reentrant-strategies.any.sharedworker.html] + expected: ERROR + +[reentrant-strategies.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[reentrant-strategies.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[reentrant-strategies.any.serviceworker.html] + expected: ERROR + +[reentrant-strategies.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[reentrant-strategies.any.shadowrealm-in-shadowrealm.html] + expected: ERROR diff --git a/tests/wpt/meta/streams/transform-streams/strategies.any.js.ini b/tests/wpt/meta/streams/transform-streams/strategies.any.js.ini new file mode 100644 index 00000000000..52c18b67fd4 --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/strategies.any.js.ini @@ -0,0 +1,24 @@ +[strategies.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[strategies.any.serviceworker.html] + expected: ERROR + +[strategies.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[strategies.any.shadowrealm-in-window.html] + expected: ERROR + +[strategies.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[strategies.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[strategies.any.sharedworker.html] + expected: ERROR + +[strategies.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + diff --git a/tests/wpt/meta/streams/transform-streams/terminate.any.js.ini b/tests/wpt/meta/streams/transform-streams/terminate.any.js.ini new file mode 100644 index 00000000000..c81b1e40d1e --- /dev/null +++ b/tests/wpt/meta/streams/transform-streams/terminate.any.js.ini @@ -0,0 +1,23 @@ +[terminate.any.serviceworker.html] + expected: ERROR + +[terminate.any.shadowrealm-in-sharedworker.html] + expected: ERROR + +[terminate.https.any.shadowrealm-in-serviceworker.html] + expected: ERROR + +[terminate.any.shadowrealm-in-window.html] + expected: ERROR + +[terminate.any.shadowrealm-in-shadowrealm.html] + expected: ERROR + +[terminate.https.any.shadowrealm-in-audioworklet.html] + expected: ERROR + +[terminate.any.shadowrealm-in-dedicatedworker.html] + expected: ERROR + +[terminate.any.sharedworker.html] + expected: ERROR diff --git a/tests/wpt/meta/trusted-types/default-policy.html.ini b/tests/wpt/meta/trusted-types/default-policy.html.ini index 15588646951..cf57031ddbe 100644 --- a/tests/wpt/meta/trusted-types/default-policy.html.ini +++ b/tests/wpt/meta/trusted-types/default-policy.html.ini @@ -1,7 +1,7 @@ [default-policy.html] - expected: TIMEOUT + expected: OK [Count SecurityPolicyViolation events.] - expected: TIMEOUT + expected: FAIL [div.innerHTML no default policy] expected: FAIL diff --git a/tests/wpt/meta/trusted-types/empty-default-policy.html.ini b/tests/wpt/meta/trusted-types/empty-default-policy.html.ini index 4f06e4c971f..c3f34522557 100644 --- a/tests/wpt/meta/trusted-types/empty-default-policy.html.ini +++ b/tests/wpt/meta/trusted-types/empty-default-policy.html.ini @@ -1,7 +1,7 @@ [empty-default-policy.html] - expected: TIMEOUT + expected: OK [Count SecurityPolicyViolation events.] - expected: TIMEOUT + expected: FAIL [div.innerHTML default] expected: FAIL diff --git a/tests/wpt/meta/wasm/webapi/esm-integration/script-src-blocks-wasm.tentative.sub.html.ini b/tests/wpt/meta/wasm/webapi/esm-integration/script-src-blocks-wasm.tentative.sub.html.ini deleted file mode 100644 index c804530024c..00000000000 --- a/tests/wpt/meta/wasm/webapi/esm-integration/script-src-blocks-wasm.tentative.sub.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[script-src-blocks-wasm.tentative.sub.html] - [Importing a WebAssembly module should be guarded by script-src CSP.] - expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/element_click/click.py.ini b/tests/wpt/meta/webdriver/tests/classic/element_click/click.py.ini index ad0f9714ad1..9cdf1e0d0da 100644 --- a/tests/wpt/meta/webdriver/tests/classic/element_click/click.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/element_click/click.py.ini @@ -13,9 +13,3 @@ [test_no_such_element_from_other_frame[closed\]] expected: FAIL - - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/element_click/scroll_into_view.py.ini b/tests/wpt/meta/webdriver/tests/classic/element_click/scroll_into_view.py.ini index 87c9c813881..2627072cf91 100644 --- a/tests/wpt/meta/webdriver/tests/classic/element_click/scroll_into_view.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/element_click/scroll_into_view.py.ini @@ -1,30 +1,30 @@ [scroll_into_view.py] [test_scroll_into_view] - expected: ERROR + expected: FAIL [test_partially_visible_does_not_scroll[9\]] - expected: ERROR + expected: FAIL [test_partially_visible_does_not_scroll[8\]] - expected: ERROR + expected: FAIL [test_partially_visible_does_not_scroll[7\]] - expected: ERROR + expected: FAIL [test_partially_visible_does_not_scroll[6\]] - expected: ERROR + expected: FAIL [test_partially_visible_does_not_scroll[5\]] - expected: ERROR + expected: FAIL [test_partially_visible_does_not_scroll[4\]] - expected: ERROR + expected: FAIL [test_partially_visible_does_not_scroll[3\]] - expected: ERROR + expected: FAIL [test_partially_visible_does_not_scroll[2\]] - expected: ERROR + expected: FAIL [test_partially_visible_does_not_scroll[1\]] - expected: ERROR + expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/element_send_keys/form_controls.py.ini b/tests/wpt/meta/webdriver/tests/classic/element_send_keys/form_controls.py.ini index 5d4a3bd4de5..1dea194f9b2 100644 --- a/tests/wpt/meta/webdriver/tests/classic/element_send_keys/form_controls.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/element_send_keys/form_controls.py.ini @@ -4,3 +4,6 @@ [test_textarea_append] expected: FAIL + + [test_date] + expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/element_send_keys/scroll_into_view.py.ini b/tests/wpt/meta/webdriver/tests/classic/element_send_keys/scroll_into_view.py.ini index 3e260cade03..d141c6022db 100644 --- a/tests/wpt/meta/webdriver/tests/classic/element_send_keys/scroll_into_view.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/element_send_keys/scroll_into_view.py.ini @@ -1,7 +1,4 @@ [scroll_into_view.py] - [test_element_outside_of_not_scrollable_viewport] - expected: FAIL - [test_element_outside_of_scrollable_viewport] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/element_send_keys/send_keys.py.ini b/tests/wpt/meta/webdriver/tests/classic/element_send_keys/send_keys.py.ini index b2b09490191..10a5a86e3d2 100644 --- a/tests/wpt/meta/webdriver/tests/classic/element_send_keys/send_keys.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/element_send_keys/send_keys.py.ini @@ -13,9 +13,3 @@ [test_no_such_element_from_other_frame[closed\]] expected: FAIL - - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/execute_async_script/collections.py.ini b/tests/wpt/meta/webdriver/tests/classic/execute_async_script/collections.py.ini index 5d0711fe4ad..710ae93d053 100644 --- a/tests/wpt/meta/webdriver/tests/classic/execute_async_script/collections.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/execute_async_script/collections.py.ini @@ -1,10 +1,4 @@ [collections.py] - [test_array_in_array] - expected: FAIL - - [test_dom_token_list] - expected: FAIL - [test_file_list] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/execute_script/collections.py.ini b/tests/wpt/meta/webdriver/tests/classic/execute_script/collections.py.ini index 68e5ec4b830..710ae93d053 100644 --- a/tests/wpt/meta/webdriver/tests/classic/execute_script/collections.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/execute_script/collections.py.ini @@ -1,12 +1,6 @@ [collections.py] - [test_dom_token_list] - expected: FAIL - [test_file_list] expected: FAIL [test_html_all_collection] expected: FAIL - - [test_array_in_array] - expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/get_computed_role/get.py.ini b/tests/wpt/meta/webdriver/tests/classic/get_computed_role/get.py.ini index abd4a7750b7..f00172fdc5a 100644 --- a/tests/wpt/meta/webdriver/tests/classic/get_computed_role/get.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/get_computed_role/get.py.ini @@ -14,11 +14,5 @@ [test_no_such_element_from_other_frame[closed\]] expected: FAIL - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL - [test_computed_roles[<article>foo</article>-article-article\]] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/get_element_css_value/get.py.ini b/tests/wpt/meta/webdriver/tests/classic/get_element_css_value/get.py.ini index bcd25112776..d55c5312a47 100644 --- a/tests/wpt/meta/webdriver/tests/classic/get_element_css_value/get.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/get_element_css_value/get.py.ini @@ -13,9 +13,3 @@ [test_no_such_element_from_other_frame[closed\]] expected: FAIL - - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/get_element_rect/get.py.ini b/tests/wpt/meta/webdriver/tests/classic/get_element_rect/get.py.ini index 065e9fbc4ce..67875a58cd9 100644 --- a/tests/wpt/meta/webdriver/tests/classic/get_element_rect/get.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/get_element_rect/get.py.ini @@ -14,11 +14,5 @@ [test_no_such_element_from_other_frame[closed\]] expected: FAIL - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL - [test_basic] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/get_element_tag_name/get.py.ini b/tests/wpt/meta/webdriver/tests/classic/get_element_tag_name/get.py.ini index b810c389100..0ac8ff98d59 100644 --- a/tests/wpt/meta/webdriver/tests/classic/get_element_tag_name/get.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/get_element_tag_name/get.py.ini @@ -14,11 +14,5 @@ [test_no_such_element_from_other_frame[closed\]] expected: FAIL - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL - [test_get_element_tag_name] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/get_element_text/get.py.ini b/tests/wpt/meta/webdriver/tests/classic/get_element_text/get.py.ini index 5f04f967054..ad870f8f49b 100644 --- a/tests/wpt/meta/webdriver/tests/classic/get_element_text/get.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/get_element_text/get.py.ini @@ -14,12 +14,6 @@ [test_no_such_element_from_other_frame[closed\]] expected: FAIL - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL - [test_transform_capitalize[space\]] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/is_element_selected/selected.py.ini b/tests/wpt/meta/webdriver/tests/classic/is_element_selected/selected.py.ini index f75724979c5..eb4c0299197 100644 --- a/tests/wpt/meta/webdriver/tests/classic/is_element_selected/selected.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/is_element_selected/selected.py.ini @@ -13,9 +13,3 @@ [test_no_such_element_from_other_frame[closed\]] expected: FAIL - - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/new_window/new_tab.py.ini b/tests/wpt/meta/webdriver/tests/classic/new_window/new_tab.py.ini index 77be6174789..db51a3496ae 100644 --- a/tests/wpt/meta/webdriver/tests/classic/new_window/new_tab.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/new_window/new_tab.py.ini @@ -1,15 +1,9 @@ [new_tab.py] [test_keeps_current_window_handle] - expected: ERROR + expected: FAIL [test_opens_about_blank_in_new_tab] - expected: ERROR + expected: FAIL [test_initial_selection_for_contenteditable] - expected: ERROR - - [test_sets_no_window_name] - expected: ERROR - - [test_sets_no_opener] - expected: ERROR + expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/perform_actions/perform.py.ini b/tests/wpt/meta/webdriver/tests/classic/perform_actions/perform.py.ini deleted file mode 100644 index b4a8841b9ae..00000000000 --- a/tests/wpt/meta/webdriver/tests/classic/perform_actions/perform.py.ini +++ /dev/null @@ -1,9 +0,0 @@ -[perform.py] - [test_input_source_action_sequence_actions_pause_duration_valid[wheel\]] - expected: FAIL - - [test_input_source_action_sequence_actions_pause_duration_missing[wheel\]] - expected: FAIL - - [test_input_source_action_sequence_pointer_parameters_not_processed[wheel\]] - expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_mouse.py.ini b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_mouse.py.ini index 4a163fe2fac..4222966b349 100644 --- a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_mouse.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_mouse.py.ini @@ -1,18 +1,11 @@ [pointer_mouse.py] - expected: TIMEOUT [test_no_top_browsing_context] expected: FAIL [test_no_browsing_context] - expected: ERROR - - [test_pointer_down_closes_browsing_context] - expected: FAIL - - [test_stale_element_reference[top_context\]] expected: FAIL - [test_stale_element_reference[child_context\]] + [test_pointer_down_closes_browsing_context] expected: FAIL [test_click_at_coordinates] @@ -39,9 +32,6 @@ [test_click_element_in_shadow_tree[inner-closed\]] expected: FAIL - [test_click_navigation] - expected: FAIL - [test_move_to_position_in_viewport[x\]] expected: FAIL @@ -60,5 +50,5 @@ [test_move_to_origin_position_within_frame[element\]] expected: FAIL - [test_invalid_element_origin] + [test_params_actions_origin_outside_viewport[element\]] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_mouse_drag.py.ini b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_mouse_drag.py.ini index 8657edd79e8..3fa735ac7ea 100644 --- a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_mouse_drag.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_mouse_drag.py.ini @@ -1,63 +1,63 @@ [pointer_mouse_drag.py] [test_drag_and_drop[20-0-0\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[20-0-300\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[20-0-800\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[0-15-0\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[0-15-300\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[0-15-800\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[10-15-0\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[10-15-300\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[10-15-800\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[-20-0-0\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[-20-0-300\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[-20-0-800\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[10--15-0\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[10--15-300\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[10--15-800\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[-10--15-0\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[-10--15-300\]] - expected: ERROR + expected: FAIL [test_drag_and_drop[-10--15-800\]] - expected: ERROR + expected: FAIL [test_drag_and_drop_with_draggable_element[0\]] - expected: ERROR + expected: FAIL [test_drag_and_drop_with_draggable_element[300\]] - expected: ERROR + expected: FAIL [test_drag_and_drop_with_draggable_element[800\]] - expected: ERROR + expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_origin.py.ini b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_origin.py.ini index 112a4f9ddf4..8102334d66b 100644 --- a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_origin.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_origin.py.ini @@ -1,27 +1,18 @@ [pointer_origin.py] [test_viewport_inside] - expected: ERROR + expected: FAIL [test_pointer_inside] - expected: ERROR + expected: FAIL [test_element_center_point] - expected: ERROR + expected: FAIL [test_element_center_point_with_offset] - expected: ERROR + expected: FAIL [test_element_in_view_center_point_partly_visible] - expected: ERROR + expected: FAIL [test_element_larger_than_viewport] - expected: ERROR - - [test_element_outside_of_view_port] - expected: ERROR - - [test_viewport_outside] - expected: ERROR - - [test_pointer_outside] - expected: ERROR + expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_pen.py.ini b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_pen.py.ini index a3d52579f7d..5c08076b7b2 100644 --- a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_pen.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_pen.py.ini @@ -8,12 +8,6 @@ [test_pointer_down_closes_browsing_context] expected: FAIL - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL - [test_pen_pointer_in_shadow_tree[outer-open\]] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_touch.py.ini b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_touch.py.ini index 65101d138aa..2dd2ee19891 100644 --- a/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_touch.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/perform_actions/pointer_touch.py.ini @@ -8,12 +8,6 @@ [test_pointer_down_closes_browsing_context] expected: FAIL - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL - [test_touch_pointer_in_shadow_tree[outer-open\]] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/perform_actions/sequence.py.ini b/tests/wpt/meta/webdriver/tests/classic/perform_actions/sequence.py.ini deleted file mode 100644 index 54ee376545d..00000000000 --- a/tests/wpt/meta/webdriver/tests/classic/perform_actions/sequence.py.ini +++ /dev/null @@ -1,3 +0,0 @@ -[sequence.py] - [test_perform_no_actions_send_no_events] - expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/perform_actions/wheel.py.ini b/tests/wpt/meta/webdriver/tests/classic/perform_actions/wheel.py.ini index 3f6abc70f3e..c8a0364b783 100644 --- a/tests/wpt/meta/webdriver/tests/classic/perform_actions/wheel.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/perform_actions/wheel.py.ini @@ -1,19 +1,10 @@ [wheel.py] - [test_null_response_value] - expected: FAIL - [test_no_top_browsing_context] expected: FAIL [test_no_browsing_context] expected: FAIL - [test_params_actions_origin_outside_viewport[element\]] - expected: FAIL - - [test_params_actions_origin_outside_viewport[viewport\]] - expected: FAIL - [test_scroll_not_scrollable] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/switch_to_frame/switch_webelement.py.ini b/tests/wpt/meta/webdriver/tests/classic/switch_to_frame/switch_webelement.py.ini index 9932ab9a5d5..1f469cd85c3 100644 --- a/tests/wpt/meta/webdriver/tests/classic/switch_to_frame/switch_webelement.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/switch_to_frame/switch_webelement.py.ini @@ -1,10 +1,4 @@ [switch_webelement.py] - [test_frame_id_webelement_stale_element_reference[top_context\]] - expected: FAIL - - [test_frame_id_webelement_stale_element_reference[child_context\]] - expected: FAIL - [test_frame_id_webelement_frame[0-foo\]] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/take_element_screenshot/screenshot.py.ini b/tests/wpt/meta/webdriver/tests/classic/take_element_screenshot/screenshot.py.ini index 319e4bf848f..981c68641a8 100644 --- a/tests/wpt/meta/webdriver/tests/classic/take_element_screenshot/screenshot.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/take_element_screenshot/screenshot.py.ini @@ -19,12 +19,3 @@ [test_no_such_element_from_other_frame[closed\]] expected: FAIL - - [test_stale_element_reference[top_context\]] - expected: FAIL - - [test_stale_element_reference[child_context\]] - expected: FAIL - - [test_format_and_dimensions] - expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/take_screenshot/iframe.py.ini b/tests/wpt/meta/webdriver/tests/classic/take_screenshot/iframe.py.ini deleted file mode 100644 index 62f7ab5e4da..00000000000 --- a/tests/wpt/meta/webdriver/tests/classic/take_screenshot/iframe.py.ini +++ /dev/null @@ -1,3 +0,0 @@ -[iframe.py] - [test_always_captures_top_browsing_context] - expected: FAIL diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 7035ae424dc..296804116f8 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -13575,14 +13575,14 @@ ] ], "interfaces.https.html": [ - "76d746b0663ed73865816e678c2536eceff31f2d", + "72918e837726b58740a491a9223eeeb625055ae5", [ null, {} ] ], "interfaces.worker.js": [ - "8d109502622fac7266a4564de09684a3ab94118c", + "e86f34f261442aeaa7074c525fb4b1206219769d", [ "mozilla/interfaces.worker.html", {} diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.https.html b/tests/wpt/mozilla/tests/mozilla/interfaces.https.html index 76d746b0663..72918e83772 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.https.html +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.https.html @@ -371,6 +371,8 @@ test_interfaces([ "WritableStream", "WritableStreamDefaultController", "WritableStreamDefaultWriter", + "TransformStream", + "TransformStreamDefaultController", "WGSLLanguageFeatures", "XMLDocument", "XMLHttpRequest", diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js b/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js index 8d109502622..e86f34f2614 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js @@ -137,6 +137,8 @@ test_interfaces([ "WritableStream", "WritableStreamDefaultController", "WritableStreamDefaultWriter", + "TransformStream", + "TransformStreamDefaultController", "WGSLLanguageFeatures", "XMLHttpRequest", "XMLHttpRequestEventTarget", diff --git a/tests/wpt/tests/content-security-policy/img-src/icon-blocked.sub.html b/tests/wpt/tests/content-security-policy/img-src/icon-blocked.sub.html index cc882347a1a..4c39e5dec73 100644 --- a/tests/wpt/tests/content-security-policy/img-src/icon-blocked.sub.html +++ b/tests/wpt/tests/content-security-policy/img-src/icon-blocked.sub.html @@ -12,6 +12,7 @@ var t_spv = async_test("Test that spv event is fired"); window.addEventListener("securitypolicyviolation", t_spv.step_func_done(function(e) { assert_equals(e.violatedDirective, 'img-src'); + assert_equals(e.target, document); assert_true(e.blockedURI.endsWith('/support/fail.png')); })); diff --git a/tests/wpt/tests/content-security-policy/img-src/img-src-targeting.html b/tests/wpt/tests/content-security-policy/img-src/img-src-targeting.html new file mode 100644 index 00000000000..3b4fe7c690b --- /dev/null +++ b/tests/wpt/tests/content-security-policy/img-src/img-src-targeting.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="Content-Security-Policy" content="img-src 'none';"> + <script src='/resources/testharness.js'></script> + <script src='/resources/testharnessreport.js'></script> +</head> +<body> +<p>Check that img-src sets correct target</p> + <script> + var t = async_test("Test that image does not load"); + var t_spv = async_test("Test that spv event is fired"); + window.addEventListener("securitypolicyviolation", t_spv.step_func_done(function(e) { + assert_equals(e.violatedDirective, 'img-src'); + assert_equals(e.target, document); + assert_true(e.blockedURI.endsWith('/support/fail.png')); + })); + </script> + <img src='/content-security-policy/support/fail.png' + onload='t.step(function() { assert_unreached("Image should not have loaded"); t.done(); });' + onerror='t.done();'> +</body> + +</html> diff --git a/tests/wpt/webgl/meta/conformance/context/premultiplyalpha-test.html.ini b/tests/wpt/webgl/meta/conformance/context/premultiplyalpha-test.html.ini deleted file mode 100644 index ffc971bc861..00000000000 --- a/tests/wpt/webgl/meta/conformance/context/premultiplyalpha-test.html.ini +++ /dev/null @@ -1,8 +0,0 @@ -[premultiplyalpha-test.html] - bug: https://github.com/servo/servo/issues/21132 - - [WebGL test #62] - expected: FAIL - - [WebGL test #69] - expected: FAIL |