diff options
author | Kenzie Raditya Tirtarahardja <kenzieradityatirtarahardja18@gmail.com> | 2025-03-23 08:45:59 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-03-23 00:45:59 +0000 |
commit | 40270cb6269f3f2d054bcfe51c69c8c545447f9a (patch) | |
tree | 9992a0aa8a6f1eac75ff97d1c0b11ca1cf4281a6 | |
parent | 8dda64f14bb0214f773e0135dc7df190f7a04e1b (diff) | |
download | servo-40270cb6269f3f2d054bcfe51c69c8c545447f9a.tar.gz servo-40270cb6269f3f2d054bcfe51c69c8c545447f9a.zip |
Make input element display-inside always flow-root (#35908)
Signed-off-by: Kenzie Raditya Tirtarahardja <kenzieradityatirtarahardja.18@gmail.com>
Co-authored-by: Kenzie Raditya Tirtarahardja <kenzieradityatirtarahardja.18@gmail.com>
8 files changed, 136 insertions, 16 deletions
diff --git a/components/layout_2020/dom_traversal.rs b/components/layout_2020/dom_traversal.rs index 6b44c7f46b4..b8a39ad27fe 100644 --- a/components/layout_2020/dom_traversal.rs +++ b/components/layout_2020/dom_traversal.rs @@ -116,6 +116,15 @@ where }, _ => {}, } + + if matches!( + element.type_id(), + Some(LayoutNodeType::Element( + LayoutElementType::HTMLInputElement | LayoutElementType::HTMLTextAreaElement + )) + ) { + flags.insert(FragmentFlags::IS_TEXT_CONTROL); + } }; Self { @@ -135,12 +144,15 @@ pub(super) enum Contents { } #[derive(Debug)] +#[allow(clippy::enum_variant_names)] pub(super) enum NonReplacedContents { /// Refers to a DOM subtree, plus `::before` and `::after` pseudo-elements. OfElement, /// Content of a `::before` or `::after` pseudo-element that is being generated. /// <https://drafts.csswg.org/css2/generate.html#content> OfPseudoElement(Vec<PseudoElementContentItem>), + /// Workaround for input and textarea element until we properly implement `display-inside`. + OfTextControl, } #[derive(Debug)] @@ -236,8 +248,18 @@ fn traverse_element<'dom, Node>( } }, Display::GeneratingBox(display) => { - let contents = - replaced.map_or(NonReplacedContents::OfElement.into(), Contents::Replaced); + let contents = if let Some(replaced) = replaced { + Contents::Replaced(replaced) + } else if matches!( + element.type_id(), + LayoutNodeType::Element( + LayoutElementType::HTMLInputElement | LayoutElementType::HTMLTextAreaElement + ) + ) { + NonReplacedContents::OfTextControl.into() + } else { + NonReplacedContents::OfElement.into() + }; let display = display.used_value_for_contents(&contents); let box_slot = element.element_box_slot(); let info = NodeAndStyleInfo::new(element, style); @@ -366,7 +388,9 @@ impl NonReplacedContents { }, }; match self { - NonReplacedContents::OfElement => traverse_children_of(node, context, handler), + NonReplacedContents::OfElement | NonReplacedContents::OfTextControl => { + traverse_children_of(node, context, handler) + }, NonReplacedContents::OfPseudoElement(items) => { traverse_pseudo_element_contents(info, context, handler, items) }, diff --git a/components/layout_2020/fragment_tree/base_fragment.rs b/components/layout_2020/fragment_tree/base_fragment.rs index 38f4f6ba815..45d10d5e282 100644 --- a/components/layout_2020/fragment_tree/base_fragment.rs +++ b/components/layout_2020/fragment_tree/base_fragment.rs @@ -75,31 +75,33 @@ impl From<BaseFragmentInfo> for BaseFragment { bitflags! { /// Flags used to track various information about a DOM node during layout. #[derive(Clone, Copy, Debug)] - pub(crate) struct FragmentFlags: u8 { + pub(crate) struct FragmentFlags: u16 { /// Whether or not the node that created this fragment is a `<body>` element on an HTML document. const IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT = 1 << 0; /// Whether or not the node that created this Fragment is a `<br>` element. const IS_BR_ELEMENT = 1 << 1; + /// Whether or not the node that created this Fragment is a `<input>` or `<textarea>` element. + const IS_TEXT_CONTROL = 1 << 2; /// Whether or not this Fragment is a flex item or a grid item. - const IS_FLEX_OR_GRID_ITEM = 1 << 2; + const IS_FLEX_OR_GRID_ITEM = 1 << 3; /// Whether or not this Fragment was created to contain a replaced element or is /// a replaced element. - const IS_REPLACED = 1 << 3; + const IS_REPLACED = 1 << 4; /// Whether or not the node that created was a `<table>`, `<th>` or /// `<td>` element. Note that this does *not* include elements with /// `display: table` or `display: table-cell`. - const IS_TABLE_TH_OR_TD_ELEMENT = 1 << 4; + const IS_TABLE_TH_OR_TD_ELEMENT = 1 << 5; /// Whether or not this Fragment was created to contain a list item marker /// with a used value of `list-style-position: outside`. - const IS_OUTSIDE_LIST_ITEM_MARKER = 1 << 5; + const IS_OUTSIDE_LIST_ITEM_MARKER = 1 << 6; /// Avoid painting the borders, backgrounds, and drop shadow for this fragment, this is used /// for empty table cells when 'empty-cells' is 'hide' and also table wrappers. This flag /// doesn't avoid hit-testing nor does it prevent the painting outlines. - const DO_NOT_PAINT = 1 << 6; + const DO_NOT_PAINT = 1 << 7; /// Whether or not the size of this fragment depends on the block size of its container /// and the fragment can be a flex item. This flag is used to cache items during flex /// layout. - const SIZE_DEPENDS_ON_BLOCK_CONSTRAINTS_AND_CAN_BE_CHILD_OF_FLEX_ITEM = 1 << 7; + const SIZE_DEPENDS_ON_BLOCK_CONSTRAINTS_AND_CAN_BE_CHILD_OF_FLEX_ITEM = 1 << 8; } } diff --git a/components/layout_2020/fragment_tree/box_fragment.rs b/components/layout_2020/fragment_tree/box_fragment.rs index 1c8862b0822..f8f0e233a2f 100644 --- a/components/layout_2020/fragment_tree/box_fragment.rs +++ b/components/layout_2020/fragment_tree/box_fragment.rs @@ -15,7 +15,6 @@ use style::properties::ComputedValues; use super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment}; use crate::formatting_contexts::Baselines; -use crate::fragment_tree::FragmentFlags; use crate::geom::{ AuOrAuto, LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides, ToLogical, }; @@ -346,8 +345,7 @@ impl BoxFragment { /// Whether this is a non-replaced inline-level box whose inner display type is `flow`. /// <https://drafts.csswg.org/css-display-3/#inline-box> pub(crate) fn is_inline_box(&self) -> bool { - self.style.get_box().display.is_inline_flow() && - !self.base.flags.contains(FragmentFlags::IS_REPLACED) + self.style.is_inline_box(self.base.flags) } /// Whether this is a table wrapper box. diff --git a/components/layout_2020/style_ext.rs b/components/layout_2020/style_ext.rs index 92c7b1f3314..57207186f81 100644 --- a/components/layout_2020/style_ext.rs +++ b/components/layout_2020/style_ext.rs @@ -30,7 +30,7 @@ use style::values::specified::{Overflow, WillChangeBits, box_ as stylo}; use webrender_api as wr; use webrender_api::units::LayoutTransform; -use crate::dom_traversal::Contents; +use crate::dom_traversal::{Contents, NonReplacedContents}; use crate::fragment_tree::FragmentFlags; use crate::geom::{ AuOrAuto, LengthPercentageOrAuto, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalSides, @@ -83,6 +83,22 @@ impl DisplayGeneratingBox { is_list_item: false, }, } + } else if matches!( + contents, + Contents::NonReplaced(NonReplacedContents::OfTextControl) + ) { + // If it's an input or textarea, make sure the display-inside is flow-root. + // <https://html.spec.whatwg.org/multipage/#form-controls> + if let DisplayGeneratingBox::OutsideInside { outside, .. } = self { + DisplayGeneratingBox::OutsideInside { + outside: *outside, + inside: DisplayInside::FlowRoot { + is_list_item: false, + }, + } + } else { + *self + } } else { *self } @@ -334,6 +350,7 @@ pub(crate) trait ComputedValuesExt { &self, writing_mode: WritingMode, ) -> bool; + fn is_inline_box(&self, fragment_flags: FragmentFlags) -> bool; } impl ComputedValuesExt for ComputedValues { @@ -483,6 +500,12 @@ impl ComputedValuesExt for ComputedValues { LogicalSides::from_physical(&self.physical_margin(), containing_block_writing_mode) } + fn is_inline_box(&self, fragment_flags: FragmentFlags) -> bool { + self.get_box().display.is_inline_flow() && + !fragment_flags + .intersects(FragmentFlags::IS_REPLACED | FragmentFlags::IS_TEXT_CONTROL) + } + /// Returns true if this is a transformable element. fn is_transformable(&self, fragment_flags: FragmentFlags) -> bool { // "A transformable element is an element in one of these categories: @@ -494,8 +517,7 @@ impl ComputedValuesExt for ComputedValues { // elements." // <https://drafts.csswg.org/css-transforms/#transformable-element> // TODO: check for all cases listed in the above spec. - !self.get_box().display.is_inline_flow() || - fragment_flags.contains(FragmentFlags::IS_REPLACED) + !self.is_inline_box(fragment_flags) } /// Returns true if this style has a transform, or perspective property set and diff --git a/tests/wpt/meta/MANIFEST.json b/tests/wpt/meta/MANIFEST.json index 642d038d314..117f1af832b 100644 --- a/tests/wpt/meta/MANIFEST.json +++ b/tests/wpt/meta/MANIFEST.json @@ -347578,6 +347578,19 @@ {} ] ], + "text-control-flow-root.html": [ + "54f7612da34262e9f804d5fcc6af0e71dafd3af8", + [ + null, + [ + [ + "/html/rendering/widgets/text-control-flow-root-ref.html", + "==" + ] + ], + {} + ] + ], "the-select-element": { "option-add-label-quirks.html": [ "2c3c8093e253250f11a7e84a7ba89f3535d2eb20", @@ -473527,6 +473540,10 @@ "938d2659a8a8c02230c92db5b575818a5d056809", [] ], + "text-control-flow-root-ref.html": [ + "df2783540d87462a6c0f99c7d2e6bf6e7fd657a1", + [] + ], "the-select-element": { "option-checked-styling-ref.html": [ "92504a47b5961a7fa64b98b9382327b7be8e3c83", @@ -717904,6 +717921,13 @@ {} ] ], + "text-control-client-width.html": [ + "cfded6804dba6d5cc82472cae2cc276a7092741a", + [ + null, + {} + ] + ], "textarea-cols-rows.html": [ "5d02a4653ef77fbaab8b1c7aa8df933a8eabcd47", [ diff --git a/tests/wpt/tests/html/rendering/widgets/text-control-client-width.html b/tests/wpt/tests/html/rendering/widgets/text-control-client-width.html new file mode 100644 index 00000000000..cfded6804db --- /dev/null +++ b/tests/wpt/tests/html/rendering/widgets/text-control-client-width.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>text control with `display: inline` must not have 0 client width</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/rendering.html#form-controls"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<input id="input" style="display: inline;"> +<textarea id="textarea" style="display: inline;"></textarea> + +<script> +test(() => { + assert_greater_than(document.querySelector("#input").clientWidth, 0); +}, "Input with `display: inline` should have positive client width"); + +test(() => { + assert_greater_than(document.querySelector("#textarea").clientWidth, 0); +}, "Textarea with `display: inline` should have positive client width"); +</script> diff --git a/tests/wpt/tests/html/rendering/widgets/text-control-flow-root-ref.html b/tests/wpt/tests/html/rendering/widgets/text-control-flow-root-ref.html new file mode 100644 index 00000000000..df2783540d8 --- /dev/null +++ b/tests/wpt/tests/html/rendering/widgets/text-control-flow-root-ref.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> + +<input id="input1"> +<br> +<input style="transform: scale(0.5);"> +<br> +aaa<input>aaa +<br> +<textarea></textarea> +<br> +<textarea style="transform: scale(0.5);"></textarea> +<br> +aa<textarea style="transform: scale(0.5);">aa</textarea>aa diff --git a/tests/wpt/tests/html/rendering/widgets/text-control-flow-root.html b/tests/wpt/tests/html/rendering/widgets/text-control-flow-root.html new file mode 100644 index 00000000000..54f7612da34 --- /dev/null +++ b/tests/wpt/tests/html/rendering/widgets/text-control-flow-root.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<meta charset="UTF-8"> +<title>display inside of text control should always be flow-root</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/rendering.html#form-controls"> +<link rel=match href="text-control-flow-root-ref.html"> + +<input id="input1" style="display: inline;"> +<br> +<input style="display: inline; transform: scale(0.5);"> +<br> +aaa<input style="display: inline;">aaa +<br> +<textarea style="display: inline;"></textarea> +<br> +<textarea style="display: inline; transform: scale(0.5);"></textarea> +<br> +aa<textarea style="display: inline; transform: scale(0.5);">aa</textarea>aa |