diff options
Diffstat (limited to 'components')
35 files changed, 630 insertions, 697 deletions
diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index ad89c435717..f3a15d7708d 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -371,6 +371,7 @@ pub struct Constellation<STF, SWF> { mem_profiler_chan: mem::ProfilerChan, /// A single WebRender document the constellation operates on. + #[cfg(feature = "webgpu")] webrender_document: DocumentId, /// Webrender related objects required by WebGPU threads @@ -715,6 +716,7 @@ where phantom: PhantomData, webdriver: WebDriverData::new(), document_states: HashMap::new(), + #[cfg(feature = "webgpu")] webrender_document: state.webrender_document, #[cfg(feature = "webgpu")] webrender_wgpu, diff --git a/components/layout/flexbox/layout.rs b/components/layout/flexbox/layout.rs index a5540123681..e69b792e272 100644 --- a/components/layout/flexbox/layout.rs +++ b/components/layout/flexbox/layout.rs @@ -49,7 +49,6 @@ use crate::{ struct FlexContext<'a> { config: FlexContainerConfig, layout_context: &'a LayoutContext<'a>, - positioning_context: &'a mut PositioningContext, containing_block: &'a ContainingBlock<'a>, // For items container_inner_size_constraint: FlexRelativeVec2<SizeConstraint>, } @@ -657,7 +656,6 @@ impl FlexContainer { let mut flex_context = FlexContext { config: self.config.clone(), layout_context, - positioning_context, containing_block, // https://drafts.csswg.org/css-flexbox/#definite-sizes container_inner_size_constraint: self.config.flex_axis.vec2_to_flex_relative( @@ -1775,16 +1773,7 @@ impl FlexItem<'_> { ) -> Option<FlexItemLayoutResult> { let containing_block = flex_context.containing_block; let independent_formatting_context = &self.box_.independent_formatting_context; - let mut positioning_context = independent_formatting_context - .new_positioning_context() - .unwrap_or_else(|| { - PositioningContext::new_for_subtree( - flex_context - .positioning_context - .collects_for_nearest_positioned_ancestor(), - ) - }); - + let mut positioning_context = PositioningContext::default(); let item_writing_mode = independent_formatting_context.style().writing_mode; let item_is_horizontal = item_writing_mode.is_horizontal(); let flex_axis = flex_context.config.flex_axis; @@ -2617,17 +2606,7 @@ impl FlexItemBox { cross_size_stretches_to_container_size: bool, intrinsic_sizing_mode: IntrinsicSizingMode, ) -> Au { - let mut positioning_context = self - .independent_formatting_context - .new_positioning_context() - .unwrap_or_else(|| { - PositioningContext::new_for_subtree( - flex_context - .positioning_context - .collects_for_nearest_positioned_ancestor(), - ) - }); - + let mut positioning_context = PositioningContext::default(); let style = self.independent_formatting_context.style(); match &self.independent_formatting_context.contents { IndependentFormattingContextContents::Replaced(replaced) => { diff --git a/components/layout/flow/inline/line.rs b/components/layout/flow/inline/line.rs index e65eaed2367..80bab1080ed 100644 --- a/components/layout/flow/inline/line.rs +++ b/components/layout/flow/inline/line.rs @@ -331,7 +331,7 @@ impl LineItemLayout<'_, '_> { self.calculate_inline_box_block_start(inline_box_state, space_above_baseline); let positioning_context_or_start_offset_in_parent = - match inline_box.base.new_positioning_context() { + match PositioningContext::new_for_layout_box_base(&inline_box.base) { Some(positioning_context) => Either::Left(positioning_context), None => Either::Right(self.current_positioning_context_mut().len()), }; diff --git a/components/layout/flow/inline/mod.rs b/components/layout/flow/inline/mod.rs index 25fbaa324b1..2023f4e7174 100644 --- a/components/layout/flow/inline/mod.rs +++ b/components/layout/flow/inline/mod.rs @@ -2004,9 +2004,7 @@ impl IndependentFormattingContext { bidi_level: Level, ) { // We need to know the inline size of the atomic before deciding whether to do the line break. - let mut child_positioning_context = self - .new_positioning_context() - .unwrap_or_else(|| PositioningContext::new_for_subtree(true)); + let mut child_positioning_context = PositioningContext::default(); let IndependentFloatOrAtomicLayoutResult { mut fragment, baselines, diff --git a/components/layout/flow/mod.rs b/components/layout/flow/mod.rs index 983282dc389..772b150ae1c 100644 --- a/components/layout/flow/mod.rs +++ b/components/layout/flow/mod.rs @@ -689,16 +689,13 @@ fn layout_block_level_children_in_parallel( placement_state: &mut PlacementState, ignore_block_margins_for_stretch: LogicalSides1D<bool>, ) -> Vec<Fragment> { - let collects_for_nearest_positioned_ancestor = - positioning_context.collects_for_nearest_positioned_ancestor(); let mut layout_results: Vec<(Fragment, PositioningContext)> = Vec::with_capacity(child_boxes.len()); child_boxes .par_iter() .map(|child_box| { - let mut child_positioning_context = - PositioningContext::new_for_subtree(collects_for_nearest_positioned_ancestor); + let mut child_positioning_context = PositioningContext::default(); let fragment = child_box.borrow().layout( layout_context, &mut child_positioning_context, diff --git a/components/layout/flow/root.rs b/components/layout/flow/root.rs index c6498eeed63..bb9ff1d337a 100644 --- a/components/layout/flow/root.rs +++ b/components/layout/flow/root.rs @@ -385,8 +385,7 @@ impl BoxTree { style, }; - let mut positioning_context = - PositioningContext::new_for_containing_block_for_all_descendants(); + let mut positioning_context = PositioningContext::default(); let independent_layout = self.root.layout( layout_context, &mut positioning_context, diff --git a/components/layout/formatting_contexts.rs b/components/layout/formatting_contexts.rs index 4661c44592c..4982d0dae1a 100644 --- a/components/layout/formatting_contexts.rs +++ b/components/layout/formatting_contexts.rs @@ -295,10 +295,7 @@ impl IndependentNonReplacedContents { ); } - let mut child_positioning_context = PositioningContext::new_for_subtree( - positioning_context.collects_for_nearest_positioned_ancestor(), - ); - + let mut child_positioning_context = PositioningContext::default(); let result = self.layout_without_caching( layout_context, &mut child_positioning_context, diff --git a/components/layout/positioned.rs b/components/layout/positioned.rs index 6bfe2af87ef..ff361396fa3 100644 --- a/components/layout/positioned.rs +++ b/components/layout/positioned.rs @@ -43,16 +43,6 @@ pub(crate) struct AbsolutelyPositionedBox { } #[derive(Clone, MallocSizeOf)] -pub(crate) struct PositioningContext { - for_nearest_positioned_ancestor: Option<Vec<HoistedAbsolutelyPositionedBox>>, - - // For nearest `containing block for all descendants` as defined by the CSS transforms - // spec. - // https://www.w3.org/TR/css-transforms-1/#containing-block-for-all-descendants - for_nearest_containing_block_for_all_descendants: Vec<HoistedAbsolutelyPositionedBox>, -} - -#[derive(Clone, MallocSizeOf)] pub(crate) struct HoistedAbsolutelyPositionedBox { absolutely_positioned_box: ArcRefCell<AbsolutelyPositionedBox>, @@ -104,55 +94,26 @@ impl AbsolutelyPositionedBox { } } -impl IndependentFormattingContext { - #[inline] - pub(crate) fn new_positioning_context(&self) -> Option<PositioningContext> { - self.base.new_positioning_context() - } -} - -impl LayoutBoxBase { - #[inline] - pub(crate) fn new_positioning_context(&self) -> Option<PositioningContext> { - PositioningContext::new_for_style(&self.style, &self.base_fragment_info.flags) - } +#[derive(Clone, Default, MallocSizeOf)] +pub(crate) struct PositioningContext { + absolutes: Vec<HoistedAbsolutelyPositionedBox>, } impl PositioningContext { - pub(crate) fn new_for_containing_block_for_all_descendants() -> Self { - Self { - for_nearest_positioned_ancestor: None, - for_nearest_containing_block_for_all_descendants: Vec::new(), - } - } - - /// Create a [PositioningContext] to use for laying out a subtree. The idea is that - /// when subtree layout is finished, the newly hoisted boxes can be processed - /// (normally adjusting their static insets) and then appended to the parent - /// [PositioningContext]. - pub(crate) fn new_for_subtree(collects_for_nearest_positioned_ancestor: bool) -> Self { - Self { - for_nearest_positioned_ancestor: if collects_for_nearest_positioned_ancestor { - Some(Vec::new()) - } else { - None - }, - for_nearest_containing_block_for_all_descendants: Vec::new(), - } - } - - pub(crate) fn collects_for_nearest_positioned_ancestor(&self) -> bool { - self.for_nearest_positioned_ancestor.is_some() + #[inline] + pub(crate) fn new_for_layout_box_base(layout_box_base: &LayoutBoxBase) -> Option<Self> { + Self::new_for_style_and_fragment_flags( + &layout_box_base.style, + &layout_box_base.base_fragment_info.flags, + ) } - fn new_for_style(style: &ComputedValues, flags: &FragmentFlags) -> Option<Self> { - if style.establishes_containing_block_for_all_descendants(*flags) { - Some(Self::new_for_containing_block_for_all_descendants()) - } else if style.establishes_containing_block_for_absolute_descendants(*flags) { - Some(Self { - for_nearest_positioned_ancestor: Some(Vec::new()), - for_nearest_containing_block_for_all_descendants: Vec::new(), - }) + fn new_for_style_and_fragment_flags( + style: &ComputedValues, + flags: &FragmentFlags, + ) -> Option<Self> { + if style.establishes_containing_block_for_absolute_descendants(*flags) { + Some(Self::default()) } else { None } @@ -195,20 +156,9 @@ impl PositioningContext { offset: &PhysicalVec<Au>, index: PositioningContextLength, ) { - if let Some(hoisted_boxes) = self.for_nearest_positioned_ancestor.as_mut() { - hoisted_boxes - .iter_mut() - .skip(index.for_nearest_positioned_ancestor) - .for_each(|hoisted_fragment| { - hoisted_fragment - .fragment - .borrow_mut() - .adjust_offsets(offset) - }) - } - self.for_nearest_containing_block_for_all_descendants + self.absolutes .iter_mut() - .skip(index.for_nearest_containing_block_for_all_descendants) + .skip(index.0) .for_each(|hoisted_fragment| { hoisted_fragment .fragment @@ -227,19 +177,23 @@ impl PositioningContext { base: &LayoutBoxBase, fragment_layout_fn: impl FnOnce(&mut Self) -> BoxFragment, ) -> BoxFragment { - // Try to create a context, but if one isn't necessary, simply create the fragment - // using the given closure and the current `PositioningContext`. - let mut new_context = match base.new_positioning_context() { - Some(new_context) => new_context, - None => return fragment_layout_fn(self), - }; + // If a new `PositioningContext` isn't necessary, simply create the fragment using + // the given closure and the current `PositioningContext`. + let establishes_containing_block_for_absolutes = base + .style + .establishes_containing_block_for_absolute_descendants(base.base_fragment_info.flags); + if !establishes_containing_block_for_absolutes { + return fragment_layout_fn(self); + } + let mut new_context = PositioningContext::default(); let mut new_fragment = fragment_layout_fn(&mut new_context); - new_context.layout_collected_children(layout_context, &mut new_fragment); - // If the new context has any hoisted boxes for the nearest containing block for - // pass them up the tree. + // Lay out all of the absolutely positioned children for this fragment, and, if it + // isn't a containing block for fixed elements, then pass those up to the parent. + new_context.layout_collected_children(layout_context, &mut new_fragment); self.append(new_context); + if base.style.clone_position() == Position::Relative { new_fragment.content_rect.origin += relative_adjustement(&base.style, containing_block) .to_physical_vector(containing_block.style.writing_mode) @@ -248,13 +202,61 @@ impl PositioningContext { new_fragment } + fn take_boxes_for_fragment( + &mut self, + new_fragment: &BoxFragment, + boxes_to_layout_out: &mut Vec<HoistedAbsolutelyPositionedBox>, + boxes_to_continue_hoisting_out: &mut Vec<HoistedAbsolutelyPositionedBox>, + ) { + debug_assert!( + new_fragment + .style + .establishes_containing_block_for_absolute_descendants(new_fragment.base.flags) + ); + + if new_fragment + .style + .establishes_containing_block_for_all_descendants(new_fragment.base.flags) + { + boxes_to_layout_out.append(&mut self.absolutes); + return; + } + + // TODO: This could potentially use `extract_if` when that is stabilized. + let (mut boxes_to_layout, mut boxes_to_continue_hoisting) = self + .absolutes + .drain(..) + .partition(|hoisted_box| hoisted_box.position() != Position::Fixed); + boxes_to_layout_out.append(&mut boxes_to_layout); + boxes_to_continue_hoisting_out.append(&mut boxes_to_continue_hoisting); + } + // Lay out the hoisted boxes collected into this `PositioningContext` and add them // to the given `BoxFragment`. - pub fn layout_collected_children( + pub(crate) fn layout_collected_children( &mut self, layout_context: &LayoutContext, new_fragment: &mut BoxFragment, ) { + if self.absolutes.is_empty() { + return; + } + + // Sometimes we create temporary PositioningContexts just to collect hoisted absolutes and + // then these are processed later. In that case and if this fragment doesn't establish a + // containing block for absolutes at all, we just do nothing. All hoisted fragments will + // later be passed up to a parent PositioningContext. + // + // Handling this case here, when the PositioningContext is completely ineffectual other than + // as a temporary container for hoisted boxes, means that callers can execute less conditional + // code. + if !new_fragment + .style + .establishes_containing_block_for_absolute_descendants(new_fragment.base.flags) + { + return; + } + let padding_rect = PhysicalRect::new( // Ignore the content rect’s position in its own containing block: PhysicalPoint::origin(), @@ -268,83 +270,58 @@ impl PositioningContext { style: &new_fragment.style, }; - let take_hoisted_boxes_pending_layout = - |context: &mut Self| match context.for_nearest_positioned_ancestor.as_mut() { - Some(fragments) => mem::take(fragments), - None => mem::take(&mut context.for_nearest_containing_block_for_all_descendants), - }; + let mut fixed_position_boxes_to_hoist = Vec::new(); + let mut boxes_to_layout = Vec::new(); + self.take_boxes_for_fragment( + new_fragment, + &mut boxes_to_layout, + &mut fixed_position_boxes_to_hoist, + ); - // Loop because it’s possible that we discover (the static position of) - // more absolutely-positioned boxes while doing layout for others. - let mut hoisted_boxes = take_hoisted_boxes_pending_layout(self); - let mut laid_out_child_fragments = Vec::new(); - while !hoisted_boxes.is_empty() { + // Laying out a `position: absolute` child (which only establishes a containing block for + // `position: absolute` descendants) can result in more `position: fixed` descendants + // collecting in `self.absolutes`. We need to loop here in order to keep either laying them + // out or putting them into `fixed_position_boxes_to_hoist`. We know there aren't any more + // when `self.absolutes` is empty. + while !boxes_to_layout.is_empty() { HoistedAbsolutelyPositionedBox::layout_many( layout_context, - &mut hoisted_boxes, - &mut laid_out_child_fragments, - &mut self.for_nearest_containing_block_for_all_descendants, + std::mem::take(&mut boxes_to_layout), + &mut new_fragment.children, + &mut self.absolutes, &containing_block, new_fragment.padding, ); - hoisted_boxes = take_hoisted_boxes_pending_layout(self); - } - new_fragment.children.extend(laid_out_child_fragments); - } - - pub(crate) fn push(&mut self, box_: HoistedAbsolutelyPositionedBox) { - if let Some(nearest) = &mut self.for_nearest_positioned_ancestor { - let position = box_ - .absolutely_positioned_box - .borrow() - .context - .style() - .clone_position(); - match position { - Position::Fixed => {}, // fall through - Position::Absolute => return nearest.push(box_), - Position::Static | Position::Relative | Position::Sticky => unreachable!(), - } + self.take_boxes_for_fragment( + new_fragment, + &mut boxes_to_layout, + &mut fixed_position_boxes_to_hoist, + ); } - self.for_nearest_containing_block_for_all_descendants - .push(box_) + + // We replace here instead of simply preserving these in `take_boxes_for_fragment` + // so that we don't have to continually re-iterate over them when laying out in the + // loop above. + self.absolutes = fixed_position_boxes_to_hoist; } - pub(crate) fn is_empty(&self) -> bool { - self.for_nearest_containing_block_for_all_descendants - .is_empty() && - self.for_nearest_positioned_ancestor - .as_ref() - .is_none_or(|vector| vector.is_empty()) + pub(crate) fn push(&mut self, hoisted_box: HoistedAbsolutelyPositionedBox) { + debug_assert!(matches!( + hoisted_box.position(), + Position::Absolute | Position::Fixed + )); + self.absolutes.push(hoisted_box); } - pub(crate) fn append(&mut self, other: Self) { - if other.is_empty() { + pub(crate) fn append(&mut self, mut other: Self) { + if other.absolutes.is_empty() { return; } - - vec_append_owned( - &mut self.for_nearest_containing_block_for_all_descendants, - other.for_nearest_containing_block_for_all_descendants, - ); - - match ( - self.for_nearest_positioned_ancestor.as_mut(), - other.for_nearest_positioned_ancestor, - ) { - (Some(us), Some(them)) => vec_append_owned(us, them), - (None, Some(them)) => { - // This is the case where we have laid out the absolute children in a containing - // block for absolutes and we then are passing up the fixed-position descendants - // to the containing block for all descendants. - vec_append_owned( - &mut self.for_nearest_containing_block_for_all_descendants, - them, - ); - }, - (None, None) => {}, - _ => unreachable!(), + if self.absolutes.is_empty() { + self.absolutes = other.absolutes; + } else { + self.absolutes.append(&mut other.absolutes) } } @@ -354,19 +331,16 @@ impl PositioningContext { initial_containing_block: &DefiniteContainingBlock, fragments: &mut Vec<Fragment>, ) { - debug_assert!(self.for_nearest_positioned_ancestor.is_none()); - - // Loop because it’s possible that we discover (the static position of) - // more absolutely-positioned boxes while doing layout for others. - while !self - .for_nearest_containing_block_for_all_descendants - .is_empty() - { + // Laying out a `position: absolute` child (which only establishes a containing block for + // `position: absolute` descendants) can result in more `position: fixed` descendants + // collecting in `self.absolutes`. We need to loop here in order to keep laying them out. We + // know there aren't any more when `self.absolutes` is empty. + while !self.absolutes.is_empty() { HoistedAbsolutelyPositionedBox::layout_many( layout_context, - &mut mem::take(&mut self.for_nearest_containing_block_for_all_descendants), + mem::take(&mut self.absolutes), fragments, - &mut self.for_nearest_containing_block_for_all_descendants, + &mut self.absolutes, initial_containing_block, Default::default(), ) @@ -375,58 +349,46 @@ impl PositioningContext { /// Get the length of this [PositioningContext]. pub(crate) fn len(&self) -> PositioningContextLength { - PositioningContextLength { - for_nearest_positioned_ancestor: self - .for_nearest_positioned_ancestor - .as_ref() - .map_or(0, |vec| vec.len()), - for_nearest_containing_block_for_all_descendants: self - .for_nearest_containing_block_for_all_descendants - .len(), - } + PositioningContextLength(self.absolutes.len()) } /// Truncate this [PositioningContext] to the given [PositioningContextLength]. This /// is useful for "unhoisting" boxes in this context and returning it to the state at /// the time that [`PositioningContext::len()`] was called. pub(crate) fn truncate(&mut self, length: &PositioningContextLength) { - if let Some(vec) = self.for_nearest_positioned_ancestor.as_mut() { - vec.truncate(length.for_nearest_positioned_ancestor); - } - self.for_nearest_containing_block_for_all_descendants - .truncate(length.for_nearest_containing_block_for_all_descendants); + self.absolutes.truncate(length.0) } } /// A data structure which stores the size of a positioning context. #[derive(Clone, Copy, Debug, PartialEq)] -pub(crate) struct PositioningContextLength { - /// The number of boxes that will be hoisted the the nearest positioned ancestor for - /// layout. - for_nearest_positioned_ancestor: usize, - /// The number of boxes that will be hoisted the the nearest ancestor which - /// establishes a containing block for all descendants for layout. - for_nearest_containing_block_for_all_descendants: usize, -} +pub(crate) struct PositioningContextLength(usize); impl Zero for PositioningContextLength { fn zero() -> Self { - PositioningContextLength { - for_nearest_positioned_ancestor: 0, - for_nearest_containing_block_for_all_descendants: 0, - } + Self(0) } fn is_zero(&self) -> bool { - self.for_nearest_positioned_ancestor == 0 && - self.for_nearest_containing_block_for_all_descendants == 0 + self.0.is_zero() } } impl HoistedAbsolutelyPositionedBox { + fn position(&self) -> Position { + let position = self + .absolutely_positioned_box + .borrow() + .context + .style() + .clone_position(); + assert!(position == Position::Fixed || position == Position::Absolute); + position + } + pub(crate) fn layout_many( layout_context: &LayoutContext, - boxes: &mut [Self], + mut boxes: Vec<Self>, fragments: &mut Vec<Fragment>, for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>, containing_block: &DefiniteContainingBlock, @@ -473,7 +435,7 @@ impl HoistedAbsolutelyPositionedBox { pub(crate) fn layout( &mut self, layout_context: &LayoutContext, - for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>, + hoisted_absolutes_from_children: &mut Vec<HoistedAbsolutelyPositionedBox>, containing_block: &DefiniteContainingBlock, containing_block_padding: PhysicalSides<Au>, ) -> Fragment { @@ -596,7 +558,7 @@ impl HoistedAbsolutelyPositionedBox { .sizes })); - let mut positioning_context = context.new_positioning_context().unwrap(); + let mut positioning_context = PositioningContext::default(); let mut new_fragment = { let content_size: LogicalVec2<Au>; let fragments; @@ -709,6 +671,10 @@ impl HoistedAbsolutelyPositionedBox { ) .with_specific_layout_info(specific_layout_info) }; + + // This is an absolutely positioned element, which means it also establishes a + // containing block for absolutes. We lay out any absolutely positioned children + // here and pass the rest to `hoisted_absolutes_from_children.` positioning_context.layout_collected_children(layout_context, &mut new_fragment); // Any hoisted boxes that remain in this positioning context are going to be hoisted @@ -721,8 +687,7 @@ impl HoistedAbsolutelyPositionedBox { PositioningContextLength::zero(), ); - for_nearest_containing_block_for_all_descendants - .extend(positioning_context.for_nearest_containing_block_for_all_descendants); + hoisted_absolutes_from_children.extend(positioning_context.absolutes); let fragment = Fragment::Box(ArcRefCell::new(new_fragment)); context.base.set_fragment(fragment.clone()); @@ -1024,14 +989,6 @@ impl AbsoluteAxisSolver<'_> { } } -fn vec_append_owned<T>(a: &mut Vec<T>, mut b: Vec<T>) { - if a.is_empty() { - *a = b - } else { - a.append(&mut b) - } -} - /// <https://drafts.csswg.org/css2/visuren.html#relative-positioning> pub(crate) fn relative_adjustement( style: &ComputedValues, diff --git a/components/layout/style_ext.rs b/components/layout/style_ext.rs index 023db6b07f1..68a4481a2be 100644 --- a/components/layout/style_ext.rs +++ b/components/layout/style_ext.rs @@ -712,15 +712,19 @@ impl ComputedValuesExt for ComputedValues { // From <https://www.w3.org/TR/css-transforms-1/#transform-rendering> // > For elements whose layout is governed by the CSS box model, any value other than // > `none` for the `transform` property results in the creation of a stacking context. + // + // From <https://www.w3.org/TR/css-transforms-2/#individual-transforms> + // > all other values […] create a stacking context and containing block for all + // > descendants, per usual for transforms. + // + // From <https://www.w3.org/TR/css-transforms-2/#perspective-property> + // > any value other than none establishes a stacking context. + // // From <https://www.w3.org/TR/css-transforms-2/#transform-style-property> // > A computed value of `preserve-3d` for `transform-style` on a transformable element // > establishes both a stacking context and a containing block for all descendants. - // From <https://www.w3.org/TR/css-transforms-2/#perspective-property> - // > any value other than none establishes a stacking context. - // TODO: handle individual transform properties (`translate`, `scale` and `rotate`). - // <https://www.w3.org/TR/css-transforms-2/#individual-transforms> if self.is_transformable(fragment_flags) && - (!self.get_box().transform.0.is_empty() || + (self.has_transform_or_perspective_style() || self.get_box().transform_style == ComputedTransformStyle::Preserve3d || will_change_bits .intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE)) diff --git a/components/layout/table/layout.rs b/components/layout/table/layout.rs index 0cbe3e9ca76..2efe339837e 100644 --- a/components/layout/table/layout.rs +++ b/components/layout/table/layout.rs @@ -1068,7 +1068,6 @@ impl<'a> TableLayout<'a> { &mut self, layout_context: &LayoutContext, containing_block_for_table: &ContainingBlock, - parent_positioning_context: &mut PositioningContext, ) { self.cells_laid_out = self .table @@ -1076,30 +1075,6 @@ impl<'a> TableLayout<'a> { .par_iter() .enumerate() .map(|(row_index, row_slots)| { - // When building the PositioningContext for this cell, we want it to have the same - // configuration for whatever PositioningContext the contents are ultimately added to. - let collect_for_nearest_positioned_ancestor = parent_positioning_context - .collects_for_nearest_positioned_ancestor() || - self.table.rows.get(row_index).is_some_and(|row| { - let row = row.borrow(); - let row_group_collects_for_nearest_positioned_ancestor = - row.group_index.is_some_and(|group_index| { - self.table.row_groups[group_index] - .borrow() - .base - .style - .establishes_containing_block_for_absolute_descendants( - FragmentFlags::empty(), - ) - }); - row_group_collects_for_nearest_positioned_ancestor || - row.base - .style - .establishes_containing_block_for_absolute_descendants( - FragmentFlags::empty(), - ) - }); - row_slots .par_iter() .enumerate() @@ -1141,10 +1116,7 @@ impl<'a> TableLayout<'a> { style: &cell.base.style, }; - let mut positioning_context = PositioningContext::new_for_subtree( - collect_for_nearest_positioned_ancestor, - ); - + let mut positioning_context = PositioningContext::default(); let layout = cell.contents.layout( layout_context, &mut positioning_context, @@ -1503,7 +1475,6 @@ impl<'a> TableLayout<'a> { layout_context: &LayoutContext, parent_positioning_context: &mut PositioningContext, ) -> BoxFragment { - let mut positioning_context = caption.context.new_positioning_context(); let containing_block = &ContainingBlock { size: ContainingBlockSize { inline: self.table_width + self.pbm.padding_border_sums.inline, @@ -1517,6 +1488,8 @@ impl<'a> TableLayout<'a> { // stretch block size. https://drafts.csswg.org/css-sizing-4/#stretch-fit-sizing let ignore_block_margins_for_stretch = LogicalSides1D::new(false, false); + let mut positioning_context = + PositioningContext::new_for_layout_box_base(&caption.context.base); let mut box_fragment = caption.context.layout_in_flow_block_level( layout_context, positioning_context @@ -1769,11 +1742,7 @@ impl<'a> TableLayout<'a> { ) -> BoxFragment { self.distributed_column_widths = Self::distribute_width_to_columns(self.assignable_width, &self.columns); - self.layout_cells_in_row( - layout_context, - containing_block_for_children, - positioning_context, - ); + self.layout_cells_in_row(layout_context, containing_block_for_children); let table_writing_mode = containing_block_for_children.style.writing_mode; let first_layout_row_heights = self.do_first_row_layout(table_writing_mode); self.compute_table_height_and_final_row_heights( @@ -2325,7 +2294,7 @@ impl<'a> RowFragmentLayout<'a> { Self { row: table_row, rect, - positioning_context: table_row.base.new_positioning_context(), + positioning_context: PositioningContext::new_for_layout_box_base(&table_row.base), containing_block, fragments: Vec::new(), } @@ -2379,11 +2348,11 @@ impl<'a> RowFragmentLayout<'a> { if let Some(mut row_positioning_context) = self.positioning_context.take() { row_positioning_context.layout_collected_children(layout_context, &mut row_fragment); - let positioning_context = row_group_fragment_layout + let parent_positioning_context = row_group_fragment_layout .as_mut() .and_then(|layout| layout.positioning_context.as_mut()) .unwrap_or(table_positioning_context); - positioning_context.append(row_positioning_context); + parent_positioning_context.append(row_positioning_context); } let fragment = Fragment::Box(ArcRefCell::new(row_fragment)); @@ -2410,7 +2379,7 @@ impl RowGroupFragmentLayout { let row_group = row_group.borrow(); ( dimensions.get_row_group_rect(&row_group), - row_group.base.new_positioning_context(), + PositioningContext::new_for_layout_box_base(&row_group.base), ) }; Self { diff --git a/components/layout/taffy/layout.rs b/components/layout/taffy/layout.rs index 3777c902053..a5838c1bd65 100644 --- a/components/layout/taffy/layout.rs +++ b/components/layout/taffy/layout.rs @@ -29,7 +29,7 @@ use crate::geom::{ use crate::layout_box_base::CacheableLayoutResult; use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength}; use crate::sizing::{ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult}; -use crate::style_ext::{ComputedValuesExt, LayoutStyle}; +use crate::style_ext::LayoutStyle; use crate::{ConstraintSpace, ContainingBlock, ContainingBlockSize}; const DUMMY_NODE_ID: taffy::NodeId = taffy::NodeId::new(u64::MAX); @@ -250,29 +250,15 @@ impl taffy::LayoutPartialTree for TaffyContainerContext<'_> { }, style, }; - let layout = { - let mut child_positioning_context = independent_context - .new_positioning_context() - .unwrap_or_else(|| { - PositioningContext::new_for_subtree( - self.positioning_context - .collects_for_nearest_positioned_ancestor(), - ) - }); - - let layout = non_replaced.layout_without_caching( - self.layout_context, - &mut child_positioning_context, - &content_box_size_override, - containing_block, - false, /* depends_on_block_constraints */ - ); - // Store layout data on child for later access - child.positioning_context = child_positioning_context; - - layout - }; + child.positioning_context = PositioningContext::default(); + let layout = non_replaced.layout_without_caching( + self.layout_context, + &mut child.positioning_context, + &content_box_size_override, + containing_block, + false, /* depends_on_block_constraints */ + ); child.child_fragments = layout.fragments; self.child_specific_layout_infos[usize::from(node_id)] = @@ -373,8 +359,7 @@ impl ComputeInlineContentSizes for TaffyContainer { let mut grid_context = TaffyContainerContext { layout_context, - positioning_context: - &mut PositioningContext::new_for_containing_block_for_all_descendants(), + positioning_context: &mut PositioningContext::default(), content_box_size_override: containing_block, style, source_child_nodes: &self.children, @@ -540,17 +525,6 @@ impl TaffyContainer { let child_specific_layout_info: Option<SpecificLayoutInfo> = std::mem::take(&mut container_ctx.child_specific_layout_infos[child_id]); - let establishes_containing_block_for_absolute_descendants = - if let TaffyItemBoxInner::InFlowBox(independent_box) = &child.taffy_level_box { - child - .style - .establishes_containing_block_for_absolute_descendants( - independent_box.base_fragment_info().flags, - ) - } else { - false - }; - let fragment = match &mut child.taffy_level_box { TaffyItemBoxInner::InFlowBox(independent_box) => { let mut fragment_info = independent_box.base_fragment_info(); @@ -573,29 +547,21 @@ impl TaffyContainer { }) .with_specific_layout_info(child_specific_layout_info); - if establishes_containing_block_for_absolute_descendants { - child.positioning_context.layout_collected_children( - container_ctx.layout_context, - &mut box_fragment, - ); - } - - let fragment = Fragment::Box(ArcRefCell::new(box_fragment)); - + child.positioning_context.layout_collected_children( + container_ctx.layout_context, + &mut box_fragment, + ); child .positioning_context - .adjust_static_position_of_hoisted_fragments( - &fragment, + .adjust_static_position_of_hoisted_fragments_with_offset( + &box_fragment.content_rect.origin.to_vector(), PositioningContextLength::zero(), ); - let child_positioning_context = std::mem::replace( - &mut child.positioning_context, - PositioningContext::new_for_containing_block_for_all_descendants(), - ); container_ctx .positioning_context - .append(child_positioning_context); - fragment + .append(std::mem::take(&mut child.positioning_context)); + + Fragment::Box(ArcRefCell::new(box_fragment)) }, TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(abs_pos_box) => { fn resolve_alignment(value: AlignFlags, auto: AlignFlags) -> AlignFlags { diff --git a/components/layout/taffy/mod.rs b/components/layout/taffy/mod.rs index 55a678cd89a..b1ff753ea78 100644 --- a/components/layout/taffy/mod.rs +++ b/components/layout/taffy/mod.rs @@ -110,7 +110,7 @@ impl TaffyItemBox { Self { taffy_layout: Default::default(), child_fragments: Vec::new(), - positioning_context: PositioningContext::new_for_containing_block_for_all_descendants(), + positioning_context: PositioningContext::default(), style, taffy_level_box: inner, } @@ -118,8 +118,7 @@ impl TaffyItemBox { pub(crate) fn invalidate_cached_fragment(&mut self) { self.taffy_layout = Default::default(); - self.positioning_context = - PositioningContext::new_for_containing_block_for_all_descendants(); + self.positioning_context = PositioningContext::default(); match self.taffy_level_box { TaffyItemBoxInner::InFlowBox(ref independent_formatting_context) => { independent_formatting_context diff --git a/components/script/canvas_context.rs b/components/script/canvas_context.rs index 0a7545e9594..7dfdf48e3f5 100644 --- a/components/script/canvas_context.rs +++ b/components/script/canvas_context.rs @@ -14,8 +14,10 @@ use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanva use crate::dom::bindings::inheritance::Castable; use crate::dom::htmlcanvaselement::HTMLCanvasElement; use crate::dom::node::{Node, NodeDamage}; +#[cfg(feature = "webgpu")] +use crate::dom::types::GPUCanvasContext; use crate::dom::types::{ - CanvasRenderingContext2D, GPUCanvasContext, OffscreenCanvas, OffscreenCanvasRenderingContext2D, + CanvasRenderingContext2D, OffscreenCanvas, OffscreenCanvasRenderingContext2D, WebGL2RenderingContext, WebGLRenderingContext, }; diff --git a/components/script/dom/gamepad.rs b/components/script/dom/gamepad.rs index dcafc58bcd9..baf3af7466f 100644 --- a/components/script/dom/gamepad.rs +++ b/components/script/dom/gamepad.rs @@ -13,7 +13,7 @@ use crate::dom::bindings::codegen::Bindings::GamepadBinding::{GamepadHand, Gamep use crate::dom::bindings::codegen::Bindings::GamepadButtonListBinding::GamepadButtonListMethods; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::num::Finite; -use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto}; +use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object}; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::str::DOMString; use crate::dom::event::Event; @@ -23,6 +23,7 @@ use crate::dom::gamepadevent::{GamepadEvent, GamepadEventType}; use crate::dom::gamepadhapticactuator::GamepadHapticActuator; use crate::dom::gamepadpose::GamepadPose; use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; use crate::script_runtime::{CanGc, JSContext}; // This value is for determining when to consider a gamepad as having a user gesture @@ -88,39 +89,14 @@ impl Gamepad { } } - #[allow(clippy::too_many_arguments)] - pub(crate) fn new( - global: &GlobalScope, - gamepad_id: u32, - id: String, - mapping_type: String, - axis_bounds: (f64, f64), - button_bounds: (f64, f64), - supported_haptic_effects: GamepadSupportedHapticEffects, - xr: bool, - can_gc: CanGc, - ) -> DomRoot<Gamepad> { - Self::new_with_proto( - global, - gamepad_id, - id, - mapping_type, - axis_bounds, - button_bounds, - supported_haptic_effects, - xr, - can_gc, - ) - } - /// When we construct a new gamepad, we initialize the number of buttons and /// axes corresponding to the "standard" gamepad mapping. /// The spec says UAs *may* do this for fingerprint mitigation, and it also /// happens to simplify implementation /// <https://www.w3.org/TR/gamepad/#fingerprinting-mitigation> #[allow(clippy::too_many_arguments)] - fn new_with_proto( - global: &GlobalScope, + pub(crate) fn new( + window: &Window, gamepad_id: u32, id: String, mapping_type: String, @@ -130,11 +106,11 @@ impl Gamepad { xr: bool, can_gc: CanGc, ) -> DomRoot<Gamepad> { - let button_list = GamepadButtonList::init_buttons(global, can_gc); + let button_list = GamepadButtonList::init_buttons(window, can_gc); let vibration_actuator = - GamepadHapticActuator::new(global, gamepad_id, supported_haptic_effects, can_gc); + GamepadHapticActuator::new(window, gamepad_id, supported_haptic_effects, can_gc); let index = if xr { -1 } else { 0 }; - let gamepad = reflect_dom_object_with_proto( + let gamepad = reflect_dom_object( Box::new(Gamepad::new_inherited( gamepad_id, id, @@ -149,8 +125,7 @@ impl Gamepad { button_bounds, &vibration_actuator, )), - global, - None, + window, can_gc, ); gamepad.init_axes(can_gc); diff --git a/components/script/dom/gamepadbutton.rs b/components/script/dom/gamepadbutton.rs index fead990ccd3..3588ba775ca 100644 --- a/components/script/dom/gamepadbutton.rs +++ b/components/script/dom/gamepadbutton.rs @@ -10,7 +10,7 @@ use crate::dom::bindings::codegen::Bindings::GamepadButtonBinding::GamepadButton use crate::dom::bindings::num::Finite; use crate::dom::bindings::reflector::{Reflector, reflect_dom_object}; use crate::dom::bindings::root::DomRoot; -use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; use crate::script_runtime::CanGc; #[dom_struct] @@ -32,14 +32,14 @@ impl GamepadButton { } pub(crate) fn new( - global: &GlobalScope, + window: &Window, pressed: bool, touched: bool, can_gc: CanGc, ) -> DomRoot<GamepadButton> { reflect_dom_object( Box::new(GamepadButton::new_inherited(pressed, touched)), - global, + window, can_gc, ) } diff --git a/components/script/dom/gamepadbuttonlist.rs b/components/script/dom/gamepadbuttonlist.rs index 50d9c8172bc..7e540ab56bb 100644 --- a/components/script/dom/gamepadbuttonlist.rs +++ b/components/script/dom/gamepadbuttonlist.rs @@ -8,7 +8,7 @@ use crate::dom::bindings::codegen::Bindings::GamepadButtonListBinding::GamepadBu use crate::dom::bindings::reflector::{Reflector, reflect_dom_object}; use crate::dom::bindings::root::{Dom, DomRoot, DomSlice}; use crate::dom::gamepadbutton::GamepadButton; -use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; use crate::script_runtime::CanGc; // https://w3c.github.io/gamepad/#gamepadbutton-interface @@ -28,13 +28,13 @@ impl GamepadButtonList { } pub(crate) fn new( - global: &GlobalScope, + window: &Window, list: &[&GamepadButton], can_gc: CanGc, ) -> DomRoot<GamepadButtonList> { reflect_dom_object( Box::new(GamepadButtonList::new_inherited(list)), - global, + window, can_gc, ) } @@ -62,27 +62,27 @@ impl GamepadButtonListMethods<crate::DomTypeHolder> for GamepadButtonList { impl GamepadButtonList { /// Initialize the number of buttons in the "standard" gamepad mapping. /// <https://www.w3.org/TR/gamepad/#dfn-initializing-buttons> - pub(crate) fn init_buttons(global: &GlobalScope, can_gc: CanGc) -> DomRoot<GamepadButtonList> { + pub(crate) fn init_buttons(window: &Window, can_gc: CanGc) -> DomRoot<GamepadButtonList> { let standard_buttons = &[ - GamepadButton::new(global, false, false, can_gc), // Bottom button in right cluster - GamepadButton::new(global, false, false, can_gc), // Right button in right cluster - GamepadButton::new(global, false, false, can_gc), // Left button in right cluster - GamepadButton::new(global, false, false, can_gc), // Top button in right cluster - GamepadButton::new(global, false, false, can_gc), // Top left front button - GamepadButton::new(global, false, false, can_gc), // Top right front button - GamepadButton::new(global, false, false, can_gc), // Bottom left front button - GamepadButton::new(global, false, false, can_gc), // Bottom right front button - GamepadButton::new(global, false, false, can_gc), // Left button in center cluster - GamepadButton::new(global, false, false, can_gc), // Right button in center cluster - GamepadButton::new(global, false, false, can_gc), // Left stick pressed button - GamepadButton::new(global, false, false, can_gc), // Right stick pressed button - GamepadButton::new(global, false, false, can_gc), // Top button in left cluster - GamepadButton::new(global, false, false, can_gc), // Bottom button in left cluster - GamepadButton::new(global, false, false, can_gc), // Left button in left cluster - GamepadButton::new(global, false, false, can_gc), // Right button in left cluster - GamepadButton::new(global, false, false, can_gc), // Center button in center cluster + GamepadButton::new(window, false, false, can_gc), // Bottom button in right cluster + GamepadButton::new(window, false, false, can_gc), // Right button in right cluster + GamepadButton::new(window, false, false, can_gc), // Left button in right cluster + GamepadButton::new(window, false, false, can_gc), // Top button in right cluster + GamepadButton::new(window, false, false, can_gc), // Top left front button + GamepadButton::new(window, false, false, can_gc), // Top right front button + GamepadButton::new(window, false, false, can_gc), // Bottom left front button + GamepadButton::new(window, false, false, can_gc), // Bottom right front button + GamepadButton::new(window, false, false, can_gc), // Left button in center cluster + GamepadButton::new(window, false, false, can_gc), // Right button in center cluster + GamepadButton::new(window, false, false, can_gc), // Left stick pressed button + GamepadButton::new(window, false, false, can_gc), // Right stick pressed button + GamepadButton::new(window, false, false, can_gc), // Top button in left cluster + GamepadButton::new(window, false, false, can_gc), // Bottom button in left cluster + GamepadButton::new(window, false, false, can_gc), // Left button in left cluster + GamepadButton::new(window, false, false, can_gc), // Right button in left cluster + GamepadButton::new(window, false, false, can_gc), // Center button in center cluster ]; rooted_vec!(let buttons <- standard_buttons.iter().map(DomRoot::as_traced)); - Self::new(global, buttons.r(), can_gc) + Self::new(window, buttons.r(), can_gc) } } diff --git a/components/script/dom/gamepadhapticactuator.rs b/components/script/dom/gamepadhapticactuator.rs index d19db6d1279..ddea21b97ee 100644 --- a/components/script/dom/gamepadhapticactuator.rs +++ b/components/script/dom/gamepadhapticactuator.rs @@ -18,12 +18,12 @@ use crate::dom::bindings::codegen::Bindings::GamepadHapticActuatorBinding::{ use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods; use crate::dom::bindings::error::Error; use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; -use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto}; +use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object}; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; use crate::dom::bindings::utils::to_frozen_array; -use crate::dom::globalscope::GlobalScope; use crate::dom::promise::Promise; +use crate::dom::window::Window; use crate::realms::InRealm; use crate::script_runtime::{CanGc, JSContext}; use crate::task_source::SendableTaskSource; @@ -98,27 +98,17 @@ impl GamepadHapticActuator { } pub(crate) fn new( - global: &GlobalScope, + window: &Window, gamepad_index: u32, supported_haptic_effects: GamepadSupportedHapticEffects, can_gc: CanGc, ) -> DomRoot<GamepadHapticActuator> { - Self::new_with_proto(global, gamepad_index, supported_haptic_effects, can_gc) - } - - fn new_with_proto( - global: &GlobalScope, - gamepad_index: u32, - supported_haptic_effects: GamepadSupportedHapticEffects, - can_gc: CanGc, - ) -> DomRoot<GamepadHapticActuator> { - reflect_dom_object_with_proto( + reflect_dom_object( Box::new(GamepadHapticActuator::new_inherited( gamepad_index, supported_haptic_effects, )), - global, - None, + window, can_gc, ) } diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index efa9a9a97ab..527d03eed4e 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -18,18 +18,17 @@ use base::id::{ ServiceWorkerId, ServiceWorkerRegistrationId, WebViewId, }; use constellation_traits::{ - BlobData, BlobImpl, BroadcastMsg, FileBlob, MessagePortImpl, MessagePortMsg, PortMessageTask, - ScriptToConstellationChan, ScriptToConstellationMessage, + BlobData, BlobImpl, BroadcastMsg, FileBlob, LoadData, LoadOrigin, MessagePortImpl, + MessagePortMsg, PortMessageTask, ScriptToConstellationChan, ScriptToConstellationMessage, }; use content_security_policy::{ - CheckResult, CspList, PolicyDisposition, PolicySource, Violation, ViolationResource, + CheckResult, CspList, Destination, Initiator, NavigationCheckType, ParserMetadata, + PolicyDisposition, PolicySource, Request, Violation, ViolationResource, }; use crossbeam_channel::Sender; use devtools_traits::{PageError, ScriptToDevtoolsControlMsg}; use dom_struct::dom_struct; -use embedder_traits::{ - EmbedderMsg, GamepadEvent, GamepadSupportedHapticEffects, GamepadUpdateType, -}; +use embedder_traits::EmbedderMsg; use http::HeaderMap; use hyper_serde::Serde; use ipc_channel::ipc::{self, IpcSender}; @@ -64,6 +63,7 @@ use profile_traits::{ipc as profile_ipc, mem as profile_mem, time as profile_tim use script_bindings::interfaces::GlobalScopeHelpers; use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl}; use timers::{TimerEventId, TimerEventRequest, TimerSource}; +use url::Origin; use uuid::Uuid; #[cfg(feature = "webgpu")] use webgpu_traits::{DeviceLostReason, WebGPUDevice}; @@ -81,9 +81,7 @@ use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function; use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::{ ImageBitmapOptions, ImageBitmapSource, }; -use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods; use crate::dom::bindings::codegen::Bindings::NotificationBinding::NotificationPermissionCallback; -use crate::dom::bindings::codegen::Bindings::PerformanceBinding::Performance_Binding::PerformanceMethods; use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{ PermissionName, PermissionState, }; @@ -113,8 +111,6 @@ use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; use crate::dom::eventsource::EventSource; use crate::dom::eventtarget::EventTarget; use crate::dom::file::File; -use crate::dom::gamepad::{Gamepad, contains_user_gesture}; -use crate::dom::gamepadevent::GamepadEventType; use crate::dom::htmlscriptelement::{ScriptId, SourceCode}; use crate::dom::imagebitmap::ImageBitmap; use crate::dom::messageevent::MessageEvent; @@ -2957,6 +2953,33 @@ impl GlobalScope { is_js_evaluation_allowed == CheckResult::Allowed } + pub(crate) fn should_navigation_request_be_blocked(&self, load_data: &LoadData) -> bool { + let Some(csp_list) = self.get_csp_list() else { + return false; + }; + let request = Request { + url: load_data.url.clone().into_url(), + origin: match &load_data.load_origin { + LoadOrigin::Script(immutable_origin) => immutable_origin.clone().into_url_origin(), + _ => Origin::new_opaque(), + }, + // TODO: populate this field correctly + redirect_count: 0, + destination: Destination::None, + initiator: Initiator::None, + nonce: "".to_owned(), + integrity_metadata: "".to_owned(), + parser_metadata: ParserMetadata::None, + }; + // TODO: set correct navigation check type for form submission if applicable + let (result, violations) = + csp_list.should_navigation_request_be_blocked(&request, NavigationCheckType::Other); + + self.report_csp_violations(violations); + + result == CheckResult::Blocked + } + pub(crate) fn create_image_bitmap( &self, image: ImageBitmapSource, @@ -3287,134 +3310,6 @@ impl GlobalScope { } } - pub(crate) fn handle_gamepad_event(&self, gamepad_event: GamepadEvent) { - match gamepad_event { - GamepadEvent::Connected(index, name, bounds, supported_haptic_effects) => { - self.handle_gamepad_connect( - index.0, - name, - bounds.axis_bounds, - bounds.button_bounds, - supported_haptic_effects, - ); - }, - GamepadEvent::Disconnected(index) => { - self.handle_gamepad_disconnect(index.0); - }, - GamepadEvent::Updated(index, update_type) => { - self.receive_new_gamepad_button_or_axis(index.0, update_type); - }, - }; - } - - /// <https://www.w3.org/TR/gamepad/#dfn-gamepadconnected> - fn handle_gamepad_connect( - &self, - // As the spec actually defines how to set the gamepad index, the GilRs index - // is currently unused, though in practice it will almost always be the same. - // More infra is currently needed to track gamepads across windows. - _index: usize, - name: String, - axis_bounds: (f64, f64), - button_bounds: (f64, f64), - supported_haptic_effects: GamepadSupportedHapticEffects, - ) { - // TODO: 2. If document is not null and is not allowed to use the "gamepad" permission, - // then abort these steps. - let this = Trusted::new(self); - self.task_manager() - .gamepad_task_source() - .queue(task!(gamepad_connected: move || { - let global = this.root(); - - if let Some(window) = global.downcast::<Window>() { - let navigator = window.Navigator(); - let selected_index = navigator.select_gamepad_index(); - let gamepad = Gamepad::new( - &global, - selected_index, - name, - "standard".into(), - axis_bounds, - button_bounds, - supported_haptic_effects, - false, - CanGc::note(), - ); - navigator.set_gamepad(selected_index as usize, &gamepad, CanGc::note()); - } - })); - } - - /// <https://www.w3.org/TR/gamepad/#dfn-gamepaddisconnected> - pub(crate) fn handle_gamepad_disconnect(&self, index: usize) { - let this = Trusted::new(self); - self.task_manager() - .gamepad_task_source() - .queue(task!(gamepad_disconnected: move || { - let global = this.root(); - if let Some(window) = global.downcast::<Window>() { - let navigator = window.Navigator(); - if let Some(gamepad) = navigator.get_gamepad(index) { - if window.Document().is_fully_active() { - gamepad.update_connected(false, gamepad.exposed(), CanGc::note()); - navigator.remove_gamepad(index); - } - } - } - })); - } - - /// <https://www.w3.org/TR/gamepad/#receiving-inputs> - pub(crate) fn receive_new_gamepad_button_or_axis( - &self, - index: usize, - update_type: GamepadUpdateType, - ) { - let this = Trusted::new(self); - - // <https://w3c.github.io/gamepad/#dfn-update-gamepad-state> - self.task_manager().gamepad_task_source().queue( - task!(update_gamepad_state: move || { - let global = this.root(); - if let Some(window) = global.downcast::<Window>() { - let navigator = window.Navigator(); - if let Some(gamepad) = navigator.get_gamepad(index) { - let current_time = global.performance().Now(); - gamepad.update_timestamp(*current_time); - match update_type { - GamepadUpdateType::Axis(index, value) => { - gamepad.map_and_normalize_axes(index, value); - }, - GamepadUpdateType::Button(index, value) => { - gamepad.map_and_normalize_buttons(index, value); - } - }; - if !navigator.has_gamepad_gesture() && contains_user_gesture(update_type) { - navigator.set_has_gamepad_gesture(true); - navigator.GetGamepads() - .iter() - .filter_map(|g| g.as_ref()) - .for_each(|gamepad| { - gamepad.set_exposed(true); - gamepad.update_timestamp(*current_time); - let new_gamepad = Trusted::new(&**gamepad); - if window.Document().is_fully_active() { - global.task_manager().gamepad_task_source().queue( - task!(update_gamepad_connect: move || { - let gamepad = new_gamepad.root(); - gamepad.notify_event(GamepadEventType::Connected, CanGc::note()); - }) - ); - } - }); - } - } - } - }) - ); - } - pub(crate) fn current_group_label(&self) -> Option<DOMString> { self.console_group_stack .borrow() diff --git a/components/script/dom/gpucanvascontext.rs b/components/script/dom/gpucanvascontext.rs index 2bdabf3e0ab..f47e1dfddd1 100644 --- a/components/script/dom/gpucanvascontext.rs +++ b/components/script/dom/gpucanvascontext.rs @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use dom_struct::dom_struct; +use webrender_api::ImageKey; use crate::dom::bindings::codegen::Bindings::GPUCanvasContextBinding::GPUCanvasContextMethods; use crate::dom::bindings::codegen::UnionTypes; diff --git a/components/script/dom/htmliframeelement.rs b/components/script/dom/htmliframeelement.rs index c5194c4527f..da7a53bbf0b 100644 --- a/components/script/dom/htmliframeelement.rs +++ b/components/script/dom/htmliframeelement.rs @@ -162,8 +162,13 @@ impl HTMLIFrameElement { if load_data.url.scheme() == "javascript" { let window_proxy = self.GetContentWindow(); if let Some(window_proxy) = window_proxy { + if document + .global() + .should_navigation_request_be_blocked(&load_data) + { + return; + } // Important re security. See https://github.com/servo/servo/issues/23373 - // TODO: check according to https://w3c.github.io/webappsec-csp/#should-block-navigation-request if ScriptThread::check_load_origin(&load_data.load_origin, &document.url().origin()) { ScriptThread::eval_js_url(&window_proxy.global(), &mut load_data, can_gc); diff --git a/components/script/dom/webxr/xrhittestsource.rs b/components/script/dom/webxr/xrhittestsource.rs index 0ec9560db6e..f73f8f79655 100644 --- a/components/script/dom/webxr/xrhittestsource.rs +++ b/components/script/dom/webxr/xrhittestsource.rs @@ -8,7 +8,7 @@ use webxr_api::HitTestId; use crate::dom::bindings::codegen::Bindings::XRHitTestSourceBinding::XRHitTestSourceMethods; use crate::dom::bindings::reflector::{Reflector, reflect_dom_object}; use crate::dom::bindings::root::{Dom, DomRoot}; -use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; use crate::dom::xrsession::XRSession; use crate::script_runtime::CanGc; @@ -31,14 +31,14 @@ impl XRHitTestSource { } pub(crate) fn new( - global: &GlobalScope, + window: &Window, id: HitTestId, session: &XRSession, can_gc: CanGc, ) -> DomRoot<XRHitTestSource> { reflect_dom_object( Box::new(XRHitTestSource::new_inherited(id, session)), - global, + window, can_gc, ) } diff --git a/components/script/dom/webxr/xrinputsource.rs b/components/script/dom/webxr/xrinputsource.rs index 009b210646a..e454e785424 100644 --- a/components/script/dom/webxr/xrinputsource.rs +++ b/components/script/dom/webxr/xrinputsource.rs @@ -17,6 +17,7 @@ use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object}; use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; use crate::dom::gamepad::Gamepad; use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; use crate::dom::xrhand::XRHand; use crate::dom::xrsession::XRSession; use crate::dom::xrspace::XRSpace; @@ -40,14 +41,14 @@ pub(crate) struct XRInputSource { impl XRInputSource { pub(crate) fn new_inherited( - global: &GlobalScope, + window: &Window, session: &XRSession, info: InputSource, can_gc: CanGc, ) -> XRInputSource { // <https://www.w3.org/TR/webxr-gamepads-module-1/#gamepad-differences> let gamepad = Gamepad::new( - global, + window, 0, "".into(), "xr-standard".into(), @@ -74,18 +75,18 @@ impl XRInputSource { #[allow(unsafe_code)] pub(crate) fn new( - global: &GlobalScope, + window: &Window, session: &XRSession, info: InputSource, can_gc: CanGc, ) -> DomRoot<XRInputSource> { let source = reflect_dom_object( - Box::new(XRInputSource::new_inherited(global, session, info, can_gc)), - global, + Box::new(XRInputSource::new_inherited(window, session, info, can_gc)), + window, can_gc, ); - let _ac = enter_realm(global); + let _ac = enter_realm(window); let cx = GlobalScope::get_cx(); unsafe { rooted!(in(*cx) let mut profiles = UndefinedValue()); diff --git a/components/script/dom/webxr/xrinputsourcearray.rs b/components/script/dom/webxr/xrinputsourcearray.rs index d7dcdfcbb6d..26a2c42f598 100644 --- a/components/script/dom/webxr/xrinputsourcearray.rs +++ b/components/script/dom/webxr/xrinputsourcearray.rs @@ -11,7 +11,7 @@ use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object}; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::event::Event; -use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; use crate::dom::xrinputsource::XRInputSource; use crate::dom::xrinputsourceschangeevent::XRInputSourcesChangeEvent; use crate::dom::xrsession::XRSession; @@ -31,10 +31,10 @@ impl XRInputSourceArray { } } - pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<XRInputSourceArray> { + pub(crate) fn new(window: &Window, can_gc: CanGc) -> DomRoot<XRInputSourceArray> { reflect_dom_object( Box::new(XRInputSourceArray::new_inherited()), - global, + window, can_gc, ) } @@ -60,7 +60,7 @@ impl XRInputSourceArray { .any(|i| i.id() == info.id), "Should never add a duplicate input id!" ); - let input = XRInputSource::new(&global, session, info.clone(), can_gc); + let input = XRInputSource::new(window, session, info.clone(), can_gc); self.input_sources.borrow_mut().push(Dom::from_ref(&input)); added.push(input); } @@ -121,7 +121,7 @@ impl XRInputSourceArray { &[] }; self.input_sources.borrow_mut().retain(|i| i.id() != id); - let input = XRInputSource::new(&global, session, info, can_gc); + let input = XRInputSource::new(window, session, info, can_gc); self.input_sources.borrow_mut().push(Dom::from_ref(&input)); let added = [input]; diff --git a/components/script/dom/webxr/xrrenderstate.rs b/components/script/dom/webxr/xrrenderstate.rs index 3f546c2353d..d114020e16e 100644 --- a/components/script/dom/webxr/xrrenderstate.rs +++ b/components/script/dom/webxr/xrrenderstate.rs @@ -14,7 +14,7 @@ use crate::dom::bindings::num::Finite; use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object}; use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; use crate::dom::bindings::utils::to_frozen_array; -use crate::dom::globalscope::GlobalScope; +use crate::dom::window::Window; use crate::dom::xrlayer::XRLayer; use crate::dom::xrwebgllayer::XRWebGLLayer; use crate::script_runtime::{CanGc, JSContext}; @@ -49,7 +49,7 @@ impl XRRenderState { } pub(crate) fn new( - global: &GlobalScope, + window: &Window, depth_near: f64, depth_far: f64, inline_vertical_fov: Option<f64>, @@ -65,14 +65,14 @@ impl XRRenderState { layer, layers, )), - global, + window, can_gc, ) } pub(crate) fn clone_object(&self) -> DomRoot<Self> { XRRenderState::new( - &self.global(), + self.global().as_window(), self.depth_near.get(), self.depth_far.get(), self.inline_vertical_fov.get(), diff --git a/components/script/dom/webxr/xrsession.rs b/components/script/dom/webxr/xrsession.rs index a171a769b71..6ead8f65445 100644 --- a/components/script/dom/webxr/xrsession.rs +++ b/components/script/dom/webxr/xrsession.rs @@ -54,8 +54,8 @@ use crate::dom::bindings::root::{Dom, DomRoot, MutDom, MutNullableDom}; use crate::dom::bindings::utils::to_frozen_array; use crate::dom::event::Event; use crate::dom::eventtarget::EventTarget; -use crate::dom::globalscope::GlobalScope; use crate::dom::promise::Promise; +use crate::dom::window::Window; use crate::dom::xrboundedreferencespace::XRBoundedReferenceSpace; use crate::dom::xrframe::XRFrame; use crate::dom::xrhittestsource::XRHitTestSource; @@ -152,7 +152,7 @@ impl XRSession { } pub(crate) fn new( - global: &GlobalScope, + window: &Window, session: Session, mode: XRSessionMode, frame_receiver: IpcReceiver<Frame>, @@ -163,8 +163,8 @@ impl XRSession { } else { None }; - let render_state = XRRenderState::new(global, 0.1, 1000.0, ivfov, None, Vec::new(), can_gc); - let input_sources = XRInputSourceArray::new(global, can_gc); + let render_state = XRRenderState::new(window, 0.1, 1000.0, ivfov, None, Vec::new(), can_gc); + let input_sources = XRInputSourceArray::new(window, can_gc); let ret = reflect_dom_object( Box::new(XRSession::new_inherited( session, @@ -172,7 +172,7 @@ impl XRSession { &input_sources, mode, )), - global, + window, can_gc, ); ret.attach_event_handler(); @@ -587,7 +587,7 @@ impl XRSession { FrameUpdateEvent::HitTestSourceAdded(id) => { if let Some(promise) = self.pending_hit_test_promises.borrow_mut().remove(&id) { promise.resolve_native( - &XRHitTestSource::new(&self.global(), id, self, can_gc), + &XRHitTestSource::new(self.global().as_window(), id, self, can_gc), can_gc, ); } else { diff --git a/components/script/dom/webxr/xrsystem.rs b/components/script/dom/webxr/xrsystem.rs index eabe7a72119..9963d92fa59 100644 --- a/components/script/dom/webxr/xrsystem.rs +++ b/components/script/dom/webxr/xrsystem.rs @@ -297,7 +297,13 @@ impl XRSystem { return; }, }; - let session = XRSession::new(&self.global(), session, mode, frame_receiver, CanGc::note()); + let session = XRSession::new( + self.global().as_window(), + session, + mode, + frame_receiver, + CanGc::note(), + ); if mode == XRSessionMode::Inline { self.active_inline_sessions .borrow_mut() diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index a685bbb25f2..b115add8611 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -32,8 +32,9 @@ use devtools_traits::{ScriptToDevtoolsControlMsg, TimelineMarker, TimelineMarker use dom_struct::dom_struct; use embedder_traits::user_content_manager::{UserContentManager, UserScript}; use embedder_traits::{ - AlertResponse, ConfirmResponse, EmbedderMsg, PromptResponse, SimpleDialog, Theme, - ViewportDetails, WebDriverJSError, WebDriverJSResult, + AlertResponse, ConfirmResponse, EmbedderMsg, GamepadEvent, GamepadSupportedHapticEffects, + GamepadUpdateType, PromptResponse, SimpleDialog, Theme, ViewportDetails, WebDriverJSError, + WebDriverJSResult, }; use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect}; use euclid::{Point2D, Rect, Scale, Size2D, Vector2D}; @@ -61,6 +62,8 @@ use num_traits::ToPrimitive; use profile_traits::ipc as ProfiledIpc; use profile_traits::mem::ProfilerChan as MemProfilerChan; use profile_traits::time::ProfilerChan as TimeProfilerChan; +use script_bindings::codegen::GenericBindings::NavigatorBinding::NavigatorMethods; +use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods; use script_bindings::interfaces::WindowHelpers; use script_layout_interface::{ FragmentType, Layout, PendingImageState, QueryMsg, Reflow, ReflowGoal, ReflowRequest, @@ -125,6 +128,8 @@ use crate::dom::document::{AnimationFrameCallback, Document, ReflowTriggerCondit use crate::dom::element::Element; use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; use crate::dom::eventtarget::EventTarget; +use crate::dom::gamepad::{Gamepad, contains_user_gesture}; +use crate::dom::gamepadevent::GamepadEventType; use crate::dom::globalscope::GlobalScope; use crate::dom::hashchangeevent::HashChangeEvent; use crate::dom::history::History; @@ -642,6 +647,126 @@ impl Window { pub(crate) fn font_context(&self) -> &Arc<FontContext> { &self.font_context } + + pub(crate) fn handle_gamepad_event(&self, gamepad_event: GamepadEvent) { + match gamepad_event { + GamepadEvent::Connected(index, name, bounds, supported_haptic_effects) => { + self.handle_gamepad_connect( + index.0, + name, + bounds.axis_bounds, + bounds.button_bounds, + supported_haptic_effects, + ); + }, + GamepadEvent::Disconnected(index) => { + self.handle_gamepad_disconnect(index.0); + }, + GamepadEvent::Updated(index, update_type) => { + self.receive_new_gamepad_button_or_axis(index.0, update_type); + }, + }; + } + + /// <https://www.w3.org/TR/gamepad/#dfn-gamepadconnected> + fn handle_gamepad_connect( + &self, + // As the spec actually defines how to set the gamepad index, the GilRs index + // is currently unused, though in practice it will almost always be the same. + // More infra is currently needed to track gamepads across windows. + _index: usize, + name: String, + axis_bounds: (f64, f64), + button_bounds: (f64, f64), + supported_haptic_effects: GamepadSupportedHapticEffects, + ) { + // TODO: 2. If document is not null and is not allowed to use the "gamepad" permission, + // then abort these steps. + let this = Trusted::new(self); + self.upcast::<GlobalScope>() + .task_manager() + .gamepad_task_source() + .queue(task!(gamepad_connected: move || { + let window = this.root(); + + let navigator = window.Navigator(); + let selected_index = navigator.select_gamepad_index(); + let gamepad = Gamepad::new( + &window, + selected_index, + name, + "standard".into(), + axis_bounds, + button_bounds, + supported_haptic_effects, + false, + CanGc::note(), + ); + navigator.set_gamepad(selected_index as usize, &gamepad, CanGc::note()); + })); + } + + /// <https://www.w3.org/TR/gamepad/#dfn-gamepaddisconnected> + fn handle_gamepad_disconnect(&self, index: usize) { + let this = Trusted::new(self); + self.upcast::<GlobalScope>() + .task_manager() + .gamepad_task_source() + .queue(task!(gamepad_disconnected: move || { + let window = this.root(); + let navigator = window.Navigator(); + if let Some(gamepad) = navigator.get_gamepad(index) { + if window.Document().is_fully_active() { + gamepad.update_connected(false, gamepad.exposed(), CanGc::note()); + navigator.remove_gamepad(index); + } + } + })); + } + + /// <https://www.w3.org/TR/gamepad/#receiving-inputs> + fn receive_new_gamepad_button_or_axis(&self, index: usize, update_type: GamepadUpdateType) { + let this = Trusted::new(self); + + // <https://w3c.github.io/gamepad/#dfn-update-gamepad-state> + self.upcast::<GlobalScope>().task_manager().gamepad_task_source().queue( + task!(update_gamepad_state: move || { + let window = this.root(); + let navigator = window.Navigator(); + if let Some(gamepad) = navigator.get_gamepad(index) { + let current_time = window.Performance().Now(); + gamepad.update_timestamp(*current_time); + match update_type { + GamepadUpdateType::Axis(index, value) => { + gamepad.map_and_normalize_axes(index, value); + }, + GamepadUpdateType::Button(index, value) => { + gamepad.map_and_normalize_buttons(index, value); + } + }; + if !navigator.has_gamepad_gesture() && contains_user_gesture(update_type) { + navigator.set_has_gamepad_gesture(true); + navigator.GetGamepads() + .iter() + .filter_map(|g| g.as_ref()) + .for_each(|gamepad| { + gamepad.set_exposed(true); + gamepad.update_timestamp(*current_time); + let new_gamepad = Trusted::new(&**gamepad); + if window.Document().is_fully_active() { + window.upcast::<GlobalScope>().task_manager().gamepad_task_source().queue( + task!(update_gamepad_connect: move || { + let gamepad = new_gamepad.root(); + gamepad.notify_event(GamepadEventType::Connected, CanGc::note()); + }) + ); + } + }); + } + } + }) + ); + } } // https://html.spec.whatwg.org/multipage/#atob @@ -1246,7 +1371,7 @@ impl WindowMethods<crate::DomTypeHolder> for Window { 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(); + let _ = chan.send(rv); } } @@ -1255,9 +1380,9 @@ impl WindowMethods<crate::DomTypeHolder> for Window { 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(); + let _ = chan.send(Err(WebDriverJSError::JSException(rv))); } else { - chan.send(rv).unwrap(); + let _ = chan.send(rv); } } } @@ -1265,7 +1390,7 @@ impl WindowMethods<crate::DomTypeHolder> for Window { fn WebdriverTimeout(&self) { let opt_chan = self.webdriver_script_chan.borrow_mut().take(); if let Some(chan) = opt_chan { - chan.send(Err(WebDriverJSError::Timeout)).unwrap(); + let _ = chan.send(Err(WebDriverJSError::Timeout)); } } diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 2129979ad42..bd4de9d893b 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -598,7 +598,7 @@ impl ScriptThread { with_script_thread(|script_thread| { let is_javascript = load_data.url.scheme() == "javascript"; // If resource is a request whose url's scheme is "javascript" - // https://html.spec.whatwg.org/multipage/#javascript-protocol + // https://html.spec.whatwg.org/multipage/#navigate-to-a-javascript:-url if is_javascript { let window = match script_thread.documents.borrow().find_window(pipeline_id) { None => return, @@ -612,8 +612,12 @@ impl ScriptThread { .clone(); let task = task!(navigate_javascript: move || { // Important re security. See https://github.com/servo/servo/issues/23373 - // TODO: check according to https://w3c.github.io/webappsec-csp/#should-block-navigation-request if let Some(window) = trusted_global.root().downcast::<Window>() { + // Step 5: If the result of should navigation request of type be blocked by + // Content Security Policy? given request and cspNavigationType is "Blocked", then return. [CSP] + if trusted_global.root().should_navigation_request_be_blocked(&load_data) { + return; + } if ScriptThread::check_load_origin(&load_data.load_origin, &window.get_url().origin()) { ScriptThread::eval_js_url(&trusted_global.root(), &mut load_data, CanGc::note()); sender @@ -622,6 +626,7 @@ impl ScriptThread { } } }); + // Step 19 of <https://html.spec.whatwg.org/multipage/#navigate> global .task_manager() .dom_manipulation_task_source() @@ -1126,7 +1131,7 @@ impl ScriptThread { document.dispatch_ime_event(ime_event, can_gc); }, InputEvent::Gamepad(gamepad_event) => { - window.as_global_scope().handle_gamepad_event(gamepad_event); + window.handle_gamepad_event(gamepad_event); }, InputEvent::EditingAction(editing_action_event) => { document.handle_editing_action(editing_action_event, can_gc); diff --git a/components/servo/Cargo.toml b/components/servo/Cargo.toml index b49f60e742a..fa64638fa4c 100644 --- a/components/servo/Cargo.toml +++ b/components/servo/Cargo.toml @@ -141,3 +141,12 @@ libservo = { path = ".", features = ["tracing"] } rustls = { version = "0.23", default-features = false, features = ["aws-lc-rs"] } tracing = { workspace = true } winit = "0.30.8" + + +[[test]] +name = "webview" +harness = false + +[[test]] +name = "servo" +harness = false diff --git a/components/servo/tests/common/mod.rs b/components/servo/tests/common/mod.rs index 8c00826a0d8..de71361e9be 100644 --- a/components/servo/tests/common/mod.rs +++ b/components/servo/tests/common/mod.rs @@ -3,18 +3,52 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::rc::Rc; +use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, OnceLock}; use std::time::Duration; use anyhow::Error; use compositing_traits::rendering_context::{RenderingContext, SoftwareRenderingContext}; -use crossbeam_channel::{Receiver, Sender, unbounded}; use dpi::PhysicalSize; use embedder_traits::EventLoopWaker; -use parking_lot::Mutex; use servo::{Servo, ServoBuilder}; +macro_rules! run_api_tests { + ($($test_function:ident), +) => { + let mut failed = false; + + // Be sure that `servo_test` is dropped before exiting early. + { + let servo_test = ServoTest::new(); + $( + common::run_test($test_function, stringify!($test_function), &servo_test, &mut failed); + )+ + } + + if failed { + std::process::exit(1); + } + } +} + +pub(crate) use run_api_tests; + +pub(crate) fn run_test( + test_function: fn(&ServoTest) -> Result<(), Error>, + test_name: &str, + servo_test: &ServoTest, + failed: &mut bool, +) { + match test_function(servo_test) { + Ok(_) => println!(" ✅ {test_name}"), + Err(error) => { + *failed = true; + println!(" ❌ {test_name}"); + println!("{}", format!("\n{error:?}").replace("\n", "\n ")); + }, + } +} + pub struct ServoTest { servo: Servo, } @@ -30,7 +64,7 @@ impl Drop for ServoTest { } impl ServoTest { - fn new() -> Self { + pub(crate) fn new() -> Self { let rendering_context = Rc::new( SoftwareRenderingContext::new(PhysicalSize { width: 500, @@ -63,58 +97,28 @@ impl ServoTest { &self.servo } - /// Run a Servo test. All tests are run in a `ServoTestThread` and serially. Currently - /// Servo does not support launching concurrent instances, in order to ensure - /// isolation and allow for more than a single test per instance. - pub fn run( - test_function: impl FnOnce(&ServoTest) -> Result<(), anyhow::Error> + Send + Sync + 'static, - ) { - static SERVO_TEST_THREAD: Mutex<OnceLock<ServoTestThread>> = Mutex::new(OnceLock::new()); - let test_thread = SERVO_TEST_THREAD.lock(); - test_thread - .get_or_init(ServoTestThread::new) - .run_test(Box::new(test_function)); - } -} - -type TestFunction = - Box<dyn FnOnce(&ServoTest) -> Result<(), anyhow::Error> + Send + Sync + 'static>; - -struct ServoTestThread { - test_function_sender: Sender<TestFunction>, - result_receiver: Receiver<Result<(), Error>>, -} - -impl ServoTestThread { - fn new() -> Self { - let (result_sender, result_receiver) = unbounded(); - let (test_function_sender, test_function_receiver) = unbounded(); - - // Defined here rather than at the end of this method in order to take advantage - // of Rust type inference. - let thread = Self { - test_function_sender, - result_receiver, - }; - - let _ = std::thread::spawn(move || { - let servo_test = ServoTest::new(); - while let Ok(incoming_test_function) = test_function_receiver.recv() { - let _ = result_sender.send(incoming_test_function(&servo_test)); + /// Spin the Servo event loop until one of: + /// - The given callback returns `Ok(false)`. + /// - The given callback returns an `Error`, in which case the `Error` will be returned. + /// - Servo has indicated that shut down is complete and we cannot spin the event loop + /// any longer. + // The dead code exception here is because not all test suites that use `common` also + // use `spin()`. + #[allow(dead_code)] + pub fn spin(&self, callback: impl Fn() -> Result<bool, Error> + 'static) -> Result<(), Error> { + let mut keep_going = true; + while keep_going { + std::thread::sleep(Duration::from_millis(1)); + if !self.servo.spin_event_loop() { + return Ok(()); + } + let result = callback(); + match result { + Ok(result) => keep_going = result, + Err(error) => return Err(error), } - }); - - thread - } - - fn run_test(&self, test_function: TestFunction) { - let _ = self.test_function_sender.send(Box::new(test_function)); - let result = self - .result_receiver - .recv() - .expect("Servo test thread should always return a result."); - if let Err(result) = result { - unreachable!("{result}"); } + + Ok(()) } } diff --git a/components/servo/tests/servo.rs b/components/servo/tests/servo.rs index 2f71e909bee..6333b0af50a 100644 --- a/components/servo/tests/servo.rs +++ b/components/servo/tests/servo.rs @@ -4,7 +4,7 @@ //! Servo API unit tests. //! -//! Since all Servo tests must rust serially on the same thread, it is important +//! Since all Servo tests must run serially on the same thread, it is important //! that tests never panic. In order to ensure this, use `anyhow::ensure!` instead //! of `assert!` for test assertions. `ensure!` will produce a `Result::Err` in //! place of panicking. @@ -12,21 +12,15 @@ mod common; use anyhow::ensure; -use common::*; -use servo::WebViewBuilder; +use common::{ServoTest, run_api_tests}; -#[test] -fn test_simple_servo_is_not_animating_by_default() { - ServoTest::run(|servo_test| { - ensure!(!servo_test.servo().animating()); - Ok(()) - }); +fn test_simple_servo_is_not_animating_by_default( + servo_test: &ServoTest, +) -> Result<(), anyhow::Error> { + ensure!(!servo_test.servo().animating()); + Ok(()) } -#[test] -fn test_simple_servo_construct_webview() { - ServoTest::run(|servo_test| { - WebViewBuilder::new(servo_test.servo()).build(); - Ok(()) - }); +fn main() { + run_api_tests!(test_simple_servo_is_not_animating_by_default); } diff --git a/components/servo/tests/webview.rs b/components/servo/tests/webview.rs new file mode 100644 index 00000000000..4ed06e412da --- /dev/null +++ b/components/servo/tests/webview.rs @@ -0,0 +1,49 @@ +/* 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/. */ + +//! WebView API unit tests. +//! +//! Since all Servo tests must run serially on the same thread, it is important +//! that tests never panic. In order to ensure this, use `anyhow::ensure!` instead +//! of `assert!` for test assertions. `ensure!` will produce a `Result::Err` in +//! place of panicking. + +mod common; + +use std::cell::Cell; +use std::rc::Rc; + +use anyhow::ensure; +use common::{ServoTest, run_api_tests}; +use servo::{WebViewBuilder, WebViewDelegate}; + +#[derive(Default)] +struct WebViewDelegateImpl { + url_changed: Cell<bool>, +} + +impl WebViewDelegate for WebViewDelegateImpl { + fn notify_url_changed(&self, _webview: servo::WebView, _url: url::Url) { + self.url_changed.set(true); + } +} + +fn test_create_webview(servo_test: &ServoTest) -> Result<(), anyhow::Error> { + let delegate = Rc::new(WebViewDelegateImpl::default()); + let webview = WebViewBuilder::new(servo_test.servo()) + .delegate(delegate.clone()) + .build(); + + servo_test.spin(move || Ok(!delegate.url_changed.get()))?; + + let url = webview.url(); + ensure!(url.is_some()); + ensure!(url.unwrap().to_string() == "about:blank"); + + Ok(()) +} + +fn main() { + run_api_tests!(test_create_webview); +} diff --git a/components/webdriver_server/actions.rs b/components/webdriver_server/actions.rs index f69a09a2577..fbede5b5887 100644 --- a/components/webdriver_server/actions.rs +++ b/components/webdriver_server/actions.rs @@ -76,7 +76,7 @@ fn compute_tick_duration(tick_actions: &ActionSequence) -> u64 { } }, ActionsType::Key { actions: _ } => (), - ActionsType::Wheel { .. } => todo!("Not implemented."), + ActionsType::Wheel { .. } => log::error!("not implemented"), } duration } @@ -176,7 +176,10 @@ impl Handler { } } }, - ActionsType::Wheel { .. } => todo!("Not implemented."), + ActionsType::Wheel { .. } => { + log::error!("not yet implemented"); + return Err(ErrorStatus::UnsupportedOperation); + }, } Ok(()) diff --git a/components/webdriver_server/capabilities.rs b/components/webdriver_server/capabilities.rs index 477a3bfd34c..32596f5275a 100644 --- a/components/webdriver_server/capabilities.rs +++ b/components/webdriver_server/capabilities.rs @@ -4,7 +4,7 @@ use serde_json::{Map, Value}; use webdriver::capabilities::{BrowserCapabilities, Capabilities}; -use webdriver::error::{WebDriverError, WebDriverResult}; +use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult}; pub struct ServoCapabilities { pub browser_name: String, @@ -79,42 +79,42 @@ impl BrowserCapabilities for ServoCapabilities { &mut self, _: &serde_json::Map<std::string::String, Value>, ) -> Result<bool, WebDriverError> { - todo!() + Err(WebDriverError::new(ErrorStatus::UnsupportedOperation, "")) } fn webauthn_virtual_authenticators( &mut self, _: &serde_json::Map<std::string::String, Value>, ) -> Result<bool, WebDriverError> { - todo!() + Err(WebDriverError::new(ErrorStatus::UnsupportedOperation, "")) } fn webauthn_extension_uvm( &mut self, _: &serde_json::Map<std::string::String, Value>, ) -> Result<bool, WebDriverError> { - todo!() + Err(WebDriverError::new(ErrorStatus::UnsupportedOperation, "")) } fn webauthn_extension_prf( &mut self, _: &serde_json::Map<std::string::String, Value>, ) -> Result<bool, WebDriverError> { - todo!() + Err(WebDriverError::new(ErrorStatus::UnsupportedOperation, "")) } fn webauthn_extension_large_blob( &mut self, _: &serde_json::Map<std::string::String, Value>, ) -> Result<bool, WebDriverError> { - todo!() + Err(WebDriverError::new(ErrorStatus::UnsupportedOperation, "")) } fn webauthn_extension_cred_blob( &mut self, _: &serde_json::Map<std::string::String, Value>, ) -> Result<bool, WebDriverError> { - todo!() + Err(WebDriverError::new(ErrorStatus::UnsupportedOperation, "")) } } diff --git a/components/webdriver_server/lib.rs b/components/webdriver_server/lib.rs index ce83a8f3cc1..d003ebf8adb 100644 --- a/components/webdriver_server/lib.rs +++ b/components/webdriver_server/lib.rs @@ -29,7 +29,7 @@ use embedder_traits::{ use euclid::{Rect, Size2D}; use http::method::Method; use image::{DynamicImage, ImageFormat, RgbaImage}; -use ipc_channel::ipc::{self, IpcSender}; +use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; use keyboard_types::webdriver::send_keys; use log::{debug, info}; @@ -678,7 +678,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); self.top_level_script_command(WebDriverScriptCommand::GetUrl(sender))?; - let url = receiver.recv().unwrap(); + let url = wait_for_script_response(receiver)?; Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(url.as_str())?, @@ -694,7 +694,7 @@ impl Handler { .send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg)) .unwrap(); - let window_size = receiver.recv().unwrap(); + let window_size = wait_for_script_response(receiver)?; let window_size_response = WindowRectResponse { x: 0, y: 0, @@ -738,7 +738,7 @@ impl Handler { .unwrap(); }); - let window_size = receiver.recv().unwrap(); + let window_size = wait_for_script_response(receiver)?; let window_size_response = WindowRectResponse { x: 0, y: 0, @@ -756,7 +756,7 @@ impl Handler { sender, ))?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(is_enabled) => Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(is_enabled)?, ))), @@ -772,7 +772,7 @@ impl Handler { sender, ))?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(is_selected) => Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(is_selected)?, ))), @@ -812,7 +812,7 @@ impl Handler { self.top_level_script_command(WebDriverScriptCommand::GetTitle(sender))?; - let value = receiver.recv().unwrap(); + let value = wait_for_script_response(receiver)?; Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(value)?, ))) @@ -874,7 +874,7 @@ impl Handler { }, } - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(value) => { let value_resp = serde_json::to_value( value.map(|x| serde_json::to_value(WebElement(x)).unwrap()), @@ -1005,7 +1005,7 @@ impl Handler { let cmd = WebDriverScriptCommand::GetBrowsingContextId(frame_id, sender); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(browsing_context_id) => { self.session_mut()?.browsing_context_id = browsing_context_id; Ok(WebDriverResponse::Void) @@ -1047,7 +1047,7 @@ impl Handler { }, } - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(value) => { let resp_value: Vec<Value> = value .into_iter() @@ -1103,7 +1103,7 @@ impl Handler { }, } - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(value) => { let value_resp = serde_json::to_value( value.map(|x| serde_json::to_value(WebElement(x)).unwrap()), @@ -1156,7 +1156,7 @@ impl Handler { }, } - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(value) => { let resp_value: Vec<Value> = value .into_iter() @@ -1175,7 +1175,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let cmd = WebDriverScriptCommand::GetElementRect(element.to_string(), sender); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(rect) => { let response = ElementRectResponse { x: rect.origin.x, @@ -1193,7 +1193,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let cmd = WebDriverScriptCommand::GetElementText(element.to_string(), sender); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(value)?, ))), @@ -1205,9 +1205,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let cmd = WebDriverScriptCommand::GetActiveElement(sender); self.browsing_context_script_command(cmd)?; - let value = receiver - .recv() - .unwrap() + let value = wait_for_script_response(receiver)? .map(|x| serde_json::to_value(WebElement(x)).unwrap()); Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(value)?, @@ -1218,7 +1216,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let cmd = WebDriverScriptCommand::GetComputedRole(element.to_string(), sender); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(value)?, ))), @@ -1230,7 +1228,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let cmd = WebDriverScriptCommand::GetElementTagName(element.to_string(), sender); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(value)?, ))), @@ -1250,7 +1248,7 @@ impl Handler { sender, ); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(value)?, ))), @@ -1272,7 +1270,7 @@ impl Handler { ); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(SendableWebDriverJSValue(value))?, ))), @@ -1289,7 +1287,7 @@ impl Handler { let cmd = WebDriverScriptCommand::GetElementCSS(element.to_string(), name.to_owned(), sender); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(value)?, ))), @@ -1301,7 +1299,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let cmd = WebDriverScriptCommand::GetCookies(sender); self.browsing_context_script_command(cmd)?; - let cookies = receiver.recv().unwrap(); + let cookies = wait_for_script_response(receiver)?; let response = cookies .into_iter() .map(|cookie| cookie_msg_to_cookie(cookie.into_inner())) @@ -1313,12 +1311,14 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let cmd = WebDriverScriptCommand::GetCookie(name, sender); self.browsing_context_script_command(cmd)?; - let cookies = receiver.recv().unwrap(); - let response = cookies + let cookies = wait_for_script_response(receiver)?; + let Some(response) = cookies .into_iter() .map(|cookie| cookie_msg_to_cookie(cookie.into_inner())) .next() - .unwrap(); + else { + return Err(WebDriverError::new(ErrorStatus::NoSuchCookie, "")); + }; Ok(WebDriverResponse::Cookie(CookieResponse(response))) } @@ -1342,7 +1342,7 @@ impl Handler { let cmd = WebDriverScriptCommand::AddCookie(cookie_builder.build(), sender); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(_) => Ok(WebDriverResponse::Void), Err(response) => match response { WebDriverCookieError::InvalidDomain => Err(WebDriverError::new( @@ -1361,7 +1361,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let cmd = WebDriverScriptCommand::DeleteCookie(name, sender); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(_) => Ok(WebDriverResponse::Void), Err(error) => Err(WebDriverError::new(error, "")), } @@ -1371,7 +1371,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let cmd = WebDriverScriptCommand::DeleteCookies(sender); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(_) => Ok(WebDriverResponse::Void), Err(error) => Err(WebDriverError::new(error, "")), } @@ -1426,7 +1426,7 @@ impl Handler { let cmd = WebDriverScriptCommand::GetPageSource(sender); self.browsing_context_script_command(cmd)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(source) => Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(source)?, ))), @@ -1487,9 +1487,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let command = WebDriverScriptCommand::ExecuteScript(script, sender); self.browsing_context_script_command(command)?; - let result = receiver - .recv() - .unwrap_or(Err(WebDriverJSError::BrowsingContextNotFound)); + let result = wait_for_script_response(receiver)?; self.postprocess_js_result(result) } @@ -1533,9 +1531,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let command = WebDriverScriptCommand::ExecuteAsyncScript(script, sender); self.browsing_context_script_command(command)?; - let result = receiver - .recv() - .unwrap_or(Err(WebDriverJSError::BrowsingContextNotFound)); + let result = wait_for_script_response(receiver)?; self.postprocess_js_result(result) } @@ -1589,10 +1585,7 @@ impl Handler { .unwrap(); // TODO: distinguish the not found and not focusable cases - receiver - .recv() - .unwrap() - .map_err(|error| WebDriverError::new(error, ""))?; + wait_for_script_response(receiver)?.map_err(|error| WebDriverError::new(error, ""))?; let input_events = send_keys(&keys.text); @@ -1615,7 +1608,7 @@ impl Handler { let command = WebDriverScriptCommand::ElementClick(element.to_string(), sender); self.browsing_context_script_command(command)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(element_id) => match element_id { Some(element_id) => { let id = Uuid::new_v4().to_string(); @@ -1688,7 +1681,7 @@ impl Handler { .send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg)) .unwrap(); - if let Some(x) = receiver.recv().unwrap() { + if let Some(x) = wait_for_script_response(receiver)? { img = Some(x); break; }; @@ -1739,7 +1732,7 @@ impl Handler { let command = WebDriverScriptCommand::GetBoundingClientRect(element.to_string(), sender); self.browsing_context_script_command(command)?; - match receiver.recv().unwrap() { + match wait_for_script_response(receiver)? { Ok(rect) => { let encoded = self.take_screenshot(Some(Rect::from_untyped(&rect)))?; @@ -1944,3 +1937,12 @@ fn webdriver_value_to_js_argument(v: &Value) -> String { }, } } + +fn wait_for_script_response<T>(receiver: IpcReceiver<T>) -> Result<T, WebDriverError> +where + T: for<'de> Deserialize<'de> + Serialize, +{ + receiver + .recv() + .map_err(|_| WebDriverError::new(ErrorStatus::NoSuchWindow, "")) +} |