diff options
68 files changed, 1130 insertions, 663 deletions
diff --git a/.github/workflows/ohos.yml b/.github/workflows/ohos.yml index 404e305f61b..f2c8233a785 100644 --- a/.github/workflows/ohos.yml +++ b/.github/workflows/ohos.yml @@ -169,11 +169,16 @@ jobs: - name: Build for aarch64 HarmonyOS run: | ./mach build --locked --target aarch64-unknown-linux-ohos --profile=${{ inputs.profile }} --flavor=harmonyos --no-default-features --features tracing,tracing-hitrace + - name: Upload supprt/hitrace-bencher/runs.json + uses: actions/upload-artifact@v4 + with: + name: runs.json + path: support/hitrace-bencher/runs.json - uses: actions/upload-artifact@v4 with: # Upload the **unsigned** artifact - We don't have the signing materials in pull request workflows - path: target/openharmony/aarch64-unknown-linux-ohos/${{ inputs.profile }}/entry/build/harmonyos/outputs/default/servoshell-default-unsigned.hap name: servoshell-hos-${{ inputs.profile }}.hap + path: target/openharmony/aarch64-unknown-linux-ohos/${{ inputs.profile }}/entry/build/harmonyos/outputs/default/servoshell-default-unsigned.hap test-harmonyos-aarch64: @@ -239,8 +244,14 @@ jobs: [[ $servo_pid =~ ^[0-9]+$ ]] || { echo "It looks like servo crashed!" ; exit 1; } # If the grep fails, then the trace output for the "page loaded" prompt is missing grep 'org\.servo\.servo-.* tracing_mark_write.*PageLoadEndedPrompt' test_output/servo.ftrace + - name: Getting runs file + uses: actions/download-artifact@v4 + with: + # Name of the artifact to download. + # If unspecified, all artifacts for the run are downloaded. + name: runs.json - name: "Run benchmark" - run: hitrace-bench --bencher -b "org.servo.servo" -p "https://www.servo.org" -n 5 + run: hitrace-bench -r runs.json - name: Getting bencher uses: bencherdev/bencher@main - name: Getting model name diff --git a/Cargo.lock b/Cargo.lock index a4db39275a7..2327a836793 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -238,12 +238,12 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys 0.59.0", ] @@ -870,9 +870,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.17.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d4ba6e40bd1184518716a6e1a781bf9160e286d219ccdb8ab2612e74cfe4789" +checksum = "e34e221e91c7eb5e8315b5c9cf1a61670938c0626451f954a51693ed44b37f45" dependencies = [ "smallvec", "target-lexicon", @@ -1065,7 +1065,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] @@ -3395,9 +3395,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710" dependencies = [ "bytes", "futures-channel", @@ -4043,9 +4043,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806" +checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93" dependencies = [ "jiff-static", "log", @@ -4056,9 +4056,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48" +checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442" dependencies = [ "proc-macro2", "quote", @@ -4261,7 +4261,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -5392,6 +5392,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] name = "oorandom" version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -6235,9 +6241,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" @@ -6627,7 +6633,7 @@ dependencies = [ [[package]] name = "servo-media" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "once_cell", "servo-media-audio", @@ -6640,7 +6646,7 @@ dependencies = [ [[package]] name = "servo-media-audio" version = "0.2.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "byte-slice-cast", "euclid", @@ -6661,7 +6667,7 @@ dependencies = [ [[package]] name = "servo-media-derive" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "proc-macro2", "quote", @@ -6671,7 +6677,7 @@ dependencies = [ [[package]] name = "servo-media-dummy" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "ipc-channel", "servo-media", @@ -6685,7 +6691,7 @@ dependencies = [ [[package]] name = "servo-media-gstreamer" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "byte-slice-cast", "glib", @@ -6718,7 +6724,7 @@ dependencies = [ [[package]] name = "servo-media-gstreamer-render" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "gstreamer", "gstreamer-video", @@ -6728,7 +6734,7 @@ dependencies = [ [[package]] name = "servo-media-gstreamer-render-android" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "glib", "gstreamer", @@ -6742,7 +6748,7 @@ dependencies = [ [[package]] name = "servo-media-gstreamer-render-unix" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "glib", "gstreamer", @@ -6757,7 +6763,7 @@ dependencies = [ [[package]] name = "servo-media-player" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "ipc-channel", "serde", @@ -6769,7 +6775,7 @@ dependencies = [ [[package]] name = "servo-media-streams" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "uuid", ] @@ -6777,12 +6783,12 @@ dependencies = [ [[package]] name = "servo-media-traits" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" [[package]] name = "servo-media-webrtc" version = "0.1.0" -source = "git+https://github.com/servo/media#eb96030cdd153ebcbbe3836dc6471303187740e9" +source = "git+https://github.com/servo/media#4931a4b380acd9c95b8787cbcfa812823864e2d0" dependencies = [ "log", "servo-media-streams", @@ -7494,9 +7500,9 @@ dependencies = [ [[package]] name = "system-deps" -version = "7.0.3" +version = "7.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005" +checksum = "550b2c61a9c30b85ca1f6ef0afcd2befcb12e73b1d31ef0526423bc7b6a99d7f" dependencies = [ "cfg-expr", "heck", @@ -7530,9 +7536,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.16" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" [[package]] name = "task_info" @@ -8909,7 +8915,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] @@ -9257,9 +9263,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winit" -version = "0.30.10" +version = "0.30.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0d05bd8908e14618c9609471db04007e644fd9cce6529756046cfc577f9155e" +checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4" dependencies = [ "ahash", "android-activity", diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 74ada7b21c6..855f60e57b2 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -620,29 +620,37 @@ impl IOCompositor { } }, - CompositorMsg::WebDriverMouseButtonEvent(webview_id, action, button, x, y) => { + CompositorMsg::WebDriverMouseButtonEvent( + webview_id, + action, + button, + x, + y, + message_id, + ) => { let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else { warn!("Handling input event for unknown webview: {webview_id}"); return; }; let dppx = webview_renderer.device_pixels_per_page_pixel(); let point = dppx.transform_point(Point2D::new(x, y)); - webview_renderer.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent { - point, - action, - button, - })); + webview_renderer.dispatch_input_event( + InputEvent::MouseButton(MouseButtonEvent::new(action, button, point)) + .with_webdriver_message_id(Some(message_id)), + ); }, - CompositorMsg::WebDriverMouseMoveEvent(webview_id, x, y) => { + CompositorMsg::WebDriverMouseMoveEvent(webview_id, x, y, message_id) => { let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else { warn!("Handling input event for unknown webview: {webview_id}"); return; }; let dppx = webview_renderer.device_pixels_per_page_pixel(); let point = dppx.transform_point(Point2D::new(x, y)); - webview_renderer - .dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent { point })); + webview_renderer.dispatch_input_event( + InputEvent::MouseMove(MouseMoveEvent::new(point)) + .with_webdriver_message_id(Some(message_id)), + ); }, CompositorMsg::WebDriverWheelScrollEvent(webview_id, x, y, delta_x, delta_y) => { @@ -1456,10 +1464,11 @@ impl IOCompositor { format: PixelFormat::RGBA8, frames: vec![ImageFrame { delay: None, - bytes: ipc::IpcSharedMemory::from_bytes(&image), + byte_range: 0..image.len(), width: image.width(), height: image.height(), }], + bytes: ipc::IpcSharedMemory::from_bytes(&image), id: None, cors_status: CorsStatus::Safe, })) diff --git a/components/compositing/webview_renderer.rs b/components/compositing/webview_renderer.rs index 0a6bdf9ae0a..b0e91ccb02e 100644 --- a/components/compositing/webview_renderer.rs +++ b/components/compositing/webview_renderer.rs @@ -687,17 +687,17 @@ impl WebViewRenderer { /// <http://w3c.github.io/touch-events/#mouse-events> fn simulate_mouse_click(&mut self, point: DevicePoint) { let button = MouseButton::Left; - self.dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent { point })); - self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent { + self.dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point))); + self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent::new( + MouseButtonAction::Down, button, - action: MouseButtonAction::Down, point, - })); - self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent { + ))); + self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent::new( + MouseButtonAction::Up, button, - action: MouseButtonAction::Up, point, - })); + ))); } pub(crate) fn notify_scroll_event( diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 5db37800c42..1816cf05373 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -132,7 +132,7 @@ use embedder_traits::{ FocusSequenceNumber, ImeEvent, InputEvent, JSValue, JavaScriptEvaluationError, JavaScriptEvaluationId, MediaSessionActionType, MediaSessionEvent, MediaSessionPlaybackState, MouseButton, MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverCommandMsg, - WebDriverLoadStatus, + WebDriverCommandResponse, WebDriverLoadStatus, }; use euclid::Size2D; use euclid::default::Size2D as UntypedSize2D; @@ -532,6 +532,8 @@ pub struct InitialConstellationState { struct WebDriverData { load_channel: Option<(PipelineId, IpcSender<WebDriverLoadStatus>)>, resize_channel: Option<IpcSender<Size2D<f32, CSSPixel>>>, + // Forward responses from the script thread to the webdriver server. + input_command_response_sender: Option<IpcSender<WebDriverCommandResponse>>, } impl WebDriverData { @@ -539,6 +541,7 @@ impl WebDriverData { WebDriverData { load_channel: None, resize_channel: None, + input_command_response_sender: None, } } } @@ -1867,6 +1870,18 @@ where ScriptToConstellationMessage::FinishJavaScriptEvaluation(evaluation_id, result) => { self.handle_finish_javascript_evaluation(evaluation_id, result) }, + ScriptToConstellationMessage::WebDriverInputComplete(msg_id) => { + if let Some(ref reply_sender) = self.webdriver.input_command_response_sender { + reply_sender + .send(WebDriverCommandResponse { id: msg_id }) + .unwrap_or_else(|_| { + warn!("Failed to send WebDriverInputComplete {:?}", msg_id); + self.webdriver.input_command_response_sender = None; + }); + } else { + warn!("No WebDriver input_command_response_sender"); + } + }, } } @@ -4836,7 +4851,11 @@ where mouse_button, x, y, + msg_id, + response_sender, ) => { + self.webdriver.input_command_response_sender = Some(response_sender); + self.compositor_proxy .send(CompositorMsg::WebDriverMouseButtonEvent( webview_id, @@ -4844,11 +4863,16 @@ where mouse_button, x, y, + msg_id, )); }, - WebDriverCommandMsg::MouseMoveAction(webview_id, x, y) => { + WebDriverCommandMsg::MouseMoveAction(webview_id, x, y, msg_id, response_sender) => { + self.webdriver.input_command_response_sender = Some(response_sender); + self.compositor_proxy - .send(CompositorMsg::WebDriverMouseMoveEvent(webview_id, x, y)); + .send(CompositorMsg::WebDriverMouseMoveEvent( + webview_id, x, y, msg_id, + )); }, WebDriverCommandMsg::WheelScrollAction(webview, x, y, delta_x, delta_y) => { self.compositor_proxy diff --git a/components/constellation/tracing.rs b/components/constellation/tracing.rs index 940cc9614cc..6237665b87f 100644 --- a/components/constellation/tracing.rs +++ b/components/constellation/tracing.rs @@ -177,6 +177,7 @@ mod from_script { Self::TitleChanged(..) => target!("TitleChanged"), Self::IFrameSizes(..) => target!("IFrameSizes"), Self::ReportMemory(..) => target!("ReportMemory"), + Self::WebDriverInputComplete(..) => target!("WebDriverInputComplete"), Self::FinishJavaScriptEvaluation(..) => target!("FinishJavaScriptEvaluation"), } } diff --git a/components/fonts/platform/windows/font.rs b/components/fonts/platform/windows/font.rs index e33b2ad9d3e..74f592e63b9 100644 --- a/components/fonts/platform/windows/font.rs +++ b/components/fonts/platform/windows/font.rs @@ -132,7 +132,9 @@ impl PlatformFontMethods for PlatformFont { pt_size: Option<Au>, ) -> Result<PlatformFont, &'static str> { let font_face = FontCollection::system() - .get_font_from_descriptor(&font_identifier.font_descriptor) + .font_from_descriptor(&font_identifier.font_descriptor) + .ok() + .flatten() .ok_or("Could not create Font from descriptor")? .create_font_face(); Self::new(font_face, pt_size) diff --git a/components/fonts/platform/windows/font_list.rs b/components/fonts/platform/windows/font_list.rs index d1aa19e178a..e9cdaac8562 100644 --- a/components/fonts/platform/windows/font_list.rs +++ b/components/fonts/platform/windows/font_list.rs @@ -25,7 +25,9 @@ where { let system_fc = FontCollection::system(); for family in system_fc.families_iter() { - callback(family.name()); + if let Ok(family_name) = family.family_name() { + callback(family_name); + } } } @@ -40,13 +42,17 @@ pub struct LocalFontIdentifier { impl LocalFontIdentifier { pub fn index(&self) -> u32 { FontCollection::system() - .get_font_from_descriptor(&self.font_descriptor) + .font_from_descriptor(&self.font_descriptor) + .ok() + .flatten() .map_or(0, |font| font.create_font_face().get_index()) } pub(crate) fn native_font_handle(&self) -> NativeFontHandle { let face = FontCollection::system() - .get_font_from_descriptor(&self.font_descriptor) + .font_from_descriptor(&self.font_descriptor) + .ok() + .flatten() .expect("Could not create Font from FontDescriptor") .create_font_face(); let path = face @@ -62,7 +68,9 @@ impl LocalFontIdentifier { } pub(crate) fn read_data_from_file(&self) -> Option<Vec<u8>> { - let font = FontCollection::system().get_font_from_descriptor(&self.font_descriptor)?; + let font = FontCollection::system() + .font_from_descriptor(&self.font_descriptor) + .ok()??; let face = font.create_font_face(); let files = face.get_files(); assert!(!files.is_empty()); @@ -86,10 +94,12 @@ where F: FnMut(FontTemplate), { let system_fc = FontCollection::system(); - if let Some(family) = system_fc.get_font_family_by_name(family_name) { + if let Ok(Some(family)) = system_fc.font_family_by_name(family_name) { let count = family.get_font_count(); for i in 0..count { - let font = family.get_font(i); + let Ok(font) = family.font(i) else { + continue; + }; let template_descriptor = (&font).into(); let local_font_identifier = LocalFontIdentifier { font_descriptor: Arc::new(font.to_descriptor()), diff --git a/components/layout/construct_modern.rs b/components/layout/construct_modern.rs index 8f1282ec9f6..d09744b2031 100644 --- a/components/layout/construct_modern.rs +++ b/components/layout/construct_modern.rs @@ -150,7 +150,6 @@ impl<'a, 'dom> ModernContainerBuilder<'a, 'dom> { let inline_formatting_context = inline_formatting_context_builder.finish( self.context, - self.propagated_data, true, /* has_first_formatted_line */ false, /* is_single_line_text_box */ self.info.style.writing_mode.to_bidi_level(), diff --git a/components/layout/display_list/conversions.rs b/components/layout/display_list/conversions.rs index 6d78a17e886..a2517c863f1 100644 --- a/components/layout/display_list/conversions.rs +++ b/components/layout/display_list/conversions.rs @@ -125,11 +125,15 @@ impl ToWebRender for ComputedTextDecorationStyle { type Type = LineStyle; fn to_webrender(&self) -> Self::Type { match *self { - ComputedTextDecorationStyle::Solid => LineStyle::Solid, + ComputedTextDecorationStyle::Solid | ComputedTextDecorationStyle::Double => { + LineStyle::Solid + }, ComputedTextDecorationStyle::Dotted => LineStyle::Dotted, ComputedTextDecorationStyle::Dashed => LineStyle::Dashed, ComputedTextDecorationStyle::Wavy => LineStyle::Wavy, - _ => LineStyle::Solid, + ComputedTextDecorationStyle::MozNone => { + unreachable!("Should never try to draw a moz-none text decoration") + }, } } } diff --git a/components/layout/display_list/mod.rs b/components/layout/display_list/mod.rs index 95689cf1186..3716ff35b2c 100644 --- a/components/layout/display_list/mod.rs +++ b/components/layout/display_list/mod.rs @@ -11,7 +11,7 @@ use base::id::ScrollTreeNodeId; use clip::{Clip, ClipId}; use compositing_traits::display_list::{CompositorDisplayListInfo, SpatialTreeNodeInfo}; use embedder_traits::Cursor; -use euclid::{Point2D, SideOffsets2D, Size2D, UnknownUnit}; +use euclid::{Point2D, SideOffsets2D, Size2D, UnknownUnit, Vector2D}; use fonts::GlyphStore; use gradient::WebRenderGradient; use range::Range as ServoRange; @@ -21,7 +21,9 @@ use servo_geometry::MaxRect; use style::Zero; use style::color::{AbsoluteColor, ColorSpace}; use style::computed_values::border_image_outset::T as BorderImageOutset; -use style::computed_values::text_decoration_style::T as ComputedTextDecorationStyle; +use style::computed_values::text_decoration_style::{ + T as ComputedTextDecorationStyle, T as TextDecorationStyle, +}; use style::dom::OpaqueNode; use style::properties::ComputedValues; use style::properties::longhands::visibility::computed_value::T as Visibility; @@ -572,6 +574,7 @@ impl Fragment { section: StackingContextSection, is_hit_test_for_scrollable_overflow: bool, is_collapsed_table_borders: bool, + text_decorations: &Arc<Vec<FragmentTextDecoration>>, ) { let spatial_id = builder.spatial_id(builder.current_scroll_node_id); let clip_chain_id = builder.clip_chain_id(builder.current_clip_id); @@ -683,9 +686,12 @@ impl Fragment { .get_inherited_box() .visibility { - Visibility::Visible => { - self.build_display_list_for_text_fragment(text, builder, containing_block) - }, + Visibility::Visible => self.build_display_list_for_text_fragment( + text, + builder, + containing_block, + text_decorations, + ), Visibility::Hidden => (), Visibility::Collapse => (), } @@ -723,6 +729,7 @@ impl Fragment { fragment: &TextFragment, builder: &mut DisplayListBuilder, containing_block: &PhysicalRect<Au>, + text_decorations: &Arc<Vec<FragmentTextDecoration>>, ) { // NB: The order of painting text components (CSS Text Decoration Module Level 3) is: // shadows, underline, overline, text, text-emphasis, and then line-through. @@ -732,6 +739,7 @@ impl Fragment { let rect = fragment.rect.translate(containing_block.origin.to_vector()); let mut baseline_origin = rect.origin; baseline_origin.y += fragment.font_metrics.ascent; + let glyphs = glyphs( &fragment.glyphs, baseline_origin, @@ -774,23 +782,36 @@ impl Fragment { ); } - if fragment - .text_decoration_line - .contains(TextDecorationLine::UNDERLINE) - { - let mut rect = rect; - rect.origin.y += font_metrics.ascent - font_metrics.underline_offset; - rect.size.height = Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx)); - self.build_display_list_for_text_decoration(&parent_style, builder, &rect, &color); + for text_decoration in text_decorations.iter() { + if text_decoration.line.contains(TextDecorationLine::UNDERLINE) { + let mut rect = rect; + rect.origin.y += font_metrics.ascent - font_metrics.underline_offset; + rect.size.height = + Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx)); + + self.build_display_list_for_text_decoration( + &parent_style, + builder, + &rect, + text_decoration, + TextDecorationLine::UNDERLINE, + ); + } } - if fragment - .text_decoration_line - .contains(TextDecorationLine::OVERLINE) - { - let mut rect = rect; - rect.size.height = Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx)); - self.build_display_list_for_text_decoration(&parent_style, builder, &rect, &color); + for text_decoration in text_decorations.iter() { + if text_decoration.line.contains(TextDecorationLine::OVERLINE) { + let mut rect = rect; + rect.size.height = + Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx)); + self.build_display_list_for_text_decoration( + &parent_style, + builder, + &rect, + text_decoration, + TextDecorationLine::OVERLINE, + ); + } } // TODO: This caret/text selection implementation currently does not account for vertical text @@ -867,14 +888,23 @@ impl Fragment { None, ); - if fragment - .text_decoration_line - .contains(TextDecorationLine::LINE_THROUGH) - { - let mut rect = rect; - rect.origin.y += font_metrics.ascent - font_metrics.strikeout_offset; - rect.size.height = Au::from_f32_px(font_metrics.strikeout_size.to_nearest_pixel(dppx)); - self.build_display_list_for_text_decoration(&parent_style, builder, &rect, &color); + for text_decoration in text_decorations.iter() { + if text_decoration + .line + .contains(TextDecorationLine::LINE_THROUGH) + { + let mut rect = rect; + rect.origin.y += font_metrics.ascent - font_metrics.strikeout_offset; + rect.size.height = + Au::from_f32_px(font_metrics.strikeout_size.to_nearest_pixel(dppx)); + self.build_display_list_for_text_decoration( + &parent_style, + builder, + &rect, + text_decoration, + TextDecorationLine::LINE_THROUGH, + ); + } } if !shadows.0.is_empty() { @@ -887,27 +917,47 @@ impl Fragment { parent_style: &ServoArc<ComputedValues>, builder: &mut DisplayListBuilder, rect: &PhysicalRect<Au>, - color: &AbsoluteColor, + text_decoration: &FragmentTextDecoration, + line: TextDecorationLine, ) { - let rect = rect.to_webrender(); - let wavy_line_thickness = (0.33 * rect.size().height).ceil(); - let text_decoration_color = parent_style - .clone_text_decoration_color() - .resolve_to_absolute(color); - let text_decoration_style = parent_style.clone_text_decoration_style(); - if text_decoration_style == ComputedTextDecorationStyle::MozNone { + if text_decoration.style == ComputedTextDecorationStyle::MozNone { return; } + + let mut rect = rect.to_webrender(); + let line_thickness = rect.height().ceil(); + + if text_decoration.style == ComputedTextDecorationStyle::Wavy { + rect = rect.inflate(0.0, line_thickness * 1.0); + } + let common_properties = builder.common_properties(rect, parent_style); builder.wr().push_line( &common_properties, &rect, - wavy_line_thickness, + line_thickness, wr::LineOrientation::Horizontal, - &rgba(text_decoration_color), - text_decoration_style.to_webrender(), + &rgba(text_decoration.color), + text_decoration.style.to_webrender(), ); - // XXX(ferjm) support text-decoration-style: double + + if text_decoration.style == TextDecorationStyle::Double { + let half_height = (rect.height() / 2.0).floor().max(1.0); + let y_offset = match line { + TextDecorationLine::OVERLINE => -rect.height() - half_height, + _ => rect.height() + half_height, + }; + let rect = rect.translate(Vector2D::new(0.0, y_offset)); + let common_properties = builder.common_properties(rect, parent_style); + builder.wr().push_line( + &common_properties, + &rect, + line_thickness, + wr::LineOrientation::Horizontal, + &rgba(text_decoration.color), + text_decoration.style.to_webrender(), + ); + } } } diff --git a/components/layout/display_list/stacking_context.rs b/components/layout/display_list/stacking_context.rs index bcd882e3fcc..d22a2b6656a 100644 --- a/components/layout/display_list/stacking_context.rs +++ b/components/layout/display_list/stacking_context.rs @@ -5,6 +5,7 @@ use core::f32; use std::cell::RefCell; use std::mem; +use std::sync::Arc; use app_units::Au; use base::id::ScrollTreeNodeId; @@ -18,13 +19,15 @@ use euclid::default::{Point2D, Rect, Size2D}; use log::warn; use servo_config::opts::DebugOptions; use style::Zero; +use style::color::AbsoluteColor; use style::computed_values::float::T as ComputedFloat; use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode; use style::computed_values::overflow_x::T as ComputedOverflow; use style::computed_values::position::T as ComputedPosition; +use style::computed_values::text_decoration_style::T as TextDecorationStyle; use style::values::computed::angle::Angle; use style::values::computed::basic_shape::ClipPath; -use style::values::computed::{ClipRectOrAuto, Length}; +use style::values::computed::{ClipRectOrAuto, Length, TextDecorationLine}; use style::values::generics::box_::Perspective; use style::values::generics::transform::{self, GenericRotate, GenericScale, GenericTranslate}; use style::values::specified::box_::DisplayOutside; @@ -168,12 +171,14 @@ impl StackingContextTree { }; let mut root_stacking_context = StackingContext::create_root(root_scroll_node_id, debug); + let text_decorations = Default::default(); for fragment in &fragment_tree.root_fragments { fragment.build_stacking_context_tree( &mut stacking_context_tree, &containing_block_info, &mut root_stacking_context, StackingContextBuildMode::SkipHoisted, + &text_decorations, ); } root_stacking_context.sort(); @@ -246,6 +251,14 @@ impl StackingContextTree { } } +/// The text decorations for a Fragment, collecting during [`StackingContextTree`] construction. +#[derive(Clone, Debug)] +pub(crate) struct FragmentTextDecoration { + pub line: TextDecorationLine, + pub color: AbsoluteColor, + pub style: TextDecorationStyle, +} + /// A piece of content that directly belongs to a section of a stacking context. /// /// This is generally part of a fragment, like its borders or foreground, but it @@ -261,6 +274,7 @@ pub(crate) enum StackingContextContent { fragment: Fragment, is_hit_test_for_scrollable_overflow: bool, is_collapsed_table_borders: bool, + text_decorations: Arc<Vec<FragmentTextDecoration>>, }, /// An index into [StackingContext::atomic_inline_stacking_containers]. @@ -292,6 +306,7 @@ impl StackingContextContent { fragment, is_hit_test_for_scrollable_overflow, is_collapsed_table_borders, + text_decorations, } => { builder.current_scroll_node_id = *scroll_node_id; builder.current_reference_frame_scroll_node_id = *reference_frame_scroll_node_id; @@ -302,6 +317,7 @@ impl StackingContextContent { *section, *is_hit_test_for_scrollable_overflow, *is_collapsed_table_borders, + text_decorations, ); }, Self::AtomicInlineStackingContainer { index } => { @@ -800,6 +816,7 @@ impl Fragment { containing_block_info: &ContainingBlockInfo, stacking_context: &mut StackingContext, mode: StackingContextBuildMode, + text_decorations: &Arc<Vec<FragmentTextDecoration>>, ) { let containing_block = containing_block_info.get_containing_block_for_fragment(self); let fragment_clone = self.clone(); @@ -812,6 +829,11 @@ impl Fragment { return; } + let text_decorations = match self { + Fragment::Float(..) => &Default::default(), + _ => text_decorations, + }; + // If this fragment has a transform applied that makes it take up no space // then we don't need to create any stacking contexts for it. let has_non_invertible_transform = fragment @@ -828,6 +850,7 @@ impl Fragment { containing_block, containing_block_info, stacking_context, + text_decorations, ); }, Fragment::AbsoluteOrFixedPositioned(fragment) => { @@ -842,6 +865,7 @@ impl Fragment { containing_block_info, stacking_context, StackingContextBuildMode::IncludeHoisted, + &Default::default(), ); }, Fragment::Positioning(fragment) => { @@ -851,6 +875,7 @@ impl Fragment { containing_block, containing_block_info, stacking_context, + text_decorations, ); }, Fragment::Text(_) | Fragment::Image(_) | Fragment::IFrame(_) => { @@ -867,6 +892,7 @@ impl Fragment { fragment: fragment_clone, is_hit_test_for_scrollable_overflow: false, is_collapsed_table_borders: false, + text_decorations: text_decorations.clone(), }); }, } @@ -929,6 +955,7 @@ impl BoxFragment { containing_block: &ContainingBlock, containing_block_info: &ContainingBlockInfo, parent_stacking_context: &mut StackingContext, + text_decorations: &Arc<Vec<FragmentTextDecoration>>, ) { self.build_stacking_context_tree_maybe_creating_reference_frame( fragment, @@ -936,6 +963,7 @@ impl BoxFragment { containing_block, containing_block_info, parent_stacking_context, + text_decorations, ); } @@ -946,6 +974,7 @@ impl BoxFragment { containing_block: &ContainingBlock, containing_block_info: &ContainingBlockInfo, parent_stacking_context: &mut StackingContext, + text_decorations: &Arc<Vec<FragmentTextDecoration>>, ) { let reference_frame_data = match self.reference_frame_data_if_necessary(&containing_block.rect) { @@ -957,6 +986,7 @@ impl BoxFragment { containing_block, containing_block_info, parent_stacking_context, + text_decorations, ); }, }; @@ -999,6 +1029,7 @@ impl BoxFragment { &adjusted_containing_block, &new_containing_block_info, parent_stacking_context, + text_decorations, ); } @@ -1009,6 +1040,7 @@ impl BoxFragment { containing_block: &ContainingBlock, containing_block_info: &ContainingBlockInfo, parent_stacking_context: &mut StackingContext, + text_decorations: &Arc<Vec<FragmentTextDecoration>>, ) { let context_type = match self.get_stacking_context_type() { Some(context_type) => context_type, @@ -1019,6 +1051,7 @@ impl BoxFragment { containing_block, containing_block_info, parent_stacking_context, + text_decorations, ); return; }, @@ -1072,6 +1105,7 @@ impl BoxFragment { containing_block, containing_block_info, &mut child_stacking_context, + text_decorations, ); let mut stolen_children = vec![]; @@ -1097,6 +1131,7 @@ impl BoxFragment { containing_block: &ContainingBlock, containing_block_info: &ContainingBlockInfo, stacking_context: &mut StackingContext, + text_decorations: &Arc<Vec<FragmentTextDecoration>>, ) { let mut new_scroll_node_id = containing_block.scroll_node_id; let mut new_clip_id = containing_block.clip_id; @@ -1164,6 +1199,7 @@ impl BoxFragment { fragment: fragment.clone(), is_hit_test_for_scrollable_overflow: false, is_collapsed_table_borders: false, + text_decorations: text_decorations.clone(), }); }; @@ -1198,6 +1234,7 @@ impl BoxFragment { fragment: fragment.clone(), is_hit_test_for_scrollable_overflow: true, is_collapsed_table_borders: false, + text_decorations: text_decorations.clone(), }); } } @@ -1239,12 +1276,46 @@ impl BoxFragment { containing_block_info.new_for_non_absolute_descendants(&for_non_absolute_descendants) }; + // Text decorations are not propagated to atomic inline-level descendants. + // From https://drafts.csswg.org/css2/#lining-striking-props: + // > Note that text decorations are not propagated to floating and absolutely + // > positioned descendants, nor to the contents of atomic inline-level descendants + // > such as inline blocks and inline tables. + let text_decorations = match self.is_atomic_inline_level() || + self.base + .flags + .contains(FragmentFlags::IS_OUTSIDE_LIST_ITEM_MARKER) + { + true => &Default::default(), + false => text_decorations, + }; + + let new_text_decoration; + let text_decorations = match self.style.clone_text_decoration_line() { + TextDecorationLine::NONE => text_decorations, + line => { + let mut new_vector = (**text_decorations).clone(); + let color = &self.style.get_inherited_text().color; + new_vector.push(FragmentTextDecoration { + line, + color: self + .style + .clone_text_decoration_color() + .resolve_to_absolute(color), + style: self.style.clone_text_decoration_style(), + }); + new_text_decoration = Arc::new(new_vector); + &new_text_decoration + }, + }; + for child in &self.children { child.build_stacking_context_tree( stacking_context_tree, &new_containing_block_info, stacking_context, StackingContextBuildMode::SkipHoisted, + text_decorations, ); } @@ -1263,6 +1334,7 @@ impl BoxFragment { fragment: fragment.clone(), is_hit_test_for_scrollable_overflow: false, is_collapsed_table_borders: true, + text_decorations: text_decorations.clone(), }); } } @@ -1646,6 +1718,7 @@ impl PositioningFragment { containing_block: &ContainingBlock, containing_block_info: &ContainingBlockInfo, stacking_context: &mut StackingContext, + text_decorations: &Arc<Vec<FragmentTextDecoration>>, ) { let rect = self .rect @@ -1660,6 +1733,7 @@ impl PositioningFragment { &new_containing_block_info, stacking_context, StackingContextBuildMode::SkipHoisted, + text_decorations, ); } } diff --git a/components/layout/flexbox/layout.rs b/components/layout/flexbox/layout.rs index 8fc5aa0778b..985f5f3cf65 100644 --- a/components/layout/flexbox/layout.rs +++ b/components/layout/flexbox/layout.rs @@ -417,9 +417,8 @@ struct DesiredFlexFractionAndGrowOrShrinkFactor { #[derive(Default)] struct FlexItemBoxInlineContentSizesInfo { outer_flex_base_size: Au, - content_min_main_size: Au, - content_max_main_size: Option<Au>, - pbm_auto_is_zero: FlexRelativeVec2<Au>, + outer_min_main_size: Au, + outer_max_main_size: Option<Au>, min_flex_factors: DesiredFlexFractionAndGrowOrShrinkFactor, max_flex_factors: DesiredFlexFractionAndGrowOrShrinkFactor, min_content_main_size_for_multiline_container: Au, @@ -583,9 +582,8 @@ impl FlexContainer { for FlexItemBoxInlineContentSizesInfo { outer_flex_base_size, - content_min_main_size, - content_max_main_size, - pbm_auto_is_zero, + outer_min_main_size, + outer_max_main_size, min_flex_factors, max_flex_factors, min_content_main_size_for_multiline_container, @@ -595,16 +593,13 @@ impl FlexContainer { // > 4. Add each item’s flex base size to the product of its flex grow factor (scaled flex shrink // > factor, if shrinking) and the chosen flex fraction, then clamp that result by the max main size // > floored by the min main size. - let outer_min_main_size = *content_min_main_size + pbm_auto_is_zero.main; - let outer_max_main_size = content_max_main_size.map(|v| v + pbm_auto_is_zero.main); - // > 5. The flex container’s max-content size is the largest sum (among all the lines) of the // > afore-calculated sizes of all items within a single line. container_max_content_size += (*outer_flex_base_size + Au::from_f32_px( max_flex_factors.flex_grow_or_shrink_factor * chosen_max_flex_fraction, )) - .clamp_between_extremums(outer_min_main_size, outer_max_main_size); + .clamp_between_extremums(*outer_min_main_size, *outer_max_main_size); // > The min-content main size of a single-line flex container is calculated // > identically to the max-content main size, except that the flex items’ @@ -621,7 +616,7 @@ impl FlexContainer { Au::from_f32_px( min_flex_factors.flex_grow_or_shrink_factor * chosen_min_flex_fraction, )) - .clamp_between_extremums(outer_min_main_size, outer_max_main_size); + .clamp_between_extremums(*outer_min_main_size, *outer_max_main_size); } else { container_min_content_size .max_assign(*min_content_main_size_for_multiline_container); @@ -2458,6 +2453,8 @@ impl FlexItemBox { }; let outer_flex_base_size = flex_base_size + pbm_auto_is_zero.main; + let outer_min_main_size = content_min_main_size + pbm_auto_is_zero.main; + let outer_max_main_size = content_max_main_size.map(|v| v + pbm_auto_is_zero.main); let max_flex_factors = self.desired_flex_factors_for_preferred_width( content_contribution_sizes.max_content, flex_base_size, @@ -2483,20 +2480,19 @@ impl FlexItemBox { content_contribution_sizes.min_content; let style_position = &self.style().get_position(); if style_position.flex_grow.is_zero() { - min_content_main_size_for_multiline_container.min_assign(flex_base_size); + min_content_main_size_for_multiline_container.min_assign(outer_flex_base_size); } if style_position.flex_shrink.is_zero() { - min_content_main_size_for_multiline_container.max_assign(flex_base_size); + min_content_main_size_for_multiline_container.max_assign(outer_flex_base_size); } min_content_main_size_for_multiline_container = min_content_main_size_for_multiline_container - .clamp_between_extremums(content_min_main_size, content_max_main_size); + .clamp_between_extremums(outer_min_main_size, outer_max_main_size); FlexItemBoxInlineContentSizesInfo { outer_flex_base_size, - content_min_main_size, - content_max_main_size, - pbm_auto_is_zero, + outer_min_main_size, + outer_max_main_size, min_flex_factors, max_flex_factors, min_content_main_size_for_multiline_container, diff --git a/components/layout/flexbox/mod.rs b/components/layout/flexbox/mod.rs index 96a311ee2b5..7f4a869a944 100644 --- a/components/layout/flexbox/mod.rs +++ b/components/layout/flexbox/mod.rs @@ -105,8 +105,7 @@ impl FlexContainer { contents: NonReplacedContents, propagated_data: PropagatedBoxTreeData, ) -> Self { - let mut builder = - ModernContainerBuilder::new(context, info, propagated_data.union(&info.style)); + let mut builder = ModernContainerBuilder::new(context, info, propagated_data); contents.traverse(context, info, &mut builder); let items = builder.finish(); diff --git a/components/layout/flow/construct.rs b/components/layout/flow/construct.rs index 334da8ae2b0..cc3fe0e6f44 100644 --- a/components/layout/flow/construct.rs +++ b/components/layout/flow/construct.rs @@ -199,7 +199,7 @@ impl<'dom, 'style> BlockContainerBuilder<'dom, 'style> { context, info, block_level_boxes: Vec::new(), - propagated_data: propagated_data.union(&info.style), + propagated_data, have_already_seen_first_line_for_text_indent: false, anonymous_box_info: None, anonymous_table_content: Vec::new(), @@ -228,7 +228,6 @@ impl<'dom, 'style> BlockContainerBuilder<'dom, 'style> { fn finish_ongoing_inline_formatting_context(&mut self) -> Option<InlineFormattingContext> { self.inline_formatting_context_builder.take()?.finish( self.context, - self.propagated_data, !self.have_already_seen_first_line_for_text_indent, self.info.is_single_line_text_input(), self.info.style.writing_mode.to_bidi_level(), @@ -280,16 +279,6 @@ impl<'dom, 'style> BlockContainerBuilder<'dom, 'style> { // creation of an inline table. It requires the parent to be an inline box. let inline_table = self.currently_processing_inline_box(); - // Text decorations are not propagated to atomic inline-level descendants. - // From https://drafts.csswg.org/css2/#lining-striking-props: - // > Note that text decorations are not propagated to floating and absolutely - // > positioned descendants, nor to the contents of atomic inline-level descendants - // > such as inline blocks and inline tables. - let propagated_data = match inline_table { - true => self.propagated_data.without_text_decorations(), - false => self.propagated_data, - }; - let contents: Vec<AnonymousTableContent<'dom>> = self.anonymous_table_content.drain(..).collect(); let last_text = match contents.last() { @@ -298,7 +287,7 @@ impl<'dom, 'style> BlockContainerBuilder<'dom, 'style> { }; let (table_info, ifc) = - Table::construct_anonymous(self.context, self.info, contents, propagated_data); + Table::construct_anonymous(self.context, self.info, contents, self.propagated_data); if inline_table { self.ensure_inline_formatting_context_builder() @@ -315,7 +304,7 @@ impl<'dom, 'style> BlockContainerBuilder<'dom, 'style> { info: table_info, box_slot: BoxSlot::dummy(), kind: BlockLevelCreator::AnonymousTable { table_block }, - propagated_data, + propagated_data: self.propagated_data, }); } @@ -464,7 +453,7 @@ impl<'dom> BlockContainerBuilder<'dom, '_> { contents, list_item_style, }, - propagated_data: self.propagated_data.without_text_decorations(), + propagated_data: self.propagated_data, }); } @@ -480,15 +469,14 @@ impl<'dom> BlockContainerBuilder<'dom, '_> { else { // If this inline element is an atomic, handle it and return. let context = self.context; - let propagaged_data = self.propagated_data.without_text_decorations(); + let propagated_data = self.propagated_data; let atomic = self.ensure_inline_formatting_context_builder().push_atomic( IndependentFormattingContext::construct( context, info, display_inside, contents, - // Text decorations are not propagated to atomic inline-level descendants. - propagaged_data, + propagated_data, ), ); box_slot.set(LayoutBox::InlineLevel(vec![atomic])); @@ -550,7 +538,6 @@ impl<'dom> BlockContainerBuilder<'dom, '_> { .and_then(|builder| { builder.split_around_block_and_finish( self.context, - self.propagated_data, !self.have_already_seen_first_line_for_text_indent, self.info.style.writing_mode.to_bidi_level(), ) @@ -631,7 +618,7 @@ impl<'dom> BlockContainerBuilder<'dom, '_> { info: info.clone(), box_slot, kind, - propagated_data: self.propagated_data.without_text_decorations(), + propagated_data: self.propagated_data, }); } @@ -664,7 +651,7 @@ impl<'dom> BlockContainerBuilder<'dom, '_> { info: info.clone(), box_slot, kind, - propagated_data: self.propagated_data.without_text_decorations(), + propagated_data: self.propagated_data, }); } @@ -754,7 +741,7 @@ impl BlockLevelJob<'_> { context, info, contents, - self.propagated_data.without_text_decorations(), + self.propagated_data, false, /* is_list_item */ ); ArcRefCell::new(BlockLevelBox::OutsideMarker(OutsideMarker { diff --git a/components/layout/flow/float.rs b/components/layout/flow/float.rs index f1ae2b4459a..bb6c7ca8983 100644 --- a/components/layout/flow/float.rs +++ b/components/layout/flow/float.rs @@ -897,8 +897,7 @@ impl FloatBox { info, display_inside, contents, - // Text decorations are not propagated to any out-of-flow descendants - propagated_data.without_text_decorations(), + propagated_data, ), } } diff --git a/components/layout/flow/inline/construct.rs b/components/layout/flow/inline/construct.rs index 600da9b721a..07a2e914835 100644 --- a/components/layout/flow/inline/construct.rs +++ b/components/layout/flow/inline/construct.rs @@ -16,7 +16,6 @@ use super::{ InlineBox, InlineBoxIdentifier, InlineBoxes, InlineFormattingContext, InlineItem, SharedInlineStyles, }; -use crate::PropagatedBoxTreeData; use crate::cell::ArcRefCell; use crate::context::LayoutContext; use crate::dom_traversal::NodeAndStyleInfo; @@ -344,7 +343,6 @@ impl InlineFormattingContextBuilder { pub(crate) fn split_around_block_and_finish( &mut self, layout_context: &LayoutContext, - propagated_data: PropagatedBoxTreeData, has_first_formatted_line: bool, default_bidi_level: Level, ) -> Option<InlineFormattingContext> { @@ -386,7 +384,6 @@ impl InlineFormattingContextBuilder { inline_builder_from_before_split.finish( layout_context, - propagated_data, has_first_formatted_line, /* is_single_line_text_input = */ false, default_bidi_level, @@ -397,7 +394,6 @@ impl InlineFormattingContextBuilder { pub(crate) fn finish( self, layout_context: &LayoutContext, - propagated_data: PropagatedBoxTreeData, has_first_formatted_line: bool, is_single_line_text_input: bool, default_bidi_level: Level, @@ -410,7 +406,6 @@ impl InlineFormattingContextBuilder { Some(InlineFormattingContext::new_with_builder( self, layout_context, - propagated_data, has_first_formatted_line, is_single_line_text_input, default_bidi_level, diff --git a/components/layout/flow/inline/inline_box.rs b/components/layout/flow/inline/inline_box.rs index b547f3b5935..a9642d3b222 100644 --- a/components/layout/flow/inline/inline_box.rs +++ b/components/layout/flow/inline/inline_box.rs @@ -256,13 +256,7 @@ impl InlineBoxContainerState { } Self { - base: InlineContainerState::new( - style, - flags, - Some(parent_container), - parent_container.text_decoration_line, - font_metrics, - ), + base: InlineContainerState::new(style, flags, Some(parent_container), font_metrics), identifier: inline_box.identifier, base_fragment_info: inline_box.base.base_fragment_info, pbm, diff --git a/components/layout/flow/inline/line.rs b/components/layout/flow/inline/line.rs index 3b92078d67d..14a1531883f 100644 --- a/components/layout/flow/inline/line.rs +++ b/components/layout/flow/inline/line.rs @@ -15,7 +15,6 @@ use style::values::generics::box_::{GenericVerticalAlign, VerticalAlignKeyword}; use style::values::generics::font::LineHeight; use style::values::specified::align::AlignFlags; use style::values::specified::box_::DisplayOutside; -use style::values::specified::text::TextDecorationLine; use unicode_bidi::{BidiInfo, Level}; use webrender_api::FontInstanceKey; @@ -572,7 +571,6 @@ impl LineItemLayout<'_, '_> { font_metrics: text_item.font_metrics, font_key: text_item.font_key, glyphs: text_item.text, - text_decoration_line: text_item.text_decoration_line, justification_adjustment: self.justification_adjustment, selection_range: text_item.selection_range, })), @@ -765,7 +763,6 @@ pub(super) struct TextRunLineItem { pub text: Vec<std::sync::Arc<GlyphStore>>, pub font_metrics: FontMetrics, pub font_key: FontInstanceKey, - pub text_decoration_line: TextDecorationLine, /// The BiDi level of this [`TextRunLineItem`] to enable reordering. pub bidi_level: Level, pub selection_range: Option<Range<ByteIndex>>, diff --git a/components/layout/flow/inline/mod.rs b/components/layout/flow/inline/mod.rs index 74d42ca6fb4..6fd4a51a526 100644 --- a/components/layout/flow/inline/mod.rs +++ b/components/layout/flow/inline/mod.rs @@ -103,7 +103,7 @@ use style::properties::style_structs::InheritedText; use style::values::generics::box_::VerticalAlignKeyword; use style::values::generics::font::LineHeight; use style::values::specified::box_::BaselineSource; -use style::values::specified::text::{TextAlignKeyword, TextDecorationLine}; +use style::values::specified::text::TextAlignKeyword; use style::values::specified::{TextAlignLast, TextJustify}; use text_run::{ TextRun, XI_LINE_BREAKING_CLASS_GL, XI_LINE_BREAKING_CLASS_WJ, XI_LINE_BREAKING_CLASS_ZWJ, @@ -134,7 +134,7 @@ use crate::geom::{LogicalRect, LogicalVec2, ToLogical}; use crate::positioned::{AbsolutelyPositionedBox, PositioningContext}; use crate::sizing::{ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult}; use crate::style_ext::{ComputedValuesExt, PaddingBorderMargin}; -use crate::{ConstraintSpace, ContainingBlock, PropagatedBoxTreeData, SharedStyle}; +use crate::{ConstraintSpace, ContainingBlock, SharedStyle}; // From gfxFontConstants.h in Firefox. static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20; @@ -163,8 +163,6 @@ pub(crate) struct InlineFormattingContext { /// share styles with all [`TextRun`] children. pub(super) shared_inline_styles: SharedInlineStyles, - pub(super) text_decoration_line: TextDecorationLine, - /// Whether this IFC contains the 1st formatted line of an element: /// <https://www.w3.org/TR/css-pseudo-4/#first-formatted-line>. pub(super) has_first_formatted_line: bool, @@ -628,12 +626,6 @@ pub(super) struct InlineContainerState { /// this inline box on the current line OR any previous line. has_content: RefCell<bool>, - /// Indicates whether this nesting level have text decorations in effect. - /// From <https://drafts.csswg.org/css-text-decor/#line-decoration> - // "When specified on or propagated to a block container that establishes - // an IFC..." - text_decoration_line: TextDecorationLine, - /// The block size contribution of this container's default font ie the size of the /// "strut." Whether this is integrated into the [`Self::nested_strut_block_sizes`] /// depends on the line-height quirk described in @@ -1461,7 +1453,6 @@ impl InlineFormattingContextLayout<'_> { inline_styles: text_run.inline_styles.clone(), font_metrics, font_key: ifc_font_info.key, - text_decoration_line: self.current_inline_container_state().text_decoration_line, bidi_level, selection_range, }, @@ -1655,7 +1646,6 @@ impl InlineFormattingContext { pub(super) fn new_with_builder( builder: InlineFormattingContextBuilder, layout_context: &LayoutContext, - propagated_data: PropagatedBoxTreeData, has_first_formatted_line: bool, is_single_line_text_input: bool, starting_bidi_level: Level, @@ -1711,7 +1701,6 @@ impl InlineFormattingContext { .last() .expect("Should have at least one SharedInlineStyle for the root of an IFC") .clone(), - text_decoration_line: propagated_data.text_decoration, has_first_formatted_line, contains_floats: builder.contains_floats, is_single_line_text_input, @@ -1781,7 +1770,6 @@ impl InlineFormattingContext { style.to_arc(), inline_container_state_flags, None, /* parent_container */ - self.text_decoration_line, default_font_metrics.as_ref(), ), inline_box_state_stack: Vec::new(), @@ -1879,10 +1867,8 @@ impl InlineContainerState { style: Arc<ComputedValues>, flags: InlineContainerStateFlags, parent_container: Option<&InlineContainerState>, - parent_text_decoration_line: TextDecorationLine, font_metrics: Option<&FontMetrics>, ) -> Self { - let text_decoration_line = parent_text_decoration_line | style.clone_text_decoration_line(); let font_metrics = font_metrics.cloned().unwrap_or_else(FontMetrics::empty); let line_height = line_height( &style, @@ -1919,7 +1905,6 @@ impl InlineContainerState { style, flags, has_content: RefCell::new(false), - text_decoration_line, nested_strut_block_sizes: nested_block_sizes, strut_block_sizes, baseline_offset, diff --git a/components/layout/flow/root.rs b/components/layout/flow/root.rs index a37db54065d..8ad3671032e 100644 --- a/components/layout/flow/root.rs +++ b/components/layout/flow/root.rs @@ -314,7 +314,7 @@ fn construct_for_root_element( let contents = ReplacedContents::for_element(root_element, context) .map_or_else(|| NonReplacedContents::OfElement.into(), Contents::Replaced); - let propagated_data = PropagatedBoxTreeData::default().union(&info.style); + let propagated_data = PropagatedBoxTreeData::default(); let root_box = if box_style.position.is_absolutely_positioned() { BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(ArcRefCell::new( AbsolutelyPositionedBox::construct(context, &info, display_inside, contents), diff --git a/components/layout/fragment_tree/fragment.rs b/components/layout/fragment_tree/fragment.rs index 1ebc7b3c989..c81fd59e36b 100644 --- a/components/layout/fragment_tree/fragment.rs +++ b/components/layout/fragment_tree/fragment.rs @@ -14,7 +14,6 @@ use range::Range as ServoRange; use servo_arc::Arc as ServoArc; use style::Zero; use style::properties::ComputedValues; -use style::values::specified::text::TextDecorationLine; use webrender_api::{FontInstanceKey, ImageKey}; use super::{ @@ -72,9 +71,6 @@ pub(crate) struct TextFragment { #[conditional_malloc_size_of] pub glyphs: Vec<Arc<GlyphStore>>, - /// A flag that represents the _used_ value of the text-decoration property. - pub text_decoration_line: TextDecorationLine, - /// Extra space to add for each justification opportunity. pub justification_adjustment: Au, pub selection_range: Option<ServoRange<ByteIndex>>, diff --git a/components/layout/layout_impl.rs b/components/layout/layout_impl.rs index b490b4a0506..8162ed1dd0b 100644 --- a/components/layout/layout_impl.rs +++ b/components/layout/layout_impl.rs @@ -773,7 +773,7 @@ impl LayoutThread { let root_node = root_element.as_node(); let damage = compute_damage_and_repair_style(layout_context.shared_context(), root_node); - if damage == RestyleDamage::REPAINT { + if damage.is_empty() || damage == RestyleDamage::REPAINT { layout_context.style_context.stylist.rule_tree().maybe_gc(); return false; } diff --git a/components/layout/lib.rs b/components/layout/lib.rs index cd992387277..ea254723dcb 100644 --- a/components/layout/lib.rs +++ b/components/layout/lib.rs @@ -41,7 +41,6 @@ use malloc_size_of_derive::MallocSizeOf; use servo_arc::Arc as ServoArc; use style::logical_geometry::WritingMode; use style::properties::ComputedValues; -use style::values::computed::TextDecorationLine; use crate::geom::{LogicalVec2, SizeConstraint}; use crate::style_ext::AspectRatio; @@ -163,39 +162,20 @@ impl<'a> From<&'_ DefiniteContainingBlock<'a>> for ContainingBlock<'a> { /// propoagation, but only during `BoxTree` construction. #[derive(Clone, Copy, Debug)] struct PropagatedBoxTreeData { - text_decoration: TextDecorationLine, allow_percentage_column_in_tables: bool, } impl Default for PropagatedBoxTreeData { fn default() -> Self { Self { - text_decoration: Default::default(), allow_percentage_column_in_tables: true, } } } impl PropagatedBoxTreeData { - pub(crate) fn union(&self, style: &ComputedValues) -> Self { - Self { - // FIXME(#31736): This is only taking into account the line style and not the decoration - // color. This should collect information about both so that they can be rendered properly. - text_decoration: self.text_decoration | style.clone_text_decoration_line(), - allow_percentage_column_in_tables: self.allow_percentage_column_in_tables, - } - } - - pub(crate) fn without_text_decorations(&self) -> Self { - Self { - text_decoration: TextDecorationLine::NONE, - allow_percentage_column_in_tables: self.allow_percentage_column_in_tables, - } - } - fn disallowing_percentage_table_columns(&self) -> PropagatedBoxTreeData { Self { - text_decoration: self.text_decoration, allow_percentage_column_in_tables: false, } } diff --git a/components/layout/table/construct.rs b/components/layout/table/construct.rs index 0c238073df2..0b22ea1c13a 100644 --- a/components/layout/table/construct.rs +++ b/components/layout/table/construct.rs @@ -81,12 +81,7 @@ impl Table { contents: NonReplacedContents, propagated_data: PropagatedBoxTreeData, ) -> Self { - let mut traversal = TableBuilderTraversal::new( - context, - info, - grid_style, - propagated_data.union(&info.style), - ); + let mut traversal = TableBuilderTraversal::new(context, info, grid_style, propagated_data); contents.traverse(context, info, &mut traversal); traversal.finish() } @@ -771,9 +766,6 @@ impl<'dom> TraversalHandler<'dom> for TableBuilderTraversal<'_, 'dom> { }); self.builder.table.row_groups.push(row_group.clone()); - let previous_propagated_data = self.current_propagated_data; - self.current_propagated_data = self.current_propagated_data.union(&info.style); - let new_row_group_index = self.builder.table.row_groups.len() - 1; self.current_row_group_index = Some(new_row_group_index); @@ -785,7 +777,6 @@ impl<'dom> TraversalHandler<'dom> for TableBuilderTraversal<'_, 'dom> { self.finish_anonymous_row_if_needed(); self.current_row_group_index = None; - self.current_propagated_data = previous_propagated_data; self.builder.incoming_rowspans.clear(); box_slot.set(LayoutBox::TableLevelBox(TableLevelBox::TrackGroup( @@ -936,7 +927,7 @@ impl<'style, 'builder, 'dom, 'a> TableRowBuilder<'style, 'builder, 'dom, 'a> { table_traversal, info, current_anonymous_cell_content: Vec::new(), - propagated_data: propagated_data.union(&info.style), + propagated_data, } } diff --git a/components/layout/taffy/mod.rs b/components/layout/taffy/mod.rs index 2bc7a598d08..05fc09c5511 100644 --- a/components/layout/taffy/mod.rs +++ b/components/layout/taffy/mod.rs @@ -36,8 +36,7 @@ impl TaffyContainer { contents: NonReplacedContents, propagated_data: PropagatedBoxTreeData, ) -> Self { - let mut builder = - ModernContainerBuilder::new(context, info, propagated_data.union(&info.style)); + let mut builder = ModernContainerBuilder::new(context, info, propagated_data); contents.traverse(context, info, &mut builder); let items = builder.finish(); diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs index d05deb24bfa..faf25dc170d 100644 --- a/components/layout/traversal.rs +++ b/components/layout/traversal.rs @@ -105,7 +105,9 @@ pub(crate) fn compute_damage_and_repair_style_inner( parent_restyle_damage: RestyleDamage, ) -> RestyleDamage { let original_damage; - let damage = { + let damage; + + { let mut element_data = node .style_data() .expect("Should not run `compute_damage` before styling.") @@ -113,14 +115,14 @@ pub(crate) fn compute_damage_and_repair_style_inner( .borrow_mut(); original_damage = std::mem::take(&mut element_data.damage); + damage = original_damage | parent_restyle_damage; + if let Some(ref style) = element_data.styles.primary { if style.get_box().display == Display::None { - return parent_restyle_damage; + return damage; } } - - original_damage | parent_restyle_damage - }; + } let mut propagated_damage = damage; for child in iter_child_nodes(node) { diff --git a/components/net/image_cache.rs b/components/net/image_cache.rs index 46a2a4ea111..8276baa07e7 100644 --- a/components/net/image_cache.rs +++ b/components/net/image_cache.rs @@ -66,10 +66,10 @@ fn set_webrender_image_key(compositor_api: &CrossProcessCompositorApi, image: &m return; } let mut bytes = Vec::new(); - let frame_bytes = image.bytes(); + let frame_bytes = image.first_frame().bytes; let is_opaque = match image.format { PixelFormat::BGRA8 => { - bytes.extend_from_slice(&frame_bytes); + bytes.extend_from_slice(frame_bytes); pixels::rgba8_premultiply_inplace(bytes.as_mut_slice()) }, PixelFormat::RGB8 => { diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs index 8e9cc8236f3..94592d19bed 100644 --- a/components/net/resource_thread.rs +++ b/components/net/resource_thread.rs @@ -97,14 +97,15 @@ pub fn new_resource_threads( let (public_core, private_core) = new_core_resource_thread( devtools_sender, time_profiler_chan, - mem_profiler_chan, + mem_profiler_chan.clone(), embedder_proxy, config_dir.clone(), ca_certificates, ignore_certificate_errors, protocols, ); - let storage: IpcSender<StorageThreadMsg> = StorageThreadFactory::new(config_dir); + let storage: IpcSender<StorageThreadMsg> = + StorageThreadFactory::new(config_dir, mem_profiler_chan); ( ResourceThreads::new(public_core, storage.clone()), ResourceThreads::new(private_core, storage), diff --git a/components/net/storage_thread.rs b/components/net/storage_thread.rs index dd058f17170..899214a450c 100644 --- a/components/net/storage_thread.rs +++ b/components/net/storage_thread.rs @@ -8,7 +8,12 @@ use std::path::PathBuf; use std::thread; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; +use malloc_size_of::MallocSizeOf; use net_traits::storage_thread::{StorageThreadMsg, StorageType}; +use profile_traits::mem::{ + ProcessReports, ProfilerChan as MemProfilerChan, Report, ReportKind, perform_memory_report, +}; +use profile_traits::path; use servo_url::ServoUrl; use crate::resource_thread; @@ -16,17 +21,26 @@ use crate::resource_thread; const QUOTA_SIZE_LIMIT: usize = 5 * 1024 * 1024; pub trait StorageThreadFactory { - fn new(config_dir: Option<PathBuf>) -> Self; + fn new(config_dir: Option<PathBuf>, mem_profiler_chan: MemProfilerChan) -> Self; } impl StorageThreadFactory for IpcSender<StorageThreadMsg> { /// Create a storage thread - fn new(config_dir: Option<PathBuf>) -> IpcSender<StorageThreadMsg> { + fn new( + config_dir: Option<PathBuf>, + mem_profiler_chan: MemProfilerChan, + ) -> IpcSender<StorageThreadMsg> { let (chan, port) = ipc::channel().unwrap(); + let chan2 = chan.clone(); thread::Builder::new() .name("StorageManager".to_owned()) .spawn(move || { - StorageManager::new(port, config_dir).start(); + mem_profiler_chan.run_with_memory_reporting( + || StorageManager::new(port, config_dir).start(), + String::from("storage-reporter"), + chan2, + StorageThreadMsg::CollectMemoryReport, + ); }) .expect("Thread spawning failed"); chan @@ -83,6 +97,10 @@ impl StorageManager { self.clear(sender, url, storage_type); self.save_state() }, + StorageThreadMsg::CollectMemoryReport(sender) => { + let reports = self.collect_memory_reports(); + sender.send(ProcessReports::new(reports)); + }, StorageThreadMsg::Exit(sender) => { // Nothing to do since we save localstorage set eagerly. let _ = sender.send(()); @@ -92,6 +110,24 @@ impl StorageManager { } } + fn collect_memory_reports(&self) -> Vec<Report> { + let mut reports = vec![]; + perform_memory_report(|ops| { + reports.push(Report { + path: path!["storage", "local"], + kind: ReportKind::ExplicitJemallocHeapSize, + size: self.local_data.size_of(ops), + }); + + reports.push(Report { + path: path!["storage", "session"], + kind: ReportKind::ExplicitJemallocHeapSize, + size: self.session_data.size_of(ops), + }); + }); + reports + } + fn save_state(&self) { if let Some(ref config_dir) = self.config_dir { resource_thread::write_json_to_file(&self.local_data, config_dir, "local_data.json"); diff --git a/components/pixels/lib.rs b/components/pixels/lib.rs index c1f57875c6d..35b32c92414 100644 --- a/components/pixels/lib.rs +++ b/components/pixels/lib.rs @@ -4,6 +4,7 @@ use std::borrow::Cow; use std::io::Cursor; +use std::ops::Range; use std::time::Duration; use std::{cmp, fmt, vec}; @@ -126,13 +127,24 @@ pub struct Image { pub format: PixelFormat, pub id: Option<ImageKey>, pub cors_status: CorsStatus, + pub bytes: IpcSharedMemory, pub frames: Vec<ImageFrame>, } #[derive(Clone, Deserialize, MallocSizeOf, Serialize)] pub struct ImageFrame { pub delay: Option<Duration>, - pub bytes: IpcSharedMemory, + /// References a range of the `bytes` field from the image that this + /// frame belongs to. + pub byte_range: Range<usize>, + pub width: u32, + pub height: u32, +} + +/// A non-owning reference to the data of an [ImageFrame] +pub struct ImageFrameView<'a> { + pub delay: Option<Duration>, + pub bytes: &'a [u8], pub width: u32, pub height: u32, } @@ -142,12 +154,19 @@ impl Image { self.frames.len() > 1 } - pub fn bytes(&self) -> IpcSharedMemory { - self.frames - .first() - .expect("Should have at least one frame") - .bytes - .clone() + pub fn frames(&self) -> impl Iterator<Item = ImageFrameView> { + self.frames.iter().map(|frame| ImageFrameView { + delay: frame.delay, + bytes: self.bytes.get(frame.byte_range.clone()).unwrap(), + width: frame.width, + height: frame.height, + }) + } + + pub fn first_frame(&self) -> ImageFrameView { + self.frames() + .next() + .expect("All images should have at least one frame") } } @@ -189,7 +208,7 @@ pub fn load_from_memory(buffer: &[u8], cors_status: CorsStatus) -> Option<Image> rgba8_byte_swap_colors_inplace(&mut rgba); let frame = ImageFrame { delay: None, - bytes: IpcSharedMemory::from_bytes(&rgba), + byte_range: 0..rgba.len(), width: rgba.width(), height: rgba.height(), }; @@ -198,6 +217,7 @@ pub fn load_from_memory(buffer: &[u8], cors_status: CorsStatus) -> Option<Image> height: rgba.height(), format: PixelFormat::BGRA8, frames: vec![frame], + bytes: IpcSharedMemory::from_bytes(&rgba), id: None, cors_status, }) @@ -364,45 +384,61 @@ fn decode_gif(buffer: &[u8], cors_status: CorsStatus) -> Option<Image> { // This uses `map_while`, because the first non-decodable frame seems to // send the frame iterator into an infinite loop. See // <https://github.com/image-rs/image/issues/2442>. + let mut frame_data = vec![]; + let mut total_number_of_bytes = 0; let frames: Vec<ImageFrame> = decoded_gif .into_frames() .map_while(|decoded_frame| { - let mut frame = match decoded_frame { + let mut gif_frame = match decoded_frame { Ok(decoded_frame) => decoded_frame, Err(error) => { debug!("decode GIF frame error: {error}"); return None; }, }; - rgba8_byte_swap_colors_inplace(frame.buffer_mut()); + rgba8_byte_swap_colors_inplace(gif_frame.buffer_mut()); + let frame_start = total_number_of_bytes; + total_number_of_bytes += gif_frame.buffer().len(); + + // The image size should be at least as large as the largest frame. + let frame_width = gif_frame.buffer().width(); + let frame_height = gif_frame.buffer().height(); + width = cmp::max(width, frame_width); + height = cmp::max(height, frame_height); let frame = ImageFrame { - bytes: IpcSharedMemory::from_bytes(frame.buffer()), - delay: Some(Duration::from(frame.delay())), - width: frame.buffer().width(), - height: frame.buffer().height(), + byte_range: frame_start..total_number_of_bytes, + delay: Some(Duration::from(gif_frame.delay())), + width: frame_width, + height: frame_height, }; - // The image size should be at least as large as the largest frame. - width = cmp::max(width, frame.width); - height = cmp::max(height, frame.height); + frame_data.push(gif_frame); + Some(frame) }) .collect(); if frames.is_empty() { debug!("Animated Image decoding error"); - None - } else { - Some(Image { - width, - height, - cors_status, - frames, - id: None, - format: PixelFormat::BGRA8, - }) + return None; } + + // Coalesce the frame data into one single shared memory region. + let mut bytes = Vec::with_capacity(total_number_of_bytes); + for frame in frame_data { + bytes.extend_from_slice(frame.buffer()); + } + + Some(Image { + width, + height, + cors_status, + frames, + id: None, + format: PixelFormat::BGRA8, + bytes: IpcSharedMemory::from_bytes(&bytes), + }) } #[cfg(test)] diff --git a/components/script/canvas_state.rs b/components/script/canvas_state.rs index dabe6a5728b..d4840a5b470 100644 --- a/components/script/canvas_state.rs +++ b/components/script/canvas_state.rs @@ -17,7 +17,7 @@ use cssparser::color::clamp_unit_f32; use cssparser::{Parser, ParserInput}; use euclid::default::{Point2D, Rect, Size2D, Transform2D}; use euclid::vec2; -use ipc_channel::ipc::{self, IpcSender}; +use ipc_channel::ipc::{self, IpcSender, IpcSharedMemory}; use net_traits::image_cache::{ImageCache, ImageResponse}; use net_traits::request::CorsSettings; use pixels::PixelFormat; @@ -350,7 +350,7 @@ impl CanvasState { size.cast(), format, alpha_mode, - img.bytes(), + IpcSharedMemory::from_bytes(img.first_frame().bytes), )) } diff --git a/components/script/dom/headers.rs b/components/script/dom/headers.rs index 0e8dcf92ccd..f195992faa5 100644 --- a/components/script/dom/headers.rs +++ b/components/script/dom/headers.rs @@ -13,6 +13,7 @@ use net_traits::fetch::headers::{ is_forbidden_method, }; use net_traits::request::is_cors_safelisted_request_header; +use net_traits::trim_http_whitespace; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods}; @@ -33,7 +34,7 @@ pub(crate) struct Headers { header_list: DomRefCell<HyperHeaders>, } -// https://fetch.spec.whatwg.org/#concept-headers-guard +/// <https://fetch.spec.whatwg.org/#concept-headers-guard> #[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] pub(crate) enum Guard { Immutable, @@ -66,7 +67,7 @@ impl Headers { } impl HeadersMethods<crate::DomTypeHolder> for Headers { - // https://fetch.spec.whatwg.org/#dom-headers + /// <https://fetch.spec.whatwg.org/#dom-headers> fn Constructor( global: &GlobalScope, proto: Option<HandleObject>, @@ -78,47 +79,41 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers { Ok(dom_headers_new) } - // https://fetch.spec.whatwg.org/#concept-headers-append + /// <https://fetch.spec.whatwg.org/#concept-headers-append> fn Append(&self, name: ByteString, value: ByteString) -> ErrorResult { - // Step 1 - let value = normalize_value(value); + // 1. Normalize value. + let value = trim_http_whitespace(&value); - // Step 2 - // https://fetch.spec.whatwg.org/#headers-validate - let (mut valid_name, valid_value) = validate_name_and_value(name, value)?; + // 2. If validating (name, value) for headers returns false, then return. + let Some((mut valid_name, valid_value)) = + self.validate_name_and_value(name, ByteString::new(value.into()))? + else { + return Ok(()); + }; valid_name = valid_name.to_lowercase(); - if self.guard.get() == Guard::Immutable { - return Err(Error::Type("Guard is immutable".to_string())); - } - if self.guard.get() == Guard::Request && - is_forbidden_request_header(&valid_name, &valid_value) - { - return Ok(()); - } - if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) { - return Ok(()); - } - - // Step 3 + // 3. If headers’s guard is "request-no-cors": if self.guard.get() == Guard::RequestNoCors { + // 3.1. Let temporaryValue be the result of getting name from headers’s header list. let tmp_value = if let Some(mut value) = get_value_from_header_list(&valid_name, &self.header_list.borrow()) { + // 3.3. Otherwise, set temporaryValue to temporaryValue, followed by 0x2C 0x20, followed by value. value.extend(b", "); - value.extend(valid_value.clone()); + value.extend(valid_value.to_vec()); value } else { - valid_value.clone() + // 3.2. If temporaryValue is null, then set temporaryValue to value. + valid_value.to_vec() }; - + // 3.4. If (name, temporaryValue) is not a no-CORS-safelisted request-header, then return. if !is_cors_safelisted_request_header(&valid_name, &tmp_value) { return Ok(()); } } - // Step 4 + // 4. Append (name, value) to headers’s header list. match HeaderValue::from_bytes(&valid_value) { Ok(value) => { self.header_list @@ -134,7 +129,7 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers { }, }; - // Step 5 + // 5. If headers’s guard is "request-no-cors", then remove privileged no-CORS request-headers from headers. if self.guard.get() == Guard::RequestNoCors { self.remove_privileged_no_cors_request_headers(); } @@ -142,50 +137,53 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers { Ok(()) } - // https://fetch.spec.whatwg.org/#dom-headers-delete + /// <https://fetch.spec.whatwg.org/#dom-headers-delete> fn Delete(&self, name: ByteString) -> ErrorResult { - // Step 1 - let (mut valid_name, valid_value) = validate_name_and_value(name, ByteString::new(vec![]))?; + // Step 1 If validating (name, ``) for this returns false, then return. + let name_and_value = self.validate_name_and_value(name, ByteString::new(vec![]))?; + let Some((mut valid_name, _valid_value)) = name_and_value else { + return Ok(()); + }; valid_name = valid_name.to_lowercase(); - // Step 2 - if self.guard.get() == Guard::Immutable { - return Err(Error::Type("Guard is immutable".to_string())); - } - // Step 3 - if self.guard.get() == Guard::Request && - is_forbidden_request_header(&valid_name, &valid_value) - { - return Ok(()); - } - // Step 4 + // Step 2 If this’s guard is "request-no-cors", name is not a no-CORS-safelisted request-header name, + // and name is not a privileged no-CORS request-header name, then return. if self.guard.get() == Guard::RequestNoCors && !is_cors_safelisted_request_header(&valid_name, &b"invalid".to_vec()) { return Ok(()); } - // Step 5 - if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) { - return Ok(()); + + // 3. If this’s header list does not contain name, then return. + // 4. Delete name from this’s header list. + self.header_list.borrow_mut().remove(valid_name); + + // 5. If this’s guard is "request-no-cors", then remove privileged no-CORS request-headers from this. + if self.guard.get() == Guard::RequestNoCors { + self.remove_privileged_no_cors_request_headers(); } - // Step 6 - self.header_list.borrow_mut().remove(&valid_name); + Ok(()) } - // https://fetch.spec.whatwg.org/#dom-headers-get + /// <https://fetch.spec.whatwg.org/#dom-headers-get> fn Get(&self, name: ByteString) -> Fallible<Option<ByteString>> { - // Step 1 + // 1. If name is not a header name, then throw a TypeError. let valid_name = validate_name(name)?; + + // 2. Return the result of getting name from this’s header list. Ok( get_value_from_header_list(&valid_name, &self.header_list.borrow()) .map(ByteString::new), ) } - // https://fetch.spec.whatwg.org/#dom-headers-getsetcookie + /// <https://fetch.spec.whatwg.org/#dom-headers-getsetcookie> fn GetSetCookie(&self) -> Vec<ByteString> { + // 1. If this’s header list does not contain `Set-Cookie`, then return « ». + // 2. Return the values of all headers in this’s header list whose name is a + // byte-case-insensitive match for `Set-Cookie`, in order. self.header_list .borrow() .get_all("set-cookie") @@ -194,42 +192,36 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers { .collect() } - // https://fetch.spec.whatwg.org/#dom-headers-has + /// <https://fetch.spec.whatwg.org/#dom-headers-has> fn Has(&self, name: ByteString) -> Fallible<bool> { - // Step 1 + // 1. If name is not a header name, then throw a TypeError. let valid_name = validate_name(name)?; - // Step 2 + // 2. Return true if this’s header list contains name; otherwise false. Ok(self.header_list.borrow_mut().get(&valid_name).is_some()) } - // https://fetch.spec.whatwg.org/#dom-headers-set + /// <https://fetch.spec.whatwg.org/#dom-headers-set> fn Set(&self, name: ByteString, value: ByteString) -> Fallible<()> { - // Step 1 - let value = normalize_value(value); - // Step 2 - let (mut valid_name, valid_value) = validate_name_and_value(name, value)?; - valid_name = valid_name.to_lowercase(); - // Step 3 - if self.guard.get() == Guard::Immutable { - return Err(Error::Type("Guard is immutable".to_string())); - } - // Step 4 - if self.guard.get() == Guard::Request && - is_forbidden_request_header(&valid_name, &valid_value) - { + // 1. Normalize value + let value = trim_http_whitespace(&value); + + // 2. If validating (name, value) for this returns false, then return. + let Some((mut valid_name, valid_value)) = + self.validate_name_and_value(name, ByteString::new(value.into()))? + else { return Ok(()); - } - // Step 5 + }; + valid_name = valid_name.to_lowercase(); + + // 3. If this’s guard is "request-no-cors" and (name, value) is not a + // no-CORS-safelisted request-header, then return. if self.guard.get() == Guard::RequestNoCors && - !is_cors_safelisted_request_header(&valid_name, &valid_value) + !is_cors_safelisted_request_header(&valid_name, &valid_value.to_vec()) { return Ok(()); } - // Step 6 - if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) { - return Ok(()); - } - // Step 7 + + // 4. Set (name, value) in this’s header list. // https://fetch.spec.whatwg.org/#concept-header-list-set match HeaderValue::from_bytes(&valid_value) { Ok(value) => { @@ -245,6 +237,12 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers { ); }, }; + + // 5. If this’s guard is "request-no-cors", then remove privileged no-CORS request-headers from this. + if self.guard.get() == Guard::RequestNoCors { + self.remove_privileged_no_cors_request_headers(); + } + Ok(()) } } @@ -260,7 +258,7 @@ impl Headers { Ok(()) } - // https://fetch.spec.whatwg.org/#concept-headers-fill + /// <https://fetch.spec.whatwg.org/#concept-headers-fill> pub(crate) fn fill(&self, filler: Option<HeadersInit>) -> ErrorResult { match filler { Some(HeadersInit::ByteStringSequenceSequence(v)) => { @@ -316,12 +314,12 @@ impl Headers { self.header_list.borrow_mut().clone() } - // https://fetch.spec.whatwg.org/#concept-header-extract-mime-type + /// <https://fetch.spec.whatwg.org/#concept-header-extract-mime-type> pub(crate) fn extract_mime_type(&self) -> Vec<u8> { extract_mime_type(&self.header_list.borrow()).unwrap_or_default() } - // https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine + /// <https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine> pub(crate) fn sort_and_combine(&self) -> Vec<(String, Vec<u8>)> { let borrowed_header_list = self.header_list.borrow(); let mut header_vec = vec![]; @@ -341,11 +339,38 @@ impl Headers { header_vec } - // https://fetch.spec.whatwg.org/#ref-for-privileged-no-cors-request-header-name + /// <https://fetch.spec.whatwg.org/#ref-for-privileged-no-cors-request-header-name> pub(crate) fn remove_privileged_no_cors_request_headers(&self) { - // https://fetch.spec.whatwg.org/#privileged-no-cors-request-header-name + // <https://fetch.spec.whatwg.org/#privileged-no-cors-request-header-name> self.header_list.borrow_mut().remove("range"); } + + /// <https://fetch.spec.whatwg.org/#headers-validate> + pub(crate) fn validate_name_and_value( + &self, + name: ByteString, + value: ByteString, + ) -> Fallible<Option<(String, ByteString)>> { + // 1. If name is not a header name or value is not a header value, then throw a TypeError. + let valid_name = validate_name(name)?; + if !is_legal_header_value(&value) { + return Err(Error::Type("Header value is not valid".to_string())); + } + // 2. If headers’s guard is "immutable", then throw a TypeError. + if self.guard.get() == Guard::Immutable { + return Err(Error::Type("Guard is immutable".to_string())); + } + // 3. If headers’s guard is "request" and (name, value) is a forbidden request-header, then return false. + if self.guard.get() == Guard::Request && is_forbidden_request_header(&valid_name, &value) { + return Ok(None); + } + // 4. If headers’s guard is "response" and name is a forbidden response-header name, then return false. + if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) { + return Ok(None); + } + + Ok(Some((valid_name, value))) + } } impl Iterable for Headers { @@ -391,6 +416,7 @@ pub(crate) fn is_forbidden_request_header(name: &str, value: &[u8]) -> bool { "keep-alive", "origin", "referer", + "set-cookie", "te", "trailer", "transfer-encoding", @@ -448,26 +474,11 @@ pub(crate) fn is_forbidden_request_header(name: &str, value: &[u8]) -> bool { false } -// https://fetch.spec.whatwg.org/#forbidden-response-header-name +/// <https://fetch.spec.whatwg.org/#forbidden-response-header-name> fn is_forbidden_response_header(name: &str) -> bool { - matches!(name, "set-cookie" | "set-cookie2") -} - -// There is some unresolved confusion over the definition of a name and a value. -// -// As of December 2019, WHATWG has no formal grammar production for value; -// https://fetch.spec.whatg.org/#concept-header-value just says not to have -// newlines, nulls, or leading/trailing whitespace. It even allows -// octets that aren't a valid UTF-8 encoding, and WPT tests reflect this. -// The HeaderValue class does not fully reflect this, so headers -// containing bytes with values 1..31 or 127 can't be created, failing -// WPT tests but probably not affecting anything important on the real Internet. -fn validate_name_and_value(name: ByteString, value: ByteString) -> Fallible<(String, Vec<u8>)> { - let valid_name = validate_name(name)?; - if !is_legal_header_value(&value) { - return Err(Error::Type("Header value is not valid".to_string())); - } - Ok((valid_name, value.into())) + // A forbidden response-header name is a header name that is a byte-case-insensitive match for one of + let name = name.to_ascii_lowercase(); + matches!(name.as_str(), "set-cookie" | "set-cookie2") } fn validate_name(name: ByteString) -> Fallible<String> { @@ -480,47 +491,20 @@ fn validate_name(name: ByteString) -> Fallible<String> { } } -// Removes trailing and leading HTTP whitespace bytes. -// https://fetch.spec.whatwg.org/#concept-header-value-normalize -pub fn normalize_value(value: ByteString) -> ByteString { - match ( - index_of_first_non_whitespace(&value), - index_of_last_non_whitespace(&value), - ) { - (Some(begin), Some(end)) => ByteString::new(value[begin..end + 1].to_owned()), - _ => ByteString::new(vec![]), - } -} - -fn is_http_whitespace(byte: u8) -> bool { - byte == b'\t' || byte == b'\n' || byte == b'\r' || byte == b' ' -} - -fn index_of_first_non_whitespace(value: &ByteString) -> Option<usize> { - for (index, &byte) in value.iter().enumerate() { - if !is_http_whitespace(byte) { - return Some(index); - } - } - None -} - -fn index_of_last_non_whitespace(value: &ByteString) -> Option<usize> { - for (index, &byte) in value.iter().enumerate().rev() { - if !is_http_whitespace(byte) { - return Some(index); - } - } - None -} - -// http://tools.ietf.org/html/rfc7230#section-3.2 +/// <http://tools.ietf.org/html/rfc7230#section-3.2> fn is_field_name(name: &ByteString) -> bool { is_token(name) } -// https://fetch.spec.whatg.org/#concept-header-value -fn is_legal_header_value(value: &ByteString) -> bool { +// As of December 2019, WHATWG has no formal grammar production for value; +// https://fetch.spec.whatg.org/#concept-header-value just says not to have +// newlines, nulls, or leading/trailing whitespace. It even allows +// octets that aren't a valid UTF-8 encoding, and WPT tests reflect this. +// The HeaderValue class does not fully reflect this, so headers +// containing bytes with values 1..31 or 127 can't be created, failing +// WPT tests but probably not affecting anything important on the real Internet. +/// <https://fetch.spec.whatg.org/#concept-header-value> +fn is_legal_header_value(value: &[u8]) -> bool { let value_len = value.len(); if value_len == 0 { return true; @@ -533,7 +517,7 @@ fn is_legal_header_value(value: &ByteString) -> bool { b' ' | b'\t' => return false, _ => {}, }; - for &ch in &value[..] { + for &ch in value { match ch { b'\0' | b'\n' | b'\r' => return false, _ => {}, @@ -555,12 +539,12 @@ fn is_legal_header_value(value: &ByteString) -> bool { // } } -// https://tools.ietf.org/html/rfc5234#appendix-B.1 +/// <https://tools.ietf.org/html/rfc5234#appendix-B.1> pub(crate) fn is_vchar(x: u8) -> bool { matches!(x, 0x21..=0x7E) } -// http://tools.ietf.org/html/rfc7230#section-3.2.6 +/// <http://tools.ietf.org/html/rfc7230#section-3.2.6> pub(crate) fn is_obs_text(x: u8) -> bool { matches!(x, 0x80..=0xFF) } diff --git a/components/script/dom/htmlcanvaselement.rs b/components/script/dom/htmlcanvaselement.rs index 27da9f2b537..499e91c127b 100644 --- a/components/script/dom/htmlcanvaselement.rs +++ b/components/script/dom/htmlcanvaselement.rs @@ -27,6 +27,7 @@ use servo_media::streams::registry::MediaStreamId; use snapshot::Snapshot; use style::attr::AttrValue; +use super::node::NodeDamage; pub(crate) use crate::canvas_context::*; use crate::conversions::Convert; use crate::dom::attr::Attr; @@ -687,8 +688,11 @@ impl VirtualMethods for HTMLCanvasElement { .unwrap() .attribute_mutated(attr, mutation, can_gc); match attr.local_name() { - &local_name!("width") | &local_name!("height") => self.recreate_contexts_after_resize(), - _ => (), + &local_name!("width") | &local_name!("height") => { + self.recreate_contexts_after_resize(); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + }, + _ => {}, }; } diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 3c78ba67772..c6a3e2227f5 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -520,6 +520,7 @@ impl HTMLInputElement { let mut value = textinput.single_line_content().clone(); self.sanitize_value(&mut value); textinput.set_content(value); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); } fn does_minmaxlength_apply(&self) -> bool { @@ -2668,6 +2669,7 @@ impl VirtualMethods for HTMLInputElement { let mut value = textinput.single_line_content().clone(); self.sanitize_value(&mut value); textinput.set_content(value); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); // Steps 7-9 if !previously_selectable && self.selection_api_applies() { @@ -2695,6 +2697,8 @@ impl VirtualMethods for HTMLInputElement { self.sanitize_value(&mut value); self.textinput.borrow_mut().set_content(value); self.update_placeholder_shown_state(); + + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); }, local_name!("name") if self.input_type() == InputType::Radio => { self.radio_group_updated( diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index 391da272ef3..d1791620592 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -1369,7 +1369,6 @@ impl HTMLMediaElement { .lock() .unwrap() .render_poster_frame(image); - self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); if pref!(media_testing_enabled) { self.owner_global() @@ -1618,7 +1617,6 @@ impl HTMLMediaElement { // TODO: 6. Abort the overall resource selection algorithm. }, PlayerEvent::VideoFrameUpdated => { - self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); // Check if the frame was resized if let Some(frame) = self.video_renderer.lock().unwrap().current_frame { self.handle_resize(Some(frame.width as u32), Some(frame.height as u32)); @@ -2017,12 +2015,12 @@ impl HTMLMediaElement { pub(crate) fn clear_current_frame_data(&self) { self.handle_resize(None, None); self.video_renderer.lock().unwrap().current_frame = None; - self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); } fn handle_resize(&self, width: Option<u32>, height: Option<u32>) { if let Some(video_elem) = self.downcast::<HTMLVideoElement>() { video_elem.resize(width, height); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); } } diff --git a/components/script/dom/htmltablecellelement.rs b/components/script/dom/htmltablecellelement.rs index 8b553923230..78f00132580 100644 --- a/components/script/dom/htmltablecellelement.rs +++ b/components/script/dom/htmltablecellelement.rs @@ -9,6 +9,9 @@ use style::attr::{AttrValue, LengthOrPercentageOrAuto}; use style::color::AbsoluteColor; use style::context::QuirksMode; +use super::attr::Attr; +use super::element::AttributeMutation; +use super::node::NodeDamage; use crate::dom::bindings::codegen::Bindings::HTMLTableCellElementBinding::HTMLTableCellElementMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use crate::dom::bindings::inheritance::Castable; @@ -174,6 +177,19 @@ impl VirtualMethods for HTMLTableCellElement { Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } + fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) { + if let Some(super_type) = self.super_type() { + super_type.attribute_mutated(attr, mutation, can_gc); + } + + if matches!(*attr.local_name(), local_name!("colspan")) { + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + } + if matches!(*attr.local_name(), local_name!("rowspan")) { + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + } + } + fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue { match *local_name { local_name!("colspan") => { diff --git a/components/script/dom/htmltablecolelement.rs b/components/script/dom/htmltablecolelement.rs index 9e8eecf1147..70355f274fc 100644 --- a/components/script/dom/htmltablecolelement.rs +++ b/components/script/dom/htmltablecolelement.rs @@ -7,8 +7,10 @@ use html5ever::{LocalName, Prefix, local_name, ns}; use js::rust::HandleObject; use style::attr::{AttrValue, LengthOrPercentageOrAuto}; +use super::attr::Attr; use super::bindings::root::LayoutDom; -use super::element::Element; +use super::element::{AttributeMutation, Element}; +use super::node::NodeDamage; use crate::dom::bindings::codegen::Bindings::HTMLTableColElementBinding::HTMLTableColElementMethods; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::root::DomRoot; @@ -93,6 +95,16 @@ impl VirtualMethods for HTMLTableColElement { Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } + fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) { + if let Some(super_type) = self.super_type() { + super_type.attribute_mutated(attr, mutation, can_gc); + } + + if matches!(*attr.local_name(), local_name!("span")) { + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + } + } + fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue { match *local_name { local_name!("span") => { diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index 98170f9655b..b82051b3b12 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -619,7 +619,8 @@ impl WebGLRenderingContext { let size = Size2D::new(img.width, img.height); - TexPixels::new(img.bytes(), size, img.format, false) + let data = IpcSharedMemory::from_bytes(img.first_frame().bytes); + TexPixels::new(data, size, img.format, false) }, // TODO(emilio): Getting canvas data is implemented in CanvasRenderingContext2D, // but we need to refactor it moving it to `HTMLCanvasElement` and support diff --git a/components/script/dom/webgpu/gpu.rs b/components/script/dom/webgpu/gpu.rs index 20380e07bfb..b2534cda9a8 100644 --- a/components/script/dom/webgpu/gpu.rs +++ b/components/script/dom/webgpu/gpu.rs @@ -86,8 +86,12 @@ impl GPUMethods<crate::DomTypeHolder> for GPU { /// <https://gpuweb.github.io/gpuweb/#dom-gpu-getpreferredcanvasformat> fn GetPreferredCanvasFormat(&self) -> GPUTextureFormat { - // TODO: real implementation - GPUTextureFormat::Rgba8unorm + // From https://github.com/mozilla-firefox/firefox/blob/24d49101ce17b78c3ba1217d00297fe2891be6b3/dom/webgpu/Instance.h#L68 + if cfg!(target_os = "android") { + GPUTextureFormat::Rgba8unorm + } else { + GPUTextureFormat::Bgra8unorm + } } /// <https://www.w3.org/TR/webgpu/#dom-gpu-wgsllanguagefeatures> diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 4815e6feae1..3ee5bfbd662 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -1081,6 +1081,10 @@ impl ScriptThread { for event in document.take_pending_input_events().into_iter() { document.update_active_keyboard_modifiers(event.active_keyboard_modifiers); + // We do this now, because the event is consumed below, but the order doesn't really + // matter as the event will be handled before any new ScriptThread messages are processed. + self.notify_webdriver_input_event_completed(pipeline_id, &event.event); + match event.event { InputEvent::MouseButton(mouse_button_event) => { document.handle_mouse_button_event( @@ -1144,6 +1148,19 @@ impl ScriptThread { ScriptThread::set_user_interacting(false); } + fn notify_webdriver_input_event_completed(&self, pipeline_id: PipelineId, event: &InputEvent) { + let Some(id) = event.webdriver_message_id() else { + return; + }; + + if let Err(error) = self.senders.pipeline_to_constellation_sender.send(( + pipeline_id, + ScriptToConstellationMessage::WebDriverInputComplete(id), + )) { + warn!("ScriptThread failed to send WebDriverInputComplete {id:?}: {error:?}",); + } + } + /// <https://html.spec.whatwg.org/multipage/#update-the-rendering> /// /// Attempt to update the rendering and then do a microtask checkpoint if rendering was actually @@ -3420,7 +3437,7 @@ impl ScriptThread { // the pointer, when the user presses down and releases the primary pointer button" // Servo-specific: Trigger if within 10px of the down point - if let InputEvent::MouseButton(mouse_button_event) = event.event { + if let InputEvent::MouseButton(mouse_button_event) = &event.event { if let MouseButton::Left = mouse_button_event.button { match mouse_button_event.action { MouseButtonAction::Up => { @@ -3429,16 +3446,23 @@ impl ScriptThread { let pixel_dist = (pixel_dist.x * pixel_dist.x + pixel_dist.y * pixel_dist.y).sqrt(); if pixel_dist < 10.0 * document.window().device_pixel_ratio().get() { - document.note_pending_input_event(event.clone()); + // Pass webdriver_id to the newly generated click event + document.note_pending_input_event(ConstellationInputEvent { + hit_test_result: event.hit_test_result.clone(), + pressed_mouse_buttons: event.pressed_mouse_buttons, + active_keyboard_modifiers: event.active_keyboard_modifiers, + event: event.event.clone().with_webdriver_message_id(None), + }); document.note_pending_input_event(ConstellationInputEvent { hit_test_result: event.hit_test_result, pressed_mouse_buttons: event.pressed_mouse_buttons, active_keyboard_modifiers: event.active_keyboard_modifiers, - event: InputEvent::MouseButton(MouseButtonEvent { - action: MouseButtonAction::Click, - button: mouse_button_event.button, - point: mouse_button_event.point, - }), + event: InputEvent::MouseButton(MouseButtonEvent::new( + MouseButtonAction::Click, + mouse_button_event.button, + mouse_button_event.point, + )) + .with_webdriver_message_id(event.event.webdriver_message_id()), }); return; } diff --git a/components/script/test.rs b/components/script/test.rs index c5933696efc..35e05621125 100644 --- a/components/script/test.rs +++ b/components/script/test.rs @@ -7,7 +7,6 @@ pub use crate::dom::bindings::refcounted::TrustedPromise; //pub use crate::dom::bindings::root::Dom; pub use crate::dom::bindings::str::{ByteString, DOMString}; -pub use crate::dom::headers::normalize_value; //pub use crate::dom::node::Node; pub mod area { diff --git a/components/shared/compositing/lib.rs b/components/shared/compositing/lib.rs index 061dfe023df..d88217142cc 100644 --- a/components/shared/compositing/lib.rs +++ b/components/shared/compositing/lib.rs @@ -10,6 +10,7 @@ use base::id::{PipelineId, WebViewId}; use crossbeam_channel::Sender; use embedder_traits::{ AnimationState, EventLoopWaker, MouseButton, MouseButtonAction, TouchEventResult, + WebDriverMessageId, }; use euclid::Rect; use ipc_channel::ipc::IpcSender; @@ -101,9 +102,16 @@ pub enum CompositorMsg { /// The load of a page has completed LoadComplete(WebViewId), /// WebDriver mouse button event - WebDriverMouseButtonEvent(WebViewId, MouseButtonAction, MouseButton, f32, f32), + WebDriverMouseButtonEvent( + WebViewId, + MouseButtonAction, + MouseButton, + f32, + f32, + WebDriverMessageId, + ), /// WebDriver mouse move event - WebDriverMouseMoveEvent(WebViewId, f32, f32), + WebDriverMouseMoveEvent(WebViewId, f32, f32, WebDriverMessageId), // Webdriver wheel scroll event WebDriverWheelScrollEvent(WebViewId, f32, f32, f64, f64), diff --git a/components/shared/constellation/from_script_message.rs b/components/shared/constellation/from_script_message.rs index a5424abe6d1..fa391f93859 100644 --- a/components/shared/constellation/from_script_message.rs +++ b/components/shared/constellation/from_script_message.rs @@ -17,6 +17,7 @@ use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, Worke use embedder_traits::{ AnimationState, EmbedderMsg, FocusSequenceNumber, JSValue, JavaScriptEvaluationError, JavaScriptEvaluationId, MediaSessionEvent, TouchEventResult, ViewportDetails, + WebDriverMessageId, }; use euclid::default::Size2D as UntypedSize2D; use http::{HeaderMap, Method}; @@ -652,6 +653,8 @@ pub enum ScriptToConstellationMessage { JavaScriptEvaluationId, Result<JSValue, JavaScriptEvaluationError>, ), + /// Notify the completion of a webdriver command. + WebDriverInputComplete(WebDriverMessageId), } impl fmt::Debug for ScriptToConstellationMessage { diff --git a/components/shared/embedder/input_events.rs b/components/shared/embedder/input_events.rs index acaa9afb3ff..869c4eee004 100644 --- a/components/shared/embedder/input_events.rs +++ b/components/shared/embedder/input_events.rs @@ -8,6 +8,8 @@ use malloc_size_of_derive::MallocSizeOf; use serde::{Deserialize, Serialize}; use webrender_api::units::DevicePoint; +use crate::WebDriverMessageId; + /// An input event that is sent from the embedder to Servo. #[derive(Clone, Debug, Deserialize, Serialize)] pub enum InputEvent { @@ -42,6 +44,38 @@ impl InputEvent { InputEvent::Wheel(event) => Some(event.point), } } + + pub fn webdriver_message_id(&self) -> Option<WebDriverMessageId> { + match self { + InputEvent::EditingAction(..) => None, + InputEvent::Gamepad(..) => None, + InputEvent::Ime(..) => None, + InputEvent::Keyboard(..) => None, + InputEvent::MouseButton(event) => event.webdriver_id, + InputEvent::MouseMove(event) => event.webdriver_id, + InputEvent::Touch(..) => None, + InputEvent::Wheel(..) => None, + } + } + + pub fn with_webdriver_message_id(self, webdriver_id: Option<WebDriverMessageId>) -> Self { + match self { + InputEvent::EditingAction(..) => {}, + InputEvent::Gamepad(..) => {}, + InputEvent::Ime(..) => {}, + InputEvent::Keyboard(..) => {}, + InputEvent::MouseButton(mut event) => { + event.webdriver_id = webdriver_id; + }, + InputEvent::MouseMove(mut event) => { + event.webdriver_id = webdriver_id; + }, + InputEvent::Touch(..) => {}, + InputEvent::Wheel(..) => {}, + }; + + self + } } #[derive(Clone, Copy, Debug, Deserialize, Serialize)] @@ -49,6 +83,18 @@ pub struct MouseButtonEvent { pub action: MouseButtonAction, pub button: MouseButton, pub point: DevicePoint, + webdriver_id: Option<WebDriverMessageId>, +} + +impl MouseButtonEvent { + pub fn new(action: MouseButtonAction, button: MouseButton, point: DevicePoint) -> Self { + Self { + action, + button, + point, + webdriver_id: None, + } + } } #[derive(Clone, Copy, Debug, Deserialize, Serialize)] @@ -102,6 +148,16 @@ pub enum MouseButtonAction { #[derive(Clone, Copy, Debug, Deserialize, Serialize)] pub struct MouseMoveEvent { pub point: DevicePoint, + webdriver_id: Option<WebDriverMessageId>, +} + +impl MouseMoveEvent { + pub fn new(point: DevicePoint) -> Self { + Self { + point, + webdriver_id: None, + } + } } /// The type of input represented by a multi-touch event. diff --git a/components/shared/embedder/webdriver.rs b/components/shared/embedder/webdriver.rs index 3716a29951a..e7118d32737 100644 --- a/components/shared/embedder/webdriver.rs +++ b/components/shared/embedder/webdriver.rs @@ -24,6 +24,9 @@ use webrender_api::units::DeviceIntSize; use crate::{MouseButton, MouseButtonAction}; +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] +pub struct WebDriverMessageId(pub usize); + /// Messages to the constellation originating from the WebDriver server. #[derive(Debug, Deserialize, Serialize)] pub enum WebDriverCommandMsg { @@ -41,9 +44,23 @@ pub enum WebDriverCommandMsg { /// Act as if keys were pressed or release in the browsing context with the given ID. KeyboardAction(BrowsingContextId, KeyboardEvent), /// Act as if the mouse was clicked in the browsing context with the given ID. - MouseButtonAction(WebViewId, MouseButtonAction, MouseButton, f32, f32), + MouseButtonAction( + WebViewId, + MouseButtonAction, + MouseButton, + f32, + f32, + WebDriverMessageId, + IpcSender<WebDriverCommandResponse>, + ), /// Act as if the mouse was moved in the browsing context with the given ID. - MouseMoveAction(WebViewId, f32, f32), + MouseMoveAction( + WebViewId, + f32, + f32, + WebDriverMessageId, + IpcSender<WebDriverCommandResponse>, + ), /// 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. @@ -189,6 +206,11 @@ pub enum WebDriverFrameId { } #[derive(Debug, Deserialize, Serialize)] +pub struct WebDriverCommandResponse { + pub id: WebDriverMessageId, +} + +#[derive(Debug, Deserialize, Serialize)] pub enum WebDriverLoadStatus { Complete, Timeout, diff --git a/components/shared/net/storage_thread.rs b/components/shared/net/storage_thread.rs index 0253603016e..2ba0aa12445 100644 --- a/components/shared/net/storage_thread.rs +++ b/components/shared/net/storage_thread.rs @@ -4,6 +4,7 @@ use ipc_channel::ipc::IpcSender; use malloc_size_of_derive::MallocSizeOf; +use profile_traits::mem::ReportsChan; use serde::{Deserialize, Serialize}; use servo_url::ServoUrl; @@ -45,4 +46,7 @@ pub enum StorageThreadMsg { /// send a reply when done cleaning up thread resources and then shut it down Exit(IpcSender<()>), + + /// Measure memory used by this thread and send the report over the provided channel. + CollectMemoryReport(ReportsChan), } diff --git a/components/webdriver_server/actions.rs b/components/webdriver_server/actions.rs index 7965120b0fd..cde418f920c 100644 --- a/components/webdriver_server/actions.rs +++ b/components/webdriver_server/actions.rs @@ -98,20 +98,83 @@ fn compute_tick_duration(tick_actions: &ActionSequence) -> u64 { impl Handler { // https://w3c.github.io/webdriver/#dfn-dispatch-actions pub(crate) fn dispatch_actions( - &mut self, + &self, actions_by_tick: &[ActionSequence], ) -> Result<(), ErrorStatus> { + // Step 1. Wait for an action queue token with input state. + let new_token = self.id_generator.next(); + assert!(self.current_action_id.get().is_none()); + self.current_action_id.set(Some(new_token)); + + // Step 2. Let actions result be the result of dispatch actions inner. + let res = self.dispatch_actions_inner(actions_by_tick); + + // Step 3. Dequeue input state's actions queue. + self.current_action_id.set(None); + + // Step 4. Return actions result. + res + } + + // https://w3c.github.io/webdriver/#dfn-dispatch-actions-inner + fn dispatch_actions_inner( + &self, + actions_by_tick: &[ActionSequence], + ) -> Result<(), ErrorStatus> { + // Step 1. For each item tick actions in actions by tick for tick_actions in actions_by_tick.iter() { + // Step 1.2. Let tick duration be the result of + // computing the tick duration with argument tick actions. let tick_duration = compute_tick_duration(tick_actions); + + // Step 1.3. Try to dispatch tick actions self.dispatch_tick_actions(tick_actions, tick_duration)?; + + // Step 1.4. Wait for + // The user agent event loop has spun enough times to process the DOM events + // generated by the last invocation of the dispatch tick actions steps. + // + // To ensure we wait for all events to be processed, only the last event in + // this tick action step holds the message id. + // Whenever a new event is generated, the message id is passed to it. + // + // TO-DO: remove the first match after webdriver_id is implemented in all commands + match tick_actions.actions { + ActionsType::Key { .. } | ActionsType::Wheel { .. } | ActionsType::Null { .. } => { + return Ok(()); + }, + _ => {}, + } + + match self.constellation_receiver.recv() { + Ok(response) => { + let current_waiting_id = self + .current_action_id + .get() + .expect("Current id should be set before dispat_actions_inner is called"); + + if current_waiting_id != response.id { + dbg!("Dispatch actions completed with wrong id in response"); + return Err(ErrorStatus::UnknownError); + } + }, + Err(error) => { + dbg!("Dispatch actions completed with IPC error: {:?}", error); + return Err(ErrorStatus::UnknownError); + }, + }; } + + // Step 2. Return success with data null. + dbg!("Dispatch actions completed successfully"); Ok(()) } - fn dispatch_general_action(&mut self, source_id: &str) { - self.session_mut() + fn dispatch_general_action(&self, source_id: &str) { + self.session() .unwrap() .input_state_table + .borrow_mut() .entry(source_id.to_string()) .or_insert(InputSourceState::Null); // https://w3c.github.io/webdriver/#dfn-dispatch-a-pause-action @@ -120,7 +183,7 @@ impl Handler { // https://w3c.github.io/webdriver/#dfn-dispatch-tick-actions fn dispatch_tick_actions( - &mut self, + &self, tick_actions: &ActionSequence, tick_duration: u64, ) -> Result<(), ErrorStatus> { @@ -138,9 +201,10 @@ impl Handler { self.dispatch_general_action(source_id); }, KeyActionItem::Key(action) => { - self.session_mut() + self.session() .unwrap() .input_state_table + .borrow_mut() .entry(source_id.to_string()) .or_insert(InputSourceState::Key(KeyInputState::new())); match action { @@ -149,7 +213,7 @@ impl Handler { // Step 9. If subtype is "keyDown", append a copy of action // object with the subtype property changed to "keyUp" to // input state's input cancel list. - self.session_mut().unwrap().input_cancel_list.push( + self.session().unwrap().input_cancel_list.borrow_mut().push( ActionSequence { id: source_id.into(), actions: ActionsType::Key { @@ -180,9 +244,10 @@ impl Handler { self.dispatch_general_action(source_id); }, PointerActionItem::Pointer(action) => { - self.session_mut() + self.session() .unwrap() .input_state_table + .borrow_mut() .entry(source_id.to_string()) .or_insert(InputSourceState::Pointer(PointerInputState::new( ¶meters.pointer_type, @@ -195,7 +260,7 @@ impl Handler { // Step 10. If subtype is "pointerDown", append a copy of action // object with the subtype property changed to "pointerUp" to // input state's input cancel list. - self.session_mut().unwrap().input_cancel_list.push( + self.session().unwrap().input_cancel_list.borrow_mut().push( ActionSequence { id: source_id.into(), actions: ActionsType::Pointer { @@ -232,9 +297,10 @@ impl Handler { self.dispatch_general_action(source_id) }, WheelActionItem::Wheel(action) => { - self.session_mut() + self.session() .unwrap() .input_state_table + .borrow_mut() .entry(source_id.to_string()) .or_insert(InputSourceState::Wheel); match action { @@ -252,12 +318,25 @@ impl Handler { } // https://w3c.github.io/webdriver/#dfn-dispatch-a-keydown-action - fn dispatch_keydown_action(&mut self, source_id: &str, action: &KeyDownAction) { - // Step 1 + fn dispatch_keydown_action(&self, source_id: &str, action: &KeyDownAction) { + let session = self.session().unwrap(); + let raw_key = action.value.chars().next().unwrap(); - let key_input_state = self.get_key_input_state_mut(source_id); + let mut input_state_table = session.input_state_table.borrow_mut(); + let key_input_state = match input_state_table.get_mut(source_id).unwrap() { + InputSourceState::Key(key_input_state) => key_input_state, + _ => unreachable!(), + }; + + session.input_cancel_list.borrow_mut().push(ActionSequence { + id: source_id.into(), + actions: ActionsType::Key { + actions: vec![KeyActionItem::Key(KeyAction::Up(KeyUpAction { + value: action.value.clone(), + }))], + }, + }); - // Step 2 - 11. Done by `keyboard-types` crate. let keyboard_event = key_input_state.dispatch_keydown(raw_key); // Step 12 @@ -271,12 +350,25 @@ impl Handler { } // https://w3c.github.io/webdriver/#dfn-dispatch-a-keyup-action - fn dispatch_keyup_action(&mut self, source_id: &str, action: &KeyUpAction) { - // Step 1 + fn dispatch_keyup_action(&self, source_id: &str, action: &KeyUpAction) { + let session = self.session().unwrap(); + let raw_key = action.value.chars().next().unwrap(); - let key_input_state = self.get_key_input_state_mut(source_id); + let mut input_state_table = session.input_state_table.borrow_mut(); + let key_input_state = match input_state_table.get_mut(source_id).unwrap() { + InputSourceState::Key(key_input_state) => key_input_state, + _ => unreachable!(), + }; + + session.input_cancel_list.borrow_mut().push(ActionSequence { + id: source_id.into(), + actions: ActionsType::Key { + actions: vec![KeyActionItem::Key(KeyAction::Up(KeyUpAction { + value: action.value.clone(), + }))], + }, + }); - // Step 2 - 11. Done by `keyboard-types` crate. if let Some(keyboard_event) = key_input_state.dispatch_keyup(raw_key) { // Step 12 let cmd_msg = WebDriverCommandMsg::KeyboardAction( @@ -289,44 +381,49 @@ impl Handler { } } - fn get_pointer_input_state_mut(&mut self, source_id: &str) -> &mut PointerInputState { - let session = self.session_mut().unwrap(); - let pointer_input_state = match session.input_state_table.get_mut(source_id).unwrap() { - InputSourceState::Pointer(pointer_input_state) => pointer_input_state, - _ => unreachable!(), - }; - pointer_input_state - } + /// <https://w3c.github.io/webdriver/#dfn-dispatch-a-pointerdown-action> + pub(crate) fn dispatch_pointerdown_action(&self, source_id: &str, action: &PointerDownAction) { + let session = self.session().unwrap(); - fn get_key_input_state_mut(&mut self, source_id: &str) -> &mut KeyInputState { - let session = self.session_mut().unwrap(); - let key_input_state = match session.input_state_table.get_mut(source_id).unwrap() { - InputSourceState::Key(key_input_state) => key_input_state, + let mut input_state_table = session.input_state_table.borrow_mut(); + let pointer_input_state = match input_state_table.get_mut(source_id).unwrap() { + InputSourceState::Pointer(pointer_input_state) => pointer_input_state, _ => unreachable!(), }; - key_input_state - } - - // https://w3c.github.io/webdriver/#dfn-dispatch-a-pointerdown-action - pub(crate) fn dispatch_pointerdown_action( - &mut self, - source_id: &str, - action: &PointerDownAction, - ) { - let webview_id = self.session().unwrap().webview_id; - let pointer_input_state = self.get_pointer_input_state_mut(source_id); if pointer_input_state.pressed.contains(&action.button) { return; } pointer_input_state.pressed.insert(action.button); + session.input_cancel_list.borrow_mut().push(ActionSequence { + id: source_id.into(), + actions: ActionsType::Pointer { + parameters: PointerActionParameters { + pointer_type: match pointer_input_state.subtype { + PointerType::Mouse => PointerType::Mouse, + PointerType::Pen => PointerType::Pen, + PointerType::Touch => PointerType::Touch, + }, + }, + actions: vec![PointerActionItem::Pointer(PointerAction::Up( + PointerUpAction { + button: action.button, + ..Default::default() + }, + ))], + }, + }); + + let msg_id = self.current_action_id.get().unwrap(); let cmd_msg = WebDriverCommandMsg::MouseButtonAction( - webview_id, + session.webview_id, MouseButtonAction::Down, action.button.into(), pointer_input_state.x as f32, pointer_input_state.y as f32, + msg_id, + self.constellation_sender.clone(), ); self.constellation_chan .send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg)) @@ -334,21 +431,48 @@ impl Handler { } // https://w3c.github.io/webdriver/#dfn-dispatch-a-pointerup-action - pub(crate) fn dispatch_pointerup_action(&mut self, source_id: &str, action: &PointerUpAction) { - let webview_id = self.session().unwrap().webview_id; - let pointer_input_state = self.get_pointer_input_state_mut(source_id); + pub(crate) fn dispatch_pointerup_action(&self, source_id: &str, action: &PointerUpAction) { + let session = self.session().unwrap(); + + let mut input_state_table = session.input_state_table.borrow_mut(); + let pointer_input_state = match input_state_table.get_mut(source_id).unwrap() { + InputSourceState::Pointer(pointer_input_state) => pointer_input_state, + _ => unreachable!(), + }; if !pointer_input_state.pressed.contains(&action.button) { return; } pointer_input_state.pressed.remove(&action.button); + session.input_cancel_list.borrow_mut().push(ActionSequence { + id: source_id.into(), + actions: ActionsType::Pointer { + parameters: PointerActionParameters { + pointer_type: match pointer_input_state.subtype { + PointerType::Mouse => PointerType::Mouse, + PointerType::Pen => PointerType::Pen, + PointerType::Touch => PointerType::Touch, + }, + }, + actions: vec![PointerActionItem::Pointer(PointerAction::Down( + PointerDownAction { + button: action.button, + ..Default::default() + }, + ))], + }, + }); + + let msg_id = self.current_action_id.get().unwrap(); let cmd_msg = WebDriverCommandMsg::MouseButtonAction( - webview_id, + session.webview_id, MouseButtonAction::Up, action.button.into(), pointer_input_state.x as f32, pointer_input_state.y as f32, + msg_id, + self.constellation_sender.clone(), ); self.constellation_chan .send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg)) @@ -357,7 +481,7 @@ impl Handler { // https://w3c.github.io/webdriver/#dfn-dispatch-a-pointermove-action pub(crate) fn dispatch_pointermove_action( - &mut self, + &self, source_id: &str, action: &PointerMoveAction, tick_duration: u64, @@ -370,10 +494,10 @@ impl Handler { // Steps 3 - 4 let (start_x, start_y) = match self - .session - .as_ref() + .session() .unwrap() .input_state_table + .borrow_mut() .get(source_id) .unwrap() { @@ -416,7 +540,7 @@ impl Handler { /// <https://w3c.github.io/webdriver/#dfn-perform-a-pointer-move> #[allow(clippy::too_many_arguments)] fn perform_pointer_move( - &mut self, + &self, source_id: &str, duration: u64, start_x: f64, @@ -425,9 +549,13 @@ impl Handler { target_y: f64, tick_start: Instant, ) { - let webview_id = self.session().unwrap().webview_id; - let constellation_chan = self.constellation_chan.clone(); - let pointer_input_state = self.get_pointer_input_state_mut(source_id); + let session = self.session().unwrap(); + let mut input_state_table = session.input_state_table.borrow_mut(); + let pointer_input_state = match input_state_table.get_mut(source_id).unwrap() { + InputSourceState::Pointer(pointer_input_state) => pointer_input_state, + _ => unreachable!(), + }; + loop { // Step 1 let time_delta = tick_start.elapsed().as_millis(); @@ -459,9 +587,15 @@ impl Handler { // Step 7 if x != current_x || y != current_y { // Step 7.2 - let cmd_msg = WebDriverCommandMsg::MouseMoveAction(webview_id, x as f32, y as f32); - //TODO: Need Synchronization here before updating `pointer_input_state` - constellation_chan + let msg_id = self.current_action_id.get().unwrap(); + let cmd_msg = WebDriverCommandMsg::MouseMoveAction( + session.webview_id, + x as f32, + y as f32, + msg_id, + self.constellation_sender.clone(), + ); + self.constellation_chan .send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg)) .unwrap(); // Step 7.3 @@ -481,7 +615,7 @@ impl Handler { /// <https://w3c.github.io/webdriver/#dfn-dispatch-a-scroll-action> fn dispatch_scroll_action( - &mut self, + &self, action: &WheelScrollAction, tick_duration: u64, ) -> Result<(), ErrorStatus> { @@ -546,7 +680,7 @@ impl Handler { /// <https://w3c.github.io/webdriver/#dfn-perform-a-scroll> #[allow(clippy::too_many_arguments)] fn perform_scroll( - &mut self, + &self, duration: u64, x: i64, y: i64, @@ -556,7 +690,7 @@ impl Handler { mut curr_delta_y: i64, tick_start: Instant, ) { - let session = self.session_mut().unwrap(); + let session = self.session().unwrap(); // Step 1 let time_delta = tick_start.elapsed().as_millis(); diff --git a/components/webdriver_server/lib.rs b/components/webdriver_server/lib.rs index 5735594b058..0e3fa9058d6 100644 --- a/components/webdriver_server/lib.rs +++ b/components/webdriver_server/lib.rs @@ -10,11 +10,12 @@ mod actions; mod capabilities; use std::borrow::ToOwned; +use std::cell::{Cell, RefCell}; use std::collections::{BTreeMap, HashMap}; use std::io::Cursor; use std::net::{SocketAddr, SocketAddrV4}; use std::time::Duration; -use std::{env, fmt, mem, process, thread}; +use std::{env, fmt, process, thread}; use base::id::{BrowsingContextId, WebViewId}; use base64::Engine; @@ -23,8 +24,9 @@ use constellation_traits::{EmbedderToConstellationMessage, TraversalDirection}; use cookie::{CookieBuilder, Expiration}; use crossbeam_channel::{Receiver, Sender, after, select, unbounded}; use embedder_traits::{ - MouseButton, WebDriverCommandMsg, WebDriverCookieError, WebDriverFrameId, WebDriverJSError, - WebDriverJSResult, WebDriverJSValue, WebDriverLoadStatus, WebDriverScriptCommand, + MouseButton, WebDriverCommandMsg, WebDriverCommandResponse, WebDriverCookieError, + WebDriverFrameId, WebDriverJSError, WebDriverJSResult, WebDriverJSValue, WebDriverLoadStatus, + WebDriverMessageId, WebDriverScriptCommand, }; use euclid::{Rect, Size2D}; use http::method::Method; @@ -43,8 +45,8 @@ use servo_url::ServoUrl; use style_traits::CSSPixel; use uuid::Uuid; use webdriver::actions::{ - ActionSequence, PointerDownAction, PointerMoveAction, PointerOrigin, PointerType, - PointerUpAction, + ActionSequence, ActionsType, PointerAction, PointerActionItem, PointerActionParameters, + PointerDownAction, PointerMoveAction, PointerOrigin, PointerType, PointerUpAction, }; use webdriver::capabilities::CapabilitiesMatching; use webdriver::command::{ @@ -64,6 +66,26 @@ use webdriver::server::{self, Session, SessionTeardownKind, WebDriverHandler}; use crate::actions::{InputSourceState, PointerInputState}; +#[derive(Default)] +pub struct WebDriverMessageIdGenerator { + counter: Cell<usize>, +} + +impl WebDriverMessageIdGenerator { + pub fn new() -> Self { + Self { + counter: Cell::new(0), + } + } + + /// Returns a unique ID. + pub fn next(&self) -> WebDriverMessageId { + let id = self.counter.get(); + self.counter.set(id + 1); + WebDriverMessageId(id) + } +} + fn extension_routes() -> Vec<(Method, &'static str, ServoExtensionRoute)> { vec![ ( @@ -145,10 +167,11 @@ pub struct WebDriverSession { unhandled_prompt_behavior: String, - // https://w3c.github.io/webdriver/#dfn-input-state-table - input_state_table: HashMap<String, InputSourceState>, - // https://w3c.github.io/webdriver/#dfn-input-cancel-list - input_cancel_list: Vec<ActionSequence>, + /// <https://w3c.github.io/webdriver/#dfn-input-state-map> + input_state_table: RefCell<HashMap<String, InputSourceState>>, + + /// <https://w3c.github.io/webdriver/#dfn-input-cancel-list> + input_cancel_list: RefCell<Vec<ActionSequence>>, } impl WebDriverSession { @@ -172,8 +195,8 @@ impl WebDriverSession { strict_file_interactability: false, unhandled_prompt_behavior: "dismiss and notify".to_string(), - input_state_table: HashMap::new(), - input_cancel_list: Vec::new(), + input_state_table: RefCell::new(HashMap::new()), + input_cancel_list: RefCell::new(Vec::new()), } } } @@ -187,8 +210,22 @@ struct Handler { /// for it to send us a load-status. Messages sent on it /// will be forwarded to the load_status_receiver. load_status_sender: IpcSender<WebDriverLoadStatus>, + session: Option<WebDriverSession>, + + /// The channel for sending Webdriver messages to the constellation. constellation_chan: Sender<EmbedderToConstellationMessage>, + + /// The IPC sender which we can clone and pass along to the constellation + constellation_sender: IpcSender<WebDriverCommandResponse>, + + /// Receiver notification from the constellation when a command is completed + constellation_receiver: IpcReceiver<WebDriverCommandResponse>, + + id_generator: WebDriverMessageIdGenerator, + + current_action_id: Cell<Option<WebDriverMessageId>>, + resize_timeout: u32, } @@ -409,11 +446,18 @@ impl Handler { let (load_status_sender, receiver) = ipc::channel().unwrap(); let (sender, load_status_receiver) = unbounded(); ROUTER.route_ipc_receiver_to_crossbeam_sender(receiver, sender); + + let (constellation_sender, constellation_receiver) = ipc::channel().unwrap(); + Handler { load_status_sender, load_status_receiver, session: None, constellation_chan, + constellation_sender, + constellation_receiver, + id_generator: WebDriverMessageIdGenerator::new(), + current_action_id: Cell::new(None), resize_timeout: 500, } } @@ -1445,18 +1489,13 @@ impl Handler { } fn handle_release_actions(&mut self) -> WebDriverResult<WebDriverResponse> { - let input_cancel_list = { - let session = self.session_mut()?; - session.input_cancel_list.reverse(); - mem::take(&mut session.input_cancel_list) - }; - + let input_cancel_list = self.session().unwrap().input_cancel_list.borrow(); if let Err(error) = self.dispatch_actions(&input_cancel_list) { return Err(WebDriverError::new(error, "")); } - let session = self.session_mut()?; - session.input_state_table = HashMap::new(); + let session = self.session()?; + session.input_state_table.borrow_mut().clear(); Ok(WebDriverResponse::Void) } @@ -1614,7 +1653,7 @@ impl Handler { let id = Uuid::new_v4().to_string(); // Step 8.1 - self.session_mut()?.input_state_table.insert( + self.session_mut()?.input_state_table.borrow_mut().insert( id.clone(), InputSourceState::Pointer(PointerInputState::new(&PointerType::Mouse)), ); @@ -1645,19 +1684,31 @@ impl Handler { ..Default::default() }; - // 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, "")); - } + let action_sequence = ActionSequence { + id: id.clone(), + actions: ActionsType::Pointer { + parameters: PointerActionParameters { + pointer_type: PointerType::Mouse, + }, + actions: vec![ + PointerActionItem::Pointer(PointerAction::Move( + pointer_move_action, + )), + PointerActionItem::Pointer(PointerAction::Down( + pointer_down_action, + )), + PointerActionItem::Pointer(PointerAction::Up(pointer_up_action)), + ], + }, + }; - self.dispatch_pointerdown_action(&id, &pointer_down_action); - self.dispatch_pointerup_action(&id, &pointer_up_action); + let _ = self.dispatch_actions(&[action_sequence]); // Step 8.17 Remove an input source with input state and input id. - self.session_mut()?.input_state_table.remove(&id); + self.session_mut()? + .input_state_table + .borrow_mut() + .remove(&id); // Step 13 Ok(WebDriverResponse::Void) @@ -1709,7 +1760,8 @@ impl Handler { "Unexpected screenshot pixel format" ); - let rgb = RgbaImage::from_raw(img.width, img.height, img.bytes().to_vec()).unwrap(); + let rgb = + RgbaImage::from_raw(img.width, img.height, img.first_frame().bytes.to_vec()).unwrap(); let mut png_data = Cursor::new(Vec::new()); DynamicImage::ImageRgba8(rgb) .write_to(&mut png_data, ImageFormat::Png) diff --git a/ports/servoshell/Cargo.toml b/ports/servoshell/Cargo.toml index 967d72f171b..eee077f33ec 100644 --- a/ports/servoshell/Cargo.toml +++ b/ports/servoshell/Cargo.toml @@ -121,7 +121,7 @@ serde_json = { workspace = true } servo_allocator = { path = "../../components/allocator" } shellwords = "1.0.0" surfman = { workspace = true, features = ["sm-raw-window-handle-06", "sm-x11"] } -winit = "0.30.10" +winit = "0.30.11" [target.'cfg(any(all(target_os = "linux", not(target_env = "ohos")), target_os = "macos"))'.dependencies] sig = "1.0" diff --git a/ports/servoshell/desktop/headed_window.rs b/ports/servoshell/desktop/headed_window.rs index 8efb16954d8..eac8d72331d 100644 --- a/ports/servoshell/desktop/headed_window.rs +++ b/ports/servoshell/desktop/headed_window.rs @@ -262,11 +262,11 @@ impl Window { ElementState::Released => MouseButtonAction::Up, }; - webview.notify_input_event(InputEvent::MouseButton(MouseButtonEvent { + webview.notify_input_event(InputEvent::MouseButton(MouseButtonEvent::new( action, - button: mouse_button, + mouse_button, point, - })); + ))); } /// Handle key events before sending them to Servo. @@ -563,7 +563,7 @@ impl WindowPortsMethods for Window { point.y -= (self.toolbar_height() * self.hidpi_scale_factor()).0; self.webview_relative_mouse_point.set(point); - webview.notify_input_event(InputEvent::MouseMove(MouseMoveEvent { point })); + webview.notify_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point))); }, WindowEvent::MouseWheel { delta, phase, .. } => { let (mut dx, mut dy, mode) = match delta { diff --git a/ports/servoshell/egl/app_state.rs b/ports/servoshell/egl/app_state.rs index 737a2f23b7d..486b01060e8 100644 --- a/ports/servoshell/egl/app_state.rs +++ b/ports/servoshell/egl/app_state.rs @@ -537,31 +537,31 @@ impl RunningAppState { /// Register a mouse movement. pub fn mouse_move(&self, x: f32, y: f32) { self.active_webview() - .notify_input_event(InputEvent::MouseMove(MouseMoveEvent { - point: Point2D::new(x, y), - })); + .notify_input_event(InputEvent::MouseMove(MouseMoveEvent::new(Point2D::new( + x, y, + )))); self.perform_updates(); } /// Register a mouse button press. pub fn mouse_down(&self, x: f32, y: f32, button: MouseButton) { self.active_webview() - .notify_input_event(InputEvent::MouseButton(MouseButtonEvent { - action: MouseButtonAction::Down, + .notify_input_event(InputEvent::MouseButton(MouseButtonEvent::new( + MouseButtonAction::Down, button, - point: Point2D::new(x, y), - })); + Point2D::new(x, y), + ))); self.perform_updates(); } /// Register a mouse button release. pub fn mouse_up(&self, x: f32, y: f32, button: MouseButton) { self.active_webview() - .notify_input_event(InputEvent::MouseButton(MouseButtonEvent { - action: MouseButtonAction::Up, + .notify_input_event(InputEvent::MouseButton(MouseButtonEvent::new( + MouseButtonAction::Up, button, - point: Point2D::new(x, y), - })); + Point2D::new(x, y), + ))); self.perform_updates(); } @@ -589,11 +589,11 @@ impl RunningAppState { /// Perform a click. pub fn click(&self, x: f32, y: f32) { self.active_webview() - .notify_input_event(InputEvent::MouseButton(MouseButtonEvent { - action: MouseButtonAction::Click, - button: MouseButton::Left, - point: Point2D::new(x, y), - })); + .notify_input_event(InputEvent::MouseButton(MouseButtonEvent::new( + MouseButtonAction::Click, + MouseButton::Left, + Point2D::new(x, y), + ))); self.perform_updates(); } diff --git a/support/hitrace-bencher/runs.json b/support/hitrace-bencher/runs.json new file mode 100644 index 00000000000..2513e3f57d1 --- /dev/null +++ b/support/hitrace-bencher/runs.json @@ -0,0 +1,45 @@ +// This json file allows comments and is technically a hjson file. +// Different {} correspond to completely different runs which can have +// different filters and different arguments +[ + { + "args": { + "bencher": true, // output in bencher format + "url": "https://www.google.com", // the url to test + "tries": 5 // How many repeated tries we should have, we show the min,max,avg in the output + //"trace_buffer": 524288, // trace_buffer size of hitrace + //"sleep": 10, // how long should we wait for servo to load the page + //"bundle_name": "org.servo.servo", // the bundle name to start + //"commands": ["--ps=--tracing-filter", "info"] // arbitrary additional arguments + }, + "filters": [ + // Filters are currently given via the simple serialize of `filters::JsonValueFilter`. + // Filters have an arbitrary name that identifies them. This name will be used in bencher output + // where we format it with the E2E and the url. + // start_fn_partial matches a hitrace _start_ event where the given string is a substring of the function name. + // end_fn_partial matches a hitrace _start_ event where the given string is a substring of the function name. + // The filter will calculate the difference between these two start events. + // We currently do not support trace spans and only support exactly one match for start_fn_partial and end_fn_partial. + { + "name": "Load", + "start_fn_partial": "on_surface_created_cb", + "end_fn_partial": "PageLoadEndedPrompt" + } + ] + }, + { + "args": { + "bencher": true, + "url": "https://www.servo.org", + "tries": 5 + }, + "filters": [ + { + "name": "Load", + "start_fn_partial": "on_surface_created_cb", + "end_fn_partial": "PageLoadEndedPrompt" + } + ] + } +] + diff --git a/tests/html/text_deco_simple.html b/tests/html/text_deco_simple.html index 73d48199c96..52ced9aede0 100644 --- a/tests/html/text_deco_simple.html +++ b/tests/html/text_deco_simple.html @@ -1,17 +1,46 @@ <!DOCTYPE html> + <html> -<head> -<title></title> -</head> <body> - <p style="text-decoration:none;"> none </p> - <p style="text-decoration:underline;"> text underline </p> - <p style="text-decoration:overline;"> text underline </p> - <p style="text-decoration:line-through;"> text underline </p> - <p> - <p style="font-size:40px; text-decoration:underline; font-family:Verdana;"> text underline pqrstg </p> - <p style="text-decoration:overline;"> text overline xxxxxxxxXXXXXXX</p> - <p style="font-size:50px; text-decoration:line-through;"> text line-through xxxxxxxxXXXXX</p> - <p style="text-decoration:blink;"> text blink </p> + +<style> + p { + font-family: "Times New Roman"; + background: lightgrey; + padding: 5px; + margin: 5px; + } +</style> + +<p style="text-decoration: none blue;">text-decoration: </p> +<p style="text-decoration: underline blue;">text-decoration: </p> +<p style="text-decoration: overline blue;">text-decoration: </p> +<p style="text-decoration: line-through blue;">text-decoration: </p> + +<p style="text-decoration: underline double blue;">text-decoration: </p> +<p style="text-decoration: overline double blue;">text-decoration: </p> +<p style="text-decoration: line-through double blue;">text-decoration: </p> + +<p style="text-decoration: underline wavy blue;">text-decoration: </p> +<p style="text-decoration: overline wavy blue;">text-decoration: </p> +<p style="text-decoration: line-through wavy blue;">text-decoration: </p> + +<p style="text-decoration: underline dashed blue;">text-decoration: </p> +<p style="text-decoration: overline dashed blue;">text-decoration: </p> +<p style="text-decoration: line-through dashed blue;">text-decoration: </p> + +<p style="text-decoration: underline dotted blue;">text-decoration: </p> +<p style="text-decoration: overline dotted blue;">text-decoration: </p> +<p style="text-decoration: line-through dotted blue;">text-decoration: </p> + +<script> + for (paragraph of document.querySelectorAll("p")) { + paragraph.innerText += " " + + getComputedStyle(paragraph).textDecorationLine + " " + + getComputedStyle(paragraph).textDecorationStyle; + + } +</script> + </body> </html> diff --git a/tests/unit/script/headers.rs b/tests/unit/script/headers.rs deleted file mode 100644 index 530559af7ff..00000000000 --- a/tests/unit/script/headers.rs +++ /dev/null @@ -1,75 +0,0 @@ -/* 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 script::test::{ByteString, normalize_value}; - -#[test] -fn test_normalize_empty_bytestring() { - // empty ByteString test - let empty_bytestring = ByteString::new(vec![]); - let actual = normalize_value(empty_bytestring); - let expected = ByteString::new(vec![]); - assert_eq!(actual, expected); -} - -#[test] -fn test_normalize_all_whitespace_bytestring() { - // All whitespace test. A horizontal tab, a line feed, a carriage return , and a space - let all_whitespace_bytestring = ByteString::new(vec![b'\t', b'\n', b'\r', b' ']); - let actual = normalize_value(all_whitespace_bytestring); - let expected = ByteString::new(vec![]); - assert_eq!(actual, expected); -} - -#[test] -fn test_normalize_non_empty_no_whitespace_bytestring() { - // Non-empty, no whitespace ByteString test - let no_whitespace_bytestring = ByteString::new(vec![b'S', b'!']); - let actual = normalize_value(no_whitespace_bytestring); - let expected = ByteString::new(vec![b'S', b'!']); - assert_eq!(actual, expected); -} - -#[test] -fn test_normalize_non_empty_leading_whitespace_bytestring() { - // Non-empty, leading whitespace, no trailing whitespace ByteString test - let leading_whitespace_bytestring = - ByteString::new(vec![b'\t', b'\n', b' ', b'\r', b'S', b'!']); - let actual = normalize_value(leading_whitespace_bytestring); - let expected = ByteString::new(vec![b'S', b'!']); - assert_eq!(actual, expected); -} - -#[test] -fn test_normalize_non_empty_no_leading_whitespace_trailing_whitespace_bytestring() { - // Non-empty, no leading whitespace, but with trailing whitespace ByteString test - let trailing_whitespace_bytestring = - ByteString::new(vec![b'S', b'!', b'\t', b'\n', b' ', b'\r']); - let actual = normalize_value(trailing_whitespace_bytestring); - let expected = ByteString::new(vec![b'S', b'!']); - assert_eq!(actual, expected); -} - -#[test] -fn test_normalize_non_empty_leading_and_trailing_whitespace_bytestring() { - // Non-empty, leading whitespace, and trailing whitespace ByteString test - let whitespace_sandwich_bytestring = ByteString::new(vec![ - b'\t', b'\n', b' ', b'\r', b'S', b'!', b'\t', b'\n', b' ', b'\r', - ]); - let actual = normalize_value(whitespace_sandwich_bytestring); - let expected = ByteString::new(vec![b'S', b'!']); - assert_eq!(actual, expected); -} - -#[test] -fn test_normalize_non_empty_leading_trailing_and_internal_whitespace_bytestring() { - // Non-empty, leading whitespace, trailing whitespace, - // and internal whitespace ByteString test - let whitespace_bigmac_bytestring = ByteString::new(vec![ - b'\t', b'\n', b' ', b'\r', b'S', b'\t', b'\n', b' ', b'\r', b'!', b'\t', b'\n', b' ', b'\r', - ]); - let actual = normalize_value(whitespace_bigmac_bytestring); - let expected = ByteString::new(vec![b'S', b'\t', b'\n', b' ', b'\r', b'!']); - assert_eq!(actual, expected); -} diff --git a/tests/unit/script/lib.rs b/tests/unit/script/lib.rs index 4b258ede7d0..26c47e60f14 100644 --- a/tests/unit/script/lib.rs +++ b/tests/unit/script/lib.rs @@ -3,8 +3,6 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #[cfg(test)] -mod headers; -#[cfg(test)] mod htmlareaelement; #[cfg(test)] mod htmlimageelement; diff --git a/tests/wpt/meta/css/CSS2/generated-content/content-070.xht.ini b/tests/wpt/meta/css/CSS2/generated-content/content-070.xht.ini deleted file mode 100644 index 397ef880c7e..00000000000 --- a/tests/wpt/meta/css/CSS2/generated-content/content-070.xht.ini +++ /dev/null @@ -1,2 +0,0 @@ -[content-070.xht] - expected: FAIL diff --git a/tests/wpt/meta/css/CSS2/text/text-decoration-va-length-001.xht.ini b/tests/wpt/meta/css/CSS2/text/text-decoration-va-length-001.xht.ini new file mode 100644 index 00000000000..04285060eb5 --- /dev/null +++ b/tests/wpt/meta/css/CSS2/text/text-decoration-va-length-001.xht.ini @@ -0,0 +1,2 @@ +[text-decoration-va-length-001.xht] + expected: FAIL diff --git a/tests/wpt/meta/css/CSS2/text/text-decoration-va-length-002.xht.ini b/tests/wpt/meta/css/CSS2/text/text-decoration-va-length-002.xht.ini new file mode 100644 index 00000000000..8bd8764b744 --- /dev/null +++ b/tests/wpt/meta/css/CSS2/text/text-decoration-va-length-002.xht.ini @@ -0,0 +1,2 @@ +[text-decoration-va-length-002.xht] + expected: FAIL diff --git a/tests/wpt/meta/css/css-flexbox/flex-container-min-content-002.tentative.html.ini b/tests/wpt/meta/css/css-flexbox/flex-container-min-content-002.tentative.html.ini index 408a853b460..e6b8dd0cf2e 100644 --- a/tests/wpt/meta/css/css-flexbox/flex-container-min-content-002.tentative.html.ini +++ b/tests/wpt/meta/css/css-flexbox/flex-container-min-content-002.tentative.html.ini @@ -10,12 +10,3 @@ [.flex 6] expected: FAIL - - [.flex 13] - expected: FAIL - - [.flex 14] - expected: FAIL - - [.flex 15] - expected: FAIL diff --git a/tests/wpt/meta/css/css-flexbox/intrinsic-size/row-wrap-001.html.ini b/tests/wpt/meta/css/css-flexbox/intrinsic-size/row-wrap-001.html.ini index 2b9a86b2d3b..3058f3f880d 100644 --- a/tests/wpt/meta/css/css-flexbox/intrinsic-size/row-wrap-001.html.ini +++ b/tests/wpt/meta/css/css-flexbox/intrinsic-size/row-wrap-001.html.ini @@ -1,9 +1,3 @@ [row-wrap-001.html] - [.floating-flexbox 4] - expected: FAIL - - [.floating-flexbox 5] - expected: FAIL - [.floating-flexbox 1] expected: FAIL diff --git a/tests/wpt/meta/css/css-text-decor/text-decoration-decorating-box-001.html.ini b/tests/wpt/meta/css/css-text-decor/text-decoration-decorating-box-001.html.ini new file mode 100644 index 00000000000..c7c62d17c1a --- /dev/null +++ b/tests/wpt/meta/css/css-text-decor/text-decoration-decorating-box-001.html.ini @@ -0,0 +1,2 @@ +[text-decoration-decorating-box-001.html] + expected: FAIL diff --git a/tests/wpt/meta/css/css-text-decor/text-decoration-dotted-002.html.ini b/tests/wpt/meta/css/css-text-decor/text-decoration-dotted-002.html.ini deleted file mode 100644 index 971f8e0a0fd..00000000000 --- a/tests/wpt/meta/css/css-text-decor/text-decoration-dotted-002.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[text-decoration-dotted-002.html] - expected: FAIL diff --git a/tests/wpt/meta/css/css-text-decor/text-decoration-propagation-display-contents.html.ini b/tests/wpt/meta/css/css-text-decor/text-decoration-propagation-display-contents.html.ini deleted file mode 100644 index abeb0080d24..00000000000 --- a/tests/wpt/meta/css/css-text-decor/text-decoration-propagation-display-contents.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[text-decoration-propagation-display-contents.html] - expected: FAIL diff --git a/tests/wpt/meta/css/css-text-decor/text-decoration-style-multiple.html.ini b/tests/wpt/meta/css/css-text-decor/text-decoration-style-multiple.html.ini deleted file mode 100644 index efc4aa161d5..00000000000 --- a/tests/wpt/meta/css/css-text-decor/text-decoration-style-multiple.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[text-decoration-style-multiple.html] - expected: FAIL diff --git a/tests/wpt/meta/css/css-text-decor/text-shadow/decorations-multiple-zorder.html.ini b/tests/wpt/meta/css/css-text-decor/text-shadow/decorations-multiple-zorder.html.ini deleted file mode 100644 index 0a554b90ff8..00000000000 --- a/tests/wpt/meta/css/css-text-decor/text-shadow/decorations-multiple-zorder.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[decorations-multiple-zorder.html] - expected: FAIL diff --git a/tests/wpt/meta/fetch/api/request/request-headers.any.js.ini b/tests/wpt/meta/fetch/api/request/request-headers.any.js.ini index 2e9dbd2cf7e..74ec35e6dc9 100644 --- a/tests/wpt/meta/fetch/api/request/request-headers.any.js.ini +++ b/tests/wpt/meta/fetch/api/request/request-headers.any.js.ini @@ -1,19 +1,3 @@ -[request-headers.any.html] - [Adding invalid request header "Set-Cookie: KO"] - expected: FAIL - - [Adding invalid request header "Access-Control-Request-Private-Network: KO"] - expected: FAIL - - -[request-headers.any.worker.html] - [Adding invalid request header "Set-Cookie: KO"] - expected: FAIL - - [Adding invalid request header "Access-Control-Request-Private-Network: KO"] - expected: FAIL - - [request-headers.any.serviceworker.html] expected: ERROR diff --git a/tests/wpt/meta/html/semantics/embedded-content/the-video-element/intrinsic_sizes.htm.ini b/tests/wpt/meta/html/semantics/embedded-content/the-video-element/intrinsic_sizes.htm.ini deleted file mode 100644 index 4cb15eeee5e..00000000000 --- a/tests/wpt/meta/html/semantics/embedded-content/the-video-element/intrinsic_sizes.htm.ini +++ /dev/null @@ -1,3 +0,0 @@ -[intrinsic_sizes.htm] - [default object size after src is removed] - expected: FAIL |