aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKenzie Raditya Tirtarahardja <kenzieradityatirtarahardja18@gmail.com>2025-03-23 08:45:59 +0800
committerGitHub <noreply@github.com>2025-03-23 00:45:59 +0000
commit40270cb6269f3f2d054bcfe51c69c8c545447f9a (patch)
tree9992a0aa8a6f1eac75ff97d1c0b11ca1cf4281a6
parent8dda64f14bb0214f773e0135dc7df190f7a04e1b (diff)
downloadservo-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>
-rw-r--r--components/layout_2020/dom_traversal.rs30
-rw-r--r--components/layout_2020/fragment_tree/base_fragment.rs16
-rw-r--r--components/layout_2020/fragment_tree/box_fragment.rs4
-rw-r--r--components/layout_2020/style_ext.rs28
-rw-r--r--tests/wpt/meta/MANIFEST.json24
-rw-r--r--tests/wpt/tests/html/rendering/widgets/text-control-client-width.html19
-rw-r--r--tests/wpt/tests/html/rendering/widgets/text-control-flow-root-ref.html14
-rw-r--r--tests/wpt/tests/html/rendering/widgets/text-control-flow-root.html17
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