aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
Diffstat (limited to 'components')
-rw-r--r--components/canvas/Cargo.toml1
-rw-r--r--components/canvas/canvas_data.rs65
-rw-r--r--components/canvas/canvas_paint_thread.rs45
-rw-r--r--components/canvas/raqote_backend.rs17
-rw-r--r--components/canvas/webgl_thread.rs9
-rw-r--r--components/compositing/compositor.rs81
-rw-r--r--components/compositing/webview_renderer.rs82
-rw-r--r--components/constellation/constellation.rs42
-rw-r--r--components/constellation/event_loop.rs21
-rw-r--r--components/devtools/actors/browsing_context.rs39
-rw-r--r--components/devtools/actors/console.rs1
-rw-r--r--components/devtools/actors/source.rs50
-rw-r--r--components/devtools/actors/thread.rs39
-rw-r--r--components/devtools/actors/watcher.rs3
-rw-r--r--components/devtools/actors/worker.rs11
-rw-r--r--components/devtools/lib.rs11
-rw-r--r--components/devtools/resource.rs42
-rw-r--r--components/layout/dom.rs38
-rw-r--r--components/layout/flow/construct.rs21
-rw-r--r--components/layout/flow/inline/construct.rs64
-rw-r--r--components/layout/flow/inline/inline_box.rs16
-rw-r--r--components/layout/flow/inline/mod.rs8
-rw-r--r--components/layout/flow/mod.rs105
-rw-r--r--components/layout/flow/root.rs32
-rw-r--r--components/layout/fragment_tree/base_fragment.rs5
-rw-r--r--components/layout/fragment_tree/box_fragment.rs25
-rw-r--r--components/layout/fragment_tree/fragment.rs89
-rw-r--r--components/layout/fragment_tree/fragment_tree.rs160
-rw-r--r--components/layout/fragment_tree/positioning_fragment.rs14
-rw-r--r--components/layout/layout_impl.rs529
-rw-r--r--components/layout/query.rs572
-rw-r--r--components/layout/table/layout.rs12
-rw-r--r--components/layout/table/mod.rs1
-rw-r--r--components/layout/traversal.rs8
-rw-r--r--components/malloc_size_of/lib.rs1
-rw-r--r--components/net/fetch/methods.rs108
-rw-r--r--components/net/protocols/mod.rs59
-rw-r--r--components/pixels/lib.rs64
-rw-r--r--components/script/Cargo.toml1
-rw-r--r--components/script/canvas_context.rs11
-rw-r--r--components/script/canvas_state.rs64
-rw-r--r--components/script/dom/bindings/error.rs1
-rw-r--r--components/script/dom/bindings/serializable.rs14
-rw-r--r--components/script/dom/bindings/structuredclone.rs78
-rw-r--r--components/script/dom/bindings/transferable.rs9
-rw-r--r--components/script/dom/blob.rs14
-rw-r--r--components/script/dom/canvasrenderingcontext2d.rs16
-rw-r--r--components/script/dom/cssstylesheet.rs30
-rw-r--r--components/script/dom/document.rs30
-rw-r--r--components/script/dom/domexception.rs22
-rw-r--r--components/script/dom/dompoint.rs16
-rw-r--r--components/script/dom/dompointreadonly.rs16
-rw-r--r--components/script/dom/globalscope.rs73
-rw-r--r--components/script/dom/htmlcanvaselement.rs104
-rw-r--r--components/script/dom/messageport.rs12
-rw-r--r--components/script/dom/node.rs1
-rw-r--r--components/script/dom/offscreencanvas.rs27
-rw-r--r--components/script/dom/offscreencanvasrenderingcontext2d.rs6
-rw-r--r--components/script/dom/readablestream.rs12
-rw-r--r--components/script/dom/response.rs248
-rw-r--r--components/script/dom/webgl2renderingcontext.rs11
-rw-r--r--components/script/dom/webglrenderingcontext.rs36
-rw-r--r--components/script/dom/webgpu/gpucanvascontext.rs9
-rw-r--r--components/script/dom/window.rs39
-rw-r--r--components/script/dom/writablestream.rs12
-rw-r--r--components/script/layout_image.rs5
-rw-r--r--components/script/messaging.rs2
-rw-r--r--components/script/script_runtime.rs55
-rw-r--r--components/script/script_thread.rs29
-rw-r--r--components/script/timers.rs3
-rw-r--r--components/script/webdriver_handlers.rs103
-rw-r--r--components/script_bindings/codegen/Bindings.conf6
-rw-r--r--components/script_bindings/error.rs2
-rw-r--r--components/script_bindings/str.rs65
-rw-r--r--components/script_bindings/webidls/CSSStyleSheet.webidl1
-rw-r--r--components/script_bindings/webidls/Response.webidl1
-rw-r--r--components/script_bindings/webidls/Window.webidl1
-rw-r--r--components/servo/Cargo.toml1
-rw-r--r--components/servo/lib.rs5
-rw-r--r--components/servo_tracing/Cargo.toml21
-rw-r--r--components/servo_tracing/lib.rs394
-rw-r--r--components/shared/canvas/Cargo.toml1
-rw-r--r--components/shared/canvas/canvas.rs9
-rw-r--r--components/shared/canvas/webgl.rs7
-rw-r--r--components/shared/compositing/Cargo.toml1
-rw-r--r--components/shared/constellation/lib.rs17
-rw-r--r--components/shared/embedder/webdriver.rs1
-rw-r--r--components/shared/script/lib.rs4
-rw-r--r--components/shared/script_layout/lib.rs19
-rw-r--r--components/shared/script_layout/wrapper_traits.rs6
-rw-r--r--components/shared/snapshot/Cargo.toml19
-rw-r--r--components/shared/snapshot/lib.rs305
-rw-r--r--components/shared/webgpu/Cargo.toml1
-rw-r--r--components/shared/webgpu/messages/recv.rs3
-rw-r--r--components/webdriver_server/lib.rs24
-rw-r--r--components/webgpu/Cargo.toml1
-rw-r--r--components/webgpu/swapchain.rs27
97 files changed, 2870 insertions, 1671 deletions
diff --git a/components/canvas/Cargo.toml b/components/canvas/Cargo.toml
index a9c06784d18..7e7b00efe11 100644
--- a/components/canvas/Cargo.toml
+++ b/components/canvas/Cargo.toml
@@ -38,6 +38,7 @@ pixels = { path = "../pixels" }
range = { path = "../range" }
raqote = "0.8.5"
servo_arc = { workspace = true }
+snapshot = { workspace = true }
stylo = { workspace = true }
surfman = { workspace = true }
unicode-script = { workspace = true }
diff --git a/components/canvas/canvas_data.rs b/components/canvas/canvas_data.rs
index d6e35b2cbaf..99d6273813e 100644
--- a/components/canvas/canvas_data.rs
+++ b/components/canvas/canvas_data.rs
@@ -19,6 +19,7 @@ use log::warn;
use num_traits::ToPrimitive;
use range::Range;
use servo_arc::Arc as ServoArc;
+use snapshot::Snapshot;
use style::color::AbsoluteColor;
use style::properties::style_structs::Font as FontStyleStruct;
use unicode_script::Script;
@@ -521,8 +522,7 @@ pub trait GenericDrawTarget {
stroke_options: &StrokeOptions,
draw_options: &DrawOptions,
);
- fn snapshot_data(&self, f: &dyn Fn(&[u8]) -> Vec<u8>) -> Vec<u8>;
- fn snapshot_data_owned(&self) -> Vec<u8>;
+ fn snapshot_data(&self) -> &[u8];
}
pub enum GradientStop {
@@ -605,9 +605,8 @@ impl<'a> CanvasData<'a> {
offset: 0,
flags: ImageDescriptorFlags::empty(),
};
- let data = SerializableImageData::Raw(IpcSharedMemory::from_bytes(
- &draw_target.snapshot_data_owned(),
- ));
+ let data =
+ SerializableImageData::Raw(IpcSharedMemory::from_bytes(draw_target.snapshot_data()));
compositor_api.update_images(vec![ImageUpdate::AddImage(image_key, descriptor, data)]);
CanvasData {
backend,
@@ -628,7 +627,7 @@ impl<'a> CanvasData<'a> {
pub fn draw_image(
&mut self,
image_data: &[u8],
- image_size: Size2D<f64>,
+ image_size: Size2D<u64>,
dest_rect: Rect<f64>,
source_rect: Rect<f64>,
smoothing_enabled: bool,
@@ -637,8 +636,8 @@ impl<'a> CanvasData<'a> {
// We round up the floating pixel values to draw the pixels
let source_rect = source_rect.ceil();
// It discards the extra pixels (if any) that won't be painted
- let image_data = if Rect::from_size(image_size).contains_rect(&source_rect) {
- pixels::rgba8_get_rect(image_data, image_size.to_u64(), source_rect.to_u64()).into()
+ let image_data = if Rect::from_size(image_size.to_f64()).contains_rect(&source_rect) {
+ pixels::rgba8_get_rect(image_data, image_size, source_rect.to_u64()).into()
} else {
image_data.into()
};
@@ -1412,12 +1411,8 @@ impl<'a> CanvasData<'a> {
self.update_image_rendering();
}
- pub fn send_pixels(&mut self, chan: IpcSender<IpcSharedMemory>) {
- self.drawtarget.snapshot_data(&|bytes| {
- let data = IpcSharedMemory::from_bytes(bytes);
- chan.send(data).unwrap();
- vec![]
- });
+ pub fn snapshot(&self) {
+ self.drawtarget.snapshot_data();
}
/// Update image in WebRender
@@ -1430,7 +1425,7 @@ impl<'a> CanvasData<'a> {
flags: ImageDescriptorFlags::empty(),
};
let data = SerializableImageData::Raw(IpcSharedMemory::from_bytes(
- &self.drawtarget.snapshot_data_owned(),
+ self.drawtarget.snapshot_data(),
));
self.compositor_api
@@ -1529,18 +1524,36 @@ impl<'a> CanvasData<'a> {
/// canvas_size: The size of the canvas we're reading from
/// read_rect: The area of the canvas we want to read from
#[allow(unsafe_code)]
- pub fn read_pixels(&self, read_rect: Rect<u64>, canvas_size: Size2D<u64>) -> Vec<u8> {
- let canvas_rect = Rect::from_size(canvas_size);
- if canvas_rect
- .intersection(&read_rect)
- .is_none_or(|rect| rect.is_empty())
- {
- return vec![];
- }
+ pub fn read_pixels(
+ &self,
+ read_rect: Option<Rect<u64>>,
+ canvas_size: Option<Size2D<u64>>,
+ ) -> Snapshot {
+ let canvas_size = canvas_size.unwrap_or(self.drawtarget.get_size().cast());
+
+ let data = if let Some(read_rect) = read_rect {
+ let canvas_rect = Rect::from_size(canvas_size);
+ if canvas_rect
+ .intersection(&read_rect)
+ .is_none_or(|rect| rect.is_empty())
+ {
+ vec![]
+ } else {
+ let bytes = self.drawtarget.snapshot_data();
+ pixels::rgba8_get_rect(bytes, canvas_size, read_rect).to_vec()
+ }
+ } else {
+ self.drawtarget.snapshot_data().to_vec()
+ };
- self.drawtarget.snapshot_data(&|bytes| {
- pixels::rgba8_get_rect(bytes, canvas_size, read_rect).into_owned()
- })
+ Snapshot::from_vec(
+ canvas_size,
+ snapshot::PixelFormat::BGRA,
+ snapshot::AlphaMode::Transparent {
+ premultiplied: true,
+ },
+ data,
+ )
}
}
diff --git a/components/canvas/canvas_paint_thread.rs b/components/canvas/canvas_paint_thread.rs
index 8b1b5038334..bb940d7ef81 100644
--- a/components/canvas/canvas_paint_thread.rs
+++ b/components/canvas/canvas_paint_thread.rs
@@ -76,7 +76,11 @@ impl<'a> CanvasPaintThread<'a> {
},
Ok(CanvasMsg::FromScript(message, canvas_id)) => match message {
FromScriptMsg::SendPixels(chan) => {
- canvas_paint_thread.canvas(canvas_id).send_pixels(chan);
+ chan.send(canvas_paint_thread
+ .canvas(canvas_id)
+ .read_pixels(None, None)
+ .as_ipc()
+ ).unwrap();
},
},
Err(e) => {
@@ -159,24 +163,21 @@ impl<'a> CanvasPaintThread<'a> {
Canvas2dMsg::IsPointInPath(path, x, y, fill_rule, chan) => self
.canvas(canvas_id)
.is_point_in_path_(&path[..], x, y, fill_rule, chan),
- Canvas2dMsg::DrawImage(
- ref image_data,
- image_size,
- dest_rect,
- source_rect,
- smoothing_enabled,
- ) => self.canvas(canvas_id).draw_image(
- image_data,
- image_size,
- dest_rect,
- source_rect,
- smoothing_enabled,
- true,
- ),
+ Canvas2dMsg::DrawImage(snapshot, dest_rect, source_rect, smoothing_enabled) => {
+ let snapshot = snapshot.to_owned();
+ self.canvas(canvas_id).draw_image(
+ snapshot.data(),
+ snapshot.size(),
+ dest_rect,
+ source_rect,
+ smoothing_enabled,
+ !snapshot.alpha_mode().is_premultiplied(),
+ )
+ },
Canvas2dMsg::DrawEmptyImage(image_size, dest_rect, source_rect) => {
self.canvas(canvas_id).draw_image(
&vec![0; image_size.area() as usize * 4],
- image_size,
+ image_size.to_u64(),
dest_rect,
source_rect,
false,
@@ -192,10 +193,10 @@ impl<'a> CanvasPaintThread<'a> {
) => {
let image_data = self
.canvas(canvas_id)
- .read_pixels(source_rect.to_u64(), image_size.to_u64());
+ .read_pixels(Some(source_rect.to_u64()), Some(image_size.to_u64()));
self.canvas(other_canvas_id).draw_image(
- &image_data,
- source_rect.size,
+ image_data.data(),
+ source_rect.size.to_u64(),
dest_rect,
source_rect,
smoothing,
@@ -244,8 +245,10 @@ impl<'a> CanvasPaintThread<'a> {
self.canvas(canvas_id).set_global_composition(op)
},
Canvas2dMsg::GetImageData(dest_rect, canvas_size, sender) => {
- let pixels = self.canvas(canvas_id).read_pixels(dest_rect, canvas_size);
- sender.send(&pixels).unwrap();
+ let snapshot = self
+ .canvas(canvas_id)
+ .read_pixels(Some(dest_rect), Some(canvas_size));
+ sender.send(snapshot.as_ipc()).unwrap();
},
Canvas2dMsg::PutImageData(rect, receiver) => {
self.canvas(canvas_id)
diff --git a/components/canvas/raqote_backend.rs b/components/canvas/raqote_backend.rs
index e40367a4ee8..12137e41f41 100644
--- a/components/canvas/raqote_backend.rs
+++ b/components/canvas/raqote_backend.rs
@@ -630,7 +630,7 @@ impl GenericDrawTarget for raqote::DrawTarget {
self.set_transform(matrix);
}
fn snapshot(&self) -> SourceSurface {
- SourceSurface::Raqote(self.snapshot_data_owned())
+ SourceSurface::Raqote(self.snapshot_data().to_vec())
}
fn stroke(
&mut self,
@@ -694,20 +694,9 @@ impl GenericDrawTarget for raqote::DrawTarget {
);
}
#[allow(unsafe_code)]
- fn snapshot_data(&self, f: &dyn Fn(&[u8]) -> Vec<u8>) -> Vec<u8> {
+ fn snapshot_data(&self) -> &[u8] {
let v = self.get_data();
- f(
- unsafe {
- std::slice::from_raw_parts(v.as_ptr() as *const u8, std::mem::size_of_val(v))
- },
- )
- }
- #[allow(unsafe_code)]
- fn snapshot_data_owned(&self) -> Vec<u8> {
- let v = self.get_data();
- unsafe {
- std::slice::from_raw_parts(v.as_ptr() as *const u8, std::mem::size_of_val(v)).into()
- }
+ unsafe { std::slice::from_raw_parts(v.as_ptr() as *const u8, std::mem::size_of_val(v)) }
}
}
diff --git a/components/canvas/webgl_thread.rs b/components/canvas/webgl_thread.rs
index e3f0c77b4b3..b1ac2b2d3c4 100644
--- a/components/canvas/webgl_thread.rs
+++ b/components/canvas/webgl_thread.rs
@@ -31,6 +31,7 @@ use glow::{
bytes_per_type, components_per_format,
};
use half::f16;
+use ipc_channel::ipc::IpcSharedMemory;
use log::{debug, error, trace, warn};
use pixels::{self, PixelFormat, unmultiply_inplace};
use surfman::chains::{PreserveBuffer, SwapChains, SwapChainsAPI};
@@ -1212,7 +1213,13 @@ impl WebGLImpl {
glow::PixelPackData::Slice(Some(&mut pixels)),
)
};
- sender.send(&pixels).unwrap();
+ let alpha_mode = match (attributes.alpha, attributes.premultiplied_alpha) {
+ (true, premultiplied) => snapshot::AlphaMode::Transparent { premultiplied },
+ (false, _) => snapshot::AlphaMode::Opaque,
+ };
+ sender
+ .send((IpcSharedMemory::from_bytes(&pixels), alpha_mode))
+ .unwrap();
},
WebGLCommand::ReadPixelsPP(rect, format, pixel_type, offset) => unsafe {
gl.read_pixels(
diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs
index 4550188a7fa..41286a2760a 100644
--- a/components/compositing/compositor.rs
+++ b/components/compositing/compositor.rs
@@ -21,12 +21,12 @@ use compositing_traits::rendering_context::RenderingContext;
use compositing_traits::{
CompositionPipeline, CompositorMsg, ImageUpdate, SendableFrameTree, WebViewTrait,
};
-use constellation_traits::{AnimationTickType, EmbedderToConstellationMessage, PaintMetricEvent};
+use constellation_traits::{EmbedderToConstellationMessage, PaintMetricEvent};
use crossbeam_channel::{Receiver, Sender};
use dpi::PhysicalSize;
use embedder_traits::{
- AnimationState, CompositorHitTestResult, Cursor, InputEvent, MouseButtonEvent, MouseMoveEvent,
- ShutdownState, TouchEventType, UntrustedNodeAddress, ViewportDetails,
+ CompositorHitTestResult, Cursor, InputEvent, MouseButtonEvent, MouseMoveEvent, ShutdownState,
+ TouchEventType, UntrustedNodeAddress, ViewportDetails,
};
use euclid::{Point2D, Rect, Scale, Size2D, Transform3D};
use fnv::FnvHashMap;
@@ -197,9 +197,6 @@ pub(crate) struct PipelineDetails {
/// The pipeline associated with this PipelineDetails object.
pub pipeline: Option<CompositionPipeline>,
- /// The [`PipelineId`] of this pipeline.
- pub id: PipelineId,
-
/// The id of the parent pipeline, if any.
pub parent_pipeline_id: Option<PipelineId>,
@@ -243,32 +240,12 @@ impl PipelineDetails {
pub(crate) fn animating(&self) -> bool {
!self.throttled && (self.animation_callbacks_running || self.animations_running)
}
-
- pub(crate) fn tick_animations(&self, compositor: &IOCompositor) {
- if !self.animating() {
- return;
- }
-
- let mut tick_type = AnimationTickType::empty();
- if self.animations_running {
- tick_type.insert(AnimationTickType::CSS_ANIMATIONS_AND_TRANSITIONS);
- }
- if self.animation_callbacks_running {
- tick_type.insert(AnimationTickType::REQUEST_ANIMATION_FRAME);
- }
-
- let msg = EmbedderToConstellationMessage::TickAnimation(self.id, tick_type);
- if let Err(e) = compositor.global.borrow().constellation_sender.send(msg) {
- warn!("Sending tick to constellation failed ({:?}).", e);
- }
- }
}
impl PipelineDetails {
- pub(crate) fn new(id: PipelineId) -> PipelineDetails {
+ pub(crate) fn new() -> PipelineDetails {
PipelineDetails {
pipeline: None,
- id,
parent_pipeline_id: None,
most_recent_display_list_epoch: None,
animations_running: false,
@@ -543,22 +520,14 @@ impl IOCompositor {
pipeline_id,
animation_state,
) => {
- let mut throttled = true;
if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) {
- throttled = webview_renderer
- .change_running_animations_state(pipeline_id, animation_state);
- }
-
- // These operations should eventually happen per-WebView, but they are global now as rendering
- // is still global to all WebViews.
- if !throttled && animation_state == AnimationState::AnimationsPresent {
- self.set_needs_repaint(RepaintReason::ChangedAnimationState);
- }
-
- if !throttled && animation_state == AnimationState::AnimationCallbacksPresent {
- // We need to fetch the WebView again in order to avoid a double borrow.
- if let Some(webview_renderer) = self.webview_renderers.get(webview_id) {
- webview_renderer.tick_animations_for_pipeline(pipeline_id, self);
+ if webview_renderer
+ .change_pipeline_running_animations_state(pipeline_id, animation_state) &&
+ webview_renderer.animating()
+ {
+ // These operations should eventually happen per-WebView, but they are
+ // global now as rendering is still global to all WebViews.
+ self.process_animations(true);
}
}
},
@@ -605,8 +574,13 @@ impl IOCompositor {
CompositorMsg::SetThrottled(webview_id, pipeline_id, throttled) => {
if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) {
- webview_renderer.set_throttled(pipeline_id, throttled);
- self.process_animations(true);
+ if webview_renderer.set_throttled(pipeline_id, throttled) &&
+ webview_renderer.animating()
+ {
+ // These operations should eventually happen per-WebView, but they are
+ // global now as rendering is still global to all WebViews.
+ self.process_animations(true);
+ }
}
},
@@ -1283,8 +1257,23 @@ impl IOCompositor {
}
self.last_animation_tick = Instant::now();
- for webview_renderer in self.webview_renderers.iter() {
- webview_renderer.tick_all_animations(self);
+ let animating_webviews: Vec<_> = self
+ .webview_renderers
+ .iter()
+ .filter_map(|webview_renderer| {
+ if webview_renderer.animating() {
+ Some(webview_renderer.id)
+ } else {
+ None
+ }
+ })
+ .collect();
+ if !animating_webviews.is_empty() {
+ if let Err(error) = self.global.borrow().constellation_sender.send(
+ EmbedderToConstellationMessage::TickAnimation(animating_webviews),
+ ) {
+ warn!("Sending tick to constellation failed ({error:?}).");
+ }
}
}
diff --git a/components/compositing/webview_renderer.rs b/components/compositing/webview_renderer.rs
index 6ad77d46043..614ef0ff4c3 100644
--- a/components/compositing/webview_renderer.rs
+++ b/components/compositing/webview_renderer.rs
@@ -86,6 +86,9 @@ pub(crate) struct WebViewRenderer {
/// The HiDPI scale factor for the `WebView` associated with this renderer. This is controlled
/// by the embedding layer.
hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
+ /// Whether or not this [`WebViewRenderer`] isn't throttled and has a pipeline with
+ /// active animations or animation frame callbacks.
+ animating: bool,
}
impl Drop for WebViewRenderer {
@@ -119,6 +122,7 @@ impl WebViewRenderer {
min_viewport_zoom: Some(PinchZoomFactor::new(1.0)),
max_viewport_zoom: None,
hidpi_scale_factor: Scale::new(hidpi_scale_factor.0),
+ animating: false,
}
}
@@ -138,6 +142,10 @@ impl WebViewRenderer {
self.pipelines.keys()
}
+ pub(crate) fn animating(&self) -> bool {
+ self.animating
+ }
+
/// Returns the [`PipelineDetails`] for the given [`PipelineId`], creating it if needed.
pub(crate) fn ensure_pipeline_details(
&mut self,
@@ -148,14 +156,10 @@ impl WebViewRenderer {
.borrow_mut()
.pipeline_to_webview_map
.insert(pipeline_id, self.id);
- PipelineDetails::new(pipeline_id)
+ PipelineDetails::new()
})
}
- pub(crate) fn set_throttled(&mut self, pipeline_id: PipelineId, throttled: bool) {
- self.ensure_pipeline_details(pipeline_id).throttled = throttled;
- }
-
pub(crate) fn remove_pipeline(&mut self, pipeline_id: PipelineId) {
self.global
.borrow_mut()
@@ -245,51 +249,45 @@ impl WebViewRenderer {
})
}
- /// Sets or unsets the animations-running flag for the given pipeline. Returns true if
- /// the pipeline is throttled.
- pub(crate) fn change_running_animations_state(
+ /// Sets or unsets the animations-running flag for the given pipeline. Returns
+ /// true if the [`WebViewRenderer`]'s overall animating state changed.
+ pub(crate) fn change_pipeline_running_animations_state(
&mut self,
pipeline_id: PipelineId,
animation_state: AnimationState,
) -> bool {
- let throttled = {
- let pipeline_details = self.ensure_pipeline_details(pipeline_id);
- match animation_state {
- AnimationState::AnimationsPresent => {
- pipeline_details.animations_running = true;
- },
- AnimationState::AnimationCallbacksPresent => {
- pipeline_details.animation_callbacks_running = true;
- },
- AnimationState::NoAnimationsPresent => {
- pipeline_details.animations_running = false;
- },
- AnimationState::NoAnimationCallbacksPresent => {
- pipeline_details.animation_callbacks_running = false;
- },
- }
- pipeline_details.throttled
- };
-
- let animating = self.pipelines.values().any(PipelineDetails::animating);
- self.webview.set_animating(animating);
- throttled
+ let pipeline_details = self.ensure_pipeline_details(pipeline_id);
+ match animation_state {
+ AnimationState::AnimationsPresent => {
+ pipeline_details.animations_running = true;
+ },
+ AnimationState::AnimationCallbacksPresent => {
+ pipeline_details.animation_callbacks_running = true;
+ },
+ AnimationState::NoAnimationsPresent => {
+ pipeline_details.animations_running = false;
+ },
+ AnimationState::NoAnimationCallbacksPresent => {
+ pipeline_details.animation_callbacks_running = false;
+ },
+ }
+ self.update_animation_state()
}
- pub(crate) fn tick_all_animations(&self, compositor: &IOCompositor) {
- for pipeline_details in self.pipelines.values() {
- pipeline_details.tick_animations(compositor)
- }
+ /// Sets or unsets the throttled flag for the given pipeline. Returns
+ /// true if the [`WebViewRenderer`]'s overall animating state changed.
+ pub(crate) fn set_throttled(&mut self, pipeline_id: PipelineId, throttled: bool) -> bool {
+ self.ensure_pipeline_details(pipeline_id).throttled = throttled;
+
+ // Throttling a pipeline can cause it to be taken into the "not-animating" state.
+ self.update_animation_state()
}
- pub(crate) fn tick_animations_for_pipeline(
- &self,
- pipeline_id: PipelineId,
- compositor: &IOCompositor,
- ) {
- if let Some(pipeline_details) = self.pipelines.get(&pipeline_id) {
- pipeline_details.tick_animations(compositor);
- }
+ pub(crate) fn update_animation_state(&mut self) -> bool {
+ let animating = self.pipelines.values().any(PipelineDetails::animating);
+ let old_animating = std::mem::replace(&mut self.animating, animating);
+ self.webview.set_animating(self.animating);
+ old_animating != self.animating
}
/// On a Window refresh tick (e.g. vsync)
diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs
index 3f70b0abb89..05081fe0ba7 100644
--- a/components/constellation/constellation.rs
+++ b/components/constellation/constellation.rs
@@ -112,13 +112,12 @@ use compositing_traits::{
CompositorMsg, CompositorProxy, SendableFrameTree, WebrenderExternalImageRegistry,
};
use constellation_traits::{
- AnimationTickType, AuxiliaryWebViewCreationRequest, AuxiliaryWebViewCreationResponse,
- BroadcastMsg, DocumentState, EmbedderToConstellationMessage, IFrameLoadInfo,
- IFrameLoadInfoWithData, IFrameSandboxState, IFrameSizeMsg, Job, LoadData, LoadOrigin, LogEntry,
- MessagePortMsg, NavigationHistoryBehavior, PaintMetricEvent, PortMessageTask, SWManagerMsg,
- SWManagerSenders, ScriptToConstellationChan, ScriptToConstellationMessage, ScrollState,
- ServiceWorkerManagerFactory, ServiceWorkerMsg, StructuredSerializedData, TraversalDirection,
- WindowSizeType,
+ AuxiliaryWebViewCreationRequest, AuxiliaryWebViewCreationResponse, BroadcastMsg, DocumentState,
+ EmbedderToConstellationMessage, IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState,
+ IFrameSizeMsg, Job, LoadData, LoadOrigin, LogEntry, MessagePortMsg, NavigationHistoryBehavior,
+ PaintMetricEvent, PortMessageTask, SWManagerMsg, SWManagerSenders, ScriptToConstellationChan,
+ ScriptToConstellationMessage, ScrollState, ServiceWorkerManagerFactory, ServiceWorkerMsg,
+ StructuredSerializedData, TraversalDirection, WindowSizeType,
};
use crossbeam_channel::{Receiver, Select, Sender, unbounded};
use devtools_traits::{
@@ -1398,8 +1397,8 @@ where
EmbedderToConstellationMessage::ThemeChange(theme) => {
self.handle_theme_change(theme);
},
- EmbedderToConstellationMessage::TickAnimation(pipeline_id, tick_type) => {
- self.handle_tick_animation(pipeline_id, tick_type)
+ EmbedderToConstellationMessage::TickAnimation(webview_ids) => {
+ self.handle_tick_animation(webview_ids)
},
EmbedderToConstellationMessage::WebDriverCommand(command) => {
self.handle_webdriver_msg(command);
@@ -3528,15 +3527,24 @@ where
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
- fn handle_tick_animation(&mut self, pipeline_id: PipelineId, tick_type: AnimationTickType) {
- let pipeline = match self.pipelines.get(&pipeline_id) {
- Some(pipeline) => pipeline,
- None => return warn!("{}: Got script tick after closure", pipeline_id),
- };
+ fn handle_tick_animation(&mut self, webview_ids: Vec<WebViewId>) {
+ let mut animating_event_loops = HashSet::new();
- let message = ScriptThreadMessage::TickAllAnimations(pipeline_id, tick_type);
- if let Err(e) = pipeline.event_loop.send(message) {
- self.handle_send_error(pipeline_id, e);
+ for webview_id in webview_ids.iter() {
+ for browsing_context in self.fully_active_browsing_contexts_iter(*webview_id) {
+ let Some(pipeline) = self.pipelines.get(&browsing_context.pipeline_id) else {
+ continue;
+ };
+ animating_event_loops.insert(pipeline.event_loop.clone());
+ }
+ }
+
+ for event_loop in animating_event_loops {
+ // No error handling here. It's unclear what to do when this fails as the error isn't associated
+ // with a particular pipeline. In addition, the danger of not progressing animations is pretty
+ // low, so it's probably safe to ignore this error and handle the crashed ScriptThread on
+ // some other message.
+ let _ = event_loop.send(ScriptThreadMessage::TickAllAnimations(webview_ids.clone()));
}
}
diff --git a/components/constellation/event_loop.rs b/components/constellation/event_loop.rs
index 362960eba64..46542e7212f 100644
--- a/components/constellation/event_loop.rs
+++ b/components/constellation/event_loop.rs
@@ -6,17 +6,36 @@
//! view of a script thread. When an `EventLoop` is dropped, an `ExitScriptThread`
//! message is sent to the script thread, asking it to shut down.
+use std::hash::Hash;
use std::marker::PhantomData;
use std::rc::Rc;
+use std::sync::atomic::{AtomicUsize, Ordering};
use ipc_channel::Error;
use ipc_channel::ipc::IpcSender;
use script_traits::ScriptThreadMessage;
+static CURRENT_EVENT_LOOP_ID: AtomicUsize = AtomicUsize::new(0);
+
/// <https://html.spec.whatwg.org/multipage/#event-loop>
pub struct EventLoop {
script_chan: IpcSender<ScriptThreadMessage>,
dont_send_or_sync: PhantomData<Rc<()>>,
+ id: usize,
+}
+
+impl PartialEq for EventLoop {
+ fn eq(&self, other: &Self) -> bool {
+ self.id == other.id
+ }
+}
+
+impl Eq for EventLoop {}
+
+impl Hash for EventLoop {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ self.id.hash(state);
+ }
}
impl Drop for EventLoop {
@@ -28,9 +47,11 @@ impl Drop for EventLoop {
impl EventLoop {
/// Create a new event loop from the channel to its script thread.
pub fn new(script_chan: IpcSender<ScriptThreadMessage>) -> Rc<EventLoop> {
+ let id = CURRENT_EVENT_LOOP_ID.fetch_add(1, Ordering::Relaxed);
Rc::new(EventLoop {
script_chan,
dont_send_or_sync: PhantomData,
+ id,
})
}
diff --git a/components/devtools/actors/browsing_context.rs b/components/devtools/actors/browsing_context.rs
index c4ead7272bd..5de0855df4a 100644
--- a/components/devtools/actors/browsing_context.rs
+++ b/components/devtools/actors/browsing_context.rs
@@ -31,6 +31,7 @@ use crate::actors::thread::ThreadActor;
use crate::actors::watcher::{SessionContext, SessionContextType, WatcherActor};
use crate::id::{DevtoolsBrowserId, DevtoolsBrowsingContextId, DevtoolsOuterWindowId, IdMap};
use crate::protocol::JsonPacketStream;
+use crate::resource::ResourceAvailable;
use crate::{EmptyReplyMsg, StreamId};
#[derive(Serialize)]
@@ -57,14 +58,6 @@ struct FrameUpdateMsg {
}
#[derive(Serialize)]
-struct ResourceAvailableReply<T: Serialize> {
- from: String,
- #[serde(rename = "type")]
- type_: String,
- array: Vec<(String, Vec<T>)>,
-}
-
-#[derive(Serialize)]
struct TabNavigated {
from: String,
#[serde(rename = "type")]
@@ -152,6 +145,16 @@ pub(crate) struct BrowsingContextActor {
pub watcher: String,
}
+impl ResourceAvailable for BrowsingContextActor {
+ fn actor_name(&self) -> String {
+ self.name.clone()
+ }
+
+ fn get_streams(&self) -> &RefCell<HashMap<StreamId, TcpStream>> {
+ &self.streams
+ }
+}
+
impl Actor for BrowsingContextActor {
fn name(&self) -> String {
self.name.clone()
@@ -358,26 +361,6 @@ impl BrowsingContextActor {
});
}
- pub(crate) fn resource_available<T: Serialize>(&self, resource: T, resource_type: String) {
- self.resources_available(vec![resource], resource_type);
- }
-
- pub(crate) fn resources_available<T: Serialize>(
- &self,
- resources: Vec<T>,
- resource_type: String,
- ) {
- let msg = ResourceAvailableReply::<T> {
- from: self.name(),
- type_: "resources-available-array".into(),
- array: vec![(resource_type, resources)],
- };
-
- for stream in self.streams.borrow_mut().values_mut() {
- let _ = stream.write_json_packet(&msg);
- }
- }
-
pub fn simulate_color_scheme(&self, theme: Theme) -> Result<(), ()> {
self.script_chan
.send(SimulateColorScheme(self.active_pipeline_id.get(), theme))
diff --git a/components/devtools/actors/console.rs b/components/devtools/actors/console.rs
index ecd718e47d4..3897ffa0fce 100644
--- a/components/devtools/actors/console.rs
+++ b/components/devtools/actors/console.rs
@@ -30,6 +30,7 @@ use crate::actors::browsing_context::BrowsingContextActor;
use crate::actors::object::ObjectActor;
use crate::actors::worker::WorkerActor;
use crate::protocol::JsonPacketStream;
+use crate::resource::ResourceAvailable;
use crate::{StreamId, UniqueId};
trait EncodableConsoleMessage {
diff --git a/components/devtools/actors/source.rs b/components/devtools/actors/source.rs
new file mode 100644
index 00000000000..9d29bc1d3ef
--- /dev/null
+++ b/components/devtools/actors/source.rs
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use std::cell::{Ref, RefCell};
+use std::collections::BTreeSet;
+
+use serde::Serialize;
+use servo_url::ServoUrl;
+
+#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub(crate) struct SourceData {
+ pub actor: String,
+ /// URL of the script, or URL of the page for inline scripts.
+ pub url: String,
+ pub is_black_boxed: bool,
+}
+
+#[derive(Serialize)]
+pub(crate) struct SourcesReply {
+ pub from: String,
+ pub sources: Vec<SourceData>,
+}
+
+pub(crate) struct Source {
+ actor_name: String,
+ source_urls: RefCell<BTreeSet<SourceData>>,
+}
+
+impl Source {
+ pub fn new(actor_name: String) -> Self {
+ Self {
+ actor_name,
+ source_urls: RefCell::new(BTreeSet::default()),
+ }
+ }
+
+ pub fn add_source(&self, url: ServoUrl) {
+ self.source_urls.borrow_mut().insert(SourceData {
+ actor: self.actor_name.clone(),
+ url: url.to_string(),
+ is_black_boxed: false,
+ });
+ }
+
+ pub fn sources(&self) -> Ref<BTreeSet<SourceData>> {
+ self.source_urls.borrow()
+ }
+}
diff --git a/components/devtools/actors/thread.rs b/components/devtools/actors/thread.rs
index 85ff2b732eb..7ff11dff675 100644
--- a/components/devtools/actors/thread.rs
+++ b/components/devtools/actors/thread.rs
@@ -2,14 +2,12 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
-use std::cell::{Ref, RefCell};
-use std::collections::BTreeSet;
use std::net::TcpStream;
use serde::Serialize;
use serde_json::{Map, Value};
-use servo_url::ServoUrl;
+use super::source::{Source, SourcesReply};
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
use crate::{EmptyReplyMsg, StreamId};
@@ -52,45 +50,18 @@ struct ThreadInterruptedReply {
type_: String,
}
-#[derive(Serialize)]
-struct SourcesReply {
- from: String,
- sources: Vec<Source>,
-}
-
-#[derive(Eq, Ord, PartialEq, PartialOrd, Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct Source {
- pub actor: String,
- /// URL of the script, or URL of the page for inline scripts.
- pub url: String,
- pub is_black_boxed: bool,
-}
-
pub struct ThreadActor {
- name: String,
- source_urls: RefCell<BTreeSet<Source>>,
+ pub name: String,
+ pub source_manager: Source,
}
impl ThreadActor {
pub fn new(name: String) -> ThreadActor {
ThreadActor {
- name,
- source_urls: RefCell::new(BTreeSet::default()),
+ name: name.clone(),
+ source_manager: Source::new(name),
}
}
-
- pub fn add_source(&self, url: ServoUrl) {
- self.source_urls.borrow_mut().insert(Source {
- actor: self.name.clone(),
- url: url.to_string(),
- is_black_boxed: false,
- });
- }
-
- pub fn sources(&self) -> Ref<BTreeSet<Source>> {
- self.source_urls.borrow()
- }
}
impl Actor for ThreadActor {
diff --git a/components/devtools/actors/watcher.rs b/components/devtools/actors/watcher.rs
index 77f82c1023a..6a84499b6dd 100644
--- a/components/devtools/actors/watcher.rs
+++ b/components/devtools/actors/watcher.rs
@@ -29,6 +29,7 @@ use crate::actors::watcher::thread_configuration::{
ThreadConfigurationActor, ThreadConfigurationActorMsg,
};
use crate::protocol::JsonPacketStream;
+use crate::resource::ResourceAvailable;
use crate::{EmptyReplyMsg, StreamId};
pub mod network_parent;
@@ -264,7 +265,7 @@ impl Actor for WatcherActor {
},
"source" => {
let thread_actor = registry.find::<ThreadActor>(&target.thread);
- let sources = thread_actor.sources();
+ let sources = thread_actor.source_manager.sources();
target.resources_available(sources.iter().collect(), "source".into());
},
"console-message" | "error-message" => {},
diff --git a/components/devtools/actors/worker.rs b/components/devtools/actors/worker.rs
index 046befe9dc9..42c9d9a9c28 100644
--- a/components/devtools/actors/worker.rs
+++ b/components/devtools/actors/worker.rs
@@ -17,6 +17,7 @@ use servo_url::ServoUrl;
use crate::StreamId;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
+use crate::resource::ResourceAvailable;
#[derive(Clone, Copy)]
#[allow(dead_code)]
@@ -53,6 +54,16 @@ impl WorkerActor {
}
}
+impl ResourceAvailable for WorkerActor {
+ fn actor_name(&self) -> String {
+ self.name.clone()
+ }
+
+ fn get_streams(&self) -> &RefCell<HashMap<StreamId, TcpStream>> {
+ &self.streams
+ }
+}
+
impl Actor for WorkerActor {
fn name(&self) -> String {
self.name.clone()
diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs
index 74b028d5655..4d1e0222177 100644
--- a/components/devtools/lib.rs
+++ b/components/devtools/lib.rs
@@ -19,7 +19,7 @@ use std::net::{Shutdown, TcpListener, TcpStream};
use std::sync::{Arc, Mutex};
use std::thread;
-use actors::thread::Source;
+use actors::source::SourceData;
use base::id::{BrowsingContextId, PipelineId, WebViewId};
use crossbeam_channel::{Receiver, Sender, unbounded};
use devtools_traits::{
@@ -30,6 +30,7 @@ use devtools_traits::{
use embedder_traits::{AllowOrDeny, EmbedderMsg, EmbedderProxy};
use ipc_channel::ipc::{self, IpcSender};
use log::trace;
+use resource::ResourceAvailable;
use serde::Serialize;
use servo_rand::RngCore;
@@ -65,6 +66,7 @@ mod actors {
pub mod process;
pub mod reflow;
pub mod root;
+ pub mod source;
pub mod stylesheets;
pub mod tab;
pub mod thread;
@@ -75,6 +77,7 @@ mod actors {
mod id;
mod network_handler;
mod protocol;
+mod resource;
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
enum UniqueId {
@@ -523,9 +526,11 @@ impl DevtoolsInstance {
.clone();
let thread_actor = actors.find_mut::<ThreadActor>(&thread_actor_name);
- thread_actor.add_source(source_info.url.clone());
+ thread_actor
+ .source_manager
+ .add_source(source_info.url.clone());
- let source = Source {
+ let source = SourceData {
actor: thread_actor_name.clone(),
url: source_info.url.to_string(),
is_black_boxed: false,
diff --git a/components/devtools/resource.rs b/components/devtools/resource.rs
new file mode 100644
index 00000000000..7cef8188cc8
--- /dev/null
+++ b/components/devtools/resource.rs
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::net::TcpStream;
+
+use serde::Serialize;
+
+use crate::StreamId;
+use crate::protocol::JsonPacketStream;
+
+#[derive(Serialize)]
+pub(crate) struct ResourceAvailableReply<T: Serialize> {
+ pub from: String,
+ #[serde(rename = "type")]
+ pub type_: String,
+ pub array: Vec<(String, Vec<T>)>,
+}
+
+pub(crate) trait ResourceAvailable {
+ fn actor_name(&self) -> String;
+
+ fn get_streams(&self) -> &RefCell<HashMap<StreamId, TcpStream>>;
+
+ fn resource_available<T: Serialize>(&self, resource: T, resource_type: String) {
+ self.resources_available(vec![resource], resource_type);
+ }
+
+ fn resources_available<T: Serialize>(&self, resources: Vec<T>, resource_type: String) {
+ let msg = ResourceAvailableReply::<T> {
+ from: self.actor_name(),
+ type_: "resources-available-array".into(),
+ array: vec![(resource_type, resources)],
+ };
+
+ for stream in self.get_streams().borrow_mut().values_mut() {
+ let _ = stream.write_json_packet(&msg);
+ }
+ }
+}
diff --git a/components/layout/dom.rs b/components/layout/dom.rs
index 6db4dbccd41..add4b3ac2d5 100644
--- a/components/layout/dom.rs
+++ b/components/layout/dom.rs
@@ -2,18 +2,21 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+use std::any::Any;
use std::marker::PhantomData;
use std::sync::Arc;
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
use base::id::{BrowsingContextId, PipelineId};
use html5ever::{local_name, ns};
+use malloc_size_of_derive::MallocSizeOf;
use pixels::Image;
use script_layout_interface::wrapper_traits::{
LayoutDataTrait, LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
};
use script_layout_interface::{
- HTMLCanvasDataSource, LayoutElementType, LayoutNodeType as ScriptLayoutNodeType,
+ GenericLayoutDataTrait, HTMLCanvasDataSource, LayoutElementType,
+ LayoutNodeType as ScriptLayoutNodeType,
};
use servo_arc::Arc as ServoArc;
use style::properties::ComputedValues;
@@ -31,7 +34,7 @@ use crate::table::TableLevelBox;
use crate::taffy::TaffyItemBox;
/// The data that is stored in each DOM node that is used by layout.
-#[derive(Default)]
+#[derive(Default, MallocSizeOf)]
pub struct InnerDOMLayoutData {
pub(super) self_box: ArcRefCell<Option<LayoutBox>>,
pub(super) pseudo_before_box: ArcRefCell<Option<LayoutBox>>,
@@ -54,10 +57,11 @@ impl InnerDOMLayoutData {
}
/// A box that is stored in one of the `DOMLayoutData` slots.
+#[derive(MallocSizeOf)]
pub(super) enum LayoutBox {
DisplayContents,
BlockLevel(ArcRefCell<BlockLevelBox>),
- InlineLevel(ArcRefCell<InlineItem>),
+ InlineLevel(Vec<ArcRefCell<InlineItem>>),
FlexLevel(ArcRefCell<FlexLevelBox>),
TableLevelBox(TableLevelBox),
TaffyItemBox(ArcRefCell<TaffyItemBox>),
@@ -70,8 +74,10 @@ impl LayoutBox {
LayoutBox::BlockLevel(block_level_box) => {
block_level_box.borrow().invalidate_cached_fragment()
},
- LayoutBox::InlineLevel(inline_item) => {
- inline_item.borrow().invalidate_cached_fragment()
+ LayoutBox::InlineLevel(inline_items) => {
+ for inline_item in inline_items.iter() {
+ inline_item.borrow().invalidate_cached_fragment()
+ }
},
LayoutBox::FlexLevel(flex_level_box) => {
flex_level_box.borrow().invalidate_cached_fragment()
@@ -87,7 +93,10 @@ impl LayoutBox {
match self {
LayoutBox::DisplayContents => vec![],
LayoutBox::BlockLevel(block_level_box) => block_level_box.borrow().fragments(),
- LayoutBox::InlineLevel(inline_item) => inline_item.borrow().fragments(),
+ LayoutBox::InlineLevel(inline_items) => inline_items
+ .iter()
+ .flat_map(|inline_item| inline_item.borrow().fragments())
+ .collect(),
LayoutBox::FlexLevel(flex_level_box) => flex_level_box.borrow().fragments(),
LayoutBox::TaffyItemBox(taffy_item_box) => taffy_item_box.borrow().fragments(),
LayoutBox::TableLevelBox(table_box) => table_box.fragments(),
@@ -98,11 +107,16 @@ impl LayoutBox {
/// A wrapper for [`InnerDOMLayoutData`]. This is necessary to give the entire data
/// structure interior mutability, as we will need to mutate the layout data of
/// non-mutable DOM nodes.
-#[derive(Default)]
+#[derive(Default, MallocSizeOf)]
pub struct DOMLayoutData(AtomicRefCell<InnerDOMLayoutData>);
// The implementation of this trait allows the data to be stored in the DOM.
impl LayoutDataTrait for DOMLayoutData {}
+impl GenericLayoutDataTrait for DOMLayoutData {
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+}
pub struct BoxSlot<'dom> {
pub(crate) slot: Option<ArcRefCell<Option<LayoutBox>>>,
@@ -255,6 +269,7 @@ where
}
LayoutNode::layout_data(&self)
.unwrap()
+ .as_any()
.downcast_ref::<DOMLayoutData>()
.unwrap()
.0
@@ -262,8 +277,13 @@ where
}
fn layout_data(self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>> {
- LayoutNode::layout_data(&self)
- .map(|data| data.downcast_ref::<DOMLayoutData>().unwrap().0.borrow())
+ LayoutNode::layout_data(&self).map(|data| {
+ data.as_any()
+ .downcast_ref::<DOMLayoutData>()
+ .unwrap()
+ .0
+ .borrow()
+ })
}
fn element_box_slot(&self) -> BoxSlot<'dom> {
diff --git a/components/layout/flow/construct.rs b/components/layout/flow/construct.rs
index a6471756db8..5ed567f513b 100644
--- a/components/layout/flow/construct.rs
+++ b/components/layout/flow/construct.rs
@@ -458,15 +458,14 @@ where
self.propagated_data.without_text_decorations(),
),
);
- box_slot.set(LayoutBox::InlineLevel(atomic));
+ box_slot.set(LayoutBox::InlineLevel(vec![atomic]));
return;
};
// Otherwise, this is just a normal inline box. Whatever happened before, all we need to do
// before recurring is to remember this ongoing inline level box.
- let inline_item = self
- .inline_formatting_context_builder
- .start_inline_box(InlineBox::new(info));
+ self.inline_formatting_context_builder
+ .start_inline_box(InlineBox::new(info), None);
if is_list_item {
if let Some((marker_info, marker_contents)) =
@@ -486,8 +485,14 @@ where
self.finish_anonymous_table_if_needed();
- self.inline_formatting_context_builder.end_inline_box();
- box_slot.set(LayoutBox::InlineLevel(inline_item));
+ // As we are ending this inline box, during the course of the `traverse()` above, the ongoing
+ // inline formatting context may have been split around block-level elements. In that case,
+ // more than a single inline box tree item may have been produced for this inline-level box.
+ // `InlineFormattingContextBuilder::end_inline_box()` is returning all of those box tree
+ // items.
+ box_slot.set(LayoutBox::InlineLevel(
+ self.inline_formatting_context_builder.end_inline_box(),
+ ));
}
fn handle_block_level_element(
@@ -574,7 +579,7 @@ where
display_inside,
contents,
));
- box_slot.set(LayoutBox::InlineLevel(inline_level_box));
+ box_slot.set(LayoutBox::InlineLevel(vec![inline_level_box]));
return;
}
@@ -607,7 +612,7 @@ where
contents,
self.propagated_data,
));
- box_slot.set(LayoutBox::InlineLevel(inline_level_box));
+ box_slot.set(LayoutBox::InlineLevel(vec![inline_level_box]));
return;
}
diff --git a/components/layout/flow/inline/construct.rs b/components/layout/flow/inline/construct.rs
index 7c668751ef6..61292701a9f 100644
--- a/components/layout/flow/inline/construct.rs
+++ b/components/layout/flow/inline/construct.rs
@@ -6,6 +6,7 @@ use std::borrow::Cow;
use std::char::{ToLowercase, ToUppercase};
use icu_segmenter::WordSegmenter;
+use itertools::izip;
use servo_arc::Arc;
use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
use style::values::specified::text::TextTransformCase;
@@ -67,6 +68,16 @@ pub(crate) struct InlineFormattingContextBuilder {
/// When an inline box ends, it's removed from this stack.
inline_box_stack: Vec<InlineBoxIdentifier>,
+ /// Normally, an inline box produces a single box tree [`InlineItem`]. When a block
+ /// element causes an inline box [to be split], it can produce multiple
+ /// [`InlineItem`]s, all inserted into different [`InlineFormattingContext`]s.
+ /// [`Self::block_in_inline_splits`] is responsible for tracking all of these split
+ /// inline box results, so that they can be inserted into the [`crate::dom::BoxSlot`]
+ /// for the DOM element once it has been processed for BoxTree construction.
+ ///
+ /// [to be split]: https://www.w3.org/TR/CSS2/visuren.html#anonymous-block-level
+ block_in_inline_splits: Vec<Vec<ArcRefCell<InlineItem>>>,
+
/// Whether or not the inline formatting context under construction has any
/// uncollapsible text content.
pub has_uncollapsible_text_content: bool,
@@ -162,29 +173,42 @@ impl InlineFormattingContextBuilder {
inline_level_box
}
- pub(crate) fn start_inline_box(&mut self, inline_box: InlineBox) -> ArcRefCell<InlineItem> {
+ pub(crate) fn start_inline_box(
+ &mut self,
+ inline_box: InlineBox,
+ block_in_inline_splits: Option<Vec<ArcRefCell<InlineItem>>>,
+ ) {
self.push_control_character_string(inline_box.base.style.bidi_control_chars().0);
let (identifier, inline_box) = self.inline_boxes.start_inline_box(inline_box);
let inline_level_box = ArcRefCell::new(InlineItem::StartInlineBox(inline_box));
self.inline_items.push(inline_level_box.clone());
self.inline_box_stack.push(identifier);
- inline_level_box
+
+ let mut block_in_inline_splits = block_in_inline_splits.unwrap_or_default();
+ block_in_inline_splits.push(inline_level_box);
+ self.block_in_inline_splits.push(block_in_inline_splits);
}
- pub(crate) fn end_inline_box(&mut self) -> ArcRefCell<InlineBox> {
- let identifier = self.end_inline_box_internal();
+ /// End the ongoing inline box in this [`InlineFormattingContextBuilder`], returning
+ /// shared references to all of the box tree items that were created for it. More than
+ /// a single box tree items may be produced for a single inline box when that inline
+ /// box is split around a block-level element.
+ pub(crate) fn end_inline_box(&mut self) -> Vec<ArcRefCell<InlineItem>> {
+ let (identifier, block_in_inline_splits) = self.end_inline_box_internal();
let inline_level_box = self.inline_boxes.get(&identifier);
- inline_level_box.borrow_mut().is_last_fragment = true;
-
- self.push_control_character_string(
- inline_level_box.borrow().base.style.bidi_control_chars().1,
- );
+ {
+ let mut inline_level_box = inline_level_box.borrow_mut();
+ inline_level_box.is_last_split = true;
+ self.push_control_character_string(inline_level_box.base.style.bidi_control_chars().1);
+ }
- inline_level_box
+ block_in_inline_splits.unwrap_or_default()
}
- fn end_inline_box_internal(&mut self) -> InlineBoxIdentifier {
+ fn end_inline_box_internal(
+ &mut self,
+ ) -> (InlineBoxIdentifier, Option<Vec<ArcRefCell<InlineItem>>>) {
let identifier = self
.inline_box_stack
.pop()
@@ -193,7 +217,12 @@ impl InlineFormattingContextBuilder {
.push(ArcRefCell::new(InlineItem::EndInlineBox));
self.inline_boxes.end_inline_box(identifier);
- identifier
+
+ // This might be `None` if this builder has already drained its block-in-inline-splits
+ // into the new builder on the other side of a new block-in-inline split.
+ let block_in_inline_splits = self.block_in_inline_splits.pop();
+
+ (identifier, block_in_inline_splits)
}
pub(crate) fn push_text<'dom, Node: NodeExt<'dom>>(
@@ -295,12 +324,21 @@ impl InlineFormattingContextBuilder {
// marked as not being the first fragment. No inline content is carried over to this new
// builder.
let mut new_builder = InlineFormattingContextBuilder::new();
- for identifier in self.inline_box_stack.iter() {
+ let block_in_inline_splits = std::mem::take(&mut self.block_in_inline_splits);
+ for (identifier, historical_inline_boxes) in
+ izip!(self.inline_box_stack.iter(), block_in_inline_splits)
+ {
+ // Start a new inline box for every ongoing inline box in this
+ // InlineFormattingContext once we are done processing this block element,
+ // being sure to give the block-in-inline-split to the new
+ // InlineFormattingContext. These will finally be inserted into the DOM's
+ // BoxSlot once the inline box has been fully processed.
new_builder.start_inline_box(
self.inline_boxes
.get(identifier)
.borrow()
.split_around_block(),
+ Some(historical_inline_boxes),
);
}
let mut inline_builder_from_before_split = std::mem::replace(self, new_builder);
diff --git a/components/layout/flow/inline/inline_box.rs b/components/layout/flow/inline/inline_box.rs
index 97398d6e708..de79f876340 100644
--- a/components/layout/flow/inline/inline_box.rs
+++ b/components/layout/flow/inline/inline_box.rs
@@ -23,8 +23,12 @@ pub(crate) struct InlineBox {
pub base: LayoutBoxBase,
/// The identifier of this inline box in the containing [`super::InlineFormattingContext`].
pub(super) identifier: InlineBoxIdentifier,
- pub is_first_fragment: bool,
- pub is_last_fragment: bool,
+ /// Whether or not this is the first instance of an [`InlineBox`] before a possible
+ /// block-in-inline split. When no split occurs, this is always true.
+ pub is_first_split: bool,
+ /// Whether or not this is the last instance of an [`InlineBox`] before a possible
+ /// block-in-inline split. When no split occurs, this is always true.
+ pub is_last_split: bool,
/// The index of the default font in the [`super::InlineFormattingContext`]'s font metrics store.
/// This is initialized during IFC shaping.
pub default_font_index: Option<usize>,
@@ -36,8 +40,8 @@ impl InlineBox {
base: LayoutBoxBase::new(info.into(), info.style.clone()),
// This will be assigned later, when the box is actually added to the IFC.
identifier: InlineBoxIdentifier::default(),
- is_first_fragment: true,
- is_last_fragment: false,
+ is_first_split: true,
+ is_last_split: false,
default_font_index: None,
}
}
@@ -45,8 +49,8 @@ impl InlineBox {
pub(crate) fn split_around_block(&self) -> Self {
Self {
base: LayoutBoxBase::new(self.base.base_fragment_info, self.base.style.clone()),
- is_first_fragment: false,
- is_last_fragment: false,
+ is_first_split: false,
+ is_last_split: false,
..*self
}
}
diff --git a/components/layout/flow/inline/mod.rs b/components/layout/flow/inline/mod.rs
index 490917d95a3..dabb9773410 100644
--- a/components/layout/flow/inline/mod.rs
+++ b/components/layout/flow/inline/mod.rs
@@ -744,7 +744,7 @@ impl InlineFormattingContextLayout<'_> {
self.containing_block,
self.layout_context,
self.current_inline_container_state(),
- inline_box.is_last_fragment,
+ inline_box.is_last_split,
inline_box
.default_font_index
.map(|index| &self.ifc.font_metrics[index].metrics),
@@ -773,7 +773,7 @@ impl InlineFormattingContextLayout<'_> {
);
}
- if inline_box.is_first_fragment {
+ if inline_box.is_first_split {
self.current_line_segment.inline_size += inline_box_state.pbm.padding.inline_start +
inline_box_state.pbm.border.inline_start +
inline_box_state.pbm.margin.inline_start.auto_is(Au::zero);
@@ -2349,10 +2349,10 @@ impl<'layout_data> ContentSizesComputation<'layout_data> {
.auto_is(Au::zero);
let pbm = margin + padding + border;
- if inline_box.is_first_fragment {
+ if inline_box.is_first_split {
self.add_inline_size(pbm.inline_start);
}
- if inline_box.is_last_fragment {
+ if inline_box.is_last_split {
self.ending_inline_pbm_stack.push(pbm.inline_end);
} else {
self.ending_inline_pbm_stack.push(Au::zero());
diff --git a/components/layout/flow/mod.rs b/components/layout/flow/mod.rs
index f92650ef340..d983e8592c4 100644
--- a/components/layout/flow/mod.rs
+++ b/components/layout/flow/mod.rs
@@ -896,6 +896,7 @@ fn layout_in_flow_non_replaced_block_level_same_formatting_context(
block_sizes,
depends_on_block_constraints,
available_block_size,
+ justify_self,
} = solve_containing_block_padding_and_border_for_in_flow_box(
containing_block,
&layout_style,
@@ -909,6 +910,7 @@ fn layout_in_flow_non_replaced_block_level_same_formatting_context(
containing_block,
&pbm,
containing_block_for_children.size.inline,
+ justify_self,
);
let computed_block_size = style.content_block_size();
@@ -1154,6 +1156,7 @@ impl IndependentNonReplacedContents {
block_sizes,
depends_on_block_constraints,
available_block_size,
+ justify_self,
} = solve_containing_block_padding_and_border_for_in_flow_box(
containing_block,
&layout_style,
@@ -1185,7 +1188,7 @@ impl IndependentNonReplacedContents {
let ResolvedMargins {
margin,
effective_margin_inline_start,
- } = solve_margins(containing_block, &pbm, inline_size);
+ } = solve_margins(containing_block, &pbm, inline_size, justify_self);
let content_rect = LogicalRect {
start_corner: LogicalVec2 {
@@ -1300,17 +1303,12 @@ impl IndependentNonReplacedContents {
.sizes
};
- // TODO: the automatic inline size should take `justify-self` into account.
+ let justify_self = resolve_justify_self(style, containing_block.style);
let is_table = self.is_table();
- let automatic_inline_size = if is_table {
- Size::FitContent
- } else {
- Size::Stretch
- };
let compute_inline_size = |stretch_size| {
content_box_sizes.inline.resolve(
Direction::Inline,
- automatic_inline_size,
+ automatic_inline_size(justify_self, is_table),
Au::zero,
Some(stretch_size),
get_inline_content_sizes,
@@ -1472,6 +1470,7 @@ impl IndependentNonReplacedContents {
&pbm,
content_size.inline + pbm.padding_border_sums.inline,
placement_rect,
+ justify_self,
);
let margin = LogicalSides {
@@ -1558,6 +1557,7 @@ impl ReplacedContents {
let effective_margin_inline_start;
let (margin_block_start, margin_block_end) =
solve_block_margins_for_in_flow_block_level(pbm);
+ let justify_self = resolve_justify_self(&base.style, containing_block.style);
let containing_block_writing_mode = containing_block.style.writing_mode;
let physical_content_size = content_size.to_physical_size(containing_block_writing_mode);
@@ -1597,6 +1597,7 @@ impl ReplacedContents {
pbm,
size.inline,
placement_rect,
+ justify_self,
);
// Clearance prevents margin collapse between this block and previous ones,
@@ -1620,6 +1621,7 @@ impl ReplacedContents {
containing_block,
pbm,
content_size.inline,
+ justify_self,
);
};
@@ -1671,6 +1673,7 @@ struct ContainingBlockPaddingAndBorder<'a> {
block_sizes: Sizes,
depends_on_block_constraints: bool,
available_block_size: Option<Au>,
+ justify_self: AlignFlags,
}
struct ResolvedMargins {
@@ -1719,6 +1722,9 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>(
// The available block size may actually be definite, but it should be irrelevant
// since the sizing properties are set to their initial value.
available_block_size: None,
+ // The initial `justify-self` is `auto`, but use `normal` (behaving as `stretch`).
+ // This is being discussed in <https://github.com/w3c/csswg-drafts/issues/11461>.
+ justify_self: AlignFlags::NORMAL,
};
}
@@ -1755,16 +1761,11 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>(
None, /* TODO: support preferred aspect ratios on non-replaced boxes */
))
};
- // TODO: the automatic inline size should take `justify-self` into account.
+ let justify_self = resolve_justify_self(style, containing_block.style);
let is_table = layout_style.is_table();
- let automatic_inline_size = if is_table {
- Size::FitContent
- } else {
- Size::Stretch
- };
let inline_size = content_box_sizes.inline.resolve(
Direction::Inline,
- automatic_inline_size,
+ automatic_inline_size(justify_self, is_table),
Au::zero,
Some(available_inline_size),
get_inline_content_sizes,
@@ -1793,6 +1794,7 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>(
block_sizes: content_box_sizes.block,
depends_on_block_constraints,
available_block_size,
+ justify_self,
}
}
@@ -1804,9 +1806,15 @@ fn solve_margins(
containing_block: &ContainingBlock<'_>,
pbm: &PaddingBorderMargin,
inline_size: Au,
+ justify_self: AlignFlags,
) -> ResolvedMargins {
let (inline_margins, effective_margin_inline_start) =
- solve_inline_margins_for_in_flow_block_level(containing_block, pbm, inline_size);
+ solve_inline_margins_for_in_flow_block_level(
+ containing_block,
+ pbm,
+ inline_size,
+ justify_self,
+ );
let block_margins = solve_block_margins_for_in_flow_block_level(pbm);
ResolvedMargins {
margin: LogicalSides {
@@ -1829,14 +1837,63 @@ fn solve_block_margins_for_in_flow_block_level(pbm: &PaddingBorderMargin) -> (Au
)
}
-/// This is supposed to handle 'justify-self', but no browser supports it on block boxes.
-/// Instead, `<center>` and `<div align>` are implemented via internal 'text-align' values.
+/// Resolves the `justify-self` value, preserving flags.
+fn resolve_justify_self(style: &ComputedValues, parent_style: &ComputedValues) -> AlignFlags {
+ let is_ltr = |style: &ComputedValues| style.writing_mode.line_left_is_inline_start();
+ let alignment = match style.clone_justify_self().0.0 {
+ AlignFlags::AUTO => parent_style.clone_justify_items().computed.0,
+ alignment => alignment,
+ };
+ let alignment_value = match alignment.value() {
+ AlignFlags::LEFT if is_ltr(parent_style) => AlignFlags::START,
+ AlignFlags::LEFT => AlignFlags::END,
+ AlignFlags::RIGHT if is_ltr(parent_style) => AlignFlags::END,
+ AlignFlags::RIGHT => AlignFlags::START,
+ AlignFlags::SELF_START if is_ltr(parent_style) == is_ltr(style) => AlignFlags::START,
+ AlignFlags::SELF_START => AlignFlags::END,
+ AlignFlags::SELF_END if is_ltr(parent_style) == is_ltr(style) => AlignFlags::END,
+ AlignFlags::SELF_END => AlignFlags::START,
+ alignment_value => alignment_value,
+ };
+ alignment.flags() | alignment_value
+}
+
+/// Determines the automatic size for the inline axis of a block-level box.
+/// <https://drafts.csswg.org/css-sizing-3/#automatic-size>
+#[inline]
+fn automatic_inline_size<T>(justify_self: AlignFlags, is_table: bool) -> Size<T> {
+ match justify_self {
+ AlignFlags::STRETCH => Size::Stretch,
+ AlignFlags::NORMAL if !is_table => Size::Stretch,
+ _ => Size::FitContent,
+ }
+}
+
+/// Justifies a block-level box, distributing the free space according to `justify-self`.
+/// Note `<center>` and `<div align>` are implemented via internal 'text-align' values,
+/// which are also handled here.
/// The provided free space should already take margins into account. In particular,
/// it should be zero if there is an auto margin.
/// <https://drafts.csswg.org/css-align/#justify-block>
-fn justify_self_alignment(containing_block: &ContainingBlock, free_space: Au) -> Au {
+fn justify_self_alignment(
+ containing_block: &ContainingBlock,
+ free_space: Au,
+ justify_self: AlignFlags,
+) -> Au {
+ let mut alignment = justify_self.value();
+ let is_safe = justify_self.flags() == AlignFlags::SAFE || alignment == AlignFlags::NORMAL;
+ if is_safe && free_space <= Au::zero() {
+ alignment = AlignFlags::START
+ }
+ match alignment {
+ AlignFlags::NORMAL => {},
+ AlignFlags::CENTER => return free_space / 2,
+ AlignFlags::END => return free_space,
+ _ => return Au::zero(),
+ }
+
+ // For `justify-self: normal`, fall back to the special 'text-align' values.
let style = containing_block.style;
- debug_assert!(free_space >= Au::zero());
match style.clone_text_align() {
TextAlignKeyword::MozCenter => free_space / 2,
TextAlignKeyword::MozLeft if !style.writing_mode.line_left_is_inline_start() => free_space,
@@ -1861,6 +1918,7 @@ fn solve_inline_margins_for_in_flow_block_level(
containing_block: &ContainingBlock,
pbm: &PaddingBorderMargin,
inline_size: Au,
+ justify_self: AlignFlags,
) -> ((Au, Au), Au) {
let free_space = containing_block.size.inline - pbm.padding_border_sums.inline - inline_size;
let mut justification = Au::zero();
@@ -1878,8 +1936,8 @@ fn solve_inline_margins_for_in_flow_block_level(
// But here we may still have some free space to perform 'justify-self' alignment.
// This aligns the margin box within the containing block, or in other words,
// aligns the border box within the margin-shrunken containing block.
- let free_space = Au::zero().max(free_space - start - end);
- justification = justify_self_alignment(containing_block, free_space);
+ justification =
+ justify_self_alignment(containing_block, free_space - start - end, justify_self);
(start, end)
},
};
@@ -1902,6 +1960,7 @@ fn solve_inline_margins_avoiding_floats(
pbm: &PaddingBorderMargin,
inline_size: Au,
placement_rect: LogicalRect<Au>,
+ justify_self: AlignFlags,
) -> ((Au, Au), Au) {
let free_space = placement_rect.size.inline - inline_size;
debug_assert!(free_space >= Au::zero());
@@ -1922,7 +1981,7 @@ fn solve_inline_margins_avoiding_floats(
// and Blink and WebKit are broken anyways. So we match Gecko instead: this aligns
// the border box within the instersection of the float-shrunken containing-block
// and the margin-shrunken containing-block.
- justification = justify_self_alignment(containing_block, free_space);
+ justification = justify_self_alignment(containing_block, free_space, justify_self);
(start, end)
},
};
diff --git a/components/layout/flow/root.rs b/components/layout/flow/root.rs
index 390b4664e60..187726595f8 100644
--- a/components/layout/flow/root.rs
+++ b/components/layout/flow/root.rs
@@ -195,16 +195,17 @@ impl BoxTree {
},
_ => return None,
},
- LayoutBox::InlineLevel(inline_level_box) => match &*inline_level_box.borrow() {
- InlineItem::OutOfFlowAbsolutelyPositionedBox(_, text_offset_index)
- if box_style.position.is_absolutely_positioned() =>
- {
- UpdatePoint::AbsolutelyPositionedInlineLevelBox(
- inline_level_box.clone(),
- *text_offset_index,
- )
- },
- _ => return None,
+ LayoutBox::InlineLevel(inline_level_items) => {
+ let inline_level_box = inline_level_items.first()?;
+ let InlineItem::OutOfFlowAbsolutelyPositionedBox(_, text_offset_index) =
+ &*inline_level_box.borrow()
+ else {
+ return None;
+ };
+ UpdatePoint::AbsolutelyPositionedInlineLevelBox(
+ inline_level_box.clone(),
+ *text_offset_index,
+ )
},
LayoutBox::FlexLevel(flex_level_box) => match &*flex_level_box.borrow() {
FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_)
@@ -428,13 +429,14 @@ impl BoxTree {
acc.union(&child_overflow)
});
- FragmentTree {
+ FragmentTree::new(
+ layout_context,
root_fragments,
scrollable_overflow,
- initial_containing_block: physical_containing_block,
- canvas_background: self.canvas_background.clone(),
- viewport_scroll_sensitivity: self.viewport_scroll_sensitivity,
- }
+ physical_containing_block,
+ self.canvas_background.clone(),
+ self.viewport_scroll_sensitivity,
+ )
}
}
diff --git a/components/layout/fragment_tree/base_fragment.rs b/components/layout/fragment_tree/base_fragment.rs
index 48d672a8547..0cf6ee511cb 100644
--- a/components/layout/fragment_tree/base_fragment.rs
+++ b/components/layout/fragment_tree/base_fragment.rs
@@ -132,11 +132,6 @@ impl Tag {
Tag { node, pseudo }
}
- /// Returns true if this tag is for a pseudo element.
- pub(crate) fn is_pseudo(&self) -> bool {
- self.pseudo.is_some()
- }
-
pub(crate) fn to_display_list_fragment_id(self) -> u64 {
combine_id_with_fragment_type(self.node.id(), self.pseudo.into())
}
diff --git a/components/layout/fragment_tree/box_fragment.rs b/components/layout/fragment_tree/box_fragment.rs
index 30be154caf1..e87826ec3ca 100644
--- a/components/layout/fragment_tree/box_fragment.rs
+++ b/components/layout/fragment_tree/box_fragment.rs
@@ -65,6 +65,10 @@ pub(crate) struct BoxFragment {
/// does not include padding, border, or margin -- it only includes content.
pub content_rect: PhysicalRect<Au>,
+ /// This [`BoxFragment`]'s containing block rectangle in coordinates relative to
+ /// the initial containing block, but not taking into account any transforms.
+ pub cumulative_containing_block_rect: PhysicalRect<Au>,
+
pub padding: PhysicalSides<Au>,
pub border: PhysicalSides<Au>,
pub margin: PhysicalSides<Au>,
@@ -120,6 +124,7 @@ impl BoxFragment {
style,
children,
content_rect,
+ cumulative_containing_block_rect: Default::default(),
padding,
border,
margin,
@@ -195,6 +200,8 @@ impl BoxFragment {
self
}
+ /// Get the scrollable overflow for this [`BoxFragment`] relative to its
+ /// containing block.
pub fn scrollable_overflow(&self) -> PhysicalRect<Au> {
let physical_padding_rect = self.padding_rect();
let content_origin = self.content_rect.origin.to_vector();
@@ -205,6 +212,14 @@ impl BoxFragment {
)
}
+ pub(crate) fn set_containing_block(&mut self, containing_block: &PhysicalRect<Au>) {
+ self.cumulative_containing_block_rect = *containing_block;
+ }
+
+ pub fn offset_by_containing_block(&self, rect: &PhysicalRect<Au>) -> PhysicalRect<Au> {
+ rect.translate(self.cumulative_containing_block_rect.origin.to_vector())
+ }
+
pub(crate) fn padding_rect(&self) -> PhysicalRect<Au> {
self.content_rect.outer_rect(self.padding)
}
@@ -278,10 +293,7 @@ impl BoxFragment {
overflow
}
- pub(crate) fn calculate_resolved_insets_if_positioned(
- &self,
- containing_block: &PhysicalRect<Au>,
- ) -> PhysicalSides<AuOrAuto> {
+ pub(crate) fn calculate_resolved_insets_if_positioned(&self) -> PhysicalSides<AuOrAuto> {
let position = self.style.get_box().position;
debug_assert_ne!(
position,
@@ -309,7 +321,10 @@ impl BoxFragment {
// used value. Otherwise the resolved value is the computed value."
// https://drafts.csswg.org/cssom/#resolved-values
let insets = self.style.physical_box_offsets();
- let (cb_width, cb_height) = (containing_block.width(), containing_block.height());
+ let (cb_width, cb_height) = (
+ self.cumulative_containing_block_rect.width(),
+ self.cumulative_containing_block_rect.height(),
+ );
if position == ComputedPosition::Relative {
let get_resolved_axis = |start: &LengthPercentageOrAuto,
end: &LengthPercentageOrAuto,
diff --git a/components/layout/fragment_tree/fragment.rs b/components/layout/fragment_tree/fragment.rs
index d0d1b9b1104..7708b0893ee 100644
--- a/components/layout/fragment_tree/fragment.rs
+++ b/components/layout/fragment_tree/fragment.rs
@@ -7,6 +7,7 @@ use std::sync::Arc;
use app_units::Au;
use base::id::PipelineId;
use base::print_tree::PrintTree;
+use euclid::{Point2D, Rect, Size2D, UnknownUnit};
use fonts::{ByteIndex, FontMetrics, GlyphStore};
use malloc_size_of_derive::MallocSizeOf;
use range::Range as ServoRange;
@@ -21,7 +22,7 @@ use super::{
Tag,
};
use crate::cell::ArcRefCell;
-use crate::geom::{LogicalSides, PhysicalRect};
+use crate::geom::{LogicalSides, PhysicalPoint, PhysicalRect};
use crate::style_ext::ComputedValuesExt;
#[derive(Clone, MallocSizeOf)]
@@ -112,6 +113,7 @@ impl Fragment {
Fragment::Float(fragment) => fragment.borrow().base.clone(),
})
}
+
pub(crate) fn mutate_content_rect(&mut self, callback: impl FnOnce(&mut PhysicalRect<Au>)) {
match self {
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
@@ -124,6 +126,28 @@ impl Fragment {
}
}
+ pub(crate) fn set_containing_block(&self, containing_block: &PhysicalRect<Au>) {
+ match self {
+ Fragment::Box(box_fragment) => box_fragment
+ .borrow_mut()
+ .set_containing_block(containing_block),
+ Fragment::Float(float_fragment) => float_fragment
+ .borrow_mut()
+ .set_containing_block(containing_block),
+ Fragment::Positioning(positioning_fragment) => positioning_fragment
+ .borrow_mut()
+ .set_containing_block(containing_block),
+ Fragment::AbsoluteOrFixedPositioned(hoisted_shared_fragment) => {
+ if let Some(ref fragment) = hoisted_shared_fragment.borrow().fragment {
+ fragment.set_containing_block(containing_block);
+ }
+ },
+ Fragment::Text(_) => {},
+ Fragment::Image(_) => {},
+ Fragment::IFrame(_) => {},
+ }
+ }
+
pub fn tag(&self) -> Option<Tag> {
self.base().and_then(|base| base.tag)
}
@@ -146,12 +170,12 @@ impl Fragment {
}
}
- pub fn scrolling_area(&self, containing_block: &PhysicalRect<Au>) -> PhysicalRect<Au> {
+ pub fn scrolling_area(&self) -> PhysicalRect<Au> {
match self {
- Fragment::Box(fragment) | Fragment::Float(fragment) => fragment
- .borrow()
- .scrollable_overflow()
- .translate(containing_block.origin.to_vector()),
+ Fragment::Box(fragment) | Fragment::Float(fragment) => {
+ let fragment = fragment.borrow();
+ fragment.offset_by_containing_block(&fragment.scrollable_overflow())
+ },
_ => self.scrollable_overflow(),
}
}
@@ -169,6 +193,59 @@ impl Fragment {
}
}
+ pub(crate) fn cumulative_border_box_rect(&self) -> Option<PhysicalRect<Au>> {
+ match self {
+ Fragment::Box(fragment) | Fragment::Float(fragment) => {
+ let fragment = fragment.borrow();
+ Some(fragment.offset_by_containing_block(&fragment.border_rect()))
+ },
+ Fragment::Positioning(fragment) => {
+ let fragment = fragment.borrow();
+ Some(fragment.offset_by_containing_block(&fragment.rect))
+ },
+ Fragment::Text(_) |
+ Fragment::AbsoluteOrFixedPositioned(_) |
+ Fragment::Image(_) |
+ Fragment::IFrame(_) => None,
+ }
+ }
+
+ pub(crate) fn client_rect(&self) -> Rect<i32, UnknownUnit> {
+ let rect = match self {
+ Fragment::Box(fragment) | Fragment::Float(fragment) => {
+ // https://drafts.csswg.org/cssom-view/#dom-element-clienttop
+ // " If the element has no associated CSS layout box or if the
+ // CSS layout box is inline, return zero." For this check we
+ // also explicitly ignore the list item portion of the display
+ // style.
+ let fragment = fragment.borrow();
+ if fragment.is_inline_box() {
+ return Rect::zero();
+ }
+
+ if fragment.is_table_wrapper() {
+ // For tables the border actually belongs to the table grid box,
+ // so we need to include it in the dimension of the table wrapper box.
+ let mut rect = fragment.border_rect();
+ rect.origin = PhysicalPoint::zero();
+ rect
+ } else {
+ let mut rect = fragment.padding_rect();
+ rect.origin = PhysicalPoint::new(fragment.border.left, fragment.border.top);
+ rect
+ }
+ },
+ _ => return Rect::zero(),
+ }
+ .to_untyped();
+
+ let rect = Rect::new(
+ Point2D::new(rect.origin.x.to_f32_px(), rect.origin.y.to_f32_px()),
+ Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px()),
+ );
+ rect.round().to_i32()
+ }
+
pub(crate) fn find<T>(
&self,
manager: &ContainingBlockManager<PhysicalRect<Au>>,
diff --git a/components/layout/fragment_tree/fragment_tree.rs b/components/layout/fragment_tree/fragment_tree.rs
index bb3c659466c..3a082c99389 100644
--- a/components/layout/fragment_tree/fragment_tree.rs
+++ b/components/layout/fragment_tree/fragment_tree.rs
@@ -5,17 +5,17 @@
use app_units::Au;
use base::print_tree::PrintTree;
use compositing_traits::display_list::AxesScrollSensitivity;
-use euclid::default::{Point2D, Rect, Size2D};
+use euclid::default::Size2D;
use fxhash::FxHashSet;
use malloc_size_of_derive::MallocSizeOf;
use style::animation::AnimationSetKey;
-use style::dom::OpaqueNode;
use webrender_api::units;
-use super::{ContainingBlockManager, Fragment, Tag};
+use super::{ContainingBlockManager, Fragment};
+use crate::context::LayoutContext;
use crate::display_list::StackingContext;
use crate::flow::CanvasBackground;
-use crate::geom::{PhysicalPoint, PhysicalRect};
+use crate::geom::PhysicalRect;
#[derive(MallocSizeOf)]
pub struct FragmentTree {
@@ -44,6 +44,58 @@ pub struct FragmentTree {
}
impl FragmentTree {
+ pub(crate) fn new(
+ layout_context: &LayoutContext,
+ root_fragments: Vec<Fragment>,
+ scrollable_overflow: PhysicalRect<Au>,
+ initial_containing_block: PhysicalRect<Au>,
+ canvas_background: CanvasBackground,
+ viewport_scroll_sensitivity: AxesScrollSensitivity,
+ ) -> Self {
+ let fragment_tree = Self {
+ root_fragments,
+ scrollable_overflow,
+ initial_containing_block,
+ canvas_background,
+ viewport_scroll_sensitivity,
+ };
+
+ // As part of building the fragment tree, we want to stop animating elements and
+ // pseudo-elements that used to be animating or had animating images attached to
+ // them. Create a set of all elements that used to be animating.
+ let mut animations = layout_context.style_context.animations.sets.write();
+ let mut invalid_animating_nodes: FxHashSet<_> = animations.keys().cloned().collect();
+ let mut image_animations = layout_context.node_image_animation_map.write().to_owned();
+ let mut invalid_image_animating_nodes: FxHashSet<_> = image_animations
+ .keys()
+ .cloned()
+ .map(|node| AnimationSetKey::new(node, None))
+ .collect();
+
+ fragment_tree.find(|fragment, _level, containing_block| {
+ if let Some(tag) = fragment.tag() {
+ invalid_animating_nodes.remove(&AnimationSetKey::new(tag.node, tag.pseudo));
+ invalid_image_animating_nodes.remove(&AnimationSetKey::new(tag.node, tag.pseudo));
+ }
+
+ fragment.set_containing_block(containing_block);
+ None::<()>
+ });
+
+ // Cancel animations for any elements and pseudo-elements that are no longer found
+ // in the fragment tree.
+ for node in &invalid_animating_nodes {
+ if let Some(state) = animations.get_mut(node) {
+ state.cancel_all_animations();
+ }
+ }
+ for node in &invalid_image_animating_nodes {
+ image_animations.remove(&node.node);
+ }
+
+ fragment_tree
+ }
+
pub(crate) fn build_display_list(
&self,
builder: &mut crate::display_list::DisplayListBuilder,
@@ -86,109 +138,11 @@ impl FragmentTree {
.find_map(|child| child.find(&info, 0, &mut process_func))
}
- pub fn remove_nodes_in_fragment_tree_from_set(&self, set: &mut FxHashSet<AnimationSetKey>) {
- self.find(|fragment, _, _| {
- let tag = fragment.tag()?;
- set.remove(&AnimationSetKey::new(tag.node, tag.pseudo));
- None::<()>
- });
- }
-
- /// Get the vector of rectangles that surrounds the fragments of the node with the given address.
- /// This function answers the `getClientRects()` query and the union of the rectangles answers
- /// the `getBoundingClientRect()` query.
- ///
- /// TODO: This function is supposed to handle scroll offsets, but that isn't happening at all.
- pub fn get_content_boxes_for_node(&self, requested_node: OpaqueNode) -> Vec<Rect<Au>> {
- let mut content_boxes = Vec::new();
- let tag_to_find = Tag::new(requested_node);
- self.find(|fragment, _, containing_block| {
- if fragment.tag() != Some(tag_to_find) {
- return None::<()>;
- }
-
- let fragment_relative_rect = match fragment {
- Fragment::Box(fragment) | Fragment::Float(fragment) => {
- fragment.borrow().border_rect()
- },
- Fragment::Positioning(fragment) => fragment.borrow().rect,
- Fragment::Text(fragment) => fragment.borrow().rect,
- Fragment::AbsoluteOrFixedPositioned(_) |
- Fragment::Image(_) |
- Fragment::IFrame(_) => return None,
- };
-
- let rect = fragment_relative_rect.translate(containing_block.origin.to_vector());
-
- content_boxes.push(rect.to_untyped());
- None::<()>
- });
- content_boxes
- }
-
- pub fn get_border_dimensions_for_node(&self, requested_node: OpaqueNode) -> Rect<i32> {
- let tag_to_find = Tag::new(requested_node);
- self.find(|fragment, _, _containing_block| {
- if fragment.tag() != Some(tag_to_find) {
- return None;
- }
-
- let rect = match fragment {
- Fragment::Box(fragment) | Fragment::Float(fragment) => {
- // https://drafts.csswg.org/cssom-view/#dom-element-clienttop
- // " If the element has no associated CSS layout box or if the
- // CSS layout box is inline, return zero." For this check we
- // also explicitly ignore the list item portion of the display
- // style.
- let fragment = fragment.borrow();
- if fragment.is_inline_box() {
- return Some(Rect::zero());
- }
- if fragment.is_table_wrapper() {
- // For tables the border actually belongs to the table grid box,
- // so we need to include it in the dimension of the table wrapper box.
- let mut rect = fragment.border_rect();
- rect.origin = PhysicalPoint::zero();
- rect
- } else {
- let mut rect = fragment.padding_rect();
- rect.origin = PhysicalPoint::new(fragment.border.left, fragment.border.top);
- rect
- }
- },
- Fragment::Positioning(fragment) => fragment.borrow().rect.cast_unit(),
- Fragment::Text(text_fragment) => text_fragment.borrow().rect,
- _ => return None,
- };
-
- let rect = Rect::new(
- Point2D::new(rect.origin.x.to_f32_px(), rect.origin.y.to_f32_px()),
- Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px()),
- );
- Some(rect.round().to_i32().to_untyped())
- })
- .unwrap_or_else(Rect::zero)
- }
-
pub fn get_scrolling_area_for_viewport(&self) -> PhysicalRect<Au> {
let mut scroll_area = self.initial_containing_block;
for fragment in self.root_fragments.iter() {
- scroll_area = fragment
- .scrolling_area(&self.initial_containing_block)
- .union(&scroll_area);
+ scroll_area = fragment.scrolling_area().union(&scroll_area);
}
scroll_area
}
-
- pub fn get_scrolling_area_for_node(&self, requested_node: OpaqueNode) -> PhysicalRect<Au> {
- let tag_to_find = Tag::new(requested_node);
- let scroll_area = self.find(|fragment, _, containing_block| {
- if fragment.tag() == Some(tag_to_find) {
- Some(fragment.scrolling_area(containing_block))
- } else {
- None
- }
- });
- scroll_area.unwrap_or_else(PhysicalRect::<Au>::zero)
- }
}
diff --git a/components/layout/fragment_tree/positioning_fragment.rs b/components/layout/fragment_tree/positioning_fragment.rs
index 853caed6709..1fe968eb484 100644
--- a/components/layout/fragment_tree/positioning_fragment.rs
+++ b/components/layout/fragment_tree/positioning_fragment.rs
@@ -20,12 +20,17 @@ pub(crate) struct PositioningFragment {
pub base: BaseFragment,
pub rect: PhysicalRect<Au>,
pub children: Vec<Fragment>,
+
/// The scrollable overflow of this anonymous fragment's children.
pub scrollable_overflow: PhysicalRect<Au>,
/// If this fragment was created with a style, the style of the fragment.
#[conditional_malloc_size_of]
pub style: Option<ServoArc<ComputedValues>>,
+
+ /// This [`PositioningFragment`]'s containing block rectangle in coordinates relative to
+ /// the initial containing block, but not taking into account any transforms.
+ pub cumulative_containing_block_rect: PhysicalRect<Au>,
}
impl PositioningFragment {
@@ -61,9 +66,18 @@ impl PositioningFragment {
rect,
children,
scrollable_overflow,
+ cumulative_containing_block_rect: PhysicalRect::zero(),
})
}
+ pub(crate) fn set_containing_block(&mut self, containing_block: &PhysicalRect<Au>) {
+ self.cumulative_containing_block_rect = *containing_block;
+ }
+
+ pub fn offset_by_containing_block(&self, rect: &PhysicalRect<Au>) -> PhysicalRect<Au> {
+ rect.translate(self.cumulative_containing_block_rect.origin.to_vector())
+ }
+
pub fn print(&self, tree: &mut PrintTree) {
tree.new_level(format!(
"PositioningFragment\
diff --git a/components/layout/layout_impl.rs b/components/layout/layout_impl.rs
index 61550df2723..3110899d76e 100644
--- a/components/layout/layout_impl.rs
+++ b/components/layout/layout_impl.rs
@@ -23,7 +23,7 @@ use euclid::{Point2D, Scale, Size2D, Vector2D};
use fnv::FnvHashMap;
use fonts::{FontContext, FontContextWebFontMethods};
use fonts_traits::StylesheetWebFontLoadFinishedCallback;
-use fxhash::{FxHashMap, FxHashSet};
+use fxhash::FxHashMap;
use ipc_channel::ipc::IpcSender;
use log::{debug, error};
use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOf, MallocSizeOfOps};
@@ -34,21 +34,22 @@ use profile_traits::time::{
self as profile_time, TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType,
};
use profile_traits::{path, time_profile};
-use script::layout_dom::{ServoLayoutElement, ServoLayoutNode};
+use rayon::ThreadPool;
+use script::layout_dom::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode};
use script_layout_interface::{
- ImageAnimationState, Layout, LayoutConfig, LayoutFactory, NodesFromPointQueryType,
- OffsetParentResponse, ReflowGoal, ReflowRequest, ReflowResult, TrustedNodeAddress,
+ Layout, LayoutConfig, LayoutFactory, NodesFromPointQueryType, OffsetParentResponse, ReflowGoal,
+ ReflowRequest, ReflowResult, TrustedNodeAddress,
};
use script_traits::{DrawAPaintImageResult, PaintWorkletError, Painter, ScriptThreadMessage};
use servo_arc::Arc as ServoArc;
use servo_config::opts::{self, DebugOptions};
use servo_config::pref;
use servo_url::ServoUrl;
-use style::animation::{AnimationSetKey, DocumentAnimationSet};
+use style::animation::DocumentAnimationSet;
use style::context::{
QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters, SharedStyleContext,
};
-use style::dom::{OpaqueNode, TElement, TNode};
+use style::dom::{OpaqueNode, ShowSubtreeDataAndPrimaryValues, TElement, TNode};
use style::error_reporting::RustLogReporter;
use style::font_metrics::FontMetrics;
use style::global_style_data::GLOBAL_STYLE_DATA;
@@ -80,8 +81,8 @@ use webrender_api::{ExternalScrollId, HitTestFlags};
use crate::context::LayoutContext;
use crate::display_list::{DisplayList, WebRenderImageInfo};
use crate::query::{
- get_the_text_steps, process_content_box_request, process_content_boxes_request,
- process_node_geometry_request, process_node_scroll_area_request, process_offset_parent_query,
+ get_the_text_steps, process_client_rect_request, process_content_box_request,
+ process_content_boxes_request, process_node_scroll_area_request, process_offset_parent_query,
process_resolved_font_style_query, process_resolved_style_request, process_text_index_request,
};
use crate::traversal::RecalcStyle;
@@ -95,6 +96,10 @@ use crate::{BoxTree, FragmentTree};
static STYLE_THREAD_POOL: Mutex<&style::global_style_data::STYLE_THREAD_POOL> =
Mutex::new(&style::global_style_data::STYLE_THREAD_POOL);
+thread_local!(static SEEN_POINTERS: LazyCell<RefCell<HashSet<*const c_void>>> = const {
+ LazyCell::new(|| RefCell::new(HashSet::new()))
+});
+
/// Information needed by layout.
pub struct LayoutThread {
/// The ID of the pipeline that we belong to.
@@ -230,24 +235,27 @@ impl Layout for LayoutThread {
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
- fn query_content_box(&self, node: OpaqueNode) -> Option<UntypedRect<Au>> {
- process_content_box_request(node, self.fragment_tree.borrow().clone())
+ fn query_content_box(&self, node: TrustedNodeAddress) -> Option<UntypedRect<Au>> {
+ let node = unsafe { ServoLayoutNode::new(&node) };
+ process_content_box_request(node)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
- fn query_content_boxes(&self, node: OpaqueNode) -> Vec<UntypedRect<Au>> {
- process_content_boxes_request(node, self.fragment_tree.borrow().clone())
+ fn query_content_boxes(&self, node: TrustedNodeAddress) -> Vec<UntypedRect<Au>> {
+ let node = unsafe { ServoLayoutNode::new(&node) };
+ process_content_boxes_request(node)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
- fn query_client_rect(&self, node: OpaqueNode) -> UntypedRect<i32> {
- process_node_geometry_request(node, self.fragment_tree.borrow().clone())
+ fn query_client_rect(&self, node: TrustedNodeAddress) -> UntypedRect<i32> {
+ let node = unsafe { ServoLayoutNode::new(&node) };
+ process_client_rect_request(node)
}
#[cfg_attr(
@@ -292,8 +300,9 @@ impl Layout for LayoutThread {
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
- fn query_offset_parent(&self, node: OpaqueNode) -> OffsetParentResponse {
- process_offset_parent_query(node, self.fragment_tree.borrow().clone())
+ fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse {
+ let node = unsafe { ServoLayoutNode::new(&node) };
+ process_offset_parent_query(node).unwrap_or_default()
}
#[cfg_attr(
@@ -325,14 +334,7 @@ impl Layout for LayoutThread {
TraversalFlags::empty(),
);
- let fragment_tree = self.fragment_tree.borrow().clone();
- process_resolved_style_request(
- &shared_style_context,
- node,
- &pseudo,
- &property_id,
- fragment_tree,
- )
+ process_resolved_style_request(&shared_style_context, node, &pseudo, &property_id)
}
#[cfg_attr(
@@ -375,7 +377,8 @@ impl Layout for LayoutThread {
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
- fn query_scrolling_area(&self, node: Option<OpaqueNode>) -> UntypedRect<i32> {
+ fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> UntypedRect<i32> {
+ let node = node.map(|node| unsafe { ServoLayoutNode::new(&node) });
process_node_scroll_area_request(node, self.fragment_tree.borrow().clone())
}
@@ -545,43 +548,6 @@ impl LayoutThread {
}
}
- #[allow(clippy::too_many_arguments)]
- // Create a layout context for use in building display lists, hit testing, &c.
- #[allow(clippy::too_many_arguments)]
- fn build_layout_context<'a>(
- &'a self,
- guards: StylesheetGuards<'a>,
- snapshot_map: &'a SnapshotMap,
- reflow_request: &mut ReflowRequest,
- use_rayon: bool,
- ) -> LayoutContext<'a> {
- let traversal_flags = match reflow_request.stylesheets_changed {
- true => TraversalFlags::ForCSSRuleChanges,
- false => TraversalFlags::empty(),
- };
-
- LayoutContext {
- id: self.id,
- origin: reflow_request.origin.clone(),
- style_context: self.build_shared_style_context(
- guards,
- snapshot_map,
- reflow_request.animation_timeline_value,
- &reflow_request.animations,
- traversal_flags,
- ),
- image_cache: self.image_cache.clone(),
- font_context: self.font_context.clone(),
- webrender_image_cache: self.webrender_image_cache.clone(),
- pending_images: Mutex::default(),
- node_image_animation_map: Arc::new(RwLock::new(std::mem::take(
- &mut reflow_request.node_to_image_animation_map,
- ))),
- iframe_sizes: Mutex::default(),
- use_rayon,
- }
- }
-
fn load_all_web_fonts_from_stylesheet_with_guard(
&self,
stylesheet: &DocumentStyleSheet,
@@ -621,51 +587,126 @@ impl LayoutThread {
return None;
};
- // Calculate the actual viewport as per DEVICE-ADAPT § 6
- // If the entire flow tree is invalid, then it will be reflowed anyhow.
let document_shared_lock = document.style_shared_lock();
let author_guard = document_shared_lock.read();
-
let ua_stylesheets = &*UA_STYLESHEETS;
let ua_or_user_guard = ua_stylesheets.shared_lock.read();
+ let rayon_pool = STYLE_THREAD_POOL.lock();
+ let rayon_pool = rayon_pool.pool();
+ let rayon_pool = rayon_pool.as_ref();
let guards = StylesheetGuards {
author: &author_guard,
ua_or_user: &ua_or_user_guard,
};
- let had_used_viewport_units = self.stylist.device().used_viewport_units();
- let viewport_size_changed = self.viewport_did_change(reflow_request.viewport_details);
- let theme_changed = self.theme_did_change(reflow_request.theme);
-
- if viewport_size_changed || theme_changed {
- self.update_device(
- reflow_request.viewport_details,
- reflow_request.theme,
- &guards,
- );
- }
-
- if viewport_size_changed && had_used_viewport_units {
+ if self.update_device_if_necessary(&reflow_request, &guards) {
if let Some(mut data) = root_element.mutate_data() {
data.hint.insert(RestyleHint::recascade_subtree());
}
}
+ let mut snapshot_map = SnapshotMap::new();
+ let _snapshot_setter = SnapshotSetter::new(&mut reflow_request, &mut snapshot_map);
+ self.prepare_stylist_for_reflow(
+ &reflow_request,
+ document,
+ root_element,
+ &guards,
+ ua_stylesheets,
+ &snapshot_map,
+ );
+
+ let mut layout_context = LayoutContext {
+ id: self.id,
+ origin: reflow_request.origin.clone(),
+ style_context: self.build_shared_style_context(
+ guards,
+ &snapshot_map,
+ reflow_request.animation_timeline_value,
+ &reflow_request.animations,
+ match reflow_request.stylesheets_changed {
+ true => TraversalFlags::ForCSSRuleChanges,
+ false => TraversalFlags::empty(),
+ },
+ ),
+ image_cache: self.image_cache.clone(),
+ font_context: self.font_context.clone(),
+ webrender_image_cache: self.webrender_image_cache.clone(),
+ pending_images: Mutex::default(),
+ node_image_animation_map: Arc::new(RwLock::new(std::mem::take(
+ &mut reflow_request.node_to_image_animation_map,
+ ))),
+ iframe_sizes: Mutex::default(),
+ use_rayon: rayon_pool.is_some(),
+ };
+
+ self.restyle_and_build_trees(
+ &reflow_request,
+ root_element,
+ rayon_pool,
+ &mut layout_context,
+ );
+ self.build_display_list(&reflow_request, &mut layout_context);
+ self.first_reflow.set(false);
+
+ if let ReflowGoal::UpdateScrollNode(scroll_state) = reflow_request.reflow_goal {
+ self.update_scroll_node_state(&scroll_state);
+ }
+
+ let pending_images = std::mem::take(&mut *layout_context.pending_images.lock());
+ let iframe_sizes = std::mem::take(&mut *layout_context.iframe_sizes.lock());
+ let node_to_image_animation_map =
+ std::mem::take(&mut *layout_context.node_image_animation_map.write());
+ Some(ReflowResult {
+ pending_images,
+ iframe_sizes,
+ node_to_image_animation_map,
+ })
+ }
+
+ fn update_device_if_necessary(
+ &mut self,
+ reflow_request: &ReflowRequest,
+ guards: &StylesheetGuards,
+ ) -> bool {
+ let had_used_viewport_units = self.stylist.device().used_viewport_units();
+ let viewport_size_changed = self.viewport_did_change(reflow_request.viewport_details);
+ let theme_changed = self.theme_did_change(reflow_request.theme);
+ if !viewport_size_changed && !theme_changed {
+ return false;
+ }
+ self.update_device(
+ reflow_request.viewport_details,
+ reflow_request.theme,
+ guards,
+ );
+ (viewport_size_changed && had_used_viewport_units) || theme_changed
+ }
+
+ fn prepare_stylist_for_reflow<'dom>(
+ &mut self,
+ reflow_request: &ReflowRequest,
+ document: ServoLayoutDocument<'dom>,
+ root_element: ServoLayoutElement<'dom>,
+ guards: &StylesheetGuards,
+ ua_stylesheets: &UserAgentStylesheets,
+ snapshot_map: &SnapshotMap,
+ ) {
if self.first_reflow.get() {
for stylesheet in &ua_stylesheets.user_or_user_agent_stylesheets {
self.stylist
- .append_stylesheet(stylesheet.clone(), &ua_or_user_guard);
- self.load_all_web_fonts_from_stylesheet_with_guard(stylesheet, &ua_or_user_guard);
+ .append_stylesheet(stylesheet.clone(), guards.ua_or_user);
+ self.load_all_web_fonts_from_stylesheet_with_guard(stylesheet, guards.ua_or_user);
}
if self.stylist.quirks_mode() != QuirksMode::NoQuirks {
self.stylist.append_stylesheet(
ua_stylesheets.quirks_mode_stylesheet.clone(),
- &ua_or_user_guard,
+ guards.ua_or_user,
);
self.load_all_web_fonts_from_stylesheet_with_guard(
&ua_stylesheets.quirks_mode_stylesheet,
- &ua_or_user_guard,
+ guards.ua_or_user,
);
}
}
@@ -678,185 +719,105 @@ impl LayoutThread {
// Flush shadow roots stylesheets if dirty.
document.flush_shadow_roots_stylesheets(&mut self.stylist, guards.author);
- let restyles = std::mem::take(&mut reflow_request.pending_restyles);
- debug!("Draining restyles: {}", restyles.len());
-
- let mut map = SnapshotMap::new();
- let elements_with_snapshot: Vec<_> = restyles
- .iter()
- .filter(|r| r.1.snapshot.is_some())
- .map(|r| unsafe { ServoLayoutNode::new(&r.0).as_element().unwrap() })
- .collect();
-
- for (el, restyle) in restyles {
- let el = unsafe { ServoLayoutNode::new(&el).as_element().unwrap() };
-
- // If we haven't styled this node yet, we don't need to track a
- // restyle.
- let mut style_data = match el.mutate_data() {
- Some(d) => d,
- None => {
- unsafe { el.unset_snapshot_flags() };
- continue;
- },
- };
-
- if let Some(s) = restyle.snapshot {
- unsafe { el.set_has_snapshot() };
- map.insert(el.as_node().opaque(), s);
- }
-
- // Stash the data on the element for processing by the style system.
- style_data.hint.insert(restyle.hint);
- style_data.damage = restyle.damage;
- debug!("Noting restyle for {:?}: {:?}", el, style_data);
- }
-
- self.stylist.flush(&guards, Some(root_element), Some(&map));
-
- let rayon_pool = STYLE_THREAD_POOL.lock();
- let rayon_pool = rayon_pool.pool();
- let rayon_pool = rayon_pool.as_ref();
-
- // Create a layout context for use throughout the following passes.
- let mut layout_context = self.build_layout_context(
- guards.clone(),
- &map,
- &mut reflow_request,
- rayon_pool.is_some(),
- );
+ self.stylist
+ .flush(guards, Some(root_element), Some(snapshot_map));
+ }
+ #[cfg_attr(
+ feature = "tracing",
+ tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
+ )]
+ fn restyle_and_build_trees(
+ &self,
+ reflow_request: &ReflowRequest,
+ root_element: ServoLayoutElement<'_>,
+ rayon_pool: Option<&ThreadPool>,
+ layout_context: &mut LayoutContext<'_>,
+ ) {
let dirty_root = unsafe {
ServoLayoutNode::new(&reflow_request.dirty_root.unwrap())
.as_element()
.unwrap()
};
- let traversal = RecalcStyle::new(layout_context);
+ let recalc_style_traversal = RecalcStyle::new(layout_context);
let token = {
- let shared = DomTraversal::<ServoLayoutElement>::shared_context(&traversal);
+ let shared =
+ DomTraversal::<ServoLayoutElement>::shared_context(&recalc_style_traversal);
RecalcStyle::pre_traverse(dirty_root, shared)
};
- if token.should_traverse() {
- #[cfg(feature = "tracing")]
- let _span =
- tracing::trace_span!("driver::traverse_dom", servo_profiling = true).entered();
- let dirty_root: ServoLayoutNode =
- driver::traverse_dom(&traversal, token, rayon_pool).as_node();
-
- let root_node = root_element.as_node();
- let mut box_tree = self.box_tree.borrow_mut();
- let box_tree = &mut *box_tree;
- let mut build_box_tree = || {
- if !BoxTree::update(traversal.context(), dirty_root) {
- *box_tree = Some(Arc::new(BoxTree::construct(traversal.context(), root_node)));
- }
- };
- if let Some(pool) = rayon_pool {
- pool.install(build_box_tree)
- } else {
- build_box_tree()
- };
-
- let viewport_size = Size2D::new(
- self.viewport_size.width.to_f32_px(),
- self.viewport_size.height.to_f32_px(),
- );
- let run_layout = || {
- box_tree
- .as_ref()
- .unwrap()
- .layout(traversal.context(), viewport_size)
- };
- let fragment_tree = Arc::new(if let Some(pool) = rayon_pool {
- pool.install(run_layout)
- } else {
- run_layout()
- });
- *self.fragment_tree.borrow_mut() = Some(fragment_tree);
+ if !token.should_traverse() {
+ layout_context.style_context.stylist.rule_tree().maybe_gc();
+ return;
}
- layout_context = traversal.destroy();
+ let dirty_root: ServoLayoutNode =
+ driver::traverse_dom(&recalc_style_traversal, token, rayon_pool).as_node();
+
+ let root_node = root_element.as_node();
+ let mut box_tree = self.box_tree.borrow_mut();
+ let box_tree = &mut *box_tree;
+ let mut build_box_tree = || {
+ if !BoxTree::update(recalc_style_traversal.context(), dirty_root) {
+ *box_tree = Some(Arc::new(BoxTree::construct(
+ recalc_style_traversal.context(),
+ root_node,
+ )));
+ }
+ };
+ if let Some(pool) = rayon_pool {
+ pool.install(build_box_tree)
+ } else {
+ build_box_tree()
+ };
+
+ let viewport_size = Size2D::new(
+ self.viewport_size.width.to_f32_px(),
+ self.viewport_size.height.to_f32_px(),
+ );
+ let run_layout = || {
+ box_tree
+ .as_ref()
+ .unwrap()
+ .layout(recalc_style_traversal.context(), viewport_size)
+ };
+ let fragment_tree = Arc::new(if let Some(pool) = rayon_pool {
+ pool.install(run_layout)
+ } else {
+ run_layout()
+ });
- for element in elements_with_snapshot {
- unsafe { element.unset_snapshot_flags() }
- }
+ *self.fragment_tree.borrow_mut() = Some(fragment_tree);
if self.debug.dump_style_tree {
println!(
"{:?}",
- style::dom::ShowSubtreeDataAndPrimaryValues(root_element.as_node())
+ ShowSubtreeDataAndPrimaryValues(root_element.as_node())
);
}
-
if self.debug.dump_rule_tree {
- layout_context
+ recalc_style_traversal
+ .context()
.style_context
.stylist
.rule_tree()
- .dump_stdout(&guards);
+ .dump_stdout(&layout_context.shared_context().guards);
}
// GC the rule tree if some heuristics are met.
layout_context.style_context.stylist.rule_tree().maybe_gc();
-
- // Perform post-style recalculation layout passes.
- if let Some(root) = &*self.fragment_tree.borrow() {
- self.perform_post_style_recalc_layout_passes(
- root.clone(),
- &reflow_request.reflow_goal,
- &mut layout_context,
- );
- }
-
- self.first_reflow.set(false);
-
- if let ReflowGoal::UpdateScrollNode(scroll_state) = reflow_request.reflow_goal {
- self.update_scroll_node_state(&scroll_state);
- }
-
- let pending_images = std::mem::take(&mut *layout_context.pending_images.lock());
- let iframe_sizes = std::mem::take(&mut *layout_context.iframe_sizes.lock());
- let node_to_image_animation_map =
- std::mem::take(&mut *layout_context.node_image_animation_map.write());
- Some(ReflowResult {
- pending_images,
- iframe_sizes,
- node_to_image_animation_map,
- })
}
- fn update_scroll_node_state(&self, state: &ScrollState) {
- self.scroll_offsets
- .borrow_mut()
- .insert(state.scroll_id, state.scroll_offset);
- let point = Point2D::new(-state.scroll_offset.x, -state.scroll_offset.y);
- self.compositor_api.send_scroll_node(
- self.webview_id,
- self.id.into(),
- LayoutPoint::from_untyped(point),
- state.scroll_id,
- );
- }
-
- fn perform_post_style_recalc_layout_passes(
+ fn build_display_list(
&self,
- fragment_tree: Arc<FragmentTree>,
- reflow_goal: &ReflowGoal,
- context: &mut LayoutContext,
+ reflow_request: &ReflowRequest,
+ layout_context: &mut LayoutContext<'_>,
) {
- Self::cancel_animations_for_nodes_not_in_fragment_tree(
- &context.style_context.animations,
- &fragment_tree,
- );
-
- Self::cancel_image_animation_for_nodes_not_in_fragment_tree(
- context.node_image_animation_map.clone(),
- &fragment_tree,
- );
-
- if !reflow_goal.needs_display_list() {
+ let Some(fragment_tree) = &*self.fragment_tree.borrow() else {
+ return;
+ };
+ if !reflow_request.reflow_goal.needs_display_list() {
return;
}
@@ -890,10 +851,10 @@ impl LayoutThread {
// tree of fragments in CSS painting order and also creates all
// applicable spatial and clip nodes.
let root_stacking_context =
- display_list.build_stacking_context_tree(&fragment_tree, &self.debug);
+ display_list.build_stacking_context_tree(fragment_tree, &self.debug);
// Build the rest of the display list which inclues all of the WebRender primitives.
- display_list.build(context, &fragment_tree, &root_stacking_context);
+ display_list.build(layout_context, fragment_tree, &root_stacking_context);
if self.debug.dump_flow_tree {
fragment_tree.print();
@@ -901,9 +862,8 @@ impl LayoutThread {
if self.debug.dump_stacking_context_tree {
root_stacking_context.debug_print();
}
- debug!("Layout done!");
- if reflow_goal.needs_display() {
+ if reflow_request.reflow_goal.needs_display() {
self.compositor_api.send_display_list(
self.webview_id,
display_list.compositor_info,
@@ -918,6 +878,19 @@ impl LayoutThread {
}
}
+ fn update_scroll_node_state(&self, state: &ScrollState) {
+ self.scroll_offsets
+ .borrow_mut()
+ .insert(state.scroll_id, state.scroll_offset);
+ let point = Point2D::new(-state.scroll_offset.x, -state.scroll_offset.y);
+ self.compositor_api.send_scroll_node(
+ self.webview_id,
+ self.id.into(),
+ LayoutPoint::from_untyped(point),
+ state.scroll_id,
+ );
+ }
+
/// Returns profiling information which is passed to the time profiler.
fn profiler_metadata(&self) -> Option<TimerMetadata> {
Some(TimerMetadata {
@@ -935,42 +908,6 @@ impl LayoutThread {
})
}
- /// Cancel animations for any nodes which have been removed from fragment tree.
- /// TODO(mrobinson): We should look into a way of doing this during flow tree construction.
- /// This also doesn't yet handles nodes that have been reparented.
- fn cancel_animations_for_nodes_not_in_fragment_tree(
- animations: &DocumentAnimationSet,
- root: &FragmentTree,
- ) {
- // Assume all nodes have been removed until proven otherwise.
- let mut animations = animations.sets.write();
- let mut invalid_nodes = animations.keys().cloned().collect();
- root.remove_nodes_in_fragment_tree_from_set(&mut invalid_nodes);
-
- // Cancel animations for any nodes that are no longer in the fragment tree.
- for node in &invalid_nodes {
- if let Some(state) = animations.get_mut(node) {
- state.cancel_all_animations();
- }
- }
- }
-
- fn cancel_image_animation_for_nodes_not_in_fragment_tree(
- image_animation_set: Arc<RwLock<FxHashMap<OpaqueNode, ImageAnimationState>>>,
- root: &FragmentTree,
- ) {
- let mut image_animations = image_animation_set.write().to_owned();
- let mut invalid_nodes: FxHashSet<AnimationSetKey> = image_animations
- .keys()
- .cloned()
- .map(|node| AnimationSetKey::new(node, None))
- .collect();
- root.remove_nodes_in_fragment_tree_from_set(&mut invalid_nodes);
- for node in &invalid_nodes {
- image_animations.remove(&node.node);
- }
- }
-
fn viewport_did_change(&mut self, viewport_details: ViewportDetails) -> bool {
let new_pixel_ratio = viewport_details.hidpi_scale_factor.get();
let new_viewport_size = Size2D::new(
@@ -1230,6 +1167,54 @@ impl Debug for LayoutFontMetricsProvider {
}
}
-thread_local!(static SEEN_POINTERS: LazyCell<RefCell<HashSet<*const c_void>>> = const {
- LazyCell::new(|| RefCell::new(HashSet::new()))
-});
+struct SnapshotSetter<'dom> {
+ elements_with_snapshot: Vec<ServoLayoutElement<'dom>>,
+}
+
+impl SnapshotSetter<'_> {
+ fn new(reflow_request: &mut ReflowRequest, snapshot_map: &mut SnapshotMap) -> Self {
+ debug!(
+ "Draining restyles: {}",
+ reflow_request.pending_restyles.len()
+ );
+ let restyles = std::mem::take(&mut reflow_request.pending_restyles);
+
+ let elements_with_snapshot: Vec<_> = restyles
+ .iter()
+ .filter(|r| r.1.snapshot.is_some())
+ .map(|r| unsafe { ServoLayoutNode::new(&r.0).as_element().unwrap() })
+ .collect();
+
+ for (element, restyle) in restyles {
+ let element = unsafe { ServoLayoutNode::new(&element).as_element().unwrap() };
+
+ // If we haven't styled this node yet, we don't need to track a
+ // restyle.
+ let Some(mut style_data) = element.mutate_data() else {
+ unsafe { element.unset_snapshot_flags() };
+ continue;
+ };
+
+ debug!("Noting restyle for {:?}: {:?}", element, style_data);
+ if let Some(s) = restyle.snapshot {
+ unsafe { element.set_has_snapshot() };
+ snapshot_map.insert(element.as_node().opaque(), s);
+ }
+
+ // Stash the data on the element for processing by the style system.
+ style_data.hint.insert(restyle.hint);
+ style_data.damage = restyle.damage;
+ }
+ Self {
+ elements_with_snapshot,
+ }
+ }
+}
+
+impl Drop for SnapshotSetter<'_> {
+ fn drop(&mut self) {
+ for element in &self.elements_with_snapshot {
+ unsafe { element.unset_snapshot_flags() }
+ }
+ }
+}
diff --git a/components/layout/query.rs b/components/layout/query.rs
index 08b264deea9..e78acdd0ca8 100644
--- a/components/layout/query.rs
+++ b/components/layout/query.rs
@@ -7,7 +7,7 @@ use std::sync::Arc;
use app_units::Au;
use euclid::default::{Point2D, Rect};
-use euclid::{SideOffsets2D, Size2D, Vector2D};
+use euclid::{SideOffsets2D, Size2D};
use itertools::Itertools;
use script_layout_interface::wrapper_traits::{
LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
@@ -38,59 +38,60 @@ use style::values::specified::GenericGridTemplateComponent;
use style::values::specified::box_::DisplayInside;
use style_traits::{ParsingMode, ToCss};
+use crate::ArcRefCell;
use crate::dom::NodeExt;
use crate::flow::inline::construct::{TextTransformation, WhitespaceCollapse};
use crate::fragment_tree::{
- BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo, Tag,
+ BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo,
};
-use crate::geom::{PhysicalRect, PhysicalVec};
use crate::taffy::SpecificTaffyGridInfo;
-pub fn process_content_box_request(
- requested_node: OpaqueNode,
- fragment_tree: Option<Arc<FragmentTree>>,
-) -> Option<Rect<Au>> {
- let rects = fragment_tree?.get_content_boxes_for_node(requested_node);
+pub fn process_content_box_request<'dom>(node: impl LayoutNode<'dom> + 'dom) -> Option<Rect<Au>> {
+ let rects: Vec<_> = node
+ .fragments_for_pseudo(None)
+ .iter()
+ .filter_map(Fragment::cumulative_border_box_rect)
+ .collect();
if rects.is_empty() {
return None;
}
- Some(
- rects
- .iter()
- .fold(Rect::zero(), |unioned_rect, rect| rect.union(&unioned_rect)),
- )
+ Some(rects.iter().fold(Rect::zero(), |unioned_rect, rect| {
+ rect.to_untyped().union(&unioned_rect)
+ }))
}
-pub fn process_content_boxes_request(
- requested_node: OpaqueNode,
- fragment_tree: Option<Arc<FragmentTree>>,
-) -> Vec<Rect<Au>> {
- fragment_tree
- .map(|tree| tree.get_content_boxes_for_node(requested_node))
- .unwrap_or_default()
+pub fn process_content_boxes_request<'dom>(node: impl LayoutNode<'dom> + 'dom) -> Vec<Rect<Au>> {
+ node.fragments_for_pseudo(None)
+ .iter()
+ .filter_map(Fragment::cumulative_border_box_rect)
+ .map(|rect| rect.to_untyped())
+ .collect()
}
-pub fn process_node_geometry_request(
- requested_node: OpaqueNode,
- fragment_tree: Option<Arc<FragmentTree>>,
-) -> Rect<i32> {
- if let Some(fragment_tree) = fragment_tree {
- fragment_tree.get_border_dimensions_for_node(requested_node)
- } else {
- Rect::zero()
- }
+pub fn process_client_rect_request<'dom>(node: impl LayoutNode<'dom> + 'dom) -> Rect<i32> {
+ node.fragments_for_pseudo(None)
+ .first()
+ .map(Fragment::client_rect)
+ .unwrap_or_default()
}
/// <https://drafts.csswg.org/cssom-view/#scrolling-area>
-pub fn process_node_scroll_area_request(
- requested_node: Option<OpaqueNode>,
+pub fn process_node_scroll_area_request<'dom>(
+ requested_node: Option<impl LayoutNode<'dom> + 'dom>,
fragment_tree: Option<Arc<FragmentTree>>,
) -> Rect<i32> {
- let rect = match (fragment_tree, requested_node) {
- (Some(tree), Some(node)) => tree.get_scrolling_area_for_node(node),
- (Some(tree), None) => tree.get_scrolling_area_for_viewport(),
- _ => return Rect::zero(),
+ let Some(tree) = fragment_tree else {
+ return Rect::zero();
+ };
+
+ let rect = match requested_node {
+ Some(node) => node
+ .fragments_for_pseudo(None)
+ .first()
+ .map(Fragment::scrolling_area)
+ .unwrap_or_default(),
+ None => tree.get_scrolling_area_for_viewport(),
};
Rect::new(
@@ -109,7 +110,6 @@ pub fn process_resolved_style_request<'dom>(
node: impl LayoutNode<'dom> + 'dom,
pseudo: &Option<PseudoElement>,
property: &PropertyId,
- fragment_tree: Option<Arc<FragmentTree>>,
) -> String {
if !node.as_element().unwrap().has_data() {
return process_resolved_style_request_for_unstyled_node(context, node, pseudo, property);
@@ -161,8 +161,6 @@ pub fn process_resolved_style_request<'dom>(
_ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
};
- let tag_to_find = Tag::new_pseudo(node.opaque(), *pseudo);
-
// https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle
// Here we are trying to conform to the specification that says that getComputedStyle
// should return the used values in certain circumstances. For size and positional
@@ -191,107 +189,87 @@ pub fn process_resolved_style_request<'dom>(
return computed_style(None);
}
- let resolve_for_fragment =
- |fragment: &Fragment, containing_block: Option<&PhysicalRect<Au>>| {
- let (content_rect, margins, padding, specific_layout_info) = match fragment {
- Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
- let box_fragment = box_fragment.borrow();
- if style.get_box().position != Position::Static {
- let resolved_insets = || {
- box_fragment
- .calculate_resolved_insets_if_positioned(containing_block.unwrap())
- };
- match longhand_id {
- LonghandId::Top => return resolved_insets().top.to_css_string(),
- LonghandId::Right => {
- return resolved_insets().right.to_css_string();
- },
- LonghandId::Bottom => {
- return resolved_insets().bottom.to_css_string();
- },
- LonghandId::Left => {
- return resolved_insets().left.to_css_string();
- },
- _ => {},
- }
- }
- let content_rect = box_fragment.content_rect;
- let margins = box_fragment.margin;
- let padding = box_fragment.padding;
- let specific_layout_info = box_fragment.specific_layout_info.clone();
- (content_rect, margins, padding, specific_layout_info)
- },
- Fragment::Positioning(positioning_fragment) => {
- let content_rect = positioning_fragment.borrow().rect;
- (
- content_rect,
- SideOffsets2D::zero(),
- SideOffsets2D::zero(),
- None,
- )
- },
- _ => return computed_style(Some(fragment)),
- };
-
- // https://drafts.csswg.org/css-grid/#resolved-track-list
- // > The grid-template-rows and grid-template-columns properties are
- // > resolved value special case properties.
- //
- // > When an element generates a grid container box...
- if display.inside() == DisplayInside::Grid {
- if let Some(SpecificLayoutInfo::Grid(info)) = specific_layout_info {
- if let Some(value) = resolve_grid_template(&info, style, longhand_id) {
- return value;
+ let resolve_for_fragment = |fragment: &Fragment| {
+ let (content_rect, margins, padding, specific_layout_info) = match fragment {
+ Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
+ let box_fragment = box_fragment.borrow();
+ if style.get_box().position != Position::Static {
+ let resolved_insets = || box_fragment.calculate_resolved_insets_if_positioned();
+ match longhand_id {
+ LonghandId::Top => return resolved_insets().top.to_css_string(),
+ LonghandId::Right => {
+ return resolved_insets().right.to_css_string();
+ },
+ LonghandId::Bottom => {
+ return resolved_insets().bottom.to_css_string();
+ },
+ LonghandId::Left => {
+ return resolved_insets().left.to_css_string();
+ },
+ _ => {},
}
}
- }
+ let content_rect = box_fragment.content_rect;
+ let margins = box_fragment.margin;
+ let padding = box_fragment.padding;
+ let specific_layout_info = box_fragment.specific_layout_info.clone();
+ (content_rect, margins, padding, specific_layout_info)
+ },
+ Fragment::Positioning(positioning_fragment) => {
+ let content_rect = positioning_fragment.borrow().rect;
+ (
+ content_rect,
+ SideOffsets2D::zero(),
+ SideOffsets2D::zero(),
+ None,
+ )
+ },
+ _ => return computed_style(Some(fragment)),
+ };
- // https://drafts.csswg.org/cssom/#resolved-value-special-case-property-like-height
- // > If the property applies to the element or pseudo-element and the resolved value of the
- // > display property is not none or contents, then the resolved value is the used value.
- // > Otherwise the resolved value is the computed value.
- //
- // However, all browsers ignore that for margin and padding properties, and resolve to a length
- // even if the property doesn't apply: https://github.com/w3c/csswg-drafts/issues/10391
- match longhand_id {
- LonghandId::Width if resolved_size_should_be_used_value(fragment) => {
- content_rect.size.width
- },
- LonghandId::Height if resolved_size_should_be_used_value(fragment) => {
- content_rect.size.height
- },
- LonghandId::MarginBottom => margins.bottom,
- LonghandId::MarginTop => margins.top,
- LonghandId::MarginLeft => margins.left,
- LonghandId::MarginRight => margins.right,
- LonghandId::PaddingBottom => padding.bottom,
- LonghandId::PaddingTop => padding.top,
- LonghandId::PaddingLeft => padding.left,
- LonghandId::PaddingRight => padding.right,
- _ => return computed_style(Some(fragment)),
+ // https://drafts.csswg.org/css-grid/#resolved-track-list
+ // > The grid-template-rows and grid-template-columns properties are
+ // > resolved value special case properties.
+ //
+ // > When an element generates a grid container box...
+ if display.inside() == DisplayInside::Grid {
+ if let Some(SpecificLayoutInfo::Grid(info)) = specific_layout_info {
+ if let Some(value) = resolve_grid_template(&info, style, longhand_id) {
+ return value;
+ }
}
- .to_css_string()
- };
+ }
- if !matches!(
- longhand_id,
- LonghandId::Top | LonghandId::Bottom | LonghandId::Left | LonghandId::Right
- ) {
- if let Some(fragment) = node.fragments_for_pseudo(*pseudo).first() {
- return resolve_for_fragment(fragment, None);
+ // https://drafts.csswg.org/cssom/#resolved-value-special-case-property-like-height
+ // > If the property applies to the element or pseudo-element and the resolved value of the
+ // > display property is not none or contents, then the resolved value is the used value.
+ // > Otherwise the resolved value is the computed value.
+ //
+ // However, all browsers ignore that for margin and padding properties, and resolve to a length
+ // even if the property doesn't apply: https://github.com/w3c/csswg-drafts/issues/10391
+ match longhand_id {
+ LonghandId::Width if resolved_size_should_be_used_value(fragment) => {
+ content_rect.size.width
+ },
+ LonghandId::Height if resolved_size_should_be_used_value(fragment) => {
+ content_rect.size.height
+ },
+ LonghandId::MarginBottom => margins.bottom,
+ LonghandId::MarginTop => margins.top,
+ LonghandId::MarginLeft => margins.left,
+ LonghandId::MarginRight => margins.right,
+ LonghandId::PaddingBottom => padding.bottom,
+ LonghandId::PaddingTop => padding.top,
+ LonghandId::PaddingLeft => padding.left,
+ LonghandId::PaddingRight => padding.right,
+ _ => return computed_style(Some(fragment)),
}
- }
+ .to_css_string()
+ };
- fragment_tree
- .and_then(|fragment_tree| {
- fragment_tree.find(|fragment, _, containing_block| {
- if Some(tag_to_find) == fragment.tag() {
- Some(resolve_for_fragment(fragment, Some(containing_block)))
- } else {
- None
- }
- })
- })
+ node.fragments_for_pseudo(*pseudo)
+ .first()
+ .map(resolve_for_fragment)
.unwrap_or_else(|| computed_style(None))
}
@@ -450,231 +428,157 @@ fn shorthand_to_css_string(
}
}
-pub fn process_offset_parent_query(
- node: OpaqueNode,
- fragment_tree: Option<Arc<FragmentTree>>,
-) -> OffsetParentResponse {
- process_offset_parent_query_inner(node, fragment_tree).unwrap_or_default()
+struct OffsetParentFragments {
+ parent: ArcRefCell<BoxFragment>,
+ grandparent: Option<Fragment>,
}
-#[inline]
-fn process_offset_parent_query_inner(
- node: OpaqueNode,
- fragment_tree: Option<Arc<FragmentTree>>,
-) -> Option<OffsetParentResponse> {
- let fragment_tree = fragment_tree?;
-
- struct NodeOffsetBoxInfo {
- border_box: Rect<Au>,
- offset_parent_node_address: Option<OpaqueNode>,
- is_static_body_element: bool,
+/// <https://www.w3.org/TR/2016/WD-cssom-view-1-20160317/#dom-htmlelement-offsetparent>
+fn offset_parent_fragments<'dom>(
+ node: impl LayoutNode<'dom> + 'dom,
+) -> Option<OffsetParentFragments> {
+ // 1. If any of the following holds true return null and terminate this algorithm:
+ // * The element does not have an associated CSS layout box.
+ // * The element is the root element.
+ // * The element is the HTML body element.
+ // * The element’s computed value of the position property is fixed.
+ let fragment = node.fragments_for_pseudo(None).first().cloned()?;
+ let flags = fragment.base()?.flags;
+ if flags.intersects(
+ FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
+ ) {
+ return None;
+ }
+ if matches!(
+ fragment, Fragment::Box(fragment) if fragment.borrow().style.get_box().position == Position::Fixed
+ ) {
+ return None;
}
- // https://www.w3.org/TR/2016/WD-cssom-view-1-20160317/#extensions-to-the-htmlelement-interface
- let mut parent_node_addresses: Vec<Option<(OpaqueNode, bool)>> = Vec::new();
- let tag_to_find = Tag::new(node);
- let node_offset_box = fragment_tree.find(|fragment, level, containing_block| {
- let base = fragment.base()?;
- let is_body_element = base
- .flags
- .contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT);
-
- if fragment.tag() == Some(tag_to_find) {
- // Only consider the first fragment of the node found as per a
- // possible interpretation of the specification: "[...] return the
- // y-coordinate of the top border edge of the first CSS layout box
- // associated with the element [...]"
- //
- // FIXME: Browsers implement this all differently (e.g., [1]) -
- // Firefox does returns the union of all layout elements of some
- // sort. Chrome returns the first fragment for a block element (the
- // same as ours) or the union of all associated fragments in the
- // first containing block fragment for an inline element. We could
- // implement Chrome's behavior, but our fragment tree currently
- // provides insufficient information.
- //
- // [1]: https://github.com/w3c/csswg-drafts/issues/4541
- let fragment_relative_rect = match fragment {
- Fragment::Box(fragment) | Fragment::Float(fragment) => fragment.borrow().border_rect(),
- Fragment::Text(fragment) => fragment.borrow().rect,
- Fragment::Positioning(fragment) => fragment.borrow().rect,
- Fragment::AbsoluteOrFixedPositioned(_) |
- Fragment::Image(_) |
- Fragment::IFrame(_) => unreachable!(),
+ // 2. Return the nearest ancestor element of the element for which at least one of
+ // the following is true and terminate this algorithm if such an ancestor is found:
+ // * The computed value of the position property is not static.
+ // * It is the HTML body element.
+ // * The computed value of the position property of the element is static and the
+ // ancestor is one of the following HTML elements: td, th, or table.
+ let mut maybe_parent_node = node.parent_node();
+ while let Some(parent_node) = maybe_parent_node {
+ maybe_parent_node = parent_node.parent_node();
+
+ if let Some(parent_fragment) = parent_node.fragments_for_pseudo(None).first() {
+ let parent_fragment = match parent_fragment {
+ Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment,
+ _ => continue,
};
- let mut border_box = fragment_relative_rect.translate(containing_block.origin.to_vector()).to_untyped();
-
- // "If any of the following holds true return null and terminate
- // this algorithm: [...] The element’s computed value of the
- // `position` property is `fixed`."
- let is_fixed = matches!(
- fragment, Fragment::Box(fragment) if fragment.borrow().style.get_box().position == Position::Fixed
- );
+ let grandparent_fragment =
+ maybe_parent_node.and_then(|node| node.fragments_for_pseudo(None).first().cloned());
- if is_body_element {
- // "If the element is the HTML body element or [...] return zero
- // and terminate this algorithm."
- border_box.origin = Point2D::zero();
+ if parent_fragment.borrow().style.get_box().position != Position::Static {
+ return Some(OffsetParentFragments {
+ parent: parent_fragment.clone(),
+ grandparent: grandparent_fragment,
+ });
}
- let offset_parent_node = if is_fixed {
- None
- } else {
- // Find the nearest ancestor element eligible as `offsetParent`.
- parent_node_addresses[..level]
- .iter()
- .rev()
- .cloned()
- .find_map(std::convert::identity)
- };
-
- Some(NodeOffsetBoxInfo {
- border_box,
- offset_parent_node_address: offset_parent_node.map(|node| node.0),
- is_static_body_element: offset_parent_node.is_some_and(|node| node.1),
- })
- } else {
- // Record the paths of the nodes being traversed.
- let parent_node_address = match fragment {
- Fragment::Box(fragment) | Fragment::Float(fragment) => {
- let fragment = &*fragment.borrow();
- let is_eligible_parent = is_eligible_parent(fragment);
- let is_static_body_element = is_body_element &&
- fragment.style.get_box().position == Position::Static;
- match base.tag {
- Some(tag) if is_eligible_parent && !tag.is_pseudo() => {
- Some((tag.node, is_static_body_element))
- },
- _ => None,
- }
- },
- Fragment::AbsoluteOrFixedPositioned(_) |
- Fragment::IFrame(_) |
- Fragment::Image(_) |
- Fragment::Positioning(_) |
- Fragment::Text(_) => None,
- };
-
- while parent_node_addresses.len() <= level {
- parent_node_addresses.push(None);
+ let flags = parent_fragment.borrow().base.flags;
+ if flags.intersects(
+ FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT |
+ FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT,
+ ) {
+ return Some(OffsetParentFragments {
+ parent: parent_fragment.clone(),
+ grandparent: grandparent_fragment,
+ });
}
- parent_node_addresses[level] = parent_node_address;
- None
}
- });
+ }
- // Bail out if the element doesn't have an associated fragment.
- // "If any of the following holds true return null and terminate this
- // algorithm: [...] The element does not have an associated CSS layout box."
- // (`offsetParent`) "If the element is the HTML body element [...] return
- // zero and terminate this algorithm." (others)
- let node_offset_box = node_offset_box?;
+ None
+}
- let offset_parent_padding_box_corner = if let Some(offset_parent_node_address) =
- node_offset_box.offset_parent_node_address
- {
- // The spec (https://www.w3.org/TR/cssom-view-1/#extensions-to-the-htmlelement-interface)
- // says that offsetTop/offsetLeft are always relative to the padding box of the offsetParent.
- // However, in practice this is not true in major browsers in the case that the offsetParent is the body
- // element and the body element is position:static. In that case offsetLeft/offsetTop are computed
- // relative to the root node's border box.
- if node_offset_box.is_static_body_element {
- fn extract_box_fragment(
- fragment: &Fragment,
- containing_block: &PhysicalRect<Au>,
- ) -> PhysicalVec<Au> {
- let (Fragment::Box(fragment) | Fragment::Float(fragment)) = fragment else {
- unreachable!();
- };
- // Again, take the *first* associated CSS layout box.
- fragment.borrow().border_rect().origin.to_vector() +
- containing_block.origin.to_vector()
- }
+#[inline]
+pub fn process_offset_parent_query<'dom>(
+ node: impl LayoutNode<'dom> + 'dom,
+) -> Option<OffsetParentResponse> {
+ // Only consider the first fragment of the node found as per a
+ // possible interpretation of the specification: "[...] return the
+ // y-coordinate of the top border edge of the first CSS layout box
+ // associated with the element [...]"
+ //
+ // FIXME: Browsers implement this all differently (e.g., [1]) -
+ // Firefox does returns the union of all layout elements of some
+ // sort. Chrome returns the first fragment for a block element (the
+ // same as ours) or the union of all associated fragments in the
+ // first containing block fragment for an inline element. We could
+ // implement Chrome's behavior, but our fragment tree currently
+ // provides insufficient information.
+ //
+ // [1]: https://github.com/w3c/csswg-drafts/issues/4541
+ // > 1. If the element is the HTML body element or does not have any associated CSS
+ // layout box return zero and terminate this algorithm.
+ let fragment = node.fragments_for_pseudo(None).first().cloned()?;
+ let mut border_box = fragment.cumulative_border_box_rect()?;
+
+ // 2. If the offsetParent of the element is null return the x-coordinate of the left
+ // border edge of the first CSS layout box associated with the element, relative to
+ // the initial containing block origin, ignoring any transforms that apply to the
+ // element and its ancestors, and terminate this algorithm.
+ let Some(offset_parent_fragment) = offset_parent_fragments(node) else {
+ return Some(OffsetParentResponse {
+ node_address: None,
+ rect: border_box.to_untyped(),
+ });
+ };
- let containing_block = &fragment_tree.initial_containing_block;
- let fragment = &fragment_tree.root_fragments[0];
- if let Fragment::AbsoluteOrFixedPositioned(shared_fragment) = fragment {
- let shared_fragment = &*shared_fragment.borrow();
- let fragment = shared_fragment.fragment.as_ref().unwrap();
- extract_box_fragment(fragment, containing_block)
- } else {
- extract_box_fragment(fragment, containing_block)
- }
+ let parent_fragment = offset_parent_fragment.parent.borrow();
+ let parent_is_static_body_element = parent_fragment
+ .base
+ .flags
+ .contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) &&
+ parent_fragment.style.get_box().position == Position::Static;
+
+ // For `offsetLeft`:
+ // 3. Return the result of subtracting the y-coordinate of the top padding edge of the
+ // first CSS layout box associated with the offsetParent of the element from the
+ // y-coordinate of the top border edge of the first CSS layout box associated with the
+ // element, relative to the initial containing block origin, ignoring any transforms
+ // that apply to the element and its ancestors.
+ //
+ // We generalize this for `offsetRight` as described in the specification.
+ let grandparent_box_fragment = || match offset_parent_fragment.grandparent {
+ Some(Fragment::Box(box_fragment)) | Some(Fragment::Float(box_fragment)) => {
+ Some(box_fragment)
+ },
+ _ => None,
+ };
+
+ // The spec (https://www.w3.org/TR/cssom-view-1/#extensions-to-the-htmlelement-interface)
+ // says that offsetTop/offsetLeft are always relative to the padding box of the offsetParent.
+ // However, in practice this is not true in major browsers in the case that the offsetParent is the body
+ // element and the body element is position:static. In that case offsetLeft/offsetTop are computed
+ // relative to the root node's border box.
+ //
+ // See <https://github.com/w3c/csswg-drafts/issues/10549>.
+ let parent_offset_rect = if parent_is_static_body_element {
+ if let Some(grandparent_fragment) = grandparent_box_fragment() {
+ let grandparent_fragment = grandparent_fragment.borrow();
+ grandparent_fragment.offset_by_containing_block(&grandparent_fragment.border_rect())
} else {
- // Find the top and left padding edges of "the first CSS layout box
- // associated with the `offsetParent` of the element".
- //
- // Since we saw `offset_parent_node_address` once, we should be able
- // to find it again.
- let offset_parent_node_tag = Tag::new(offset_parent_node_address);
- fragment_tree
- .find(|fragment, _, containing_block| {
- match fragment {
- Fragment::Box(fragment) | Fragment::Float(fragment) => {
- let fragment = fragment.borrow();
- if fragment.base.tag == Some(offset_parent_node_tag) {
- // Again, take the *first* associated CSS layout box.
- let padding_box_corner = fragment.padding_rect().origin.to_vector()
- + containing_block.origin.to_vector();
- Some(padding_box_corner)
- } else {
- None
- }
- },
- Fragment::AbsoluteOrFixedPositioned(_)
- | Fragment::Text(_)
- | Fragment::Image(_)
- | Fragment::IFrame(_)
- | Fragment::Positioning(_) => None,
- }
- })
- .unwrap()
+ parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect())
}
} else {
- // "If the offsetParent of the element is null," subtract zero in the
- // following step.
- Vector2D::zero()
+ parent_fragment.offset_by_containing_block(&parent_fragment.padding_rect())
};
+ border_box = border_box.translate(-parent_offset_rect.origin.to_vector());
+
Some(OffsetParentResponse {
- node_address: node_offset_box.offset_parent_node_address.map(Into::into),
- // "Return the result of subtracting the x-coordinate of the left
- // padding edge of the first CSS layout box associated with the
- // `offsetParent` of the element from the x-coordinate of the left
- // border edge of the first CSS layout box associated with the element,
- // relative to the initial containing block origin, ignoring any
- // transforms that apply to the element and its ancestors." (and vice
- // versa for the top border edge)
- rect: node_offset_box
- .border_box
- .translate(-offset_parent_padding_box_corner.to_untyped()),
+ node_address: parent_fragment.base.tag.map(|tag| tag.node.into()),
+ rect: border_box.to_untyped(),
})
}
-/// Returns whether or not the element with the given style and body element determination
-/// is eligible to be a parent element for offset* queries.
-///
-/// From <https://www.w3.org/TR/cssom-view-1/#dom-htmlelement-offsetparent>:
-///
-/// > Return the nearest ancestor element of the element for which at least one of the following is
-/// > true and terminate this algorithm if such an ancestor is found:
-/// > 1. The computed value of the position property is not static.
-/// > 2. It is the HTML body element.
-/// > 3. The computed value of the position property of the element is static and the ancestor is
-/// > one of the following HTML elements: td, th, or table.
-fn is_eligible_parent(fragment: &BoxFragment) -> bool {
- fragment
- .base
- .flags
- .contains(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT) ||
- fragment.style.get_box().position != Position::Static ||
- fragment
- .base
- .flags
- .contains(FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT)
-}
-
/// <https://html.spec.whatwg.org/multipage/#get-the-text-steps>
pub fn get_the_text_steps<'dom>(node: impl LayoutNode<'dom>) -> String {
// Step 1: If element is not being rendered or if the user agent is a non-CSS user agent, then
diff --git a/components/layout/table/layout.rs b/components/layout/table/layout.rs
index 57b48ae0bca..2261f7d165c 100644
--- a/components/layout/table/layout.rs
+++ b/components/layout/table/layout.rs
@@ -2142,23 +2142,27 @@ impl<'a> TableLayout<'a> {
for column_group in self.table.column_groups.iter() {
let column_group = column_group.borrow();
if !column_group.is_empty() {
- fragments.push(Fragment::Positioning(PositioningFragment::new_empty(
+ let fragment = Fragment::Positioning(PositioningFragment::new_empty(
column_group.base.base_fragment_info,
dimensions
.get_column_group_rect(&column_group)
.as_physical(None),
column_group.base.style.clone(),
- )));
+ ));
+ column_group.base.set_fragment(fragment.clone());
+ fragments.push(fragment);
}
}
for (column_index, column) in self.table.columns.iter().enumerate() {
let column = column.borrow();
- fragments.push(Fragment::Positioning(PositioningFragment::new_empty(
+ let fragment = Fragment::Positioning(PositioningFragment::new_empty(
column.base.base_fragment_info,
dimensions.get_column_rect(column_index).as_physical(None),
column.base.style.clone(),
- )));
+ ));
+ column.base.set_fragment(fragment.clone());
+ fragments.push(fragment);
}
}
diff --git a/components/layout/table/mod.rs b/components/layout/table/mod.rs
index 120270fc7cf..fe7f90437b8 100644
--- a/components/layout/table/mod.rs
+++ b/components/layout/table/mod.rs
@@ -346,6 +346,7 @@ pub(crate) struct TableLayoutStyle<'a> {
/// Table parts that are stored in the DOM. This is used in order to map from
/// the DOM to the box tree and will eventually be important for incremental
/// layout.
+#[derive(MallocSizeOf)]
pub(crate) enum TableLevelBox {
Caption(ArcRefCell<TableCaption>),
Cell(ArcRefCell<TableSlotCell>),
diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs
index 40281b640c9..bf60c41d6ba 100644
--- a/components/layout/traversal.rs
+++ b/components/layout/traversal.rs
@@ -12,19 +12,15 @@ use crate::context::LayoutContext;
use crate::dom::DOMLayoutData;
pub struct RecalcStyle<'a> {
- context: LayoutContext<'a>,
+ context: &'a LayoutContext<'a>,
}
impl<'a> RecalcStyle<'a> {
- pub fn new(context: LayoutContext<'a>) -> Self {
+ pub fn new(context: &'a LayoutContext<'a>) -> Self {
RecalcStyle { context }
}
pub fn context(&self) -> &LayoutContext<'a> {
- &self.context
- }
-
- pub fn destroy(self) -> LayoutContext<'a> {
self.context
}
}
diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs
index b7f677f8044..52523af7cb1 100644
--- a/components/malloc_size_of/lib.rs
+++ b/components/malloc_size_of/lib.rs
@@ -746,6 +746,7 @@ malloc_size_of_is_0!(std::sync::atomic::AtomicUsize);
malloc_size_of_is_0!(std::time::Duration);
malloc_size_of_is_0!(std::time::Instant);
malloc_size_of_is_0!(std::time::SystemTime);
+malloc_size_of_is_0!(style::data::ElementData);
malloc_size_of_is_0!(style::font_face::SourceList);
malloc_size_of_is_0!(style::properties::ComputedValues);
malloc_size_of_is_0!(style::queries::values::PrefersColorScheme);
diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs
index 697a46fedda..b1ad01b81e0 100644
--- a/components/net/fetch/methods.rs
+++ b/components/net/fetch/methods.rs
@@ -23,8 +23,8 @@ use net_traits::http_status::HttpStatus;
use net_traits::policy_container::{PolicyContainer, RequestPolicyContainer};
use net_traits::request::{
BodyChunkRequest, BodyChunkResponse, CredentialsMode, Destination, Initiator,
- InsecureRequestsPolicy, Origin, RedirectMode, Referrer, Request, RequestMode, ResponseTainting,
- Window, is_cors_safelisted_method, is_cors_safelisted_request_header,
+ InsecureRequestsPolicy, Origin, ParserMetadata, RedirectMode, Referrer, Request, RequestMode,
+ ResponseTainting, Window, is_cors_safelisted_method, is_cors_safelisted_request_header,
};
use net_traits::response::{Response, ResponseBody, ResponseType};
use net_traits::{
@@ -42,7 +42,7 @@ use crate::fetch::cors_cache::CorsCache;
use crate::fetch::headers::determine_nosniff;
use crate::filemanager_thread::FileManager;
use crate::http_loader::{HttpState, determine_requests_referrer, http_fetch, set_default_accept};
-use crate::protocols::ProtocolRegistry;
+use crate::protocols::{ProtocolRegistry, is_url_potentially_trustworthy};
use crate::request_interceptor::RequestInterceptor;
use crate::subresource_integrity::is_response_integrity_valid;
@@ -169,6 +169,29 @@ pub async fn fetch_with_cors_cache(
// TODO: We don't implement fetchParams as defined in the spec
}
+fn convert_request_to_csp_request(request: &Request, origin: &ImmutableOrigin) -> csp::Request {
+ csp::Request {
+ url: request.url().into_url(),
+ origin: origin.clone().into_url_origin(),
+ redirect_count: request.redirect_count,
+ destination: request.destination,
+ initiator: match request.initiator {
+ Initiator::Download => csp::Initiator::Download,
+ Initiator::ImageSet => csp::Initiator::ImageSet,
+ Initiator::Manifest => csp::Initiator::Manifest,
+ Initiator::Prefetch => csp::Initiator::Prefetch,
+ _ => csp::Initiator::None,
+ },
+ nonce: request.cryptographic_nonce_metadata.clone(),
+ integrity_metadata: request.integrity_metadata.clone(),
+ parser_metadata: match request.parser_metadata {
+ ParserMetadata::ParserInserted => csp::ParserMetadata::ParserInserted,
+ ParserMetadata::NotParserInserted => csp::ParserMetadata::NotParserInserted,
+ ParserMetadata::Default => csp::ParserMetadata::None,
+ },
+ }
+}
+
/// <https://www.w3.org/TR/CSP/#should-block-request>
pub fn should_request_be_blocked_by_csp(
request: &Request,
@@ -178,17 +201,7 @@ pub fn should_request_be_blocked_by_csp(
Origin::Client => return (csp::CheckResult::Allowed, Vec::new()),
Origin::Origin(origin) => origin,
};
-
- let csp_request = csp::Request {
- url: request.url().into_url(),
- origin: origin.clone().into_url_origin(),
- redirect_count: request.redirect_count,
- destination: request.destination,
- initiator: csp::Initiator::None,
- nonce: request.cryptographic_nonce_metadata.clone(),
- integrity_metadata: request.integrity_metadata.clone(),
- parser_metadata: csp::ParserMetadata::None,
- };
+ let csp_request = convert_request_to_csp_request(request, origin);
policy_container
.csp_list
@@ -197,6 +210,24 @@ pub fn should_request_be_blocked_by_csp(
.unwrap_or((csp::CheckResult::Allowed, Vec::new()))
}
+/// <https://www.w3.org/TR/CSP/#report-for-request>
+pub fn report_violations_for_request_by_csp(
+ request: &Request,
+ policy_container: &PolicyContainer,
+) -> Vec<csp::Violation> {
+ let origin = match &request.origin {
+ Origin::Client => return Vec::new(),
+ Origin::Origin(origin) => origin,
+ };
+ let csp_request = convert_request_to_csp_request(request, origin);
+
+ policy_container
+ .csp_list
+ .as_ref()
+ .map(|c| c.report_violations_for_request(&csp_request))
+ .unwrap_or_default()
+}
+
/// [Main fetch](https://fetch.spec.whatwg.org/#concept-main-fetch)
pub async fn main_fetch(
fetch_params: &mut FetchParams,
@@ -232,9 +263,6 @@ pub async fn main_fetch(
)));
}
- // Step 2.2.
- // TODO: Report violations.
-
// The request should have a valid policy_container associated with it.
// TODO: This should not be `Client` here
let policy_container = match &request.policy_container {
@@ -242,12 +270,19 @@ pub async fn main_fetch(
RequestPolicyContainer::PolicyContainer(container) => container.to_owned(),
};
+ // Step 2.2.
+ let violations = report_violations_for_request_by_csp(request, &policy_container);
+
+ if !violations.is_empty() {
+ target.process_csp_violations(request, violations);
+ }
+
// Step 3.
// TODO: handle request abort.
// Step 4. Upgrade request to a potentially trustworthy URL, if appropriate.
if should_upgrade_request_to_potentially_trustworty(request, context) ||
- should_upgrade_mixed_content_request(request)
+ should_upgrade_mixed_content_request(request, &context.protocols)
{
trace!(
"upgrading {} targeting {:?}",
@@ -294,7 +329,7 @@ pub async fn main_fetch(
"Request attempted on bad port".into(),
)));
}
- if should_request_be_blocked_as_mixed_content(request) {
+ if should_request_be_blocked_as_mixed_content(request, &context.protocols) {
response = Some(Response::network_error(NetworkError::Internal(
"Blocked as mixed content".into(),
)));
@@ -359,13 +394,16 @@ pub async fn main_fetch(
if (same_origin && request.response_tainting == ResponseTainting::Basic) ||
// request's current URL's scheme is "data"
current_scheme == "data" ||
+ // Note: Although it is not part of the specification, we make an exception here
+ // for custom protocols that are explicitly marked as active for fetch.
+ context.protocols.is_fetchable(current_scheme) ||
// request's mode is "navigate" or "websocket"
matches!(
request.mode,
RequestMode::Navigate | RequestMode::WebSocket { .. }
)
{
- // Substep 1. Set request’s response tainting to "basic".
+ // Substep 1. Set request's response tainting to "basic".
request.response_tainting = ResponseTainting::Basic;
// Substep 2. Return the result of running scheme fetch given fetchParams.
@@ -373,13 +411,13 @@ pub async fn main_fetch(
} else if request.mode == RequestMode::SameOrigin {
Response::network_error(NetworkError::Internal("Cross-origin response".into()))
} else if request.mode == RequestMode::NoCors {
- // Substep 1. If request’s redirect mode is not "follow", then return a network error.
+ // Substep 1. If request's redirect mode is not "follow", then return a network error.
if request.redirect_mode != RedirectMode::Follow {
Response::network_error(NetworkError::Internal(
"NoCors requests must follow redirects".into(),
))
} else {
- // Substep 2. Set request’s response tainting to "opaque".
+ // Substep 2. Set request's response tainting to "opaque".
request.response_tainting = ResponseTainting::Opaque;
// Substep 3. Return the result of running scheme fetch given fetchParams.
@@ -490,7 +528,7 @@ pub async fn main_fetch(
let should_replace_with_mime_type_error = !response_is_network_error &&
should_be_blocked_due_to_mime_type(request.destination, &response.headers);
let should_replace_with_mixed_content = !response_is_network_error &&
- should_response_be_blocked_as_mixed_content(request, &response);
+ should_response_be_blocked_as_mixed_content(request, &response, &context.protocols);
// Step 15.
let mut network_error_response = response
@@ -933,7 +971,10 @@ pub fn should_request_be_blocked_due_to_a_bad_port(url: &ServoUrl) -> bool {
}
/// <https://w3c.github.io/webappsec-mixed-content/#should-block-fetch>
-pub fn should_request_be_blocked_as_mixed_content(request: &Request) -> bool {
+pub fn should_request_be_blocked_as_mixed_content(
+ request: &Request,
+ protocol_registry: &ProtocolRegistry,
+) -> bool {
// Step 1. Return allowed if one or more of the following conditions are met:
// 1.1. Does settings prohibit mixed security contexts?
// returns "Does Not Restrict Mixed Security Contexts" when applied to request’s client.
@@ -944,7 +985,7 @@ pub fn should_request_be_blocked_as_mixed_content(request: &Request) -> bool {
}
// 1.2. request’s URL is a potentially trustworthy URL.
- if request.url().is_potentially_trustworthy() {
+ if is_url_potentially_trustworthy(protocol_registry, &request.url()) {
return false;
}
@@ -961,7 +1002,11 @@ pub fn should_request_be_blocked_as_mixed_content(request: &Request) -> bool {
}
/// <https://w3c.github.io/webappsec-mixed-content/#should-block-response>
-pub fn should_response_be_blocked_as_mixed_content(request: &Request, response: &Response) -> bool {
+pub fn should_response_be_blocked_as_mixed_content(
+ request: &Request,
+ response: &Response,
+ protocol_registry: &ProtocolRegistry,
+) -> bool {
// Step 1. Return allowed if one or more of the following conditions are met:
// 1.1. Does settings prohibit mixed security contexts? returns Does Not Restrict Mixed Content
// when applied to request’s client.
@@ -975,7 +1020,7 @@ pub fn should_response_be_blocked_as_mixed_content(request: &Request, response:
if response
.actual_response()
.url()
- .is_some_and(|response_url| response_url.is_potentially_trustworthy())
+ .is_some_and(|response_url| is_url_potentially_trustworthy(protocol_registry, response_url))
{
return false;
}
@@ -1041,7 +1086,7 @@ fn should_upgrade_request_to_potentially_trustworty(
// request’s header list if any of the following criteria are met:
// * request’s URL is not a potentially trustworthy URL
// * request’s URL's host is not a preloadable HSTS host
- if !request.current_url().is_potentially_trustworthy() ||
+ if !is_url_potentially_trustworthy(&context.protocols, &request.current_url()) ||
!request.current_url().host_str().is_some_and(|host| {
!context.state.hsts_list.read().unwrap().is_host_secure(host)
})
@@ -1094,10 +1139,13 @@ fn do_settings_prohibit_mixed_security_contexts(request: &Request) -> MixedSecur
}
/// <https://w3c.github.io/webappsec-mixed-content/#upgrade-algorithm>
-fn should_upgrade_mixed_content_request(request: &Request) -> bool {
+fn should_upgrade_mixed_content_request(
+ request: &Request,
+ protocol_registry: &ProtocolRegistry,
+) -> bool {
let url = request.url();
// Step 1.1 : request’s URL is a potentially trustworthy URL.
- if url.is_potentially_trustworthy() {
+ if is_url_potentially_trustworthy(protocol_registry, &url) {
return false;
}
diff --git a/components/net/protocols/mod.rs b/components/net/protocols/mod.rs
index f8b989b9623..6dc58ceab64 100644
--- a/components/net/protocols/mod.rs
+++ b/components/net/protocols/mod.rs
@@ -14,6 +14,7 @@ use log::error;
use net_traits::filemanager_thread::RelativePos;
use net_traits::request::Request;
use net_traits::response::Response;
+use servo_url::ServoUrl;
use crate::fetch::methods::{DoneChannel, FetchContext, RangeRequestBounds};
@@ -47,6 +48,15 @@ pub trait ProtocolHandler: Send + Sync {
fn is_fetchable(&self) -> bool {
false
}
+
+ /// Specify if this custom protocol can be used in a [secure context]
+ ///
+ /// Note: this only works for bypassing mixed content checks right now
+ ///
+ /// [secure context]: https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts
+ fn is_secure(&self) -> bool {
+ false
+ }
}
#[derive(Default)]
@@ -54,27 +64,45 @@ pub struct ProtocolRegistry {
pub(crate) handlers: HashMap<String, Box<dyn ProtocolHandler>>, // Maps scheme -> handler
}
+#[derive(Clone, Copy, Debug)]
+pub enum ProtocolRegisterError {
+ ForbiddenScheme,
+ SchemeAlreadyRegistered,
+}
+
impl ProtocolRegistry {
pub fn with_internal_protocols() -> Self {
let mut registry = Self::default();
- registry.register("data", DataProtocolHander::default());
- registry.register("blob", BlobProtocolHander::default());
- registry.register("file", FileProtocolHander::default());
+ // We just created a new registry, and know that we aren't using
+ // any forbidden schemes, so this should never panic.
+ registry
+ .register("data", DataProtocolHander::default())
+ .expect("Infallible");
+ registry
+ .register("blob", BlobProtocolHander::default())
+ .expect("Infallible");
+ registry
+ .register("file", FileProtocolHander::default())
+ .expect("Infallible");
registry
}
- pub fn register(&mut self, scheme: &str, handler: impl ProtocolHandler + 'static) -> bool {
+ pub fn register(
+ &mut self,
+ scheme: &str,
+ handler: impl ProtocolHandler + 'static,
+ ) -> Result<(), ProtocolRegisterError> {
if FORBIDDEN_SCHEMES.contains(&scheme) {
error!("Protocol handler for '{scheme}' is not allowed to be registered.");
- return false;
+ return Err(ProtocolRegisterError::ForbiddenScheme);
}
if let Entry::Vacant(entry) = self.handlers.entry(scheme.into()) {
entry.insert(Box::new(handler));
- true
+ Ok(())
} else {
error!("Protocol handler for '{scheme}' is already registered.");
- false
+ Err(ProtocolRegisterError::SchemeAlreadyRegistered)
}
}
@@ -96,9 +124,22 @@ impl ProtocolRegistry {
pub fn is_fetchable(&self, scheme: &str) -> bool {
self.handlers
.get(scheme)
- .map(|handler| handler.is_fetchable())
- .unwrap_or(false)
+ .is_some_and(|handler| handler.is_fetchable())
}
+
+ pub fn is_secure(&self, scheme: &str) -> bool {
+ self.handlers
+ .get(scheme)
+ .is_some_and(|handler| handler.is_secure())
+ }
+}
+
+/// Test if the URL is potentially trustworthy or the custom protocol is registered as secure
+pub fn is_url_potentially_trustworthy(
+ protocol_registry: &ProtocolRegistry,
+ url: &ServoUrl,
+) -> bool {
+ url.is_potentially_trustworthy() || protocol_registry.is_secure(url.scheme())
}
pub fn range_not_satisfiable_error(response: &mut Response) {
diff --git a/components/pixels/lib.rs b/components/pixels/lib.rs
index 7cba060ec74..24386ff830a 100644
--- a/components/pixels/lib.rs
+++ b/components/pixels/lib.rs
@@ -84,6 +84,7 @@ pub fn rgba8_premultiply_inplace(pixels: &mut [u8]) -> bool {
is_opaque
}
+#[inline(always)]
pub fn multiply_u8_color(a: u8, b: u8) -> u8 {
(a as u32 * b as u32 / 255) as u8
}
@@ -254,6 +255,69 @@ pub fn unmultiply_inplace<const SWAP_RB: bool>(pixels: &mut [u8]) {
}
}
+#[repr(u8)]
+pub enum Multiply {
+ None = 0,
+ PreMultiply = 1,
+ UnMultiply = 2,
+}
+
+pub fn transform_inplace(pixels: &mut [u8], multiply: Multiply, swap_rb: bool, clear_alpha: bool) {
+ match (multiply, swap_rb, clear_alpha) {
+ (Multiply::None, true, true) => generic_transform_inplace::<0, true, true>(pixels),
+ (Multiply::None, true, false) => generic_transform_inplace::<0, true, false>(pixels),
+ (Multiply::None, false, true) => generic_transform_inplace::<0, false, true>(pixels),
+ (Multiply::None, false, false) => generic_transform_inplace::<0, false, false>(pixels),
+ (Multiply::PreMultiply, true, true) => generic_transform_inplace::<1, true, true>(pixels),
+ (Multiply::PreMultiply, true, false) => generic_transform_inplace::<1, true, false>(pixels),
+ (Multiply::PreMultiply, false, true) => generic_transform_inplace::<1, false, true>(pixels),
+ (Multiply::PreMultiply, false, false) => {
+ generic_transform_inplace::<1, false, false>(pixels)
+ },
+ (Multiply::UnMultiply, true, true) => generic_transform_inplace::<2, true, true>(pixels),
+ (Multiply::UnMultiply, true, false) => generic_transform_inplace::<2, true, false>(pixels),
+ (Multiply::UnMultiply, false, true) => generic_transform_inplace::<2, false, true>(pixels),
+ (Multiply::UnMultiply, false, false) => {
+ generic_transform_inplace::<2, false, false>(pixels)
+ },
+ }
+}
+
+pub fn generic_transform_inplace<
+ const MULTIPLY: u8, // 1 premultiply, 2 unmultiply
+ const SWAP_RB: bool,
+ const CLEAR_ALPHA: bool,
+>(
+ pixels: &mut [u8],
+) {
+ for rgba in pixels.chunks_mut(4) {
+ match MULTIPLY {
+ 1 => {
+ let a = rgba[3];
+ multiply_u8_color(rgba[0], a);
+ multiply_u8_color(rgba[1], a);
+ multiply_u8_color(rgba[2], a);
+ },
+ 2 => {
+ let a = rgba[3] as u32;
+
+ if a > 0 {
+ rgba[0] = (rgba[0] as u32 * 255 / a) as u8;
+ rgba[1] = (rgba[1] as u32 * 255 / a) as u8;
+ rgba[2] = (rgba[2] as u32 * 255 / a) as u8;
+ }
+ },
+ _ => {},
+ }
+ if SWAP_RB {
+ rgba.swap(0, 2);
+ }
+ if CLEAR_ALPHA {
+ rgba[3] = u8::MAX;
+ }
+ }
+}
+
fn is_gif(buffer: &[u8]) -> bool {
buffer.starts_with(b"GIF87a") || buffer.starts_with(b"GIF89a")
}
diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml
index 37e80d0e748..1aa821cdbd3 100644
--- a/components/script/Cargo.toml
+++ b/components/script/Cargo.toml
@@ -114,6 +114,7 @@ servo_config = { path = "../config" }
servo_geometry = { path = "../geometry" }
servo_rand = { path = "../rand" }
servo_url = { path = "../url" }
+snapshot = { workspace = true }
smallvec = { workspace = true, features = ["union"] }
strum = { workspace = true }
strum_macros = { workspace = true }
diff --git a/components/script/canvas_context.rs b/components/script/canvas_context.rs
index 8bf188a5aa9..d49d31997e1 100644
--- a/components/script/canvas_context.rs
+++ b/components/script/canvas_context.rs
@@ -5,8 +5,8 @@
//! Common interfaces for Canvas Contexts
use euclid::default::Size2D;
-use ipc_channel::ipc::IpcSharedMemory;
use script_layout_interface::{HTMLCanvasData, HTMLCanvasDataSource};
+use snapshot::Snapshot;
use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas;
use crate::dom::bindings::inheritance::Castable;
@@ -30,11 +30,10 @@ pub(crate) trait CanvasContext {
fn resize(&self);
- fn get_image_data_as_shared_memory(&self) -> Option<IpcSharedMemory>;
-
- fn get_image_data(&self) -> Option<Vec<u8>> {
- self.get_image_data_as_shared_memory().map(|sm| sm.to_vec())
- }
+ /// Returns none if area of canvas is zero.
+ ///
+ /// In case of other errors it returns cleared snapshot
+ fn get_image_data(&self) -> Option<Snapshot>;
fn origin_is_clean(&self) -> bool {
true
diff --git a/components/script/canvas_state.rs b/components/script/canvas_state.rs
index aea3012b365..408c94c124a 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, IpcSharedMemory};
+use ipc_channel::ipc::{self, IpcSender};
use net_traits::image_cache::{ImageCache, ImageResponse};
use net_traits::request::CorsSettings;
use pixels::PixelFormat;
@@ -298,7 +298,7 @@ impl CanvasState {
&self,
url: ServoUrl,
cors_setting: Option<CorsSettings>,
- ) -> Option<(IpcSharedMemory, Size2D<u32>)> {
+ ) -> Option<snapshot::Snapshot> {
let img = match self.request_image_from_cache(url, cors_setting) {
ImageResponse::Loaded(img, _) => img,
ImageResponse::PlaceholderLoaded(_, _) |
@@ -308,13 +308,22 @@ impl CanvasState {
},
};
- let image_size = Size2D::new(img.width, img.height);
- let image_data = match img.format {
- PixelFormat::BGRA8 => img.bytes(),
+ let size = Size2D::new(img.width, img.height);
+ let format = match img.format {
+ PixelFormat::BGRA8 => snapshot::PixelFormat::BGRA,
+ PixelFormat::RGBA8 => snapshot::PixelFormat::RGBA,
pixel_format => unimplemented!("unsupported pixel format ({:?})", pixel_format),
};
+ let alpha_mode = snapshot::AlphaMode::Transparent {
+ premultiplied: false,
+ };
- Some((image_data, image_size))
+ Some(snapshot::Snapshot::from_shared_memory(
+ size.cast(),
+ format,
+ alpha_mode,
+ img.bytes(),
+ ))
}
fn request_image_from_cache(
@@ -341,13 +350,16 @@ impl CanvasState {
assert!(Rect::from_size(canvas_size).contains_rect(&rect));
- let (sender, receiver) = ipc::bytes_channel().unwrap();
+ let (sender, receiver) = ipc::channel().unwrap();
self.send_canvas_2d_msg(Canvas2dMsg::GetImageData(rect, canvas_size, sender));
- let mut pixels = receiver.recv().unwrap().to_vec();
-
- pixels::unmultiply_inplace::<true>(&mut pixels);
-
- pixels
+ let mut snapshot = receiver.recv().unwrap().to_owned();
+ snapshot.transform(
+ snapshot::AlphaMode::Transparent {
+ premultiplied: false,
+ },
+ snapshot::PixelFormat::RGBA,
+ );
+ snapshot.to_vec()
}
///
@@ -594,10 +606,10 @@ impl CanvasState {
dh: Option<f64>,
) -> ErrorResult {
debug!("Fetching image {}.", url);
- let (image_data, image_size) = self
+ let snapshot = self
.fetch_image_data(url, cors_setting)
.ok_or(Error::InvalidState)?;
- let image_size = image_size.to_f64();
+ let image_size = snapshot.size().to_f64();
let dw = dw.unwrap_or(image_size.width);
let dh = dh.unwrap_or(image_size.height);
@@ -614,8 +626,7 @@ impl CanvasState {
let smoothing_enabled = self.state.borrow().image_smoothing_enabled;
self.send_canvas_2d_msg(Canvas2dMsg::DrawImage(
- image_data,
- image_size,
+ snapshot.as_ipc(),
dest_rect,
source_rect,
smoothing_enabled,
@@ -929,7 +940,7 @@ impl CanvasState {
mut repetition: DOMString,
can_gc: CanGc,
) -> Fallible<Option<DomRoot<CanvasPattern>>> {
- let (image_data, image_size) = match image {
+ let snapshot = match image {
CanvasImageSource::HTMLImageElement(ref image) => {
// https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument
if !image.is_usable()? {
@@ -941,27 +952,17 @@ impl CanvasState {
.and_then(|url| {
self.fetch_image_data(url, cors_setting_for_element(image.upcast()))
})
- .map(|data| (data.0.to_vec(), data.1))
.ok_or(Error::InvalidState)?
},
CanvasImageSource::HTMLCanvasElement(ref canvas) => {
- let (data, size) = canvas.fetch_all_data().ok_or(Error::InvalidState)?;
- let data = data
- .map(|data| data.to_vec())
- .unwrap_or_else(|| vec![0; size.area() as usize * 4]);
- (data, size)
+ canvas.get_image_data().ok_or(Error::InvalidState)?
},
CanvasImageSource::OffscreenCanvas(ref canvas) => {
- let (data, size) = canvas.fetch_all_data().ok_or(Error::InvalidState)?;
- let data = data
- .map(|data| data.to_vec())
- .unwrap_or_else(|| vec![0; size.area() as usize * 4]);
- (data, size)
+ canvas.get_image_data().ok_or(Error::InvalidState)?
},
CanvasImageSource::CSSStyleValue(ref value) => value
.get_url(self.base_url.clone())
.and_then(|url| self.fetch_image_data(url, None))
- .map(|data| (data.0.to_vec(), data.1))
.ok_or(Error::InvalidState)?,
};
@@ -970,10 +971,11 @@ impl CanvasState {
}
if let Ok(rep) = RepetitionStyle::from_str(&repetition) {
+ let size = snapshot.size();
Ok(Some(CanvasPattern::new(
global,
- image_data,
- image_size,
+ snapshot.to_vec(),
+ size.cast(),
rep,
self.is_origin_clean(image),
can_gc,
diff --git a/components/script/dom/bindings/error.rs b/components/script/dom/bindings/error.rs
index b0fd301df6a..f5bd03cd8d7 100644
--- a/components/script/dom/bindings/error.rs
+++ b/components/script/dom/bindings/error.rs
@@ -93,6 +93,7 @@ pub(crate) fn throw_dom_exception(
Error::NotReadable => DOMErrorName::NotReadableError,
Error::Data => DOMErrorName::DataError,
Error::Operation => DOMErrorName::OperationError,
+ Error::NotAllowed => DOMErrorName::NotAllowedError,
Error::Type(message) => unsafe {
assert!(!JS_IsExceptionPending(*cx));
throw_type_error(*cx, &message);
diff --git a/components/script/dom/bindings/serializable.rs b/components/script/dom/bindings/serializable.rs
index 4aa1a94a0a4..d0e851b4799 100644
--- a/components/script/dom/bindings/serializable.rs
+++ b/components/script/dom/bindings/serializable.rs
@@ -11,7 +11,7 @@ use base::id::{Index, NamespaceIndex, PipelineNamespaceId};
use crate::dom::bindings::reflector::DomObject;
use crate::dom::bindings::root::DomRoot;
-use crate::dom::bindings::structuredclone::{StructuredData, StructuredDataReader};
+use crate::dom::bindings::structuredclone::StructuredData;
use crate::dom::globalscope::GlobalScope;
use crate::script_runtime::CanGc;
@@ -65,13 +65,7 @@ where
/// Returns the field of [StructuredDataReader]/[StructuredDataWriter] that
/// should be used to read/store serialized instances of this type.
- fn serialized_storage(
- data: StructuredData<'_>,
- ) -> &mut Option<HashMap<NamespaceIndex<Self::Index>, Self::Data>>;
-
- /// Returns the field of [StructuredDataReader] that should be used to store
- /// deserialized instances of this type.
- fn deserialized_storage(
- reader: &mut StructuredDataReader,
- ) -> &mut Option<HashMap<StorageKey, DomRoot<Self>>>;
+ fn serialized_storage<'a>(
+ data: StructuredData<'a, '_>,
+ ) -> &'a mut Option<HashMap<NamespaceIndex<Self::Index>, Self::Data>>;
}
diff --git a/components/script/dom/bindings/structuredclone.rs b/components/script/dom/bindings/structuredclone.rs
index 915a4951bb5..c9a49ba00c9 100644
--- a/components/script/dom/bindings/structuredclone.rs
+++ b/components/script/dom/bindings/structuredclone.rs
@@ -16,13 +16,14 @@ use constellation_traits::{
BlobImpl, DomException, DomPoint, MessagePortImpl, Serializable as SerializableInterface,
StructuredSerializedData, Transferrable as TransferrableInterface,
};
+use js::gc::RootedVec;
use js::glue::{
CopyJSStructuredCloneData, DeleteJSAutoStructuredCloneBuffer, GetLengthOfJSStructuredCloneData,
NewJSAutoStructuredCloneBuffer, WriteBytesToJSStructuredCloneData,
};
use js::jsapi::{
- CloneDataPolicy, HandleObject as RawHandleObject, JS_ClearPendingException, JS_ReadUint32Pair,
- JS_STRUCTURED_CLONE_VERSION, JS_WriteUint32Pair, JSContext, JSObject,
+ CloneDataPolicy, HandleObject as RawHandleObject, Heap, JS_ClearPendingException,
+ JS_ReadUint32Pair, JS_STRUCTURED_CLONE_VERSION, JS_WriteUint32Pair, JSContext, JSObject,
JSStructuredCloneCallbacks, JSStructuredCloneReader, JSStructuredCloneWriter,
MutableHandleObject as RawMutableHandleObject, StructuredCloneScope, TransferableOwnership,
};
@@ -93,7 +94,7 @@ fn reader_for_type(
) -> unsafe fn(
&GlobalScope,
*mut JSStructuredCloneReader,
- &mut StructuredDataReader,
+ &mut StructuredDataReader<'_>,
CanGc,
) -> *mut JSObject {
match val {
@@ -107,7 +108,7 @@ fn reader_for_type(
unsafe fn read_object<T: Serializable>(
owner: &GlobalScope,
r: *mut JSStructuredCloneReader,
- sc_reader: &mut StructuredDataReader,
+ sc_reader: &mut StructuredDataReader<'_>,
can_gc: CanGc,
) -> *mut JSObject {
let mut name_space: u32 = 0;
@@ -136,9 +137,8 @@ unsafe fn read_object<T: Serializable>(
}
if let Ok(obj) = T::deserialize(owner, serialized, can_gc) {
- let destination = T::deserialized_storage(sc_reader).get_or_insert_with(HashMap::new);
let reflector = obj.reflector().get_jsobject().get();
- destination.insert(storage_key, obj);
+ sc_reader.roots.push(Heap::boxed(reflector));
return reflector;
}
warn!("Reading structured data failed in {:?}.", owner.get_url());
@@ -191,7 +191,7 @@ unsafe extern "C" fn read_callback(
"tag should be higher than StructuredCloneTags::Min"
);
- let sc_reader = &mut *(closure as *mut StructuredDataReader);
+ let sc_reader = &mut *(closure as *mut StructuredDataReader<'_>);
let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx));
let global = GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof));
for serializable in SerializableInterface::iter() {
@@ -259,7 +259,8 @@ unsafe extern "C" fn write_callback(
fn receiver_for_type(
val: TransferrableInterface,
-) -> fn(&GlobalScope, &mut StructuredDataReader, u64, RawMutableHandleObject) -> Result<(), ()> {
+) -> fn(&GlobalScope, &mut StructuredDataReader<'_>, u64, RawMutableHandleObject) -> Result<(), ()>
+{
match val {
TransferrableInterface::MessagePort => receive_object::<MessagePort>,
TransferrableInterface::ReadableStream => receive_object::<ReadableStream>,
@@ -269,7 +270,7 @@ fn receiver_for_type(
fn receive_object<T: Transferable>(
owner: &GlobalScope,
- sc_reader: &mut StructuredDataReader,
+ sc_reader: &mut StructuredDataReader<'_>,
extra_data: u64,
return_object: RawMutableHandleObject,
) -> Result<(), ()> {
@@ -305,13 +306,12 @@ fn receive_object<T: Transferable>(
);
};
- if let Ok(received) = T::transfer_receive(owner, id, serialized) {
- return_object.set(received.reflector().rootable().get());
- let storage = T::deserialized_storage(sc_reader).get_or_insert_with(Vec::new);
- storage.push(received);
- return Ok(());
- }
- Err(())
+ let Ok(received) = T::transfer_receive(owner, id, serialized) else {
+ return Err(());
+ };
+ return_object.set(received.reflector().rootable().get());
+ sc_reader.roots.push(Heap::boxed(return_object.get()));
+ Ok(())
}
unsafe extern "C" fn read_transfer_callback(
@@ -324,7 +324,7 @@ unsafe extern "C" fn read_transfer_callback(
closure: *mut raw::c_void,
return_object: RawMutableHandleObject,
) -> bool {
- let sc_reader = &mut *(closure as *mut StructuredDataReader);
+ let sc_reader = &mut *(closure as *mut StructuredDataReader<'_>);
let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx));
let owner = GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof));
@@ -489,8 +489,8 @@ static STRUCTURED_CLONE_CALLBACKS: JSStructuredCloneCallbacks = JSStructuredClon
sabCloned: Some(sab_cloned_callback),
};
-pub(crate) enum StructuredData<'a> {
- Reader(&'a mut StructuredDataReader),
+pub(crate) enum StructuredData<'a, 'b> {
+ Reader(&'a mut StructuredDataReader<'b>),
Writer(&'a mut StructuredDataWriter),
}
@@ -503,19 +503,11 @@ pub(crate) struct DOMErrorRecord {
/// Reader and writer structs for results from, and inputs to, structured-data read/write operations.
/// <https://html.spec.whatwg.org/multipage/#safe-passing-of-structured-data>
#[repr(C)]
-pub(crate) struct StructuredDataReader {
+pub(crate) struct StructuredDataReader<'a> {
/// A struct of error message.
- pub(crate) errors: DOMErrorRecord,
- /// A map of deserialized blobs, stored temporarily here to keep them rooted.
- pub(crate) blobs: Option<HashMap<StorageKey, DomRoot<Blob>>>,
- /// A map of deserialized points, stored temporarily here to keep them rooted.
- pub(crate) points_read_only: Option<HashMap<StorageKey, DomRoot<DOMPointReadOnly>>>,
- pub(crate) dom_points: Option<HashMap<StorageKey, DomRoot<DOMPoint>>>,
- /// A map of deserialized exceptions, stored temporarily here to keep them rooted.
- pub(crate) dom_exceptions: Option<HashMap<StorageKey, DomRoot<DOMException>>>,
- /// A vec of transfer-received DOM ports,
- /// to be made available to script through a message event.
- pub(crate) message_ports: Option<Vec<DomRoot<MessagePort>>>,
+ errors: DOMErrorRecord,
+ /// Rooted copies of every deserialized object to ensure they are not garbage collected.
+ roots: RootedVec<'a, Box<Heap<*mut JSObject>>>,
/// A map of port implementations,
/// used as part of the "transfer-receiving" steps of ports,
/// to produce the DOM ports stored in `message_ports` above.
@@ -528,12 +520,6 @@ pub(crate) struct StructuredDataReader {
pub(crate) points: Option<HashMap<DomPointId, DomPoint>>,
/// A map of serialized exceptions.
pub(crate) exceptions: Option<HashMap<DomExceptionId, DomException>>,
-
- /// <https://streams.spec.whatwg.org/#rs-transfer>
- pub(crate) readable_streams: Option<Vec<DomRoot<ReadableStream>>>,
-
- /// <https://streams.spec.whatwg.org/#ws-transfer>
- pub(crate) writable_streams: Option<Vec<DomRoot<WritableStream>>>,
}
/// A data holder for transferred and serialized objects.
@@ -618,19 +604,14 @@ pub(crate) fn read(
) -> Fallible<Vec<DomRoot<MessagePort>>> {
let cx = GlobalScope::get_cx();
let _ac = enter_realm(global);
+ rooted_vec!(let mut roots);
let mut sc_reader = StructuredDataReader {
- blobs: None,
- message_ports: None,
- points_read_only: None,
- dom_points: None,
- dom_exceptions: None,
+ roots,
port_impls: data.ports.take(),
blob_impls: data.blobs.take(),
points: data.points.take(),
exceptions: data.exceptions.take(),
errors: DOMErrorRecord { message: None },
- readable_streams: None,
- writable_streams: None,
};
let sc_reader_ptr = &mut sc_reader as *mut _;
unsafe {
@@ -666,8 +647,15 @@ pub(crate) fn read(
DeleteJSAutoStructuredCloneBuffer(scbuf);
+ let mut message_ports = vec![];
+ for reflector in sc_reader.roots.iter() {
+ let Ok(message_port) = root_from_object::<MessagePort>(reflector.get(), *cx) else {
+ continue;
+ };
+ message_ports.push(message_port);
+ }
// Any transfer-received port-impls should have been taken out.
assert!(sc_reader.port_impls.is_none());
- Ok(sc_reader.message_ports.take().unwrap_or_default())
+ Ok(message_ports)
}
}
diff --git a/components/script/dom/bindings/transferable.rs b/components/script/dom/bindings/transferable.rs
index b720c05ae37..e6b2f000f3a 100644
--- a/components/script/dom/bindings/transferable.rs
+++ b/components/script/dom/bindings/transferable.rs
@@ -12,7 +12,7 @@ use base::id::NamespaceIndex;
use crate::dom::bindings::reflector::DomObject;
use crate::dom::bindings::root::DomRoot;
-use crate::dom::bindings::structuredclone::{StructuredData, StructuredDataReader};
+use crate::dom::bindings::structuredclone::StructuredData;
use crate::dom::globalscope::GlobalScope;
pub(crate) trait Transferable: DomObject
where
@@ -32,8 +32,7 @@ where
serialized: Self::Data,
) -> Result<DomRoot<Self>, ()>;
- fn serialized_storage(
- data: StructuredData<'_>,
- ) -> &mut Option<HashMap<NamespaceIndex<Self::Index>, Self::Data>>;
- fn deserialized_storage(reader: &mut StructuredDataReader) -> &mut Option<Vec<DomRoot<Self>>>;
+ fn serialized_storage<'a>(
+ data: StructuredData<'a, '_>,
+ ) -> &'a mut Option<HashMap<NamespaceIndex<Self::Index>, Self::Data>>;
}
diff --git a/components/script/dom/blob.rs b/components/script/dom/blob.rs
index df2afafd698..27aa382c3fc 100644
--- a/components/script/dom/blob.rs
+++ b/components/script/dom/blob.rs
@@ -24,9 +24,9 @@ use crate::dom::bindings::codegen::UnionTypes::ArrayBufferOrArrayBufferViewOrBlo
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::root::DomRoot;
-use crate::dom::bindings::serializable::{Serializable, StorageKey};
+use crate::dom::bindings::serializable::Serializable;
use crate::dom::bindings::str::DOMString;
-use crate::dom::bindings::structuredclone::{StructuredData, StructuredDataReader};
+use crate::dom::bindings::structuredclone::StructuredData;
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::dom::readablestream::ReadableStream;
@@ -119,18 +119,14 @@ impl Serializable for Blob {
Ok(deserialized_blob)
}
- fn serialized_storage(reader: StructuredData<'_>) -> &mut Option<HashMap<BlobId, Self::Data>> {
+ fn serialized_storage<'a>(
+ reader: StructuredData<'a, '_>,
+ ) -> &'a mut Option<HashMap<BlobId, Self::Data>> {
match reader {
StructuredData::Reader(r) => &mut r.blob_impls,
StructuredData::Writer(w) => &mut w.blobs,
}
}
-
- fn deserialized_storage(
- data: &mut StructuredDataReader,
- ) -> &mut Option<HashMap<StorageKey, DomRoot<Self>>> {
- &mut data.blobs
- }
}
/// Extract bytes from BlobParts, used by Blob and File constructor
diff --git a/components/script/dom/canvasrenderingcontext2d.rs b/components/script/dom/canvasrenderingcontext2d.rs
index f6bf432de69..73052e6906e 100644
--- a/components/script/dom/canvasrenderingcontext2d.rs
+++ b/components/script/dom/canvasrenderingcontext2d.rs
@@ -5,11 +5,11 @@
use canvas_traits::canvas::{Canvas2dMsg, CanvasId, CanvasMsg, FromScriptMsg};
use dom_struct::dom_struct;
use euclid::default::{Point2D, Rect, Size2D};
-use ipc_channel::ipc::IpcSharedMemory;
use profile_traits::ipc;
use script_bindings::inheritance::Castable;
use script_layout_interface::HTMLCanvasDataSource;
use servo_url::ServoUrl;
+use snapshot::Snapshot;
use crate::canvas_context::{CanvasContext, CanvasHelpers, LayoutCanvasRenderingContextHelpers};
use crate::canvas_state::CanvasState;
@@ -142,16 +142,18 @@ impl CanvasContext for CanvasRenderingContext2D {
self.set_bitmap_dimensions(self.size().cast())
}
- fn get_image_data_as_shared_memory(&self) -> Option<IpcSharedMemory> {
+ fn get_image_data(&self) -> Option<Snapshot> {
+ let size = self.size();
+
+ if size.is_empty() {
+ return None;
+ }
+
let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
let msg = CanvasMsg::FromScript(FromScriptMsg::SendPixels(sender), self.get_canvas_id());
self.canvas_state.get_ipc_renderer().send(msg).unwrap();
- Some(receiver.recv().unwrap())
- }
-
- fn get_image_data(&self) -> Option<Vec<u8>> {
- Some(self.get_rect(Rect::from_size(self.size().cast())))
+ Some(receiver.recv().unwrap().to_owned())
}
fn origin_is_clean(&self) -> bool {
diff --git a/components/script/dom/cssstylesheet.rs b/components/script/dom/cssstylesheet.rs
index 421e8f45523..a367c9943de 100644
--- a/components/script/dom/cssstylesheet.rs
+++ b/components/script/dom/cssstylesheet.rs
@@ -24,7 +24,7 @@ use crate::dom::bindings::reflector::{
DomGlobal, reflect_dom_object, reflect_dom_object_with_proto,
};
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
-use crate::dom::bindings::str::DOMString;
+use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::cssrulelist::{CSSRuleList, RulesSource};
use crate::dom::element::Element;
use crate::dom::medialist::MediaList;
@@ -290,4 +290,32 @@ impl CSSStyleSheetMethods<crate::DomTypeHolder> for CSSStyleSheet {
// > 8. Return -1.
Ok(-1)
}
+
+ /// <https://drafts.csswg.org/cssom/#synchronously-replace-the-rules-of-a-cssstylesheet>
+ fn ReplaceSync(&self, text: USVString) -> Result<(), Error> {
+ // Step 1. If the constructed flag is not set throw a NotAllowedError
+ if !self.is_constructed {
+ return Err(Error::NotAllowed);
+ }
+
+ // Step 2. Let rules be the result of running parse a stylesheet’s contents from text.
+ let global = self.global();
+ let window = global.as_window();
+
+ StyleStyleSheet::update_from_str(
+ &self.style_stylesheet,
+ &text,
+ UrlExtraData(window.get_url().get_arc()),
+ None,
+ window.css_error_reporter(),
+ AllowImportRules::No, // Step 3.If rules contains one or more @import rules, remove those rules from rules.
+ );
+
+ // Step 4. Set sheet’s CSS rules to rules.
+ // We reset our rule list, which will be initialized properly
+ // at the next getter access.
+ self.rulelist.set(None);
+
+ Ok(())
+ }
}
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs
index 852a12fc7c5..02bdd343d89 100644
--- a/components/script/dom/document.rs
+++ b/components/script/dom/document.rs
@@ -21,9 +21,7 @@ use base::id::WebViewId;
use canvas_traits::canvas::CanvasId;
use canvas_traits::webgl::{self, WebGLContextId, WebGLMsg};
use chrono::Local;
-use constellation_traits::{
- AnimationTickType, NavigationHistoryBehavior, ScriptToConstellationMessage,
-};
+use constellation_traits::{NavigationHistoryBehavior, ScriptToConstellationMessage};
use content_security_policy::{self as csp, CspList, PolicyDisposition};
use cookie::Cookie;
use cssparser::match_ignore_ascii_case;
@@ -516,10 +514,6 @@ pub(crate) struct Document {
pending_input_events: DomRefCell<Vec<ConstellationInputEvent>>,
/// The index of the last mouse move event in the pending compositor events queue.
mouse_move_event_index: DomRefCell<Option<usize>>,
- /// Pending animation ticks, to be handled at the next rendering opportunity.
- #[no_trace]
- #[ignore_malloc_size_of = "AnimationTickType contains data from an outside crate"]
- pending_animation_ticks: DomRefCell<AnimationTickType>,
/// <https://drafts.csswg.org/resize-observer/#dom-document-resizeobservers-slot>
///
/// Note: we are storing, but never removing, resize observers.
@@ -2397,10 +2391,6 @@ impl Document {
pub(crate) fn run_the_animation_frame_callbacks(&self, can_gc: CanGc) {
let _realm = enter_realm(self);
- self.pending_animation_ticks
- .borrow_mut()
- .remove(AnimationTickType::REQUEST_ANIMATION_FRAME);
-
self.running_animation_callbacks.set(true);
let was_faking_animation_frames = self.is_faking_animation_frames();
let timing = self.global().performance().Now();
@@ -3916,7 +3906,6 @@ impl Document {
image_animation_manager: DomRefCell::new(ImageAnimationManager::new()),
dirty_root: Default::default(),
declarative_refresh: Default::default(),
- pending_animation_ticks: Default::default(),
pending_input_events: Default::default(),
mouse_move_event_index: Default::default(),
resize_observers: Default::default(),
@@ -4689,18 +4678,6 @@ impl Document {
.collect()
}
- /// Note a pending animation tick, to be processed at the next `update_the_rendering` task.
- pub(crate) fn note_pending_animation_tick(&self, tick_type: AnimationTickType) {
- self.pending_animation_ticks.borrow_mut().extend(tick_type);
- }
-
- /// Whether this document has received an animation tick for rafs.
- pub(crate) fn has_received_raf_tick(&self) -> bool {
- self.pending_animation_ticks
- .borrow()
- .contains(AnimationTickType::REQUEST_ANIMATION_FRAME)
- }
-
pub(crate) fn advance_animation_timeline_for_testing(&self, delta: f64) {
self.animation_timeline.borrow_mut().advance_specific(delta);
let current_timeline_value = self.current_animation_timeline_value();
@@ -6437,10 +6414,7 @@ impl FakeRequestAnimationFrameCallback {
pub(crate) fn invoke(self, can_gc: CanGc) {
// TODO: Once there is a more generic mechanism to trigger `update_the_rendering` when
// not driven by the compositor, it should be used here.
- self.document
- .root()
- .note_pending_animation_tick(AnimationTickType::REQUEST_ANIMATION_FRAME);
- with_script_thread(|script_thread| script_thread.update_the_rendering(false, can_gc))
+ with_script_thread(|script_thread| script_thread.update_the_rendering(true, can_gc))
}
}
diff --git a/components/script/dom/domexception.rs b/components/script/dom/domexception.rs
index e63c3ff1f53..fbb807b7a95 100644
--- a/components/script/dom/domexception.rs
+++ b/components/script/dom/domexception.rs
@@ -17,9 +17,9 @@ use crate::dom::bindings::reflector::{
Reflector, reflect_dom_object, reflect_dom_object_with_proto,
};
use crate::dom::bindings::root::DomRoot;
-use crate::dom::bindings::serializable::{Serializable, StorageKey};
+use crate::dom::bindings::serializable::Serializable;
use crate::dom::bindings::str::DOMString;
-use crate::dom::bindings::structuredclone::{StructuredData, StructuredDataReader};
+use crate::dom::bindings::structuredclone::StructuredData;
use crate::dom::globalscope::GlobalScope;
use crate::script_runtime::CanGc;
@@ -53,6 +53,7 @@ pub(crate) enum DOMErrorName {
NotReadableError,
DataError,
OperationError,
+ NotAllowedError,
}
impl DOMErrorName {
@@ -84,6 +85,7 @@ impl DOMErrorName {
"NotReadableError" => Some(DOMErrorName::NotReadableError),
"DataError" => Some(DOMErrorName::DataError),
"OperationError" => Some(DOMErrorName::OperationError),
+ "NotAllowedError" => Some(DOMErrorName::NotAllowedError),
_ => None,
}
}
@@ -135,6 +137,10 @@ impl DOMException {
DOMErrorName::OperationError => {
"The operation failed for an operation-specific reason."
},
+ DOMErrorName::NotAllowedError => {
+ r#"The request is not allowed by the user agent or the platform in the current context,
+ possibly because the user denied permission."#
+ },
};
(
@@ -252,18 +258,12 @@ impl Serializable for DOMException {
))
}
- fn serialized_storage(
- data: StructuredData<'_>,
- ) -> &mut Option<HashMap<DomExceptionId, Self::Data>> {
+ fn serialized_storage<'a>(
+ data: StructuredData<'a, '_>,
+ ) -> &'a mut Option<HashMap<DomExceptionId, Self::Data>> {
match data {
StructuredData::Reader(reader) => &mut reader.exceptions,
StructuredData::Writer(writer) => &mut writer.exceptions,
}
}
-
- fn deserialized_storage(
- reader: &mut StructuredDataReader,
- ) -> &mut Option<HashMap<StorageKey, DomRoot<Self>>> {
- &mut reader.dom_exceptions
- }
}
diff --git a/components/script/dom/dompoint.rs b/components/script/dom/dompoint.rs
index 006abb50def..5e848c43c47 100644
--- a/components/script/dom/dompoint.rs
+++ b/components/script/dom/dompoint.rs
@@ -14,8 +14,8 @@ use crate::dom::bindings::codegen::Bindings::DOMPointReadOnlyBinding::DOMPointRe
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::reflector::reflect_dom_object_with_proto;
use crate::dom::bindings::root::DomRoot;
-use crate::dom::bindings::serializable::{Serializable, StorageKey};
-use crate::dom::bindings::structuredclone::{StructuredData, StructuredDataReader};
+use crate::dom::bindings::serializable::Serializable;
+use crate::dom::bindings::structuredclone::StructuredData;
use crate::dom::dompointreadonly::{DOMPointReadOnly, DOMPointWriteMethods};
use crate::dom::globalscope::GlobalScope;
use crate::script_runtime::CanGc;
@@ -163,18 +163,12 @@ impl Serializable for DOMPoint {
))
}
- fn serialized_storage(
- data: StructuredData<'_>,
- ) -> &mut Option<HashMap<DomPointId, Self::Data>> {
+ fn serialized_storage<'a>(
+ data: StructuredData<'a, '_>,
+ ) -> &'a mut Option<HashMap<DomPointId, Self::Data>> {
match data {
StructuredData::Reader(reader) => &mut reader.points,
StructuredData::Writer(writer) => &mut writer.points,
}
}
-
- fn deserialized_storage(
- reader: &mut StructuredDataReader,
- ) -> &mut Option<HashMap<StorageKey, DomRoot<Self>>> {
- &mut reader.dom_points
- }
}
diff --git a/components/script/dom/dompointreadonly.rs b/components/script/dom/dompointreadonly.rs
index 44e4a70b680..eb6bc9b93a9 100644
--- a/components/script/dom/dompointreadonly.rs
+++ b/components/script/dom/dompointreadonly.rs
@@ -15,8 +15,8 @@ use crate::dom::bindings::codegen::Bindings::DOMPointReadOnlyBinding::DOMPointRe
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::root::DomRoot;
-use crate::dom::bindings::serializable::{Serializable, StorageKey};
-use crate::dom::bindings::structuredclone::{StructuredData, StructuredDataReader};
+use crate::dom::bindings::serializable::Serializable;
+use crate::dom::bindings::structuredclone::StructuredData;
use crate::dom::globalscope::GlobalScope;
use crate::script_runtime::CanGc;
@@ -172,18 +172,12 @@ impl Serializable for DOMPointReadOnly {
))
}
- fn serialized_storage(
- data: StructuredData<'_>,
- ) -> &mut Option<HashMap<DomPointId, Self::Data>> {
+ fn serialized_storage<'a>(
+ data: StructuredData<'a, '_>,
+ ) -> &'a mut Option<HashMap<DomPointId, Self::Data>> {
match data {
StructuredData::Reader(r) => &mut r.points,
StructuredData::Writer(w) => &mut w.points,
}
}
-
- fn deserialized_storage(
- reader: &mut StructuredDataReader,
- ) -> &mut Option<HashMap<StorageKey, DomRoot<Self>>> {
- &mut reader.points_read_only
- }
}
diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs
index 2658911c795..b3345b90fc0 100644
--- a/components/script/dom/globalscope.rs
+++ b/components/script/dom/globalscope.rs
@@ -2422,7 +2422,8 @@ impl GlobalScope {
headers: &Option<Serde<HeaderMap>>,
) -> Option<CspList> {
// TODO: Implement step 1 (local scheme special case)
- let mut csp = headers.as_ref()?.get_all("content-security-policy").iter();
+ let headers = headers.as_ref()?;
+ let mut csp = headers.get_all("content-security-policy").iter();
// This silently ignores the CSP if it contains invalid Unicode.
// We should probably report an error somewhere.
let c = csp.next().and_then(|c| c.to_str().ok())?;
@@ -2435,6 +2436,19 @@ impl GlobalScope {
PolicyDisposition::Enforce,
));
}
+ let csp_report = headers
+ .get_all("content-security-policy-report-only")
+ .iter();
+ // This silently ignores the CSP if it contains invalid Unicode.
+ // We should probably report an error somewhere.
+ for c in csp_report {
+ let c = c.to_str().ok()?;
+ csp_list.append(CspList::parse(
+ c,
+ PolicySource::Header,
+ PolicyDisposition::Report,
+ ));
+ }
Some(csp_list)
}
@@ -2822,36 +2836,16 @@ impl GlobalScope {
}))
}
- #[allow(unsafe_code)]
- pub(crate) fn is_js_evaluation_allowed(&self, cx: SafeJSContext) -> bool {
+ pub(crate) fn is_js_evaluation_allowed(&self, source: &str) -> bool {
let Some(csp_list) = self.get_csp_list() else {
return true;
};
- let scripted_caller = unsafe { describe_scripted_caller(*cx) }.unwrap_or_default();
- let is_js_evaluation_allowed = csp_list.is_js_evaluation_allowed() == CheckResult::Allowed;
-
- if !is_js_evaluation_allowed {
- // FIXME: Don't fire event if `script-src` and `default-src`
- // were not passed.
- for policy in csp_list.0 {
- let report = CSPViolationReportBuilder::default()
- .resource("eval".to_owned())
- .effective_directive("script-src".to_owned())
- .report_only(policy.disposition == PolicyDisposition::Report)
- .source_file(scripted_caller.filename.clone())
- .line_number(scripted_caller.line)
- .column_number(scripted_caller.col)
- .build(self);
- let task = CSPViolationReportTask::new(self, report);
+ let (is_js_evaluation_allowed, violations) = csp_list.is_js_evaluation_allowed(source);
- self.task_manager()
- .dom_manipulation_task_source()
- .queue(task);
- }
- }
+ self.report_csp_violations(violations);
- is_js_evaluation_allowed
+ is_js_evaluation_allowed == CheckResult::Allowed
}
pub(crate) fn create_image_bitmap(
@@ -2880,15 +2874,11 @@ impl GlobalScope {
return p;
}
- if let Some((data, size)) = canvas.fetch_all_data() {
- let data = data
- .map(|data| data.to_vec())
- .unwrap_or_else(|| vec![0; size.area() as usize * 4]);
-
+ if let Some(snapshot) = canvas.get_image_data() {
+ let size = snapshot.size().cast();
let image_bitmap =
ImageBitmap::new(self, size.width, size.height, can_gc).unwrap();
-
- image_bitmap.set_bitmap_data(data);
+ image_bitmap.set_bitmap_data(snapshot.to_vec());
image_bitmap.set_origin_clean(canvas.origin_is_clean());
p.resolve_native(&(image_bitmap), can_gc);
}
@@ -2901,14 +2891,11 @@ impl GlobalScope {
return p;
}
- if let Some((data, size)) = canvas.fetch_all_data() {
- let data = data
- .map(|data| data.to_vec())
- .unwrap_or_else(|| vec![0; size.area() as usize * 4]);
-
+ if let Some(snapshot) = canvas.get_image_data() {
+ let size = snapshot.size().cast();
let image_bitmap =
ImageBitmap::new(self, size.width, size.height, can_gc).unwrap();
- image_bitmap.set_bitmap_data(data);
+ image_bitmap.set_bitmap_data(snapshot.to_vec());
image_bitmap.set_origin_clean(canvas.origin_is_clean());
p.resolve_native(&(image_bitmap), can_gc);
}
@@ -3471,10 +3458,13 @@ impl GlobalScope {
unreachable!();
}
+ #[allow(unsafe_code)]
pub(crate) fn report_csp_violations(&self, violations: Vec<Violation>) {
+ let scripted_caller =
+ unsafe { describe_scripted_caller(*GlobalScope::get_cx()) }.unwrap_or_default();
for violation in violations {
let (sample, resource) = match violation.resource {
- ViolationResource::Inline { .. } => (None, "inline".to_owned()),
+ ViolationResource::Inline { sample } => (sample, "inline".to_owned()),
ViolationResource::Url(url) => (None, url.into()),
ViolationResource::TrustedTypePolicy { sample } => {
(Some(sample), "trusted-types-policy".to_owned())
@@ -3482,6 +3472,8 @@ impl GlobalScope {
ViolationResource::TrustedTypeSink { sample } => {
(Some(sample), "trusted-types-sink".to_owned())
},
+ ViolationResource::Eval { sample } => (sample, "eval".to_owned()),
+ ViolationResource::WasmEval => (None, "wasm-eval".to_owned()),
};
let report = CSPViolationReportBuilder::default()
.resource(resource)
@@ -3489,6 +3481,9 @@ impl GlobalScope {
.effective_directive(violation.directive.name)
.original_policy(violation.policy.to_string())
.report_only(violation.policy.disposition == PolicyDisposition::Report)
+ .source_file(scripted_caller.filename.clone())
+ .line_number(scripted_caller.line)
+ .column_number(scripted_caller.col + 1)
.build(self);
let task = CSPViolationReportTask::new(self, report);
self.task_manager()
diff --git a/components/script/dom/htmlcanvaselement.rs b/components/script/dom/htmlcanvaselement.rs
index 9e20539ceca..bb27d28cea8 100644
--- a/components/script/dom/htmlcanvaselement.rs
+++ b/components/script/dom/htmlcanvaselement.rs
@@ -17,7 +17,6 @@ use image::codecs::jpeg::JpegEncoder;
use image::codecs::png::PngEncoder;
use image::codecs::webp::WebPEncoder;
use image::{ColorType, ImageEncoder};
-use ipc_channel::ipc::IpcSharedMemory;
#[cfg(feature = "webgpu")]
use ipc_channel::ipc::{self as ipcchan};
use js::error::throw_type_error;
@@ -25,6 +24,7 @@ use js::rust::{HandleObject, HandleValue};
use script_layout_interface::{HTMLCanvasData, HTMLCanvasDataSource};
use servo_media::streams::MediaStreamType;
use servo_media::streams::registry::MediaStreamId;
+use snapshot::Snapshot;
use style::attr::AttrValue;
use crate::canvas_context::CanvasContext as _;
@@ -69,6 +69,7 @@ use crate::script_runtime::{CanGc, JSContext};
const DEFAULT_WIDTH: u32 = 300;
const DEFAULT_HEIGHT: u32 = 150;
+#[derive(PartialEq)]
enum EncodedImageType {
Png,
Jpeg,
@@ -375,42 +376,21 @@ impl HTMLCanvasElement {
self.Height() != 0 && self.Width() != 0
}
- pub(crate) fn fetch_all_data(&self) -> Option<(Option<IpcSharedMemory>, Size2D<u32>)> {
- let size = self.get_size();
-
- if size.width == 0 || size.height == 0 {
- return None;
- }
-
- let data = match self.context.borrow().as_ref() {
- Some(CanvasContext::Context2d(context)) => context.get_image_data_as_shared_memory(),
- Some(CanvasContext::WebGL(_context)) => {
- // TODO: add a method in WebGLRenderingContext to get the pixels.
- return None;
- },
- Some(CanvasContext::WebGL2(_context)) => {
- // TODO: add a method in WebGL2RenderingContext to get the pixels.
- return None;
- },
- #[cfg(feature = "webgpu")]
- Some(CanvasContext::WebGPU(context)) => context.get_image_data_as_shared_memory(),
- Some(CanvasContext::Placeholder(context)) => return context.fetch_all_data(),
- None => None,
- };
-
- Some((data, size))
- }
-
- fn get_content(&self) -> Option<Vec<u8>> {
- match *self.context.borrow() {
- Some(CanvasContext::Context2d(ref context)) => context.get_image_data(),
- Some(CanvasContext::WebGL(ref context)) => context.get_image_data(),
- Some(CanvasContext::WebGL2(ref context)) => context.get_image_data(),
+ pub(crate) fn get_image_data(&self) -> Option<Snapshot> {
+ match self.context.borrow().as_ref() {
+ Some(CanvasContext::Context2d(context)) => context.get_image_data(),
+ Some(CanvasContext::WebGL(context)) => context.get_image_data(),
+ Some(CanvasContext::WebGL2(context)) => context.get_image_data(),
#[cfg(feature = "webgpu")]
- Some(CanvasContext::WebGPU(ref context)) => context.get_image_data(),
- Some(CanvasContext::Placeholder(_)) | None => {
- // Each pixel is fully-transparent black.
- Some(vec![0; (self.Width() * self.Height() * 4) as usize])
+ Some(CanvasContext::WebGPU(context)) => context.get_image_data(),
+ Some(CanvasContext::Placeholder(context)) => context.get_image_data(),
+ None => {
+ let size = self.get_size();
+ if size.width == 0 || size.height == 0 {
+ None
+ } else {
+ Some(Snapshot::cleared(size.cast()))
+ }
},
}
}
@@ -427,15 +407,23 @@ impl HTMLCanvasElement {
&self,
image_type: &EncodedImageType,
quality: Option<f64>,
- bytes: &[u8],
+ snapshot: &Snapshot,
encoder: &mut W,
) {
+ // We can't use self.Width() or self.Height() here, since the size of the canvas
+ // may have changed since the snapshot was created. Truncating the dimensions to a
+ // u32 can't panic, since the data comes from a canvas which is always smaller than
+ // u32::MAX.
+ let canvas_data = snapshot.data();
+ let width = snapshot.size().width as u32;
+ let height = snapshot.size().height as u32;
+
match image_type {
EncodedImageType::Png => {
// FIXME(nox): https://github.com/image-rs/image-png/issues/86
// FIXME(nox): https://github.com/image-rs/image-png/issues/87
PngEncoder::new(encoder)
- .write_image(bytes, self.Width(), self.Height(), ColorType::Rgba8)
+ .write_image(canvas_data, width, height, ColorType::Rgba8)
.unwrap();
},
EncodedImageType::Jpeg => {
@@ -455,14 +443,14 @@ impl HTMLCanvasElement {
};
jpeg_encoder
- .write_image(bytes, self.Width(), self.Height(), ColorType::Rgba8)
+ .write_image(canvas_data, width, height, ColorType::Rgba8)
.unwrap();
},
EncodedImageType::Webp => {
// No quality support because of https://github.com/image-rs/image/issues/1984
WebPEncoder::new_lossless(encoder)
- .write_image(bytes, self.Width(), self.Height(), ColorType::Rgba8)
+ .write_image(canvas_data, width, height, ColorType::Rgba8)
.unwrap();
},
}
@@ -560,11 +548,23 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement {
}
// Step 3.
- let Some(file) = self.get_content() else {
+ let Some(mut snapshot) = self.get_image_data() else {
return Ok(USVString("data:,".into()));
};
let image_type = EncodedImageType::from(mime_type);
+ snapshot.transform(
+ if image_type == EncodedImageType::Jpeg {
+ snapshot::AlphaMode::AsOpaque {
+ premultiplied: true,
+ }
+ } else {
+ snapshot::AlphaMode::Transparent {
+ premultiplied: false,
+ }
+ },
+ snapshot::PixelFormat::RGBA,
+ );
let mut url = format!("data:{};base64,", image_type.as_mime_type());
let mut encoder = base64::write::EncoderStringWriter::from_consumer(
@@ -575,7 +575,7 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement {
self.encode_for_mime_type(
&image_type,
Self::maybe_quality(quality),
- &file,
+ &snapshot,
&mut encoder,
);
encoder.into_inner();
@@ -597,14 +597,14 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement {
return Err(Error::Security);
}
- // Step 2. and 3.
- // If this canvas element's bitmap has pixels (i.e., neither its horizontal dimension
+ // Step 2. Let result be null.
+ // Step 3. If this canvas element's bitmap has pixels (i.e., neither its horizontal dimension
// nor its vertical dimension is zero),
// then set result to a copy of this canvas element's bitmap.
let result = if self.Width() == 0 || self.Height() == 0 {
None
} else {
- self.get_content()
+ self.get_image_data()
};
let this = Trusted::new(self);
@@ -625,18 +625,22 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement {
return error!("Expected blob callback, but found none!");
};
- if let Some(bytes) = result {
+ if let Some(mut snapshot) = result {
+ snapshot.transform(
+ snapshot::AlphaMode::Transparent{ premultiplied: false },
+ snapshot::PixelFormat::RGBA
+ );
// Step 4.1
// If result is non-null, then set result to a serialization of result as a file with
// type and quality if given.
let mut encoded: Vec<u8> = vec![];
- this.encode_for_mime_type(&image_type, quality, &bytes, &mut encoded);
+ this.encode_for_mime_type(&image_type, quality, &snapshot, &mut encoded);
let blob_impl = BlobImpl::new_from_bytes(encoded, image_type.as_mime_type());
- // Step 4.2.1 & 4.2.2
- // Set result to a new Blob object, created in the relevant realm of this canvas element
- // Invoke callback with « result » and "report".
+ // Step 4.2.1 Set result to a new Blob object, created in the relevant realm of this canvas element
let blob = Blob::new(&this.global(), blob_impl, CanGc::note());
+
+ // Step 4.2.2 Invoke callback with « result » and "report".
let _ = callback.Call__(Some(&blob), ExceptionHandling::Report, CanGc::note());
} else {
let _ = callback.Call__(None, ExceptionHandling::Report, CanGc::note());
diff --git a/components/script/dom/messageport.rs b/components/script/dom/messageport.rs
index fe590052db4..85d94c1aa7a 100644
--- a/components/script/dom/messageport.rs
+++ b/components/script/dom/messageport.rs
@@ -23,7 +23,7 @@ use crate::dom::bindings::error::{Error, ErrorResult, ErrorToJsval};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
use crate::dom::bindings::root::DomRoot;
-use crate::dom::bindings::structuredclone::{self, StructuredData, StructuredDataReader};
+use crate::dom::bindings::structuredclone::{self, StructuredData};
use crate::dom::bindings::trace::RootedTraceableBox;
use crate::dom::bindings::transferable::Transferable;
use crate::dom::bindings::utils::set_dictionary_property;
@@ -268,18 +268,14 @@ impl Transferable for MessagePort {
Ok(transferred_port)
}
- fn serialized_storage(
- data: StructuredData<'_>,
- ) -> &mut Option<HashMap<MessagePortId, Self::Data>> {
+ fn serialized_storage<'a>(
+ data: StructuredData<'a, '_>,
+ ) -> &'a mut Option<HashMap<MessagePortId, Self::Data>> {
match data {
StructuredData::Reader(r) => &mut r.port_impls,
StructuredData::Writer(w) => &mut w.ports,
}
}
-
- fn deserialized_storage(reader: &mut StructuredDataReader) -> &mut Option<Vec<DomRoot<Self>>> {
- &mut reader.message_ports
- }
}
impl MessagePortMethods<crate::DomTypeHolder> for MessagePort {
diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs
index 2a01370085a..45a107ae673 100644
--- a/components/script/dom/node.rs
+++ b/components/script/dom/node.rs
@@ -167,7 +167,6 @@ pub struct Node {
/// Layout data for this node. This is populated during layout and can
/// be used for incremental relayout and script queries.
- #[ignore_malloc_size_of = "trait object"]
#[no_trace]
layout_data: DomRefCell<Option<Box<GenericLayoutData>>>,
}
diff --git a/components/script/dom/offscreencanvas.rs b/components/script/dom/offscreencanvas.rs
index 0587fbad12b..aabe5955e12 100644
--- a/components/script/dom/offscreencanvas.rs
+++ b/components/script/dom/offscreencanvas.rs
@@ -6,8 +6,8 @@ use std::cell::Cell;
use dom_struct::dom_struct;
use euclid::default::Size2D;
-use ipc_channel::ipc::IpcSharedMemory;
use js::rust::{HandleObject, HandleValue};
+use snapshot::Snapshot;
use crate::dom::bindings::cell::{DomRefCell, Ref, ref_filter_map};
use crate::dom::bindings::codegen::Bindings::OffscreenCanvasBinding::{
@@ -88,21 +88,18 @@ impl OffscreenCanvas {
ref_filter_map(self.context.borrow(), |ctx| ctx.as_ref())
}
- pub(crate) fn fetch_all_data(&self) -> Option<(Option<IpcSharedMemory>, Size2D<u32>)> {
- let size = self.get_size();
-
- if size.width == 0 || size.height == 0 {
- return None;
- }
-
- let data = match self.context.borrow().as_ref() {
- Some(OffscreenCanvasContext::OffscreenContext2d(context)) => {
- context.get_image_data_as_shared_memory()
+ pub(crate) fn get_image_data(&self) -> Option<Snapshot> {
+ match self.context.borrow().as_ref() {
+ Some(OffscreenCanvasContext::OffscreenContext2d(context)) => context.get_image_data(),
+ None => {
+ let size = self.get_size();
+ if size.width == 0 || size.height == 0 {
+ None
+ } else {
+ Some(Snapshot::cleared(size))
+ }
},
- None => None,
- };
-
- Some((data, size.to_u32()))
+ }
}
pub(crate) fn get_or_init_2d_context(
diff --git a/components/script/dom/offscreencanvasrenderingcontext2d.rs b/components/script/dom/offscreencanvasrenderingcontext2d.rs
index 69a1d41af2e..2f9b52640e6 100644
--- a/components/script/dom/offscreencanvasrenderingcontext2d.rs
+++ b/components/script/dom/offscreencanvasrenderingcontext2d.rs
@@ -8,7 +8,7 @@ use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanva
use canvas_traits::canvas::Canvas2dMsg;
use dom_struct::dom_struct;
use euclid::default::Size2D;
-use ipc_channel::ipc::IpcSharedMemory;
+use snapshot::Snapshot;
use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::{
CanvasDirection, CanvasFillRule, CanvasImageSource, CanvasLineCap, CanvasLineJoin,
@@ -76,8 +76,8 @@ impl OffscreenCanvasRenderingContext2D {
self.context.origin_is_clean()
}
- pub(crate) fn get_image_data_as_shared_memory(&self) -> Option<IpcSharedMemory> {
- self.context.get_image_data_as_shared_memory()
+ pub(crate) fn get_image_data(&self) -> Option<Snapshot> {
+ self.context.get_image_data()
}
}
diff --git a/components/script/dom/readablestream.rs b/components/script/dom/readablestream.rs
index b445fb4b9fc..37899f18fec 100644
--- a/components/script/dom/readablestream.rs
+++ b/components/script/dom/readablestream.rs
@@ -59,7 +59,7 @@ use crate::realms::{enter_realm, InRealm};
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
use crate::dom::bindings::transferable::Transferable;
-use crate::dom::bindings::structuredclone::{StructuredData, StructuredDataReader};
+use crate::dom::bindings::structuredclone::StructuredData;
use super::bindings::buffer_source::HeapBufferSource;
use super::bindings::codegen::Bindings::ReadableStreamBYOBReaderBinding::ReadableStreamBYOBReaderReadOptions;
@@ -2247,16 +2247,12 @@ impl Transferable for ReadableStream {
}
/// Note: we are relying on the port transfer, so the data returned here are related to the port.
- fn serialized_storage(
- data: StructuredData<'_>,
- ) -> &mut Option<HashMap<MessagePortId, Self::Data>> {
+ fn serialized_storage<'a>(
+ data: StructuredData<'a, '_>,
+ ) -> &'a mut Option<HashMap<MessagePortId, Self::Data>> {
match data {
StructuredData::Reader(r) => &mut r.port_impls,
StructuredData::Writer(w) => &mut w.ports,
}
}
-
- fn deserialized_storage(reader: &mut StructuredDataReader) -> &mut Option<Vec<DomRoot<Self>>> {
- &mut reader.readable_streams
- }
}
diff --git a/components/script/dom/response.rs b/components/script/dom/response.rs
index 283b7d615aa..cbdfbe94603 100644
--- a/components/script/dom/response.rs
+++ b/components/script/dom/response.rs
@@ -8,7 +8,7 @@ use std::str::FromStr;
use dom_struct::dom_struct;
use http::header::HeaderMap as HyperHeaders;
use hyper_serde::Serde;
-use js::rust::HandleObject;
+use js::rust::{HandleObject, HandleValue};
use net_traits::http_status::HttpStatus;
use servo_url::ServoUrl;
use url::Position;
@@ -24,13 +24,13 @@ use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
-use crate::dom::bindings::str::{ByteString, USVString};
+use crate::dom::bindings::str::{ByteString, USVString, serialize_jsval_to_json_utf8};
use crate::dom::globalscope::GlobalScope;
use crate::dom::headers::{Guard, Headers, is_obs_text, is_vchar};
use crate::dom::promise::Promise;
use crate::dom::readablestream::ReadableStream;
use crate::dom::underlyingsourcecontainer::UnderlyingSourceType;
-use crate::script_runtime::{CanGc, StreamConsumer};
+use crate::script_runtime::{CanGc, JSContext, StreamConsumer};
#[dom_struct]
pub(crate) struct Response {
@@ -72,7 +72,7 @@ impl Response {
}
}
- // https://fetch.spec.whatwg.org/#dom-response
+ /// <https://fetch.spec.whatwg.org/#dom-response>
pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Response> {
Self::new_with_proto(global, None, can_gc)
}
@@ -142,92 +142,43 @@ fn is_null_body_status(status: u16) -> bool {
}
impl ResponseMethods<crate::DomTypeHolder> for Response {
- // https://fetch.spec.whatwg.org/#initialize-a-response
+ /// <https://fetch.spec.whatwg.org/#dom-response>
fn Constructor(
global: &GlobalScope,
proto: Option<HandleObject>,
can_gc: CanGc,
- body: Option<BodyInit>,
+ body_init: Option<BodyInit>,
init: &ResponseBinding::ResponseInit,
) -> Fallible<DomRoot<Response>> {
- // Step 1
- if init.status < 200 || init.status > 599 {
- return Err(Error::Range(format!(
- "init's status member should be in the range 200 to 599, inclusive, but is {}",
- init.status
- )));
- }
-
- // Step 2
- if !is_valid_status_text(&init.statusText) {
- return Err(Error::Type(
- "init's statusText member does not match the reason-phrase token production"
- .to_string(),
- ));
- }
-
- let r = Response::new_with_proto(global, proto, can_gc);
-
- // Step 3 & 4
- *r.status.borrow_mut() = HttpStatus::new_raw(init.status, init.statusText.clone().into());
-
- // Step 5
- if let Some(ref headers_member) = init.headers {
- r.Headers(can_gc).fill(Some(headers_member.clone()))?;
- }
-
- // Step 6
- if let Some(ref body) = body {
- // Step 6.1
- if is_null_body_status(init.status) {
- return Err(Error::Type(
- "Body is non-null but init's status member is a null body status".to_string(),
- ));
- };
-
- // Step 6.2
- let ExtractedBody {
- stream,
- total_bytes: _,
- content_type,
- source: _,
- } = body.extract(global, can_gc)?;
-
- r.body_stream.set(Some(&*stream));
-
- // Step 6.3
- if let Some(content_type_contents) = content_type {
- if !r
- .Headers(can_gc)
- .Has(ByteString::new(b"Content-Type".to_vec()))
- .unwrap()
- {
- r.Headers(can_gc).Append(
- ByteString::new(b"Content-Type".to_vec()),
- ByteString::new(content_type_contents.as_bytes().to_vec()),
- )?;
- }
- };
- } else {
- // Reset FetchResponse to an in-memory stream with empty byte sequence here for
- // no-init-body case
- let stream = ReadableStream::new_from_bytes(global, Vec::with_capacity(0), can_gc)?;
- r.body_stream.set(Some(&*stream));
- }
+ // 1. Set this’s response to a new response.
+ // Our Response/Body types don't actually hold onto an internal fetch Response.
+ let response = Response::new_with_proto(global, proto, can_gc);
+
+ // 2. Set this’s headers to a new Headers object with this’s relevant realm,
+ // whose header list is this’s response’s header list and guard is "response".
+ response.Headers(can_gc).set_guard(Guard::Response);
+
+ // 3. Let bodyWithType be null.
+ // 4. If body is non-null, then set bodyWithType to the result of extracting body.
+ let body_with_type = match body_init {
+ Some(body) => Some(body.extract(global, can_gc)?),
+ None => None,
+ };
- Ok(r)
+ // 5. Perform *initialize a response* given this, init, and bodyWithType.
+ initialize_response(global, can_gc, body_with_type, init, response)
}
- // https://fetch.spec.whatwg.org/#dom-response-error
+ /// <https://fetch.spec.whatwg.org/#dom-response-error>
fn Error(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Response> {
- let r = Response::new(global, can_gc);
- *r.response_type.borrow_mut() = DOMResponseType::Error;
- r.Headers(can_gc).set_guard(Guard::Immutable);
- *r.status.borrow_mut() = HttpStatus::new_error();
- r
+ let response = Response::new(global, can_gc);
+ *response.response_type.borrow_mut() = DOMResponseType::Error;
+ response.Headers(can_gc).set_guard(Guard::Immutable);
+ *response.status.borrow_mut() = HttpStatus::new_error();
+ response
}
- // https://fetch.spec.whatwg.org/#dom-response-redirect
+ /// <https://fetch.spec.whatwg.org/#dom-response-redirect>
fn Redirect(
global: &GlobalScope,
url: USVString,
@@ -251,31 +202,60 @@ impl ResponseMethods<crate::DomTypeHolder> for Response {
// Step 4
// see Step 4 continued
- let r = Response::new(global, can_gc);
+ let response = Response::new(global, can_gc);
// Step 5
- *r.status.borrow_mut() = HttpStatus::new_raw(status, vec![]);
+ *response.status.borrow_mut() = HttpStatus::new_raw(status, vec![]);
// Step 6
let url_bytestring =
ByteString::from_str(url.as_str()).unwrap_or(ByteString::new(b"".to_vec()));
- r.Headers(can_gc)
+ response
+ .Headers(can_gc)
.Set(ByteString::new(b"Location".to_vec()), url_bytestring)?;
// Step 4 continued
// Headers Guard is set to Immutable here to prevent error in Step 6
- r.Headers(can_gc).set_guard(Guard::Immutable);
+ response.Headers(can_gc).set_guard(Guard::Immutable);
// Step 7
- Ok(r)
+ Ok(response)
+ }
+
+ /// <https://fetch.spec.whatwg.org/#dom-response-json>
+ #[allow(unsafe_code)]
+ fn CreateFromJson(
+ cx: JSContext,
+ global: &GlobalScope,
+ data: HandleValue,
+ init: &ResponseBinding::ResponseInit,
+ can_gc: CanGc,
+ ) -> Fallible<DomRoot<Response>> {
+ // 1. Let bytes the result of running serialize a JavaScript value to JSON bytes on data.
+ let json_str = serialize_jsval_to_json_utf8(cx, data)?;
+
+ // 2. Let body be the result of extracting bytes
+ // The spec's definition of JSON bytes is a UTF-8 encoding so using a DOMString here handles
+ // the encoding part.
+ let body_init = BodyInit::String(json_str);
+ let mut body = body_init.extract(global, can_gc)?;
+
+ // 3. Let responseObject be the result of creating a Response object, given a new response,
+ // "response", and the current realm.
+ let response = Response::new(global, can_gc);
+ response.Headers(can_gc).set_guard(Guard::Response);
+
+ // 4. Perform initialize a response given responseObject, init, and (body, "application/json").
+ body.content_type = Some("application/json".into());
+ initialize_response(global, can_gc, Some(body), init, response)
}
- // https://fetch.spec.whatwg.org/#dom-response-type
+ /// <https://fetch.spec.whatwg.org/#dom-response-type>
fn Type(&self) -> DOMResponseType {
*self.response_type.borrow() //into()
}
- // https://fetch.spec.whatwg.org/#dom-response-url
+ /// <https://fetch.spec.whatwg.org/#dom-response-url>
fn Url(&self) -> USVString {
USVString(String::from(
(*self.url.borrow())
@@ -285,33 +265,33 @@ impl ResponseMethods<crate::DomTypeHolder> for Response {
))
}
- // https://fetch.spec.whatwg.org/#dom-response-redirected
+ /// <https://fetch.spec.whatwg.org/#dom-response-redirected>
fn Redirected(&self) -> bool {
return *self.redirected.borrow();
}
- // https://fetch.spec.whatwg.org/#dom-response-status
+ /// <https://fetch.spec.whatwg.org/#dom-response-status>
fn Status(&self) -> u16 {
self.status.borrow().raw_code()
}
- // https://fetch.spec.whatwg.org/#dom-response-ok
+ /// <https://fetch.spec.whatwg.org/#dom-response-ok>
fn Ok(&self) -> bool {
self.status.borrow().is_success()
}
- // https://fetch.spec.whatwg.org/#dom-response-statustext
+ /// <https://fetch.spec.whatwg.org/#dom-response-statustext>
fn StatusText(&self) -> ByteString {
ByteString::new(self.status.borrow().message().to_vec())
}
- // https://fetch.spec.whatwg.org/#dom-response-headers
+ /// <https://fetch.spec.whatwg.org/#dom-response-headers>
fn Headers(&self, can_gc: CanGc) -> DomRoot<Headers> {
self.headers_reflector
.or_init(|| Headers::for_response(&self.global(), can_gc))
}
- // https://fetch.spec.whatwg.org/#dom-response-clone
+ /// <https://fetch.spec.whatwg.org/#dom-response-clone>
fn Clone(&self, can_gc: CanGc) -> Fallible<DomRoot<Response>> {
// Step 1
if self.is_locked() || self.is_disturbed() {
@@ -352,7 +332,7 @@ impl ResponseMethods<crate::DomTypeHolder> for Response {
Ok(new_response)
}
- // https://fetch.spec.whatwg.org/#dom-body-bodyused
+ /// <https://fetch.spec.whatwg.org/#dom-body-bodyused>
fn BodyUsed(&self) -> bool {
self.is_disturbed()
}
@@ -362,27 +342,27 @@ impl ResponseMethods<crate::DomTypeHolder> for Response {
self.body()
}
- // https://fetch.spec.whatwg.org/#dom-body-text
+ /// <https://fetch.spec.whatwg.org/#dom-body-text>
fn Text(&self, can_gc: CanGc) -> Rc<Promise> {
consume_body(self, BodyType::Text, can_gc)
}
- // https://fetch.spec.whatwg.org/#dom-body-blob
+ /// <https://fetch.spec.whatwg.org/#dom-body-blob>
fn Blob(&self, can_gc: CanGc) -> Rc<Promise> {
consume_body(self, BodyType::Blob, can_gc)
}
- // https://fetch.spec.whatwg.org/#dom-body-formdata
+ /// <https://fetch.spec.whatwg.org/#dom-body-formdata>
fn FormData(&self, can_gc: CanGc) -> Rc<Promise> {
consume_body(self, BodyType::FormData, can_gc)
}
- // https://fetch.spec.whatwg.org/#dom-body-json
+ /// <https://fetch.spec.whatwg.org/#dom-body-json>
fn Json(&self, can_gc: CanGc) -> Rc<Promise> {
consume_body(self, BodyType::Json, can_gc)
}
- // https://fetch.spec.whatwg.org/#dom-body-arraybuffer
+ /// <https://fetch.spec.whatwg.org/#dom-body-arraybuffer>
fn ArrayBuffer(&self, can_gc: CanGc) -> Rc<Promise> {
consume_body(self, BodyType::ArrayBuffer, can_gc)
}
@@ -393,6 +373,80 @@ impl ResponseMethods<crate::DomTypeHolder> for Response {
}
}
+/// <https://fetch.spec.whatwg.org/#initialize-a-response>
+fn initialize_response(
+ global: &GlobalScope,
+ can_gc: CanGc,
+ body: Option<ExtractedBody>,
+ init: &ResponseBinding::ResponseInit,
+ response: DomRoot<Response>,
+) -> Result<DomRoot<Response>, Error> {
+ // 1. If init["status"] is not in the range 200 to 599, inclusive, then throw a RangeError.
+ if init.status < 200 || init.status > 599 {
+ return Err(Error::Range(format!(
+ "init's status member should be in the range 200 to 599, inclusive, but is {}",
+ init.status
+ )));
+ }
+
+ // 2. If init["statusText"] is not the empty string and does not match the reason-phrase token production,
+ // then throw a TypeError.
+ if !is_valid_status_text(&init.statusText) {
+ return Err(Error::Type(
+ "init's statusText member does not match the reason-phrase token production"
+ .to_string(),
+ ));
+ }
+
+ // 3. Set response’s response’s status to init["status"].
+ // 4. Set response’s response’s status message to init["statusText"].
+ *response.status.borrow_mut() =
+ HttpStatus::new_raw(init.status, init.statusText.clone().into());
+
+ // 5. If init["headers"] exists, then fill response’s headers with init["headers"].
+ if let Some(ref headers_member) = init.headers {
+ response
+ .Headers(can_gc)
+ .fill(Some(headers_member.clone()))?;
+ }
+
+ // 6. If body is non-null, then:
+ if let Some(ref body) = body {
+ // 6.1 If response’s status is a null body status, then throw a TypeError.
+ if is_null_body_status(init.status) {
+ return Err(Error::Type(
+ "Body is non-null but init's status member is a null body status".to_string(),
+ ));
+ };
+
+ // 6.2 Set response’s body to body’s body.
+ response.body_stream.set(Some(&*body.stream));
+
+ // 6.3 If body’s type is non-null and response’s header list does not contain `Content-Type`,
+ // then append (`Content-Type`, body’s type) to response’s header list.
+ if let Some(content_type_contents) = &body.content_type {
+ if !response
+ .Headers(can_gc)
+ .Has(ByteString::new(b"Content-Type".to_vec()))
+ .unwrap()
+ {
+ response.Headers(can_gc).Append(
+ ByteString::new(b"Content-Type".to_vec()),
+ ByteString::new(content_type_contents.as_bytes().to_vec()),
+ )?;
+ }
+ };
+ } else {
+ // Reset FetchResponse to an in-memory stream with empty byte sequence here for
+ // no-init-body case. This is because the Response/Body types here do not hold onto a
+ // fetch Response object.
+ let stream = ReadableStream::new_from_bytes(global, Vec::with_capacity(0), can_gc)?;
+ response.body_stream.set(Some(&*stream));
+ }
+
+ Ok(response)
+}
+
fn serialize_without_fragment(url: &ServoUrl) -> &str {
&url[..Position::AfterQuery]
}
diff --git a/components/script/dom/webgl2renderingcontext.rs b/components/script/dom/webgl2renderingcontext.rs
index bb6ffa11849..416454d8719 100644
--- a/components/script/dom/webgl2renderingcontext.rs
+++ b/components/script/dom/webgl2renderingcontext.rs
@@ -24,6 +24,7 @@ use js::typedarray::{ArrayBufferView, CreateWith, Float32, Int32Array, Uint32, U
use script_bindings::interfaces::WebGL2RenderingContextHelpers;
use script_layout_interface::HTMLCanvasDataSource;
use servo_config::pref;
+use snapshot::Snapshot;
use url::Host;
use crate::canvas_context::CanvasContext;
@@ -549,11 +550,11 @@ impl WebGL2RenderingContext {
return
);
- let (sender, receiver) = ipc::bytes_channel().unwrap();
+ let (sender, receiver) = ipc::channel().unwrap();
self.base.send_command(WebGLCommand::ReadPixels(
src_rect, format, pixel_type, sender,
));
- let src = receiver.recv().unwrap();
+ let (src, _) = receiver.recv().unwrap();
for i in 0..src_rect.size.height as usize {
let src_start = i * src_row_bytes as usize;
@@ -916,11 +917,7 @@ impl CanvasContext for WebGL2RenderingContext {
self.base.resize();
}
- fn get_image_data_as_shared_memory(&self) -> Option<IpcSharedMemory> {
- self.base.get_image_data_as_shared_memory()
- }
-
- fn get_image_data(&self) -> Option<Vec<u8>> {
+ fn get_image_data(&self) -> Option<Snapshot> {
self.base.get_image_data()
}
diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs
index 1e51ac4baf9..9996a3cf504 100644
--- a/components/script/dom/webglrenderingcontext.rs
+++ b/components/script/dom/webglrenderingcontext.rs
@@ -34,6 +34,7 @@ use pixels::{self, PixelFormat};
use script_layout_interface::HTMLCanvasDataSource;
use serde::{Deserialize, Serialize};
use servo_config::pref;
+use snapshot::Snapshot;
use webrender_api::ImageKey;
use crate::canvas_context::CanvasContext;
@@ -628,11 +629,15 @@ impl WebGLRenderingContext {
if !canvas.origin_is_clean() {
return Err(Error::Security);
}
- if let Some((data, size)) = canvas.fetch_all_data() {
- let data = data.unwrap_or_else(|| {
- IpcSharedMemory::from_bytes(&vec![0; size.area() as usize * 4])
- });
- TexPixels::new(data, size, PixelFormat::BGRA8, true)
+ if let Some(snapshot) = canvas.get_image_data() {
+ let snapshot = snapshot.as_ipc();
+ let size = snapshot.size().cast();
+ let format = match snapshot.format() {
+ snapshot::PixelFormat::RGBA => PixelFormat::RGBA8,
+ snapshot::PixelFormat::BGRA => PixelFormat::BGRA8,
+ };
+ let premultiply = snapshot.alpha_mode().is_premultiplied();
+ TexPixels::new(snapshot.to_ipc_shared_memory(), size, format, premultiply)
} else {
return Ok(None);
}
@@ -1922,18 +1927,13 @@ impl CanvasContext for WebGLRenderingContext {
}
}
- fn get_image_data_as_shared_memory(&self) -> Option<IpcSharedMemory> {
- // TODO: add a method in WebGLRenderingContext to get the pixels.
- None
- }
-
// Used by HTMLCanvasElement.toDataURL
//
// This emits errors quite liberally, but the spec says that this operation
// can fail and that it is UB what happens in that case.
//
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#2.2
- fn get_image_data(&self) -> Option<Vec<u8>> {
+ fn get_image_data(&self) -> Option<Snapshot> {
handle_potential_webgl_error!(self, self.validate_framebuffer(), return None);
let mut size = self.size().cast();
@@ -1945,14 +1945,20 @@ impl CanvasContext for WebGLRenderingContext {
size.width = cmp::min(size.width, fb_width as u32);
size.height = cmp::min(size.height, fb_height as u32);
- let (sender, receiver) = ipc::bytes_channel().unwrap();
+ let (sender, receiver) = ipc::channel().unwrap();
self.send_command(WebGLCommand::ReadPixels(
Rect::from_size(size),
constants::RGBA,
constants::UNSIGNED_BYTE,
sender,
));
- Some(receiver.recv().unwrap())
+ let (data, alpha_mode) = receiver.recv().unwrap();
+ Some(Snapshot::from_vec(
+ size.cast(),
+ snapshot::PixelFormat::RGBA,
+ alpha_mode,
+ data.to_vec(),
+ ))
}
fn mark_as_dirty(&self) {
@@ -3826,11 +3832,11 @@ impl WebGLRenderingContextMethods<crate::DomTypeHolder> for WebGLRenderingContex
dest_offset += -y * row_len;
}
- let (sender, receiver) = ipc::bytes_channel().unwrap();
+ let (sender, receiver) = ipc::channel().unwrap();
self.send_command(WebGLCommand::ReadPixels(
src_rect, format, pixel_type, sender,
));
- let src = receiver.recv().unwrap();
+ let (src, _) = receiver.recv().unwrap();
let src_row_len = src_rect.size.width as usize * bytes_per_pixel as usize;
for i in 0..src_rect.size.height {
diff --git a/components/script/dom/webgpu/gpucanvascontext.rs b/components/script/dom/webgpu/gpucanvascontext.rs
index 595b54c58d7..c81f96f651f 100644
--- a/components/script/dom/webgpu/gpucanvascontext.rs
+++ b/components/script/dom/webgpu/gpucanvascontext.rs
@@ -7,8 +7,9 @@ use std::cell::RefCell;
use arrayvec::ArrayVec;
use dom_struct::dom_struct;
-use ipc_channel::ipc::{self, IpcSharedMemory};
+use ipc_channel::ipc::{self};
use script_layout_interface::HTMLCanvasDataSource;
+use snapshot::Snapshot;
use webgpu_traits::{
ContextConfiguration, PRESENTATION_BUFFER_COUNT, WebGPU, WebGPUContextId, WebGPURequest,
WebGPUTexture,
@@ -277,10 +278,10 @@ impl CanvasContext for GPUCanvasContext {
}
/// <https://gpuweb.github.io/gpuweb/#ref-for-abstract-opdef-get-a-copy-of-the-image-contents-of-a-context%E2%91%A5>
- fn get_image_data_as_shared_memory(&self) -> Option<IpcSharedMemory> {
+ fn get_image_data(&self) -> Option<Snapshot> {
// 1. Return a copy of the image contents of context.
Some(if self.drawing_buffer.borrow().cleared {
- IpcSharedMemory::from_byte(0, self.size().area() as usize * 4)
+ Snapshot::cleared(self.size())
} else {
let (sender, receiver) = ipc::channel().unwrap();
self.channel
@@ -290,7 +291,7 @@ impl CanvasContext for GPUCanvasContext {
sender,
})
.unwrap();
- receiver.recv().unwrap()
+ receiver.recv().unwrap().to_owned()
})
}
diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs
index 96176132b6b..932a9ec7f2d 100644
--- a/components/script/dom/window.rs
+++ b/components/script/dom/window.rs
@@ -1216,15 +1216,26 @@ impl WindowMethods<crate::DomTypeHolder> for Window {
}
}
- #[allow(unsafe_code)]
- fn WebdriverCallback(&self, cx: JSContext, val: HandleValue) {
- let rv = unsafe { jsval_to_webdriver(*cx, &self.globalscope, val) };
+ fn WebdriverCallback(&self, cx: JSContext, val: HandleValue, realm: InRealm, can_gc: CanGc) {
+ let rv = jsval_to_webdriver(cx, &self.globalscope, val, realm, can_gc);
let opt_chan = self.webdriver_script_chan.borrow_mut().take();
if let Some(chan) = opt_chan {
chan.send(rv).unwrap();
}
}
+ fn WebdriverException(&self, cx: JSContext, val: HandleValue, realm: InRealm, can_gc: CanGc) {
+ let rv = jsval_to_webdriver(cx, &self.globalscope, val, realm, can_gc);
+ let opt_chan = self.webdriver_script_chan.borrow_mut().take();
+ if let Some(chan) = opt_chan {
+ if let Ok(rv) = rv {
+ chan.send(Err(WebDriverJSError::JSException(rv))).unwrap();
+ } else {
+ chan.send(rv).unwrap();
+ }
+ }
+ }
+
fn WebdriverTimeout(&self) {
let opt_chan = self.webdriver_script_chan.borrow_mut().take();
if let Some(chan) = opt_chan {
@@ -2250,7 +2261,9 @@ impl Window {
// Query content box without considering any reflow
pub(crate) fn content_box_query_unchecked(&self, node: &Node) -> Option<UntypedRect<Au>> {
- self.layout.borrow().query_content_box(node.to_opaque())
+ self.layout
+ .borrow()
+ .query_content_box(node.to_trusted_node_address())
}
pub(crate) fn content_box_query(&self, node: &Node, can_gc: CanGc) -> Option<UntypedRect<Au>> {
@@ -2264,14 +2277,18 @@ impl Window {
if !self.layout_reflow(QueryMsg::ContentBoxes, can_gc) {
return vec![];
}
- self.layout.borrow().query_content_boxes(node.to_opaque())
+ self.layout
+ .borrow()
+ .query_content_boxes(node.to_trusted_node_address())
}
pub(crate) fn client_rect_query(&self, node: &Node, can_gc: CanGc) -> UntypedRect<i32> {
if !self.layout_reflow(QueryMsg::ClientRectQuery, can_gc) {
return Rect::zero();
}
- self.layout.borrow().query_client_rect(node.to_opaque())
+ self.layout
+ .borrow()
+ .query_client_rect(node.to_trusted_node_address())
}
/// Find the scroll area of the given node, if it is not None. If the node
@@ -2281,11 +2298,12 @@ impl Window {
node: Option<&Node>,
can_gc: CanGc,
) -> UntypedRect<i32> {
- let opaque = node.map(|node| node.to_opaque());
if !self.layout_reflow(QueryMsg::ScrollingAreaQuery, can_gc) {
return Rect::zero();
}
- self.layout.borrow().query_scrolling_area(opaque)
+ self.layout
+ .borrow()
+ .query_scrolling_area(node.map(Node::to_trusted_node_address))
}
pub(crate) fn scroll_offset_query(&self, node: &Node) -> Vector2D<f32, LayoutPixel> {
@@ -2374,7 +2392,10 @@ impl Window {
return (None, Rect::zero());
}
- let response = self.layout.borrow().query_offset_parent(node.to_opaque());
+ let response = self
+ .layout
+ .borrow()
+ .query_offset_parent(node.to_trusted_node_address());
let element = response.node_address.and_then(|parent_node_address| {
let node = unsafe { from_untrusted_node_address(parent_node_address) };
DomRoot::downcast(node)
diff --git a/components/script/dom/writablestream.rs b/components/script/dom/writablestream.rs
index 1490fc694ef..e7e9ce906a6 100644
--- a/components/script/dom/writablestream.rs
+++ b/components/script/dom/writablestream.rs
@@ -27,7 +27,7 @@ use crate::dom::bindings::conversions::ConversionResult;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
-use crate::dom::bindings::structuredclone::{StructuredData, StructuredDataReader};
+use crate::dom::bindings::structuredclone::StructuredData;
use crate::dom::bindings::transferable::Transferable;
use crate::dom::countqueuingstrategy::{extract_high_water_mark, extract_size_algorithm};
use crate::dom::domexception::{DOMErrorName, DOMException};
@@ -1182,16 +1182,12 @@ impl Transferable for WritableStream {
}
/// Note: we are relying on the port transfer, so the data returned here are related to the port.
- fn serialized_storage(
- data: StructuredData<'_>,
- ) -> &mut Option<HashMap<MessagePortId, Self::Data>> {
+ fn serialized_storage<'a>(
+ data: StructuredData<'a, '_>,
+ ) -> &'a mut Option<HashMap<MessagePortId, Self::Data>> {
match data {
StructuredData::Reader(r) => &mut r.port_impls,
StructuredData::Writer(w) => &mut w.ports,
}
}
-
- fn deserialized_storage(reader: &mut StructuredDataReader) -> &mut Option<Vec<DomRoot<Self>>> {
- &mut reader.writable_streams
- }
}
diff --git a/components/script/layout_image.rs b/components/script/layout_image.rs
index 7fd23804ffd..df542b4b759 100644
--- a/components/script/layout_image.rs
+++ b/components/script/layout_image.rs
@@ -119,7 +119,10 @@ pub(crate) fn fetch_image_for_layout(
)
.origin(document.origin().immutable().clone())
.destination(Destination::Image)
- .pipeline_id(Some(document.global().pipeline_id()));
+ .pipeline_id(Some(document.global().pipeline_id()))
+ .insecure_requests_policy(document.insecure_requests_policy())
+ .has_trustworthy_ancestor_origin(document.has_trustworthy_ancestor_origin())
+ .policy_container(document.policy_container().to_owned());
// Layout image loads do not delay the document load event.
document.fetch_background(request, context);
diff --git a/components/script/messaging.rs b/components/script/messaging.rs
index 808b338e709..7d0b7aabe05 100644
--- a/components/script/messaging.rs
+++ b/components/script/messaging.rs
@@ -73,7 +73,7 @@ impl MixedMessage {
ScriptThreadMessage::RemoveHistoryStates(id, ..) => Some(*id),
ScriptThreadMessage::FocusIFrame(id, ..) => Some(*id),
ScriptThreadMessage::WebDriverScriptCommand(id, ..) => Some(*id),
- ScriptThreadMessage::TickAllAnimations(id, ..) => Some(*id),
+ ScriptThreadMessage::TickAllAnimations(..) => None,
ScriptThreadMessage::WebFontLoaded(id, ..) => Some(*id),
ScriptThreadMessage::DispatchIFrameLoadEvent {
target: _,
diff --git a/components/script/script_runtime.rs b/components/script/script_runtime.rs
index d6832a644ec..1f05c15d74e 100644
--- a/components/script/script_runtime.rs
+++ b/components/script/script_runtime.rs
@@ -19,7 +19,7 @@ use std::time::{Duration, Instant};
use std::{os, ptr, thread};
use background_hang_monitor_api::ScriptHangAnnotation;
-use content_security_policy::{CheckResult, PolicyDisposition};
+use content_security_policy::CheckResult;
use js::conversions::jsstr_to_string;
use js::glue::{
CollectServoSizes, CreateJobQueue, DeleteJobQueue, DispatchableRun, JobQueueTraps,
@@ -45,7 +45,7 @@ pub(crate) use js::rust::ThreadSafeJSContext;
use js::rust::wrappers::{GetPromiseIsHandled, JS_GetPromiseResult};
use js::rust::{
Handle, HandleObject as RustHandleObject, IntoHandle, JSEngine, JSEngineHandle, ParentRuntime,
- Runtime as RustRuntime, describe_scripted_caller,
+ Runtime as RustRuntime,
};
use malloc_size_of::MallocSizeOfOps;
use malloc_size_of_derive::MallocSizeOf;
@@ -82,7 +82,6 @@ use crate::microtask::{EnqueuedPromiseCallback, Microtask, MicrotaskQueue};
use crate::realms::{AlreadyInRealm, InRealm};
use crate::script_module::EnsureModuleHooksInitialized;
use crate::script_thread::trace_thread;
-use crate::security_manager::{CSPViolationReportBuilder, CSPViolationReportTask};
use crate::task_source::SendableTaskSource;
static JOB_QUEUE_TRAPS: JobQueueTraps = JobQueueTraps {
@@ -373,10 +372,6 @@ unsafe extern "C" fn content_security_policy_allows(
let cx = JSContext::from_ptr(cx);
wrap_panic(&mut || {
// SpiderMonkey provides null pointer when executing webassembly.
- let sample = match sample {
- sample if !sample.is_null() => Some(jsstr_to_string(*cx, *sample)),
- _ => None,
- };
let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
let global = GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof));
let Some(csp_list) = global.get_csp_list() else {
@@ -384,43 +379,19 @@ unsafe extern "C" fn content_security_policy_allows(
return;
};
- let is_js_evaluation_allowed = csp_list.is_js_evaluation_allowed() == CheckResult::Allowed;
- let is_wasm_evaluation_allowed =
- csp_list.is_wasm_evaluation_allowed() == CheckResult::Allowed;
- let scripted_caller = describe_scripted_caller(*cx).unwrap_or_default();
-
- let resource = match runtime_code {
- RuntimeCode::JS => "eval".to_owned(),
- RuntimeCode::WASM => "wasm-eval".to_owned(),
- };
-
- allowed = match runtime_code {
- RuntimeCode::JS if is_js_evaluation_allowed => true,
- RuntimeCode::WASM if is_wasm_evaluation_allowed => true,
- _ => false,
+ let (is_evaluation_allowed, violations) = match runtime_code {
+ RuntimeCode::JS => {
+ let source = match sample {
+ sample if !sample.is_null() => &jsstr_to_string(*cx, *sample),
+ _ => "",
+ };
+ csp_list.is_js_evaluation_allowed(source)
+ },
+ RuntimeCode::WASM => csp_list.is_wasm_evaluation_allowed(),
};
- if !allowed {
- // FIXME: Don't fire event if `script-src` and `default-src`
- // were not passed.
- for policy in csp_list.0 {
- let report = CSPViolationReportBuilder::default()
- .resource(resource.clone())
- .sample(sample.clone())
- .report_only(policy.disposition == PolicyDisposition::Report)
- .source_file(scripted_caller.filename.clone())
- .line_number(scripted_caller.line)
- .column_number(scripted_caller.col)
- .effective_directive("script-src".to_owned())
- .build(&global);
- let task = CSPViolationReportTask::new(&global, report);
-
- global
- .task_manager()
- .dom_manipulation_task_source()
- .queue(task);
- }
- }
+ global.report_csp_violations(violations);
+ allowed = is_evaluation_allowed == CheckResult::Allowed;
});
allowed
}
diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs
index c9b27bb6c56..f78b5bf281b 100644
--- a/components/script/script_thread.rs
+++ b/components/script/script_thread.rs
@@ -1147,14 +1147,6 @@ impl ScriptThread {
return;
}
- // Run rafs for all pipeline, if a raf tick was received for any.
- // This ensures relative ordering of rafs between parent doc and iframes.
- let should_run_rafs = self
- .documents
- .borrow()
- .iter()
- .any(|(_, doc)| doc.is_fully_active() && doc.has_received_raf_tick());
-
let any_animations_running = self.documents.borrow().iter().any(|(_, document)| {
document.is_fully_active() && document.animations().running_animation_count() != 0
});
@@ -1242,7 +1234,7 @@ impl ScriptThread {
// > 14. For each doc of docs, run the animation frame callbacks for doc, passing
// > in the relative high resolution time given frameTimestamp and doc's
// > relevant global object as the timestamp.
- if should_run_rafs {
+ if requested_by_compositor {
document.run_the_animation_frame_callbacks(can_gc);
}
@@ -1421,18 +1413,9 @@ impl ScriptThread {
self.handle_viewport(id, rect);
}),
MixedMessage::FromConstellation(ScriptThreadMessage::TickAllAnimations(
- pipeline_id,
- tick_type,
+ _webviews,
)) => {
- if let Some(document) = self.documents.borrow().find_document(pipeline_id) {
- document.note_pending_animation_tick(tick_type);
- compositor_requested_update_the_rendering = true;
- } else {
- warn!(
- "Trying to note pending animation tick for closed pipeline {}.",
- pipeline_id
- )
- }
+ compositor_requested_update_the_rendering = true;
},
MixedMessage::FromConstellation(ScriptThreadMessage::SendInputEvent(id, event)) => {
self.handle_input_event(id, event)
@@ -2291,6 +2274,7 @@ impl ScriptThread {
node_id,
name,
reply,
+ can_gc,
)
},
WebDriverScriptCommand::GetElementCSS(node_id, name, reply) => {
@@ -2439,8 +2423,6 @@ impl ScriptThread {
let mut reports = vec![];
perform_memory_report(|ops| {
- let prefix = format!("url({urls})");
- reports.extend(self.get_cx().get_reports(prefix.clone(), ops));
for (_, document) in documents.iter() {
document
.window()
@@ -2448,6 +2430,9 @@ impl ScriptThread {
.collect_reports(&mut reports, ops);
}
+ let prefix = format!("url({urls})");
+ reports.extend(self.get_cx().get_reports(prefix.clone(), ops));
+
reports.push(self.image_cache.memory_report(&prefix, ops));
});
diff --git a/components/script/timers.rs b/components/script/timers.rs
index 244aa2df4ed..0afc3da164a 100644
--- a/components/script/timers.rs
+++ b/components/script/timers.rs
@@ -421,8 +421,7 @@ impl JsTimers {
) -> i32 {
let callback = match callback {
TimerCallback::StringTimerCallback(code_str) => {
- let cx = GlobalScope::get_cx();
- if global.is_js_evaluation_allowed(cx) {
+ if global.is_js_evaluation_allowed(code_str.as_ref()) {
InternalTimerCallback::StringTimerCallback(code_str)
} else {
return 0;
diff --git a/components/script/webdriver_handlers.rs b/components/script/webdriver_handlers.rs
index c6303ca89e0..781ac53f415 100644
--- a/components/script/webdriver_handlers.rs
+++ b/components/script/webdriver_handlers.rs
@@ -3,7 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cmp;
-use std::collections::HashMap;
+use std::collections::{HashMap, HashSet};
use std::ffi::CString;
use std::ptr::NonNull;
@@ -48,7 +48,7 @@ use crate::dom::bindings::conversions::{
ConversionBehavior, ConversionResult, FromJSValConvertible, StringificationBehavior,
get_property, get_property_jsval, jsid_to_string, jsstring_to_str, root_from_object,
};
-use crate::dom::bindings::error::{Error, throw_dom_exception};
+use crate::dom::bindings::error::{Error, report_pending_exception, throw_dom_exception};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{DomGlobal, DomObject};
use crate::dom::bindings::root::DomRoot;
@@ -67,7 +67,7 @@ use crate::dom::node::{Node, NodeTraits, ShadowIncluding};
use crate::dom::nodelist::NodeList;
use crate::dom::window::Window;
use crate::dom::xmlserializer::XMLSerializer;
-use crate::realms::enter_realm;
+use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
use crate::script_module::ScriptFetchOptions;
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
use crate::script_thread::ScriptThread;
@@ -183,12 +183,44 @@ unsafe fn is_arguments_object(cx: *mut JSContext, value: HandleValue) -> bool {
jsstring_to_str(cx, class_name) == "[object Arguments]"
}
+#[derive(Eq, Hash, PartialEq)]
+struct HashableJSVal(u64);
+
+impl From<HandleValue<'_>> for HashableJSVal {
+ fn from(v: HandleValue<'_>) -> HashableJSVal {
+ HashableJSVal(v.get().asBits_)
+ }
+}
+
#[allow(unsafe_code)]
-pub(crate) unsafe fn jsval_to_webdriver(
+pub(crate) fn jsval_to_webdriver(
+ cx: SafeJSContext,
+ global_scope: &GlobalScope,
+ val: HandleValue,
+ realm: InRealm,
+ can_gc: CanGc,
+) -> WebDriverJSResult {
+ let mut seen = HashSet::new();
+ let result = unsafe { jsval_to_webdriver_inner(*cx, global_scope, val, &mut seen) };
+ if result.is_err() {
+ report_pending_exception(cx, true, realm, can_gc);
+ }
+ result
+}
+
+#[allow(unsafe_code)]
+unsafe fn jsval_to_webdriver_inner(
cx: *mut JSContext,
global_scope: &GlobalScope,
val: HandleValue,
+ seen: &mut HashSet<HashableJSVal>,
) -> WebDriverJSResult {
+ let hashable = val.into();
+ if seen.contains(&hashable) {
+ return Err(WebDriverJSError::JSError);
+ }
+ seen.insert(hashable);
+
let _ac = enter_realm(global_scope);
if val.get().is_undefined() {
Ok(WebDriverJSValue::Undefined)
@@ -254,9 +286,11 @@ pub(crate) unsafe fn jsval_to_webdriver(
for i in 0..length {
rooted!(in(cx) let mut item = UndefinedValue());
match get_property_jsval(cx, object.handle(), &i.to_string(), item.handle_mut()) {
- Ok(_) => match jsval_to_webdriver(cx, global_scope, item.handle()) {
- Ok(converted_item) => result.push(converted_item),
- err @ Err(_) => return err,
+ Ok(_) => {
+ match jsval_to_webdriver_inner(cx, global_scope, item.handle(), seen) {
+ Ok(converted_item) => result.push(converted_item),
+ err @ Err(_) => return err,
+ }
},
Err(error) => {
throw_dom_exception(
@@ -298,7 +332,7 @@ pub(crate) unsafe fn jsval_to_webdriver(
&HandleValueArray::empty(),
value.handle_mut(),
) {
- jsval_to_webdriver(cx, global_scope, value.handle())
+ jsval_to_webdriver_inner(cx, global_scope, value.handle(), seen)
} else {
throw_dom_exception(
SafeJSContext::from_ptr(cx),
@@ -349,7 +383,9 @@ pub(crate) unsafe fn jsval_to_webdriver(
return Err(WebDriverJSError::JSError);
};
- if let Ok(value) = jsval_to_webdriver(cx, global_scope, property.handle()) {
+ if let Ok(value) =
+ jsval_to_webdriver_inner(cx, global_scope, property.handle(), seen)
+ {
result.insert(name.into(), value);
} else {
return Err(WebDriverJSError::JSError);
@@ -373,18 +409,22 @@ pub(crate) fn handle_execute_script(
) {
match window {
Some(window) => {
- let result = unsafe {
- let cx = window.get_cx();
- rooted!(in(*cx) let mut rval = UndefinedValue());
- let global = window.as_global_scope();
- global.evaluate_js_on_global_with_result(
- &eval,
- rval.handle_mut(),
- ScriptFetchOptions::default_classic_script(global),
- global.api_base_url(),
- can_gc,
- );
- jsval_to_webdriver(*cx, global, rval.handle())
+ let cx = window.get_cx();
+ let realm = AlreadyInRealm::assert_for_cx(cx);
+ let realm = InRealm::already(&realm);
+
+ rooted!(in(*cx) let mut rval = UndefinedValue());
+ let global = window.as_global_scope();
+ let result = if global.evaluate_js_on_global_with_result(
+ &eval,
+ rval.handle_mut(),
+ ScriptFetchOptions::default_classic_script(global),
+ global.api_base_url(),
+ can_gc,
+ ) {
+ jsval_to_webdriver(cx, global, rval.handle(), realm, can_gc)
+ } else {
+ Err(WebDriverJSError::JSError)
};
reply.send(result).unwrap();
@@ -406,17 +446,20 @@ pub(crate) fn handle_execute_async_script(
match window {
Some(window) => {
let cx = window.get_cx();
+ let reply_sender = reply.clone();
window.set_webdriver_script_chan(Some(reply));
rooted!(in(*cx) let mut rval = UndefinedValue());
let global_scope = window.as_global_scope();
- global_scope.evaluate_js_on_global_with_result(
+ if !global_scope.evaluate_js_on_global_with_result(
&eval,
rval.handle_mut(),
ScriptFetchOptions::default_classic_script(global_scope),
global_scope.api_base_url(),
can_gc,
- );
+ ) {
+ reply_sender.send(Err(WebDriverJSError::JSError)).unwrap();
+ }
},
None => {
reply
@@ -1136,12 +1179,13 @@ pub(crate) fn handle_get_property(
node_id: String,
name: String,
reply: IpcSender<Result<WebDriverJSValue, ErrorStatus>>,
+ can_gc: CanGc,
) {
reply
.send(
find_node_by_unique_id(documents, pipeline, node_id).map(|node| {
let document = documents.find_document(pipeline).unwrap();
- let _ac = enter_realm(&*document);
+ let realm = enter_realm(&*document);
let cx = document.window().get_cx();
rooted!(in(*cx) let mut property = UndefinedValue());
@@ -1154,14 +1198,19 @@ pub(crate) fn handle_get_property(
)
} {
Ok(_) => {
- match unsafe { jsval_to_webdriver(*cx, &node.global(), property.handle()) }
- {
+ match jsval_to_webdriver(
+ cx,
+ &node.global(),
+ property.handle(),
+ InRealm::entered(&realm),
+ can_gc,
+ ) {
Ok(property) => property,
Err(_) => WebDriverJSValue::Undefined,
}
},
Err(error) => {
- throw_dom_exception(cx, &node.global(), error, CanGc::note());
+ throw_dom_exception(cx, &node.global(), error, can_gc);
WebDriverJSValue::Undefined
},
}
diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf
index 4946177e0b3..c457bf70b85 100644
--- a/components/script_bindings/codegen/Bindings.conf
+++ b/components/script_bindings/codegen/Bindings.conf
@@ -551,7 +551,7 @@ DOMInterfaces = {
},
'Response': {
- 'canGc': ['Error', 'Redirect', 'Clone', 'Text', 'Blob', 'FormData', 'Json', 'ArrayBuffer', 'Headers', 'Bytes'],
+ 'canGc': ['Error', 'Redirect', 'Clone', 'CreateFromJson', 'Text', 'Blob', 'FormData', 'Json', 'ArrayBuffer', 'Headers', 'Bytes'],
},
'RTCPeerConnection': {
@@ -642,8 +642,8 @@ DOMInterfaces = {
},
'Window': {
- 'canGc': ['Stop', 'Fetch', 'Scroll', 'Scroll_','ScrollBy', 'ScrollBy_', 'Stop', 'Fetch', 'Open', 'CreateImageBitmap', 'TrustedTypes'],
- 'inRealms': ['Fetch', 'GetOpener'],
+ 'canGc': ['Stop', 'Fetch', 'Scroll', 'Scroll_','ScrollBy', 'ScrollBy_', 'Stop', 'Fetch', 'Open', 'CreateImageBitmap', 'TrustedTypes', 'WebdriverCallback', 'WebdriverException'],
+ 'inRealms': ['Fetch', 'GetOpener', 'WebdriverCallback', 'WebdriverException'],
'additionalTraits': ['crate::interfaces::WindowHelpers'],
},
diff --git a/components/script_bindings/error.rs b/components/script_bindings/error.rs
index 8424ff0fa95..a95d0b0b78c 100644
--- a/components/script_bindings/error.rs
+++ b/components/script_bindings/error.rs
@@ -59,6 +59,8 @@ pub enum Error {
Data,
/// OperationError DOMException
Operation,
+ /// NotAllowedError DOMException
+ NotAllowed,
/// TypeError JavaScript Error
Type(String),
diff --git a/components/script_bindings/str.rs b/components/script_bindings/str.rs
index 09d48512f3e..0ef6e0c528a 100644
--- a/components/script_bindings/str.rs
+++ b/components/script_bindings/str.rs
@@ -10,14 +10,19 @@ use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::str::FromStr;
use std::sync::LazyLock;
-use std::{fmt, ops, str};
+use std::{fmt, ops, slice, str};
use cssparser::CowRcStr;
use html5ever::{LocalName, Namespace};
+use js::rust::wrappers::ToJSON;
+use js::rust::{HandleObject, HandleValue};
use num_traits::Zero;
use regex::Regex;
use stylo_atoms::Atom;
+use crate::error::Error;
+use crate::script_runtime::JSContext as SafeJSContext;
+
/// Encapsulates the IDL `ByteString` type.
#[derive(Clone, Debug, Default, Eq, JSTraceable, MallocSizeOf, PartialEq)]
pub struct ByteString(Vec<u8>);
@@ -293,6 +298,64 @@ impl DOMString {
}
}
+/// Because this converts to a DOMString it becomes UTF-8 encoded which is closer to
+/// the spec definition of <https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-json-bytes>
+/// but we generally do not operate on anything that is truly a WTF-16 string.
+///
+/// <https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string>
+pub fn serialize_jsval_to_json_utf8(
+ cx: SafeJSContext,
+ data: HandleValue,
+) -> Result<DOMString, Error> {
+ #[repr(C)]
+ struct ToJSONCallbackData {
+ string: Option<String>,
+ }
+
+ let mut out_str = ToJSONCallbackData { string: None };
+
+ #[allow(unsafe_code)]
+ unsafe extern "C" fn write_callback(
+ string: *const u16,
+ len: u32,
+ data: *mut std::ffi::c_void,
+ ) -> bool {
+ let data = data as *mut ToJSONCallbackData;
+ let string_chars = slice::from_raw_parts(string, len as usize);
+ (*data)
+ .string
+ .get_or_insert_with(Default::default)
+ .push_str(&String::from_utf16_lossy(string_chars));
+ true
+ }
+
+ // 1. Let result be ? Call(%JSON.stringify%, undefined, « value »).
+ unsafe {
+ let stringify_result = ToJSON(
+ *cx,
+ data,
+ HandleObject::null(),
+ HandleValue::null(),
+ Some(write_callback),
+ &mut out_str as *mut ToJSONCallbackData as *mut _,
+ );
+ // Note: ToJSON returns false when a JS error is thrown, so we need to return
+ // JSFailed to propagate the raised exception
+ if !stringify_result {
+ return Err(Error::JSFailed);
+ }
+ }
+
+ // 2. If result is undefined, then throw a TypeError.
+ // Note: ToJSON will not call the callback if the data cannot be serialized.
+ // 3. Assert: result is a string.
+ // 4. Return result.
+ out_str
+ .string
+ .map(Into::into)
+ .ok_or_else(|| Error::Type("unable to serialize JSON".to_owned()))
+}
+
impl Borrow<str> for DOMString {
#[inline]
fn borrow(&self) -> &str {
diff --git a/components/script_bindings/webidls/CSSStyleSheet.webidl b/components/script_bindings/webidls/CSSStyleSheet.webidl
index 1241b5c2769..302e7433300 100644
--- a/components/script_bindings/webidls/CSSStyleSheet.webidl
+++ b/components/script_bindings/webidls/CSSStyleSheet.webidl
@@ -11,6 +11,7 @@ interface CSSStyleSheet : StyleSheet {
[Throws, SameObject] readonly attribute CSSRuleList cssRules;
[Throws] unsigned long insertRule(DOMString rule, optional unsigned long index = 0);
[Throws] undefined deleteRule(unsigned long index);
+ [Throws] undefined replaceSync(USVString text);
};
dictionary CSSStyleSheetInit {
diff --git a/components/script_bindings/webidls/Response.webidl b/components/script_bindings/webidls/Response.webidl
index 0ced0c13794..d37538d4b6b 100644
--- a/components/script_bindings/webidls/Response.webidl
+++ b/components/script_bindings/webidls/Response.webidl
@@ -9,6 +9,7 @@ interface Response {
[Throws] constructor(optional BodyInit? body = null, optional ResponseInit init = {});
[NewObject] static Response error();
[NewObject, Throws] static Response redirect(USVString url, optional unsigned short status = 302);
+ [NewObject, Throws, BinaryName="createFromJson"] static Response json(any data, optional ResponseInit init = {});
readonly attribute ResponseType type;
diff --git a/components/script_bindings/webidls/Window.webidl b/components/script_bindings/webidls/Window.webidl
index d42ba22ea66..81c442b119f 100644
--- a/components/script_bindings/webidls/Window.webidl
+++ b/components/script_bindings/webidls/Window.webidl
@@ -148,6 +148,7 @@ partial interface Window {
partial interface Window {
// Shouldn't be public, but just to make things work for now
undefined webdriverCallback(optional any result);
+ undefined webdriverException(optional any result);
undefined webdriverTimeout();
};
diff --git a/components/servo/Cargo.toml b/components/servo/Cargo.toml
index 0c4eaf40904..498d170492d 100644
--- a/components/servo/Cargo.toml
+++ b/components/servo/Cargo.toml
@@ -104,6 +104,7 @@ serde = { workspace = true }
servo-media = { workspace = true }
servo-media-dummy = { workspace = true }
servo-media-gstreamer = { workspace = true, optional = true }
+servo-tracing = { workspace = true }
servo_allocator = { path = "../allocator" }
servo_config = { path = "../config" }
servo_geometry = { path = "../geometry" }
diff --git a/components/servo/lib.rs b/components/servo/lib.rs
index be56ffd5c1f..7fb990527ec 100644
--- a/components/servo/lib.rs
+++ b/components/servo/lib.rs
@@ -246,10 +246,7 @@ impl webrender_api::RenderNotifier for RenderNotifier {
}
impl Servo {
- #[cfg_attr(
- feature = "tracing",
- tracing::instrument(skip(builder), fields(servo_profiling = true), level = "trace",)
- )]
+ #[servo_tracing::instrument(skip(builder))]
fn new(builder: ServoBuilder) -> Self {
// Global configuration options, parsed from the command line.
let opts = builder.opts.map(|opts| *opts);
diff --git a/components/servo_tracing/Cargo.toml b/components/servo_tracing/Cargo.toml
new file mode 100644
index 00000000000..1660aa7f691
--- /dev/null
+++ b/components/servo_tracing/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "servo-tracing"
+edition.workspace = true
+version.workspace = true
+authors.workspace = true
+license.workspace = true
+publish.workspace = true
+rust-version.workspace = true
+
+[dependencies]
+quote = { workspace = true }
+proc-macro2 = { workspace = true }
+syn = { version = "2", features = ["full"] }
+
+[lib]
+path = "lib.rs"
+proc-macro = true
+doctest = false
+
+[dev-dependencies]
+prettyplease = "0.2.32"
diff --git a/components/servo_tracing/lib.rs b/components/servo_tracing/lib.rs
new file mode 100644
index 00000000000..04e87ee6cc0
--- /dev/null
+++ b/components/servo_tracing/lib.rs
@@ -0,0 +1,394 @@
+/* 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/. */
+extern crate proc_macro;
+
+use proc_macro::TokenStream;
+use proc_macro2::Punct;
+use quote::{ToTokens, TokenStreamExt, quote};
+use syn::parse::{Parse, Parser};
+use syn::punctuated::Punctuated;
+use syn::token::Comma;
+use syn::{Expr, ItemFn, Meta, MetaList, Token, parse_quote, parse2};
+
+struct Fields(MetaList);
+impl From<MetaList> for Fields {
+ fn from(value: MetaList) -> Self {
+ Fields(value)
+ }
+}
+
+impl Fields {
+ fn create_with_servo_profiling() -> Self {
+ Fields(parse_quote! { fields(servo_profiling = true) })
+ }
+
+ fn inject_servo_profiling(&mut self) -> syn::Result<()> {
+ let metalist = std::mem::replace(&mut self.0, parse_quote! {field()});
+
+ let arguments: Punctuated<Meta, Comma> =
+ Punctuated::parse_terminated.parse2(metalist.tokens)?;
+
+ let servo_profile_given = arguments
+ .iter()
+ .any(|arg| arg.path().is_ident("servo_profiling"));
+
+ let metalist = if servo_profile_given {
+ parse_quote! {
+ fields(#arguments)
+ }
+ } else {
+ parse_quote! {
+ fields(servo_profiling=true, #arguments)
+ }
+ };
+
+ let _ = std::mem::replace(&mut self.0, metalist);
+
+ Ok(())
+ }
+}
+
+impl ToTokens for Fields {
+ fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+ let items = &self.0;
+ tokens.append_all(quote! { #items });
+ }
+}
+enum Directive {
+ Passthrough(Meta),
+ Level(Expr),
+ Fields(Fields),
+}
+
+impl From<Fields> for Directive {
+ fn from(value: Fields) -> Self {
+ Directive::Fields(value)
+ }
+}
+
+impl Directive {
+ fn is_level(&self) -> bool {
+ matches!(self, Directive::Level(..))
+ }
+
+ fn fields_mut(&mut self) -> Option<&mut Fields> {
+ match self {
+ Directive::Fields(fields) => Some(fields),
+ _ => None,
+ }
+ }
+}
+
+impl ToTokens for Directive {
+ fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+ match self {
+ Directive::Passthrough(meta) => tokens.append_all(quote! { #meta }),
+ Directive::Level(level) => tokens.append_all(quote! { level = #level }),
+ Directive::Fields(fields) => tokens.append_all(quote! { #fields }),
+ };
+ }
+}
+
+impl ToTokens for InstrumentConfiguration {
+ fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+ tokens.append_terminated(&self.0, Punct::new(',', proc_macro2::Spacing::Joint));
+ }
+}
+
+struct InstrumentConfiguration(Vec<Directive>);
+
+impl InstrumentConfiguration {
+ fn inject_servo_profiling(&mut self) -> syn::Result<()> {
+ let fields = self.0.iter_mut().find_map(Directive::fields_mut);
+ match fields {
+ None => {
+ self.0
+ .push(Directive::from(Fields::create_with_servo_profiling()));
+ Ok(())
+ },
+ Some(fields) => fields.inject_servo_profiling(),
+ }
+ }
+
+ fn inject_level(&mut self) {
+ if self.0.iter().any(|a| a.is_level()) {
+ return;
+ }
+ self.0.push(Directive::Level(parse_quote! { "trace" }));
+ }
+}
+
+impl Parse for InstrumentConfiguration {
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+ let args = Punctuated::<Meta, Token![,]>::parse_terminated(input)?;
+ let mut components = vec![];
+
+ for arg in args {
+ match arg {
+ Meta::List(meta_list) if meta_list.path.is_ident("fields") => {
+ components.push(Directive::Fields(meta_list.into()));
+ },
+ Meta::NameValue(meta_name_value) if meta_name_value.path.is_ident("level") => {
+ components.push(Directive::Level(meta_name_value.value));
+ },
+ _ => {
+ components.push(Directive::Passthrough(arg));
+ },
+ }
+ }
+ Ok(InstrumentConfiguration(components))
+ }
+}
+
+fn instrument_internal(
+ attr: proc_macro2::TokenStream,
+ item: proc_macro2::TokenStream,
+) -> syn::Result<proc_macro2::TokenStream> {
+ // Prepare passthrough arguments for tracing::instrument
+ let mut configuration: InstrumentConfiguration = parse2(attr)?;
+ let input_fn: ItemFn = parse2(item)?;
+
+ configuration.inject_servo_profiling()?;
+ configuration.inject_level();
+
+ let output = quote! {
+ #[cfg_attr(
+ feature = "tracing",
+ tracing::instrument(
+ #configuration
+ )
+ )]
+ #input_fn
+ };
+
+ Ok(output)
+}
+
+#[proc_macro_attribute]
+/// Instruments a function with some sane defaults by automatically:
+/// - setting the attribute behind the "tracing" flag
+/// - adding `servo_profiling = true` in the `tracing::instrument(fields(...))` argument.
+/// - setting `level = "trace"` if it is not given.
+///
+/// This macro assumes the consuming crate has a `tracing` feature flag.
+///
+/// We need to be able to set the following
+/// ```
+/// #[cfg_attr(
+/// feature = "tracing",
+/// tracing::instrument(
+/// name = "MyCustomName",
+/// skip_all,
+/// fields(servo_profiling = true),
+/// level = "trace",
+/// )
+/// )]
+/// fn my_fn() { /* .... */ }
+/// ```
+/// from a simpler macro, such as:
+///
+/// ```
+/// #[servo_tracing::instrument(name = "MyCustomName", skip_all)]
+/// fn my_fn() { /* .... */ }
+/// ```
+pub fn instrument(attr: TokenStream, item: TokenStream) -> TokenStream {
+ match instrument_internal(attr.into(), item.into()) {
+ Ok(stream) => stream.into(),
+ Err(err) => err.to_compile_error().into(),
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use proc_macro2::TokenStream;
+ use quote::{ToTokens, quote};
+ use syn::{Attribute, ItemFn};
+
+ use crate::instrument_internal;
+
+ fn extract_instrument_attribute(item_fn: &mut ItemFn) -> TokenStream {
+ let attr: &Attribute = item_fn
+ .attrs
+ .iter()
+ .find(|attr| {
+ // because this is a very nested structure, it is easier to check
+ // by constructing the full path, and then doing a string comparison.
+ let p = attr.path().to_token_stream().to_string();
+ p == "servo_tracing :: instrument"
+ })
+ .expect("Attribute `servo_tracing::instrument` not found");
+
+ // we create a tokenstream of the actual internal contents of the attribute
+ let attr_args = attr
+ .parse_args::<TokenStream>()
+ .expect("Failed to parse attribute args");
+
+ // we remove the tracing attribute, this is to avoid passing it as an actual attribute to itself.
+ item_fn.attrs.retain(|attr| {
+ attr.path().to_token_stream().to_string() != "servo_tracing :: instrument"
+ });
+
+ attr_args
+ }
+
+ /// To make test case generation easy, we parse a test_case as a function item
+ /// with its own attributes, including [`servo_tracing::instrument`].
+ ///
+ /// We extract the [`servo_tracing::instrument`] attribute, and pass it as the first argument to
+ /// [`servo_tracing::instrument_internal`],
+ fn evaluate(function: TokenStream, test_case: TokenStream, expected: TokenStream) {
+ let test_case = quote! {
+ #test_case
+ #function
+ };
+ let expected = quote! {
+ #expected
+ #function
+ };
+ let function_str = function.to_string();
+ let function_str = syn::parse_file(&function_str).expect("function to have valid syntax");
+ let function_str = prettyplease::unparse(&function_str);
+
+ let mut item_fn: ItemFn =
+ syn::parse2(test_case).expect("Failed to parse input as function");
+
+ let attr_args = extract_instrument_attribute(&mut item_fn);
+ let item_fn = item_fn.to_token_stream();
+
+ let generated = instrument_internal(attr_args, item_fn).expect("Generation to not fail.");
+
+ let generated = syn::parse_file(generated.to_string().as_str())
+ .expect("to have generated a valid function");
+ let generated = prettyplease::unparse(&generated);
+ let expected = syn::parse_file(expected.to_string().as_str())
+ .expect("to have been given a valid expected function");
+ let expected = prettyplease::unparse(&expected);
+
+ eprintln!(
+ "Generated:---------:\n{}--------\nExpected:----------\n{}",
+ &generated, &expected
+ );
+ assert_eq!(generated, expected);
+ assert!(
+ generated.contains(&function_str),
+ "Expected generated code: {generated} to contain the function code: {function_str}"
+ );
+ }
+
+ fn function1() -> TokenStream {
+ quote! {
+ pub fn start(
+ state: (),
+ layout_factory: (),
+ random_pipeline_closure_probability: (),
+ random_pipeline_closure_seed: (),
+ hard_fail: (),
+ canvas_create_sender: (),
+ canvas_ipc_sender: (),
+ ) {
+ }
+ }
+ }
+
+ fn function2() -> TokenStream {
+ quote! {
+ fn layout(
+ mut self,
+ layout_context: &LayoutContext,
+ positioning_context: &mut PositioningContext,
+ containing_block_for_children: &ContainingBlock,
+ containing_block_for_table: &ContainingBlock,
+ depends_on_block_constraints: bool,
+ ) {
+ }
+ }
+ }
+
+ #[test]
+ fn passing_servo_profiling_and_level_and_aux() {
+ let function = function1();
+ let expected = quote! {
+ #[cfg_attr(
+ feature = "tracing",
+ tracing::instrument(skip(state, layout_factory), fields(servo_profiling = true), level = "trace",)
+ )]
+ };
+
+ let test_case = quote! {
+ #[servo_tracing::instrument(skip(state, layout_factory),fields(servo_profiling = true),level = "trace",)]
+ };
+
+ evaluate(function, test_case, expected);
+ }
+
+ #[test]
+ fn passing_servo_profiling_and_level() {
+ let function = function1();
+ let expected = quote! {
+ #[cfg_attr(
+ feature = "tracing",
+ tracing::instrument( fields(servo_profiling = true), level = "trace",)
+ )]
+ };
+
+ let test_case = quote! {
+ #[servo_tracing::instrument(fields(servo_profiling = true),level = "trace",)]
+ };
+ evaluate(function, test_case, expected);
+ }
+
+ #[test]
+ fn passing_servo_profiling() {
+ let function = function1();
+ let expected = quote! {
+ #[cfg_attr(
+ feature = "tracing",
+ tracing::instrument( fields(servo_profiling = true), level = "trace",)
+ )]
+ };
+
+ let test_case = quote! {
+ #[servo_tracing::instrument(fields(servo_profiling = true))]
+ };
+ evaluate(function, test_case, expected);
+ }
+
+ #[test]
+ fn inject_level_and_servo_profiling() {
+ let function = function1();
+ let expected = quote! {
+ #[cfg_attr(
+ feature = "tracing",
+ tracing::instrument(fields(servo_profiling = true), level = "trace",)
+ )]
+ };
+
+ let test_case = quote! {
+ #[servo_tracing::instrument()]
+ };
+ evaluate(function, test_case, expected);
+ }
+
+ #[test]
+ fn instrument_with_name() {
+ let function = function2();
+ let expected = quote! {
+ #[cfg_attr(
+ feature = "tracing",
+ tracing::instrument(
+ name = "Table::layout",
+ skip_all,
+ fields(servo_profiling = true),
+ level = "trace",
+ )
+ )]
+ };
+
+ let test_case = quote! {
+ #[servo_tracing::instrument(name="Table::layout", skip_all)]
+ };
+
+ evaluate(function, test_case, expected);
+ }
+}
diff --git a/components/shared/canvas/Cargo.toml b/components/shared/canvas/Cargo.toml
index d6e96711e1d..c77399ef847 100644
--- a/components/shared/canvas/Cargo.toml
+++ b/components/shared/canvas/Cargo.toml
@@ -26,6 +26,7 @@ pixels = { path = "../../pixels" }
serde = { workspace = true }
serde_bytes = { workspace = true }
servo_config = { path = "../../config" }
+snapshot = { workspace = true }
stylo = { workspace = true }
webrender_api = { workspace = true }
webxr-api = { workspace = true, features = ["ipc"] }
diff --git a/components/shared/canvas/canvas.rs b/components/shared/canvas/canvas.rs
index 90ba569b5eb..850f5f9bd9a 100644
--- a/components/shared/canvas/canvas.rs
+++ b/components/shared/canvas/canvas.rs
@@ -6,10 +6,11 @@ use std::default::Default;
use std::str::FromStr;
use euclid::default::{Point2D, Rect, Size2D, Transform2D};
-use ipc_channel::ipc::{IpcBytesReceiver, IpcBytesSender, IpcSender, IpcSharedMemory};
+use ipc_channel::ipc::{IpcBytesReceiver, IpcSender};
use malloc_size_of_derive::MallocSizeOf;
use serde::{Deserialize, Serialize};
use serde_bytes::ByteBuf;
+use snapshot::IpcSnapshot;
use style::color::AbsoluteColor;
use style::properties::style_structs::Font as FontStyleStruct;
@@ -87,7 +88,7 @@ pub enum CanvasMsg {
pub enum Canvas2dMsg {
Arc(Point2D<f32>, f32, f32, f32, bool),
ArcTo(Point2D<f32>, Point2D<f32>, f32),
- DrawImage(IpcSharedMemory, Size2D<f64>, Rect<f64>, Rect<f64>, bool),
+ DrawImage(IpcSnapshot, Rect<f64>, Rect<f64>, bool),
DrawEmptyImage(Size2D<f64>, Rect<f64>, Rect<f64>),
DrawImageInOther(CanvasId, Size2D<f64>, Rect<f64>, Rect<f64>, bool),
BeginPath,
@@ -101,7 +102,7 @@ pub enum Canvas2dMsg {
FillPath(FillOrStrokeStyle, Vec<PathSegment>),
FillText(String, f64, f64, Option<f64>, FillOrStrokeStyle, bool),
FillRect(Rect<f32>, FillOrStrokeStyle),
- GetImageData(Rect<u64>, Size2D<u64>, IpcBytesSender),
+ GetImageData(Rect<u64>, Size2D<u64>, IpcSender<IpcSnapshot>),
GetTransform(IpcSender<Transform2D<f32>>),
IsPointInCurrentPath(f64, f64, FillRule, IpcSender<bool>),
IsPointInPath(Vec<PathSegment>, f64, f64, FillRule, IpcSender<bool>),
@@ -137,7 +138,7 @@ pub enum Canvas2dMsg {
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum FromScriptMsg {
- SendPixels(IpcSender<IpcSharedMemory>),
+ SendPixels(IpcSender<IpcSnapshot>),
}
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
diff --git a/components/shared/canvas/webgl.rs b/components/shared/canvas/webgl.rs
index eaa934fc145..7ffae5d5fc5 100644
--- a/components/shared/canvas/webgl.rs
+++ b/components/shared/canvas/webgl.rs
@@ -298,7 +298,12 @@ pub enum WebGLCommand {
PolygonOffset(f32, f32),
RenderbufferStorage(u32, u32, i32, i32),
RenderbufferStorageMultisample(u32, i32, u32, i32, i32),
- ReadPixels(Rect<u32>, u32, u32, IpcBytesSender),
+ ReadPixels(
+ Rect<u32>,
+ u32,
+ u32,
+ IpcSender<(IpcSharedMemory, snapshot::AlphaMode)>,
+ ),
ReadPixelsPP(Rect<i32>, u32, u32, usize),
SampleCoverage(f32, bool),
Scissor(i32, i32, u32, u32),
diff --git a/components/shared/compositing/Cargo.toml b/components/shared/compositing/Cargo.toml
index e3ff81615e6..2e39ef5397b 100644
--- a/components/shared/compositing/Cargo.toml
+++ b/components/shared/compositing/Cargo.toml
@@ -38,4 +38,3 @@ stylo = { workspace = true }
stylo_traits = { workspace = true }
surfman = { workspace = true, features = ["sm-x11"] }
webrender_api = { workspace = true }
-
diff --git a/components/shared/constellation/lib.rs b/components/shared/constellation/lib.rs
index 548e17b532c..b3d4fe525a1 100644
--- a/components/shared/constellation/lib.rs
+++ b/components/shared/constellation/lib.rs
@@ -18,7 +18,6 @@ use std::time::Duration;
use base::Epoch;
use base::cross_process_instant::CrossProcessInstant;
use base::id::{MessagePortId, PipelineId, WebViewId};
-use bitflags::bitflags;
use embedder_traits::{
CompositorHitTestResult, Cursor, InputEvent, MediaSessionActionType, Theme, ViewportDetails,
WebDriverCommandMsg,
@@ -57,8 +56,9 @@ pub enum EmbedderToConstellationMessage {
ChangeViewportDetails(WebViewId, ViewportDetails, WindowSizeType),
/// Inform the constellation of a theme change.
ThemeChange(Theme),
- /// Requests that the constellation instruct layout to begin a new tick of the animation.
- TickAnimation(PipelineId, AnimationTickType),
+ /// Requests that the constellation instruct script/layout to try to layout again and tick
+ /// animations.
+ TickAnimation(Vec<WebViewId>),
/// Dispatch a webdriver command
WebDriverCommand(WebDriverCommandMsg),
/// Reload a top-level browsing context.
@@ -130,17 +130,6 @@ pub enum WindowSizeType {
Resize,
}
-bitflags! {
- #[derive(Debug, Default, Deserialize, Serialize)]
- /// Specifies if rAF should be triggered and/or CSS Animations and Transitions.
- pub struct AnimationTickType: u8 {
- /// Trigger a call to requestAnimationFrame.
- const REQUEST_ANIMATION_FRAME = 0b001;
- /// Trigger restyles for CSS Animations and Transitions.
- const CSS_ANIMATIONS_AND_TRANSITIONS = 0b010;
- }
-}
-
/// The scroll state of a stacking context.
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct ScrollState {
diff --git a/components/shared/embedder/webdriver.rs b/components/shared/embedder/webdriver.rs
index 324906c2584..9577163411e 100644
--- a/components/shared/embedder/webdriver.rs
+++ b/components/shared/embedder/webdriver.rs
@@ -170,6 +170,7 @@ pub enum WebDriverJSError {
/// Occurs when handler received an event message for a layout channel that is not
/// associated with the current script thread
BrowsingContextNotFound,
+ JSException(WebDriverJSValue),
JSError,
StaleElementReference,
Timeout,
diff --git a/components/shared/script/lib.rs b/components/shared/script/lib.rs
index a39be739fd5..7323907cba3 100644
--- a/components/shared/script/lib.rs
+++ b/components/shared/script/lib.rs
@@ -20,7 +20,7 @@ use bluetooth_traits::BluetoothRequest;
use canvas_traits::webgl::WebGLPipeline;
use compositing_traits::CrossProcessCompositorApi;
use constellation_traits::{
- AnimationTickType, LoadData, NavigationHistoryBehavior, ScriptToConstellationChan, ScrollState,
+ LoadData, NavigationHistoryBehavior, ScriptToConstellationChan, ScrollState,
StructuredSerializedData, WindowSizeType,
};
use crossbeam_channel::{RecvTimeoutError, Sender};
@@ -195,7 +195,7 @@ pub enum ScriptThreadMessage {
/// Passes a webdriver command to the script thread for execution
WebDriverScriptCommand(PipelineId, WebDriverScriptCommand),
/// Notifies script thread that all animations are done
- TickAllAnimations(PipelineId, AnimationTickType),
+ TickAllAnimations(Vec<WebViewId>),
/// Notifies the script thread that a new Web font has been loaded, and thus the page should be
/// reflowed.
WebFontLoaded(PipelineId, bool /* success */),
diff --git a/components/shared/script_layout/lib.rs b/components/shared/script_layout/lib.rs
index 9b052642c32..a40b8c403c1 100644
--- a/components/shared/script_layout/lib.rs
+++ b/components/shared/script_layout/lib.rs
@@ -27,7 +27,7 @@ use fonts::{FontContext, SystemFontServiceProxy};
use fxhash::FxHashMap;
use ipc_channel::ipc::IpcSender;
use libc::c_void;
-use malloc_size_of::MallocSizeOfOps;
+use malloc_size_of::{MallocSizeOf as MallocSizeOfTrait, MallocSizeOfOps};
use malloc_size_of_derive::MallocSizeOf;
use net_traits::image_cache::{ImageCache, PendingImageId};
use pixels::Image;
@@ -51,7 +51,11 @@ use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot};
use style::stylesheets::Stylesheet;
use webrender_api::ImageKey;
-pub type GenericLayoutData = dyn Any + Send + Sync;
+pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait {
+ fn as_any(&self) -> &dyn Any;
+}
+
+pub type GenericLayoutData = dyn GenericLayoutDataTrait + Send + Sync;
#[derive(MallocSizeOf)]
pub struct StyleData {
@@ -59,7 +63,6 @@ pub struct StyleData {
/// style system is being used standalone, this is all that hangs
/// off the node. This must be first to permit the various
/// transmutations between ElementData and PersistentLayoutData.
- #[ignore_malloc_size_of = "This probably should not be ignored"]
pub element_data: AtomicRefCell<ElementData>,
/// Information needed during parallel traversals.
@@ -240,16 +243,16 @@ pub trait Layout {
/// Set the scroll states of this layout after a compositor scroll.
fn set_scroll_offsets(&mut self, scroll_states: &[ScrollState]);
- fn query_content_box(&self, node: OpaqueNode) -> Option<Rect<Au>>;
- fn query_content_boxes(&self, node: OpaqueNode) -> Vec<Rect<Au>>;
- fn query_client_rect(&self, node: OpaqueNode) -> Rect<i32>;
+ fn query_content_box(&self, node: TrustedNodeAddress) -> Option<Rect<Au>>;
+ fn query_content_boxes(&self, node: TrustedNodeAddress) -> Vec<Rect<Au>>;
+ fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32>;
fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
fn query_nodes_from_point(
&self,
point: Point2D<f32>,
query_type: NodesFromPointQueryType,
) -> Vec<UntrustedNodeAddress>;
- fn query_offset_parent(&self, node: OpaqueNode) -> OffsetParentResponse;
+ fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
fn query_resolved_style(
&self,
node: TrustedNodeAddress,
@@ -265,7 +268,7 @@ pub trait Layout {
animations: DocumentAnimationSet,
animation_timeline_value: f64,
) -> Option<ServoArc<Font>>;
- fn query_scrolling_area(&self, node: Option<OpaqueNode>) -> Rect<i32>;
+ fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> Rect<i32>;
fn query_text_indext(&self, node: OpaqueNode, point: Point2D<f32>) -> Option<usize>;
}
diff --git a/components/shared/script_layout/wrapper_traits.rs b/components/shared/script_layout/wrapper_traits.rs
index be27050a42f..6c4de339c1b 100644
--- a/components/shared/script_layout/wrapper_traits.rs
+++ b/components/shared/script_layout/wrapper_traits.rs
@@ -25,11 +25,11 @@ use style::selector_parser::{PseudoElement, PseudoElementCascadeType, SelectorIm
use style::stylist::RuleInclusion;
use crate::{
- FragmentType, GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutNodeType, SVGSVGData,
- StyleData,
+ FragmentType, GenericLayoutData, GenericLayoutDataTrait, HTMLCanvasData, HTMLMediaData,
+ LayoutNodeType, SVGSVGData, StyleData,
};
-pub trait LayoutDataTrait: Default + Send + Sync + 'static {}
+pub trait LayoutDataTrait: GenericLayoutDataTrait + Default + Send + Sync + 'static {}
/// A wrapper so that layout can access only the methods that it should have access to. Layout must
/// only ever see these and must never see instances of `LayoutDom`.
diff --git a/components/shared/snapshot/Cargo.toml b/components/shared/snapshot/Cargo.toml
new file mode 100644
index 00000000000..9d5bd555623
--- /dev/null
+++ b/components/shared/snapshot/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "snapshot"
+version.workspace = true
+authors.workspace = true
+license.workspace = true
+edition.workspace = true
+publish.workspace = true
+rust-version.workspace = true
+
+[lib]
+name = "snapshot"
+path = "lib.rs"
+
+
+[dependencies]
+euclid = { workspace = true }
+ipc-channel = { workspace = true }
+serde = { workspace = true }
+pixels = { path = "../../pixels" }
diff --git a/components/shared/snapshot/lib.rs b/components/shared/snapshot/lib.rs
new file mode 100644
index 00000000000..5a8c1a5fbc0
--- /dev/null
+++ b/components/shared/snapshot/lib.rs
@@ -0,0 +1,305 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use std::ops::{Deref, DerefMut};
+
+use euclid::default::Size2D;
+use ipc_channel::ipc::IpcSharedMemory;
+use pixels::Multiply;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
+pub enum PixelFormat {
+ #[default]
+ RGBA,
+ BGRA,
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
+pub enum AlphaMode {
+ /// Internal data is opaque (alpha is cleared to 1)
+ Opaque,
+ /// Internal data should be threated as opaque (does not mean it actually is)
+ AsOpaque { premultiplied: bool },
+ /// Data is not opaque
+ Transparent { premultiplied: bool },
+}
+
+impl Default for AlphaMode {
+ fn default() -> Self {
+ Self::Transparent {
+ premultiplied: true,
+ }
+ }
+}
+
+impl AlphaMode {
+ pub const fn is_premultiplied(&self) -> bool {
+ match self {
+ AlphaMode::Opaque => true,
+ AlphaMode::AsOpaque { premultiplied } => *premultiplied,
+ AlphaMode::Transparent { premultiplied } => *premultiplied,
+ }
+ }
+
+ pub const fn is_opaque(&self) -> bool {
+ matches!(self, AlphaMode::Opaque | AlphaMode::AsOpaque { .. })
+ }
+}
+
+#[derive(Debug)]
+pub enum Data {
+ // TODO: https://github.com/servo/servo/issues/36594
+ //IPC(IpcSharedMemory),
+ Owned(Vec<u8>),
+}
+
+impl Deref for Data {
+ type Target = [u8];
+
+ fn deref(&self) -> &Self::Target {
+ match &self {
+ //Data::IPC(ipc_shared_memory) => ipc_shared_memory,
+ Data::Owned(items) => items,
+ }
+ }
+}
+
+impl DerefMut for Data {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ match self {
+ //Data::IPC(ipc_shared_memory) => unsafe { ipc_shared_memory.deref_mut() },
+ Data::Owned(items) => items,
+ }
+ }
+}
+
+pub type IpcSnapshot = Snapshot<IpcSharedMemory>;
+
+/// Represents image bitmap with metadata, usually as snapshot of canvas
+///
+/// This allows us to hold off conversions (BGRA <-> RGBA, (un)premultiply)
+/// to when/if they are actually needed (WebGL/WebGPU can load both BGRA and RGBA).
+///
+/// Inspired by snapshot for concept in WebGPU spec:
+/// <https://gpuweb.github.io/gpuweb/#abstract-opdef-get-a-copy-of-the-image-contents-of-a-context>
+#[derive(Debug, Deserialize, Serialize)]
+pub struct Snapshot<T = Data> {
+ size: Size2D<u64>,
+ /// internal data (can be any format it will be converted on use if needed)
+ data: T,
+ /// RGBA/BGRA (reflect internal data)
+ format: PixelFormat,
+ /// How to treat alpha channel
+ alpha_mode: AlphaMode,
+}
+
+impl<T> Snapshot<T> {
+ pub const fn size(&self) -> Size2D<u64> {
+ self.size
+ }
+
+ pub const fn format(&self) -> PixelFormat {
+ self.format
+ }
+
+ pub const fn alpha_mode(&self) -> AlphaMode {
+ self.alpha_mode
+ }
+
+ pub const fn is_premultiplied(&self) -> bool {
+ self.alpha_mode().is_premultiplied()
+ }
+
+ pub const fn is_opaque(&self) -> bool {
+ self.alpha_mode().is_opaque()
+ }
+}
+
+impl Snapshot<Data> {
+ pub fn empty() -> Self {
+ Self {
+ size: Size2D::zero(),
+ data: Data::Owned(vec![]),
+ format: PixelFormat::RGBA,
+ alpha_mode: AlphaMode::Transparent {
+ premultiplied: true,
+ },
+ }
+ }
+
+ /// Returns snapshot with provided size that is black transparent alpha
+ pub fn cleared(size: Size2D<u64>) -> Self {
+ Self {
+ size,
+ data: Data::Owned(vec![0; size.area() as usize * 4]),
+ format: PixelFormat::RGBA,
+ alpha_mode: AlphaMode::Transparent {
+ premultiplied: true,
+ },
+ }
+ }
+
+ pub fn from_vec(
+ size: Size2D<u64>,
+ format: PixelFormat,
+ alpha_mode: AlphaMode,
+ data: Vec<u8>,
+ ) -> Self {
+ Self {
+ size,
+ data: Data::Owned(data),
+ format,
+ alpha_mode,
+ }
+ }
+
+ pub fn from_shared_memory(
+ size: Size2D<u64>,
+ format: PixelFormat,
+ alpha_mode: AlphaMode,
+ ism: IpcSharedMemory,
+ ) -> Self {
+ Self {
+ size,
+ data: Data::Owned(ism.to_vec()),
+ format,
+ alpha_mode,
+ }
+ }
+
+ // TODO: https://github.com/servo/servo/issues/36594
+ /*
+ /// # Safety
+ ///
+ /// This is safe if data is owned by this process only
+ /// (ownership is transferred on send)
+ pub unsafe fn from_shared_memory(
+ size: Size2D<u64>,
+ format: PixelFormat,
+ alpha_mode: AlphaMode,
+ ism: IpcSharedMemory,
+ ) -> Self {
+ Self {
+ size,
+ data: Data::IPC(ism),
+ format,
+ alpha_mode,
+ }
+ }
+ */
+
+ pub fn data(&self) -> &[u8] {
+ &self.data
+ }
+
+ /// Convert inner data of snapshot to target format and alpha mode.
+ /// If data is already in target format and alpha mode no work will be done.
+ pub fn transform(&mut self, target_alpha_mode: AlphaMode, target_format: PixelFormat) {
+ let swap_rb = target_format != self.format;
+ let multiply = match (self.alpha_mode, target_alpha_mode) {
+ (AlphaMode::Opaque, _) => Multiply::None,
+ (alpha_mode, AlphaMode::Opaque) => {
+ if alpha_mode.is_premultiplied() {
+ Multiply::UnMultiply
+ } else {
+ Multiply::None
+ }
+ },
+ (
+ AlphaMode::Transparent { premultiplied } | AlphaMode::AsOpaque { premultiplied },
+ AlphaMode::Transparent {
+ premultiplied: target_premultiplied,
+ } |
+ AlphaMode::AsOpaque {
+ premultiplied: target_premultiplied,
+ },
+ ) => {
+ if premultiplied == target_premultiplied {
+ Multiply::None
+ } else if target_premultiplied {
+ Multiply::PreMultiply
+ } else {
+ Multiply::UnMultiply
+ }
+ },
+ };
+ let clear_alpha = !matches!(self.alpha_mode, AlphaMode::Opaque) &&
+ matches!(target_alpha_mode, AlphaMode::Opaque);
+ pixels::transform_inplace(self.data.deref_mut(), multiply, swap_rb, clear_alpha);
+ self.alpha_mode = target_alpha_mode;
+ self.format = target_format;
+ }
+
+ pub fn as_ipc(self) -> Snapshot<IpcSharedMemory> {
+ let Snapshot {
+ size,
+ data,
+ format,
+ alpha_mode,
+ } = self;
+ let data = match data {
+ //Data::IPC(ipc_shared_memory) => ipc_shared_memory,
+ Data::Owned(items) => IpcSharedMemory::from_bytes(&items),
+ };
+ Snapshot {
+ size,
+ data,
+ format,
+ alpha_mode,
+ }
+ }
+
+ pub fn to_vec(self) -> Vec<u8> {
+ match self.data {
+ Data::Owned(data) => data,
+ }
+ }
+}
+
+impl Snapshot<IpcSharedMemory> {
+ // TODO: https://github.com/servo/servo/issues/36594
+ /*
+ /// # Safety
+ ///
+ /// This is safe if data is owned by this process only
+ /// (ownership is transferred on send)
+ pub unsafe fn to_data(self) -> Snapshot<Data> {
+ let Snapshot {
+ size,
+ data,
+ format,
+ alpha_mode,
+ } = self;
+ Snapshot {
+ size,
+ data: Data::IPC(data),
+ format,
+ alpha_mode,
+ }
+ }
+ */
+ pub fn to_owned(self) -> Snapshot<Data> {
+ let Snapshot {
+ size,
+ data,
+ format,
+ alpha_mode,
+ } = self;
+ Snapshot {
+ size,
+ data: Data::Owned(data.to_vec()),
+ format,
+ alpha_mode,
+ }
+ }
+
+ pub fn data(&self) -> &[u8] {
+ &self.data
+ }
+
+ pub fn to_ipc_shared_memory(self) -> IpcSharedMemory {
+ self.data
+ }
+}
diff --git a/components/shared/webgpu/Cargo.toml b/components/shared/webgpu/Cargo.toml
index 0a4aa4e7ff3..04efea0cb9c 100644
--- a/components/shared/webgpu/Cargo.toml
+++ b/components/shared/webgpu/Cargo.toml
@@ -20,3 +20,4 @@ serde = { workspace = true }
webrender_api = { workspace = true }
wgpu-core = { workspace = true, features = ["serde", "wgsl"] }
wgpu-types = { workspace = true }
+snapshot = { workspace = true }
diff --git a/components/shared/webgpu/messages/recv.rs b/components/shared/webgpu/messages/recv.rs
index 47c32437e45..4a30177cf0a 100644
--- a/components/shared/webgpu/messages/recv.rs
+++ b/components/shared/webgpu/messages/recv.rs
@@ -9,6 +9,7 @@ use arrayvec::ArrayVec;
use base::id::PipelineId;
use ipc_channel::ipc::{IpcSender, IpcSharedMemory};
use serde::{Deserialize, Serialize};
+use snapshot::IpcSnapshot;
use webrender_api::ImageKey;
use webrender_api::units::DeviceIntSize;
use wgpu_core::Label;
@@ -165,7 +166,7 @@ pub enum WebGPURequest {
/// Obtains image from latest presentation buffer (same as wr update)
GetImage {
context_id: WebGPUContextId,
- sender: IpcSender<IpcSharedMemory>,
+ sender: IpcSender<IpcSnapshot>,
},
ValidateTextureDescriptor {
device_id: DeviceId,
diff --git a/components/webdriver_server/lib.rs b/components/webdriver_server/lib.rs
index e9bec44afaa..ce83a8f3cc1 100644
--- a/components/webdriver_server/lib.rs
+++ b/components/webdriver_server/lib.rs
@@ -1505,7 +1505,7 @@ impl Handler {
.iter()
.map(webdriver_value_to_js_argument)
.collect();
- args_string.push("window.webdriverCallback".to_string());
+ args_string.push("resolve".to_string());
let timeout_script = if let Some(script_timeout) = self.session()?.script_timeout {
format!("setTimeout(webdriverTimeout, {});", script_timeout)
@@ -1513,7 +1513,17 @@ impl Handler {
"".into()
};
let script = format!(
- "{} (function() {{ {}\n }})({})",
+ r#"(function() {{
+ let webdriverPromise = new Promise(function(resolve, reject) {{
+ {}
+ (async function() {{
+ {}
+ }})({})
+ .then((v) => {{}}, (err) => reject(err))
+ }})
+ .then((v) => window.webdriverCallback(v), (r) => window.webdriverException(r))
+ .catch((r) => window.webdriverException(r));
+ }})();"#,
timeout_script,
func_body,
args_string.join(", "),
@@ -1541,15 +1551,21 @@ impl Handler {
ErrorStatus::NoSuchWindow,
"Pipeline id not found in browsing context",
)),
- Err(WebDriverJSError::JSError) => Err(WebDriverError::new(
+ Err(WebDriverJSError::JSException(_e)) => Err(WebDriverError::new(
ErrorStatus::JavascriptError,
"JS evaluation raised an exception",
)),
+ Err(WebDriverJSError::JSError) => Err(WebDriverError::new(
+ ErrorStatus::JavascriptError,
+ "JS evaluation raised an unknown exception",
+ )),
Err(WebDriverJSError::StaleElementReference) => Err(WebDriverError::new(
ErrorStatus::StaleElementReference,
"Stale element",
)),
- Err(WebDriverJSError::Timeout) => Err(WebDriverError::new(ErrorStatus::Timeout, "")),
+ Err(WebDriverJSError::Timeout) => {
+ Err(WebDriverError::new(ErrorStatus::ScriptTimeout, ""))
+ },
Err(WebDriverJSError::UnknownType) => Err(WebDriverError::new(
ErrorStatus::UnsupportedOperation,
"Unsupported return type",
diff --git a/components/webgpu/Cargo.toml b/components/webgpu/Cargo.toml
index f25f160b1b0..39ca562affe 100644
--- a/components/webgpu/Cargo.toml
+++ b/components/webgpu/Cargo.toml
@@ -22,6 +22,7 @@ malloc_size_of = { workspace = true }
serde = { workspace = true, features = ["serde_derive"] }
servo_config = { path = "../config" }
webgpu_traits = { workspace = true }
+snapshot = { workspace = true }
webrender = { workspace = true }
webrender_api = { workspace = true }
wgpu-core = { workspace = true, features = ["serde", "wgsl"] }
diff --git a/components/webgpu/swapchain.rs b/components/webgpu/swapchain.rs
index 6d61540581b..a3ca15b638d 100644
--- a/components/webgpu/swapchain.rs
+++ b/components/webgpu/swapchain.rs
@@ -10,9 +10,10 @@ use std::sync::{Arc, Mutex};
use arrayvec::ArrayVec;
use compositing_traits::{WebrenderExternalImageApi, WebrenderImageSource};
use euclid::default::Size2D;
-use ipc_channel::ipc::{IpcSender, IpcSharedMemory};
+use ipc_channel::ipc::IpcSender;
use log::{error, warn};
use serde::{Deserialize, Serialize};
+use snapshot::{IpcSnapshot, Snapshot};
use webgpu_traits::{
ContextConfiguration, Error, PRESENTATION_BUFFER_COUNT, WebGPUContextId, WebGPUMsg,
};
@@ -364,20 +365,34 @@ impl crate::WGPU {
);
}
- pub(crate) fn get_image(&self, context_id: WebGPUContextId) -> IpcSharedMemory {
+ pub(crate) fn get_image(&self, context_id: WebGPUContextId) -> IpcSnapshot {
let webgpu_contexts = self.wgpu_image_map.lock().unwrap();
let context_data = webgpu_contexts.get(&context_id).unwrap();
- let buffer_size = context_data.image_desc.buffer_size();
+ let size = context_data.image_desc.size().cast().cast_unit();
let data = if let Some(present_buffer) = context_data
.swap_chain
.as_ref()
.and_then(|swap_chain| swap_chain.data.as_ref())
{
- IpcSharedMemory::from_bytes(present_buffer.slice())
+ let format = match context_data.image_desc.0.format {
+ ImageFormat::RGBA8 => snapshot::PixelFormat::RGBA,
+ ImageFormat::BGRA8 => snapshot::PixelFormat::BGRA,
+ _ => unimplemented!(),
+ };
+ let alpha_mode = if context_data.image_desc.0.is_opaque() {
+ snapshot::AlphaMode::AsOpaque {
+ premultiplied: false,
+ }
+ } else {
+ snapshot::AlphaMode::Transparent {
+ premultiplied: true,
+ }
+ };
+ Snapshot::from_vec(size, format, alpha_mode, present_buffer.slice().to_vec())
} else {
- IpcSharedMemory::from_byte(0, buffer_size as usize)
+ Snapshot::cleared(size)
};
- data
+ data.as_ipc()
}
pub(crate) fn update_context(