diff options
Diffstat (limited to 'components')
-rw-r--r-- | components/canvas/canvas_data.rs | 9 | ||||
-rw-r--r-- | components/devtools/actors/watcher.rs | 69 | ||||
-rw-r--r-- | components/devtools/actors/worker.rs | 29 | ||||
-rw-r--r-- | components/devtools/lib.rs | 71 | ||||
-rw-r--r-- | components/layout/display_list/mod.rs | 9 | ||||
-rw-r--r-- | components/layout/display_list/stacking_context.rs | 2 | ||||
-rw-r--r-- | components/layout/flow/root.rs | 2 | ||||
-rw-r--r-- | components/layout/fragment_tree/box_fragment.rs | 106 | ||||
-rw-r--r-- | components/layout/fragment_tree/fragment.rs | 17 | ||||
-rw-r--r-- | components/layout/fragment_tree/fragment_tree.rs | 21 | ||||
-rw-r--r-- | components/layout/fragment_tree/positioning_fragment.rs | 2 | ||||
-rw-r--r-- | components/layout/style_ext.rs | 30 | ||||
-rw-r--r-- | components/script/canvas_state.rs | 87 | ||||
-rw-r--r-- | components/script/dom/canvasrenderingcontext2d.rs | 36 | ||||
-rw-r--r-- | components/script/dom/htmlscriptelement.rs | 2 | ||||
-rw-r--r-- | components/script/dom/offscreencanvasrenderingcontext2d.rs | 2 | ||||
-rw-r--r-- | components/script/dom/worker.rs | 12 | ||||
-rw-r--r-- | components/shared/devtools/lib.rs | 1 |
18 files changed, 389 insertions, 118 deletions
diff --git a/components/canvas/canvas_data.rs b/components/canvas/canvas_data.rs index 99d6273813e..2667b7f6b44 100644 --- a/components/canvas/canvas_data.rs +++ b/components/canvas/canvas_data.rs @@ -28,6 +28,10 @@ use webrender_api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat, ImageKey use crate::raqote_backend::Repetition; +// Asserts on WR texture cache update for zero sized image with raw data. +// https://github.com/servo/webrender/blob/main/webrender/src/texture_cache.rs#L1475 +const MIN_WR_IMAGE_SIZE: Size2D<u64> = Size2D::new(1, 1); + fn to_path(path: &[PathSegment], mut builder: Box<dyn GenericPathBuilder>) -> Path { let mut build_ref = PathBuilderRef { builder: &mut builder, @@ -595,6 +599,7 @@ impl<'a> CanvasData<'a> { compositor_api: CrossProcessCompositorApi, font_context: Arc<FontContext>, ) -> CanvasData<'a> { + let size = size.max(MIN_WR_IMAGE_SIZE); let backend = create_backend(); let draw_target = backend.create_drawtarget(size); let image_key = compositor_api.generate_image_key().unwrap(); @@ -1402,7 +1407,9 @@ impl<'a> CanvasData<'a> { } pub fn recreate(&mut self, size: Option<Size2D<u64>>) { - let size = size.unwrap_or_else(|| self.drawtarget.get_size().to_u64()); + let size = size + .unwrap_or_else(|| self.drawtarget.get_size().to_u64()) + .max(MIN_WR_IMAGE_SIZE); self.drawtarget = self .backend .create_drawtarget(Size2D::new(size.width, size.height)); diff --git a/components/devtools/actors/watcher.rs b/components/devtools/actors/watcher.rs index 6a84499b6dd..b0b2c755fd8 100644 --- a/components/devtools/actors/watcher.rs +++ b/components/devtools/actors/watcher.rs @@ -20,8 +20,10 @@ use serde_json::{Map, Value}; use self::network_parent::{NetworkParentActor, NetworkParentActorMsg}; use super::thread::ThreadActor; +use super::worker::WorkerMsg; use crate::actor::{Actor, ActorMessageStatus, ActorRegistry}; use crate::actors::browsing_context::{BrowsingContextActor, BrowsingContextActorMsg}; +use crate::actors::root::RootActor; use crate::actors::watcher::target_configuration::{ TargetConfigurationActor, TargetConfigurationActorMsg, }; @@ -29,8 +31,8 @@ use crate::actors::watcher::thread_configuration::{ ThreadConfigurationActor, ThreadConfigurationActorMsg, }; use crate::protocol::JsonPacketStream; -use crate::resource::ResourceAvailable; -use crate::{EmptyReplyMsg, StreamId}; +use crate::resource::{ResourceAvailable, ResourceAvailableReply}; +use crate::{EmptyReplyMsg, StreamId, WorkerActor}; pub mod network_parent; pub mod target_configuration; @@ -55,7 +57,7 @@ impl SessionContext { supported_targets: HashMap::from([ ("frame", true), ("process", false), - ("worker", false), + ("worker", true), ("service_worker", false), ("shared_worker", false), ]), @@ -103,11 +105,18 @@ pub enum SessionContextType { } #[derive(Serialize)] +#[serde(untagged)] +enum TargetActorMsg { + BrowsingContext(BrowsingContextActorMsg), + Worker(WorkerMsg), +} + +#[derive(Serialize)] struct WatchTargetsReply { from: String, #[serde(rename = "type")] type_: String, - target: BrowsingContextActorMsg, + target: TargetActorMsg, } #[derive(Serialize)] @@ -212,16 +221,38 @@ impl Actor for WatcherActor { _id: StreamId, ) -> Result<ActorMessageStatus, ()> { let target = registry.find::<BrowsingContextActor>(&self.browsing_context_actor); + let root = registry.find::<RootActor>("root"); Ok(match msg_type { "watchTargets" => { - let msg = WatchTargetsReply { - from: self.name(), - type_: "target-available-form".into(), - target: target.encodable(), - }; - let _ = stream.write_json_packet(&msg); + // As per logs we either get targetType as "frame" or "worker" + let target_type = msg + .get("targetType") + .and_then(Value::as_str) + .unwrap_or("frame"); // default to "frame" + + if target_type == "frame" { + let msg = WatchTargetsReply { + from: self.name(), + type_: "target-available-form".into(), + target: TargetActorMsg::BrowsingContext(target.encodable()), + }; + let _ = stream.write_json_packet(&msg); - target.frame_update(stream); + target.frame_update(stream); + } else if target_type == "worker" { + for worker_name in &root.workers { + let worker = registry.find::<WorkerActor>(worker_name); + let worker_msg = WatchTargetsReply { + from: self.name(), + type_: "target-available-form".into(), + target: TargetActorMsg::Worker(worker.encodable()), + }; + let _ = stream.write_json_packet(&worker_msg); + } + } else { + warn!("Unexpected target_type: {}", target_type); + return Ok(ActorMessageStatus::Ignored); + } // Messages that contain a `type` field are used to send event callbacks, but they // don't count as a reply. Since every message needs to be responded, we send an @@ -267,6 +298,22 @@ impl Actor for WatcherActor { let thread_actor = registry.find::<ThreadActor>(&target.thread); let sources = thread_actor.source_manager.sources(); target.resources_available(sources.iter().collect(), "source".into()); + + for worker_name in &root.workers { + let worker = registry.find::<WorkerActor>(worker_name); + let thread = registry.find::<ThreadActor>(&worker.thread); + let worker_sources = thread.source_manager.sources(); + + let msg = ResourceAvailableReply { + from: worker.name(), + type_: "resources-available-array".into(), + array: vec![( + "source".to_string(), + worker_sources.iter().cloned().collect(), + )], + }; + let _ = stream.write_json_packet(&msg); + } }, "console-message" | "error-message" => {}, _ => warn!("resource {} not handled yet", resource), diff --git a/components/devtools/actors/worker.rs b/components/devtools/actors/worker.rs index 42c9d9a9c28..68ff56fb3b2 100644 --- a/components/devtools/actors/worker.rs +++ b/components/devtools/actors/worker.rs @@ -17,7 +17,7 @@ use servo_url::ServoUrl; use crate::StreamId; use crate::actor::{Actor, ActorMessageStatus, ActorRegistry}; use crate::protocol::JsonPacketStream; -use crate::resource::ResourceAvailable; +use crate::resource::{ResourceAvailable, ResourceAvailableReply}; #[derive(Clone, Copy)] #[allow(dead_code)] @@ -48,8 +48,10 @@ impl WorkerActor { url: self.url.to_string(), traits: WorkerTraits { is_parent_intercept_enabled: false, + supports_top_level_target_flag: false, }, type_: self.type_ as u32, + target_type: "worker".to_string(), } } } @@ -131,6 +133,28 @@ impl Actor for WorkerActor { } } +impl WorkerActor { + pub(crate) fn resource_available<T: Serialize>(&self, resource: T, resource_type: String) { + self.resources_available(vec![resource], resource_type); + } + + pub(crate) fn resources_available<T: Serialize>( + &self, + resources: Vec<T>, + resource_type: String, + ) { + let msg = ResourceAvailableReply::<T> { + from: self.name(), + type_: "resources-available-array".into(), + array: vec![(resource_type, resources)], + }; + + for stream in self.streams.borrow_mut().values_mut() { + let _ = stream.write_json_packet(&msg); + } + } +} + #[derive(Serialize)] struct DetachedReply { from: String, @@ -160,6 +184,7 @@ struct ConnectReply { #[serde(rename_all = "camelCase")] struct WorkerTraits { is_parent_intercept_enabled: bool, + supports_top_level_target_flag: bool, } #[derive(Serialize)] @@ -173,4 +198,6 @@ pub(crate) struct WorkerMsg { traits: WorkerTraits, #[serde(rename = "type")] type_: u32, + #[serde(rename = "targetType")] + target_type: String, } diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs index 4d1e0222177..5fb9485e9d3 100644 --- a/components/devtools/lib.rs +++ b/components/devtools/lib.rs @@ -510,35 +510,54 @@ impl DevtoolsInstance { fn handle_script_source_info(&mut self, pipeline_id: PipelineId, source_info: SourceInfo) { let mut actors = self.actors.lock().unwrap(); - let browsing_context_id = match self.pipelines.get(&pipeline_id) { - Some(id) => id, - None => return, - }; + if let Some(worker_id) = source_info.worker_id { + let Some(worker_actor_name) = self.actor_workers.get(&worker_id) else { + return; + }; - let actor_name = match self.browsing_contexts.get(browsing_context_id) { - Some(name) => name, - None => return, - }; + let thread_actor_name = actors.find::<WorkerActor>(worker_actor_name).thread.clone(); - let thread_actor_name = actors - .find::<BrowsingContextActor>(actor_name) - .thread - .clone(); - - let thread_actor = actors.find_mut::<ThreadActor>(&thread_actor_name); - thread_actor - .source_manager - .add_source(source_info.url.clone()); - - let source = SourceData { - actor: thread_actor_name.clone(), - url: source_info.url.to_string(), - is_black_boxed: false, - }; + let thread_actor = actors.find_mut::<ThreadActor>(&thread_actor_name); + thread_actor + .source_manager + .add_source(source_info.url.clone()); + + let source = SourceData { + actor: thread_actor_name.clone(), + url: source_info.url.to_string(), + is_black_boxed: false, + }; - // Notify browsing context about the new source - let browsing_context = actors.find::<BrowsingContextActor>(actor_name); - browsing_context.resource_available(source, "source".into()); + let worker_actor = actors.find::<WorkerActor>(worker_actor_name); + worker_actor.resource_available(source, "source".into()); + } else { + let Some(browsing_context_id) = self.pipelines.get(&pipeline_id) else { + return; + }; + let Some(actor_name) = self.browsing_contexts.get(browsing_context_id) else { + return; + }; + + let thread_actor_name = actors + .find::<BrowsingContextActor>(actor_name) + .thread + .clone(); + + let thread_actor = actors.find_mut::<ThreadActor>(&thread_actor_name); + thread_actor + .source_manager + .add_source(source_info.url.clone()); + + let source = SourceData { + actor: thread_actor_name.clone(), + url: source_info.url.to_string(), + is_black_boxed: false, + }; + + // Notify browsing context about the new source + let browsing_context = actors.find::<BrowsingContextActor>(actor_name); + browsing_context.resource_available(source, "source".into()); + } } } diff --git a/components/layout/display_list/mod.rs b/components/layout/display_list/mod.rs index 912c69366bd..3908da69ce1 100644 --- a/components/layout/display_list/mod.rs +++ b/components/layout/display_list/mod.rs @@ -39,7 +39,7 @@ use webrender_api::{ use wr::units::LayoutVector2D; use crate::context::{LayoutContext, ResolvedImage}; -use crate::display_list::conversions::ToWebRender; +pub use crate::display_list::conversions::ToWebRender; use crate::display_list::stacking_context::StackingContextSection; use crate::fragment_tree::{ BackgroundMode, BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo, Tag, @@ -711,7 +711,12 @@ impl<'a> BuilderForBoxFragment<'a> { fn build(&mut self, builder: &mut DisplayListBuilder, section: StackingContextSection) { if self.is_hit_test_for_scrollable_overflow { - self.build_hit_test(builder, self.fragment.scrollable_overflow().to_webrender()); + self.build_hit_test( + builder, + self.fragment + .reachable_scrollable_overflow_region() + .to_webrender(), + ); return; } diff --git a/components/layout/display_list/stacking_context.rs b/components/layout/display_list/stacking_context.rs index 9809e428d6e..b044b713260 100644 --- a/components/layout/display_list/stacking_context.rs +++ b/components/layout/display_list/stacking_context.rs @@ -1477,7 +1477,7 @@ impl BoxFragment { y: overflow.y.into(), }; - let content_rect = self.scrollable_overflow().to_webrender(); + let content_rect = self.reachable_scrollable_overflow_region().to_webrender(); let scroll_tree_node_id = display_list.define_scroll_frame( parent_scroll_node_id, diff --git a/components/layout/flow/root.rs b/components/layout/flow/root.rs index 187726595f8..c6498eeed63 100644 --- a/components/layout/flow/root.rs +++ b/components/layout/flow/root.rs @@ -411,7 +411,7 @@ impl BoxTree { let scrollable_overflow = root_fragments .iter() .fold(PhysicalRect::zero(), |acc, child| { - let child_overflow = child.scrollable_overflow(); + let child_overflow = child.scrollable_overflow_for_parent(); // https://drafts.csswg.org/css-overflow/#scrolling-direction // We want to clip scrollable overflow on box-start and inline-start diff --git a/components/layout/fragment_tree/box_fragment.rs b/components/layout/fragment_tree/box_fragment.rs index e87826ec3ca..65ad1c4aa93 100644 --- a/components/layout/fragment_tree/box_fragment.rs +++ b/components/layout/fragment_tree/box_fragment.rs @@ -2,11 +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 app_units::Au; +use app_units::{Au, MAX_AU, MIN_AU}; use atomic_refcell::AtomicRefCell; use base::print_tree::PrintTree; use malloc_size_of_derive::MallocSizeOf; use servo_arc::Arc as ServoArc; +use servo_geometry::f32_rect_to_au_rect; use style::Zero; use style::computed_values::border_collapse::T as BorderCollapse; use style::computed_values::overflow_x::T as ComputedOverflow; @@ -16,6 +17,7 @@ use style::properties::ComputedValues; use style::values::specified::box_::DisplayOutside; use super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment}; +use crate::display_list::ToWebRender; use crate::formatting_contexts::Baselines; use crate::geom::{ AuOrAuto, LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides, ToLogical, @@ -116,7 +118,7 @@ impl BoxFragment { ) -> BoxFragment { let scrollable_overflow_from_children = children.iter().fold(PhysicalRect::zero(), |acc, child| { - acc.union(&child.scrollable_overflow()) + acc.union(&child.scrollable_overflow_for_parent()) }); BoxFragment { @@ -267,30 +269,96 @@ impl BoxFragment { pub fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> { let mut overflow = self.border_rect(); - if self.style.establishes_scroll_container(self.base.flags) { - return overflow; + if !self.style.establishes_scroll_container(self.base.flags) { + // https://www.w3.org/TR/css-overflow-3/#scrollable + // Only include the scrollable overflow of a child box if it has overflow: visible. + let scrollable_overflow = self.scrollable_overflow(); + let bottom_right = PhysicalPoint::new( + overflow.max_x().max(scrollable_overflow.max_x()), + overflow.max_y().max(scrollable_overflow.max_y()), + ); + + let overflow_style = self.style.effective_overflow(self.base.flags); + if overflow_style.y == ComputedOverflow::Visible { + overflow.origin.y = overflow.origin.y.min(scrollable_overflow.origin.y); + overflow.size.height = bottom_right.y - overflow.origin.y; + } + + if overflow_style.x == ComputedOverflow::Visible { + overflow.origin.x = overflow.origin.x.min(scrollable_overflow.origin.x); + overflow.size.width = bottom_right.x - overflow.origin.x; + } } - // https://www.w3.org/TR/css-overflow-3/#scrollable - // Only include the scrollable overflow of a child box if it has overflow: visible. - let scrollable_overflow = self.scrollable_overflow(); - let bottom_right = PhysicalPoint::new( - overflow.max_x().max(scrollable_overflow.max_x()), - overflow.max_y().max(scrollable_overflow.max_y()), - ); + // <https://drafts.csswg.org/css-overflow-3/#scrollable-overflow-region> + // > ...accounting for transforms by projecting each box onto the plane of + // > the element that establishes its 3D rendering context. [CSS3-TRANSFORMS] + // Both boxes and its scrollable overflow (if it is included) should be transformed accordingly. + // + // TODO(stevennovaryo): We are supposed to handle perspective transform and 3d context, but it is yet to happen. + if self + .style + .has_effective_transform_or_perspective(self.base.flags) + { + if let Some(transform) = + self.calculate_transform_matrix(&self.border_rect().to_untyped()) + { + if let Some(transformed_overflow_box) = + transform.outer_transformed_rect(&overflow.to_webrender().to_rect()) + { + overflow = + f32_rect_to_au_rect(transformed_overflow_box.to_untyped()).cast_unit(); + } + } + } + + overflow + } - let overflow_style = self.style.effective_overflow(self.base.flags); - if overflow_style.y == ComputedOverflow::Visible { - overflow.origin.y = overflow.origin.y.min(scrollable_overflow.origin.y); - overflow.size.height = bottom_right.y - overflow.origin.y; + /// <https://drafts.csswg.org/css-overflow/#unreachable-scrollable-overflow-region> + /// > area beyond the scroll origin in either axis is considered the unreachable scrollable overflow region + /// + /// Return the clipped the scrollable overflow based on its scroll origin, determined by overflow direction. + /// For an element, the clip rect is the padding rect and for viewport, it is the initial containing block. + pub fn clip_unreachable_scrollable_overflow_region( + &self, + scrollable_overflow: PhysicalRect<Au>, + clipping_rect: PhysicalRect<Au>, + ) -> PhysicalRect<Au> { + let scrolling_direction = self.style.overflow_direction(); + let mut scrollable_overflow_box = scrollable_overflow.to_box2d(); + let mut clipping_box = clipping_rect.to_box2d(); + + if scrolling_direction.rightward { + clipping_box.max.x = MAX_AU; + } else { + clipping_box.min.x = MIN_AU; } - if overflow_style.x == ComputedOverflow::Visible { - overflow.origin.x = overflow.origin.x.min(scrollable_overflow.origin.x); - overflow.size.width = bottom_right.x - overflow.origin.x; + if scrolling_direction.downward { + clipping_box.max.y = MAX_AU; + } else { + clipping_box.min.y = MIN_AU; } - overflow + scrollable_overflow_box = scrollable_overflow_box.intersection_unchecked(&clipping_box); + + match scrollable_overflow_box.is_negative() { + true => PhysicalRect::zero(), + false => scrollable_overflow_box.to_rect(), + } + } + + /// <https://drafts.csswg.org/css-overflow/#unreachable-scrollable-overflow-region> + /// > area beyond the scroll origin in either axis is considered the unreachable scrollable overflow region + /// + /// Return the clipped the scrollable overflow based on its scroll origin, determined by overflow direction. + /// This will coincides with the scrollport if the fragment is a scroll container. + pub fn reachable_scrollable_overflow_region(&self) -> PhysicalRect<Au> { + self.clip_unreachable_scrollable_overflow_region( + self.scrollable_overflow(), + self.padding_rect(), + ) } pub(crate) fn calculate_resolved_insets_if_positioned(&self) -> PhysicalSides<AuOrAuto> { diff --git a/components/layout/fragment_tree/fragment.rs b/components/layout/fragment_tree/fragment.rs index 7708b0893ee..1c5324fa1c4 100644 --- a/components/layout/fragment_tree/fragment.rs +++ b/components/layout/fragment_tree/fragment.rs @@ -170,17 +170,28 @@ impl Fragment { } } - pub fn scrolling_area(&self) -> PhysicalRect<Au> { + pub fn unclipped_scrolling_area(&self) -> PhysicalRect<Au> { match self { Fragment::Box(fragment) | Fragment::Float(fragment) => { let fragment = fragment.borrow(); fragment.offset_by_containing_block(&fragment.scrollable_overflow()) }, - _ => self.scrollable_overflow(), + _ => self.scrollable_overflow_for_parent(), + } + } + + pub fn scrolling_area(&self) -> PhysicalRect<Au> { + match self { + Fragment::Box(fragment) | Fragment::Float(fragment) => { + let fragment = fragment.borrow(); + fragment + .offset_by_containing_block(&fragment.reachable_scrollable_overflow_region()) + }, + _ => self.scrollable_overflow_for_parent(), } } - pub fn scrollable_overflow(&self) -> PhysicalRect<Au> { + pub fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> { match self { Fragment::Box(fragment) | Fragment::Float(fragment) => { fragment.borrow().scrollable_overflow_for_parent() diff --git a/components/layout/fragment_tree/fragment_tree.rs b/components/layout/fragment_tree/fragment_tree.rs index 3a082c99389..1499a50dacf 100644 --- a/components/layout/fragment_tree/fragment_tree.rs +++ b/components/layout/fragment_tree/fragment_tree.rs @@ -138,11 +138,26 @@ impl FragmentTree { .find_map(|child| child.find(&info, 0, &mut process_func)) } + /// <https://drafts.csswg.org/cssom-view/#scrolling-area> + /// + /// Scrolling area for a viewport that is clipped according to overflow direction of root element. 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().union(&scroll_area); + if let Some(root_fragment) = self.root_fragments.first() { + for fragment in self.root_fragments.iter() { + scroll_area = fragment.unclipped_scrolling_area().union(&scroll_area); + } + match root_fragment { + Fragment::Box(fragment) | Fragment::Float(fragment) => fragment + .borrow() + .clip_unreachable_scrollable_overflow_region( + scroll_area, + self.initial_containing_block, + ), + _ => scroll_area, + } + } else { + scroll_area } - scroll_area } } diff --git a/components/layout/fragment_tree/positioning_fragment.rs b/components/layout/fragment_tree/positioning_fragment.rs index 1fe968eb484..0cf525a3479 100644 --- a/components/layout/fragment_tree/positioning_fragment.rs +++ b/components/layout/fragment_tree/positioning_fragment.rs @@ -56,7 +56,7 @@ impl PositioningFragment { let scrollable_overflow = children.iter().fold(PhysicalRect::zero(), |acc, child| { acc.union( &child - .scrollable_overflow() + .scrollable_overflow_for_parent() .translate(content_origin.to_vector()), ) }); diff --git a/components/layout/style_ext.rs b/components/layout/style_ext.rs index b157be914e0..33a22cdf438 100644 --- a/components/layout/style_ext.rs +++ b/components/layout/style_ext.rs @@ -12,7 +12,7 @@ use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode; use style::computed_values::position::T as ComputedPosition; use style::computed_values::transform_style::T as ComputedTransformStyle; use style::computed_values::unicode_bidi::T as UnicodeBidi; -use style::logical_geometry::{Direction as AxisDirection, WritingMode}; +use style::logical_geometry::{Direction as AxisDirection, PhysicalSide, WritingMode}; use style::properties::ComputedValues; use style::properties::longhands::backface_visibility::computed_value::T as BackfaceVisiblity; use style::properties::longhands::box_sizing::computed_value::T as BoxSizing; @@ -280,6 +280,16 @@ impl Default for BorderStyleColor { } } +/// <https://drafts.csswg.org/cssom-view/#overflow-directions> +/// > A scrolling box of a viewport or element has two overflow directions, +/// > which are the block-end and inline-end directions for that viewport or element. +pub(crate) struct OverflowDirection { + /// Whether block-end or inline-end direction is [PhysicalSide::Right]. + pub rightward: bool, + /// Whether block-end or inline-end direction is [PhysicalSide::Bottom]. + pub downward: bool, +} + pub(crate) trait ComputedValuesExt { fn physical_box_offsets(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>>; fn box_offsets(&self, writing_mode: WritingMode) -> LogicalSides<LengthPercentageOrAuto<'_>>; @@ -354,6 +364,7 @@ pub(crate) trait ComputedValuesExt { writing_mode: WritingMode, ) -> bool; fn is_inline_box(&self, fragment_flags: FragmentFlags) -> bool; + fn overflow_direction(&self) -> OverflowDirection; } impl ComputedValuesExt for ComputedValues { @@ -980,6 +991,23 @@ impl ComputedValuesExt for ComputedValues { }; has_percentage(box_offsets.block_start) || has_percentage(box_offsets.block_end) } + + // <https://drafts.csswg.org/cssom-view/#overflow-directions> + fn overflow_direction(&self) -> OverflowDirection { + let inline_end_direction = self.writing_mode.inline_end_physical_side(); + let block_end_direction = self.writing_mode.block_end_physical_side(); + + let rightward = inline_end_direction == PhysicalSide::Right || + block_end_direction == PhysicalSide::Right; + let downward = inline_end_direction == PhysicalSide::Bottom || + block_end_direction == PhysicalSide::Bottom; + + // TODO(stevennovaryo): We should consider the flex-container's CSS (e.g. flow-direction: column-reverse). + OverflowDirection { + rightward, + downward, + } + } } pub(crate) enum LayoutStyle<'a> { diff --git a/components/script/canvas_state.rs b/components/script/canvas_state.rs index 408c94c124a..e9892818e92 100644 --- a/components/script/canvas_state.rs +++ b/components/script/canvas_state.rs @@ -152,6 +152,8 @@ pub(crate) struct CanvasState { canvas_id: CanvasId, #[no_trace] image_key: ImageKey, + #[no_trace] + size: Cell<Size2D<u64>>, state: DomRefCell<CanvasContextState>, origin_clean: Cell<bool>, #[ignore_malloc_size_of = "Arc"] @@ -176,6 +178,7 @@ impl CanvasState { profiled_ipc::channel(global.time_profiler_chan().clone()).unwrap(); let script_to_constellation_chan = global.script_to_constellation_chan(); debug!("Asking constellation to create new canvas thread."); + let size = adjust_canvas_size(size); script_to_constellation_chan .send(ScriptToConstellationMessage::CreateCanvasPaintThread( size, sender, @@ -194,6 +197,7 @@ impl CanvasState { CanvasState { ipc_renderer, canvas_id, + size: Cell::new(size), state: DomRefCell::new(CanvasContextState::new()), origin_clean: Cell::new(true), image_cache: global.image_cache(), @@ -221,7 +225,15 @@ impl CanvasState { self.canvas_id } + pub(crate) fn is_paintable(&self) -> bool { + !self.size.get().is_empty() + } + pub(crate) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) { + if !self.is_paintable() { + return; + } + self.ipc_renderer .send(CanvasMsg::Canvas2d(msg, self.get_canvas_id())) .unwrap() @@ -229,6 +241,10 @@ impl CanvasState { /// Updates WR image and blocks on completion pub(crate) fn update_rendering(&self) { + if !self.is_paintable() { + return; + } + let (sender, receiver) = ipc::channel().unwrap(); self.ipc_renderer .send(CanvasMsg::Canvas2d( @@ -239,16 +255,27 @@ impl CanvasState { receiver.recv().unwrap(); } - // https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions + /// <https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions> pub(crate) fn set_bitmap_dimensions(&self, size: Size2D<u64>) { self.reset_to_initial_state(); + + self.size.replace(adjust_canvas_size(size)); + self.ipc_renderer - .send(CanvasMsg::Recreate(Some(size), self.get_canvas_id())) + .send(CanvasMsg::Recreate( + Some(self.size.get()), + self.get_canvas_id(), + )) .unwrap(); } pub(crate) fn reset(&self) { self.reset_to_initial_state(); + + if !self.is_paintable() { + return; + } + self.ipc_renderer .send(CanvasMsg::Recreate(None, self.get_canvas_id())) .unwrap(); @@ -347,7 +374,6 @@ impl CanvasState { pub(crate) fn get_rect(&self, canvas_size: Size2D<u64>, rect: Rect<u64>) -> Vec<u8> { assert!(self.origin_is_clean()); - assert!(Rect::from_size(canvas_size).contains_rect(&rect)); let (sender, receiver) = ipc::channel().unwrap(); @@ -398,18 +424,22 @@ impl CanvasState { dw: Option<f64>, dh: Option<f64>, ) -> ErrorResult { + if !self.is_paintable() { + return Ok(()); + } + let result = match image { CanvasImageSource::HTMLCanvasElement(ref canvas) => { - // https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument - if !canvas.is_valid() { + // <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument> + if canvas.get_size().is_empty() { return Err(Error::InvalidState); } self.draw_html_canvas_element(canvas, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh) }, CanvasImageSource::OffscreenCanvas(ref canvas) => { - // https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument - if !canvas.is_valid() { + // <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument> + if canvas.get_size().is_empty() { return Err(Error::InvalidState); } @@ -528,11 +558,6 @@ impl CanvasState { dw: Option<f64>, dh: Option<f64>, ) -> ErrorResult { - // 1. Check the usability of the image argument - if !canvas.is_valid() { - return Err(Error::InvalidState); - } - let canvas_size = canvas.get_size(); let dw = dw.unwrap_or(canvas_size.width as f64); let dh = dh.unwrap_or(canvas_size.height as f64); @@ -1403,13 +1428,13 @@ impl CanvasState { }, }; - ImageData::new( - global, - size.width, - size.height, - Some(self.get_rect(canvas_size, read_rect)), - can_gc, - ) + let data = if self.is_paintable() { + Some(self.get_rect(canvas_size, read_rect)) + } else { + None + }; + + ImageData::new(global, size.width, size.height, data, can_gc) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata @@ -1445,6 +1470,10 @@ impl CanvasState { dirty_width: i32, dirty_height: i32, ) { + if !self.is_paintable() { + return; + } + // FIXME(nox): There are many arithmetic operations here that can // overflow or underflow, this should probably be audited. @@ -2013,3 +2042,23 @@ where style.font_family.to_css_string() ) } + +fn adjust_canvas_size(size: Size2D<u64>) -> Size2D<u64> { + // Firefox limits width/height to 32767 pixels and Chromium to 65535 pixels, + // but slows down dramatically before it reaches that limit. + // We limit by area instead, giving us larger maximum dimensions, + // in exchange for a smaller maximum canvas size. + const MAX_CANVAS_AREA: u64 = 32768 * 8192; + // Max width/height to 65535 in CSS pixels. + const MAX_CANVAS_SIZE: u64 = 65535; + + if !size.is_empty() && + size.greater_than(Size2D::new(MAX_CANVAS_SIZE, MAX_CANVAS_SIZE)) + .none() && + size.area() < MAX_CANVAS_AREA + { + size + } else { + Size2D::zero() + } +} diff --git a/components/script/dom/canvasrenderingcontext2d.rs b/components/script/dom/canvasrenderingcontext2d.rs index 73052e6906e..38bd38ad511 100644 --- a/components/script/dom/canvasrenderingcontext2d.rs +++ b/components/script/dom/canvasrenderingcontext2d.rs @@ -4,7 +4,7 @@ use canvas_traits::canvas::{Canvas2dMsg, CanvasId, CanvasMsg, FromScriptMsg}; use dom_struct::dom_struct; -use euclid::default::{Point2D, Rect, Size2D}; +use euclid::default::Size2D; use profile_traits::ipc; use script_bindings::inheritance::Castable; use script_layout_interface::HTMLCanvasDataSource; @@ -74,23 +74,12 @@ impl CanvasRenderingContext2D { reflect_dom_object(boxed, global, can_gc) } - // https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions - pub(crate) fn set_bitmap_dimensions(&self, size: Size2D<u32>) { - self.reset_to_initial_state(); - self.canvas_state - .get_ipc_renderer() - .send(CanvasMsg::Recreate( - Some(size.to_u64()), - self.canvas_state.get_canvas_id(), - )) - .unwrap(); - } - // https://html.spec.whatwg.org/multipage/#reset-the-rendering-context-to-its-default-state fn reset_to_initial_state(&self) { self.canvas_state.reset_to_initial_state(); } + /// <https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions> pub(crate) fn set_canvas_bitmap_dimensions(&self, size: Size2D<u64>) { self.canvas_state.set_bitmap_dimensions(size); } @@ -106,20 +95,17 @@ impl CanvasRenderingContext2D { pub(crate) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) { self.canvas_state.send_canvas_2d_msg(msg) } - - pub(crate) fn get_rect(&self, rect: Rect<u32>) -> Vec<u8> { - let rect = Rect::new( - Point2D::new(rect.origin.x as u64, rect.origin.y as u64), - Size2D::new(rect.size.width as u64, rect.size.height as u64), - ); - self.canvas_state.get_rect(self.canvas.size(), rect) - } } impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, CanvasRenderingContext2D> { fn canvas_data_source(self) -> HTMLCanvasDataSource { let canvas_state = &self.unsafe_get().canvas_state; - HTMLCanvasDataSource::Image(canvas_state.image_key()) + + if canvas_state.is_paintable() { + HTMLCanvasDataSource::Image(canvas_state.image_key()) + } else { + HTMLCanvasDataSource::Empty + } } } @@ -139,13 +125,11 @@ impl CanvasContext for CanvasRenderingContext2D { } fn resize(&self) { - self.set_bitmap_dimensions(self.size().cast()) + self.set_canvas_bitmap_dimensions(self.size().cast()) } fn get_image_data(&self) -> Option<Snapshot> { - let size = self.size(); - - if size.is_empty() { + if !self.canvas_state.is_paintable() { return None; } diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index 9452dcb17a6..58853f600d2 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -1008,12 +1008,12 @@ impl HTMLScriptElement { Ok(script) => script, }; - // TODO: we need to handle this for worker if let Some(chan) = self.global().devtools_chan() { let pipeline_id = self.global().pipeline_id(); let source_info = SourceInfo { url: script.url.clone(), external: script.external, + worker_id: None, }; let _ = chan.send(ScriptToDevtoolsControlMsg::ScriptSourceLoaded( pipeline_id, diff --git a/components/script/dom/offscreencanvasrenderingcontext2d.rs b/components/script/dom/offscreencanvasrenderingcontext2d.rs index 2f9b52640e6..b2d0f3201ca 100644 --- a/components/script/dom/offscreencanvasrenderingcontext2d.rs +++ b/components/script/dom/offscreencanvasrenderingcontext2d.rs @@ -65,7 +65,7 @@ impl OffscreenCanvasRenderingContext2D { } pub(crate) fn set_canvas_bitmap_dimensions(&self, size: Size2D<u64>) { - self.context.set_bitmap_dimensions(size.cast()); + self.context.set_canvas_bitmap_dimensions(size.cast()); } pub(crate) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) { diff --git a/components/script/dom/worker.rs b/components/script/dom/worker.rs index 429234c7a8e..6ad56026db6 100644 --- a/components/script/dom/worker.rs +++ b/components/script/dom/worker.rs @@ -8,7 +8,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use constellation_traits::{StructuredSerializedData, WorkerScriptLoadOrigin}; use crossbeam_channel::{Sender, unbounded}; -use devtools_traits::{DevtoolsPageInfo, ScriptToDevtoolsControlMsg, WorkerId}; +use devtools_traits::{DevtoolsPageInfo, ScriptToDevtoolsControlMsg, SourceInfo, WorkerId}; use dom_struct::dom_struct; use ipc_channel::ipc; use js::jsapi::{Heap, JSObject}; @@ -213,6 +213,16 @@ impl WorkerMethods<crate::DomTypeHolder> for Worker { devtools_sender.clone(), page_info, )); + + let source_info = SourceInfo { + url: worker_url.clone(), + external: true, // Worker scripts are always external. + worker_id: Some(worker_id), + }; + let _ = chan.send(ScriptToDevtoolsControlMsg::ScriptSourceLoaded( + pipeline_id, + source_info, + )); } } diff --git a/components/shared/devtools/lib.rs b/components/shared/devtools/lib.rs index 59857dc0d00..0cf99d22658 100644 --- a/components/shared/devtools/lib.rs +++ b/components/shared/devtools/lib.rs @@ -551,4 +551,5 @@ impl fmt::Display for ShadowRootMode { pub struct SourceInfo { pub url: ServoUrl, pub external: bool, + pub worker_id: Option<WorkerId>, } |