mirror of
https://github.com/servo/servo.git
synced 2025-08-05 05:30:08 +01:00
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>
This commit is contained in:
parent
8dda64f14b
commit
40270cb626
8 changed files with 136 additions and 16 deletions
|
@ -116,6 +116,15 @@ where
|
||||||
},
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if matches!(
|
||||||
|
element.type_id(),
|
||||||
|
Some(LayoutNodeType::Element(
|
||||||
|
LayoutElementType::HTMLInputElement | LayoutElementType::HTMLTextAreaElement
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
flags.insert(FragmentFlags::IS_TEXT_CONTROL);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
@ -135,12 +144,15 @@ pub(super) enum Contents {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
pub(super) enum NonReplacedContents {
|
pub(super) enum NonReplacedContents {
|
||||||
/// Refers to a DOM subtree, plus `::before` and `::after` pseudo-elements.
|
/// Refers to a DOM subtree, plus `::before` and `::after` pseudo-elements.
|
||||||
OfElement,
|
OfElement,
|
||||||
/// Content of a `::before` or `::after` pseudo-element that is being generated.
|
/// Content of a `::before` or `::after` pseudo-element that is being generated.
|
||||||
/// <https://drafts.csswg.org/css2/generate.html#content>
|
/// <https://drafts.csswg.org/css2/generate.html#content>
|
||||||
OfPseudoElement(Vec<PseudoElementContentItem>),
|
OfPseudoElement(Vec<PseudoElementContentItem>),
|
||||||
|
/// Workaround for input and textarea element until we properly implement `display-inside`.
|
||||||
|
OfTextControl,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -236,8 +248,18 @@ fn traverse_element<'dom, Node>(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Display::GeneratingBox(display) => {
|
Display::GeneratingBox(display) => {
|
||||||
let contents =
|
let contents = if let Some(replaced) = replaced {
|
||||||
replaced.map_or(NonReplacedContents::OfElement.into(), Contents::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 display = display.used_value_for_contents(&contents);
|
||||||
let box_slot = element.element_box_slot();
|
let box_slot = element.element_box_slot();
|
||||||
let info = NodeAndStyleInfo::new(element, style);
|
let info = NodeAndStyleInfo::new(element, style);
|
||||||
|
@ -366,7 +388,9 @@ impl NonReplacedContents {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
match self {
|
match self {
|
||||||
NonReplacedContents::OfElement => traverse_children_of(node, context, handler),
|
NonReplacedContents::OfElement | NonReplacedContents::OfTextControl => {
|
||||||
|
traverse_children_of(node, context, handler)
|
||||||
|
},
|
||||||
NonReplacedContents::OfPseudoElement(items) => {
|
NonReplacedContents::OfPseudoElement(items) => {
|
||||||
traverse_pseudo_element_contents(info, context, handler, items)
|
traverse_pseudo_element_contents(info, context, handler, items)
|
||||||
},
|
},
|
||||||
|
|
|
@ -75,31 +75,33 @@ impl From<BaseFragmentInfo> for BaseFragment {
|
||||||
bitflags! {
|
bitflags! {
|
||||||
/// Flags used to track various information about a DOM node during layout.
|
/// Flags used to track various information about a DOM node during layout.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[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.
|
/// 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;
|
const IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT = 1 << 0;
|
||||||
/// Whether or not the node that created this Fragment is a `<br>` element.
|
/// Whether or not the node that created this Fragment is a `<br>` element.
|
||||||
const IS_BR_ELEMENT = 1 << 1;
|
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.
|
/// 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
|
/// Whether or not this Fragment was created to contain a replaced element or is
|
||||||
/// a replaced element.
|
/// 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
|
/// Whether or not the node that created was a `<table>`, `<th>` or
|
||||||
/// `<td>` element. Note that this does *not* include elements with
|
/// `<td>` element. Note that this does *not* include elements with
|
||||||
/// `display: table` or `display: table-cell`.
|
/// `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
|
/// Whether or not this Fragment was created to contain a list item marker
|
||||||
/// with a used value of `list-style-position: outside`.
|
/// 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
|
/// 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
|
/// 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.
|
/// 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
|
/// 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
|
/// and the fragment can be a flex item. This flag is used to cache items during flex
|
||||||
/// layout.
|
/// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ use style::properties::ComputedValues;
|
||||||
|
|
||||||
use super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment};
|
use super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment};
|
||||||
use crate::formatting_contexts::Baselines;
|
use crate::formatting_contexts::Baselines;
|
||||||
use crate::fragment_tree::FragmentFlags;
|
|
||||||
use crate::geom::{
|
use crate::geom::{
|
||||||
AuOrAuto, LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides, ToLogical,
|
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`.
|
/// Whether this is a non-replaced inline-level box whose inner display type is `flow`.
|
||||||
/// <https://drafts.csswg.org/css-display-3/#inline-box>
|
/// <https://drafts.csswg.org/css-display-3/#inline-box>
|
||||||
pub(crate) fn is_inline_box(&self) -> bool {
|
pub(crate) fn is_inline_box(&self) -> bool {
|
||||||
self.style.get_box().display.is_inline_flow() &&
|
self.style.is_inline_box(self.base.flags)
|
||||||
!self.base.flags.contains(FragmentFlags::IS_REPLACED)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this is a table wrapper box.
|
/// Whether this is a table wrapper box.
|
||||||
|
|
|
@ -30,7 +30,7 @@ use style::values::specified::{Overflow, WillChangeBits, box_ as stylo};
|
||||||
use webrender_api as wr;
|
use webrender_api as wr;
|
||||||
use webrender_api::units::LayoutTransform;
|
use webrender_api::units::LayoutTransform;
|
||||||
|
|
||||||
use crate::dom_traversal::Contents;
|
use crate::dom_traversal::{Contents, NonReplacedContents};
|
||||||
use crate::fragment_tree::FragmentFlags;
|
use crate::fragment_tree::FragmentFlags;
|
||||||
use crate::geom::{
|
use crate::geom::{
|
||||||
AuOrAuto, LengthPercentageOrAuto, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalSides,
|
AuOrAuto, LengthPercentageOrAuto, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalSides,
|
||||||
|
@ -83,6 +83,22 @@ impl DisplayGeneratingBox {
|
||||||
is_list_item: false,
|
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 {
|
} else {
|
||||||
*self
|
*self
|
||||||
}
|
}
|
||||||
|
@ -334,6 +350,7 @@ pub(crate) trait ComputedValuesExt {
|
||||||
&self,
|
&self,
|
||||||
writing_mode: WritingMode,
|
writing_mode: WritingMode,
|
||||||
) -> bool;
|
) -> bool;
|
||||||
|
fn is_inline_box(&self, fragment_flags: FragmentFlags) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComputedValuesExt for ComputedValues {
|
impl ComputedValuesExt for ComputedValues {
|
||||||
|
@ -483,6 +500,12 @@ impl ComputedValuesExt for ComputedValues {
|
||||||
LogicalSides::from_physical(&self.physical_margin(), containing_block_writing_mode)
|
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.
|
/// Returns true if this is a transformable element.
|
||||||
fn is_transformable(&self, fragment_flags: FragmentFlags) -> bool {
|
fn is_transformable(&self, fragment_flags: FragmentFlags) -> bool {
|
||||||
// "A transformable element is an element in one of these categories:
|
// "A transformable element is an element in one of these categories:
|
||||||
|
@ -494,8 +517,7 @@ impl ComputedValuesExt for ComputedValues {
|
||||||
// elements."
|
// elements."
|
||||||
// <https://drafts.csswg.org/css-transforms/#transformable-element>
|
// <https://drafts.csswg.org/css-transforms/#transformable-element>
|
||||||
// TODO: check for all cases listed in the above spec.
|
// TODO: check for all cases listed in the above spec.
|
||||||
!self.get_box().display.is_inline_flow() ||
|
!self.is_inline_box(fragment_flags)
|
||||||
fragment_flags.contains(FragmentFlags::IS_REPLACED)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if this style has a transform, or perspective property set and
|
/// Returns true if this style has a transform, or perspective property set and
|
||||||
|
|
24
tests/wpt/meta/MANIFEST.json
vendored
24
tests/wpt/meta/MANIFEST.json
vendored
|
@ -347578,6 +347578,19 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
"text-control-flow-root.html": [
|
||||||
|
"54f7612da34262e9f804d5fcc6af0e71dafd3af8",
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"/html/rendering/widgets/text-control-flow-root-ref.html",
|
||||||
|
"=="
|
||||||
|
]
|
||||||
|
],
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"the-select-element": {
|
"the-select-element": {
|
||||||
"option-add-label-quirks.html": [
|
"option-add-label-quirks.html": [
|
||||||
"2c3c8093e253250f11a7e84a7ba89f3535d2eb20",
|
"2c3c8093e253250f11a7e84a7ba89f3535d2eb20",
|
||||||
|
@ -473527,6 +473540,10 @@
|
||||||
"938d2659a8a8c02230c92db5b575818a5d056809",
|
"938d2659a8a8c02230c92db5b575818a5d056809",
|
||||||
[]
|
[]
|
||||||
],
|
],
|
||||||
|
"text-control-flow-root-ref.html": [
|
||||||
|
"df2783540d87462a6c0f99c7d2e6bf6e7fd657a1",
|
||||||
|
[]
|
||||||
|
],
|
||||||
"the-select-element": {
|
"the-select-element": {
|
||||||
"option-checked-styling-ref.html": [
|
"option-checked-styling-ref.html": [
|
||||||
"92504a47b5961a7fa64b98b9382327b7be8e3c83",
|
"92504a47b5961a7fa64b98b9382327b7be8e3c83",
|
||||||
|
@ -717904,6 +717921,13 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
"text-control-client-width.html": [
|
||||||
|
"cfded6804dba6d5cc82472cae2cc276a7092741a",
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"textarea-cols-rows.html": [
|
"textarea-cols-rows.html": [
|
||||||
"5d02a4653ef77fbaab8b1c7aa8df933a8eabcd47",
|
"5d02a4653ef77fbaab8b1c7aa8df933a8eabcd47",
|
||||||
[
|
[
|
||||||
|
|
19
tests/wpt/tests/html/rendering/widgets/text-control-client-width.html
vendored
Normal file
19
tests/wpt/tests/html/rendering/widgets/text-control-client-width.html
vendored
Normal file
|
@ -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>
|
14
tests/wpt/tests/html/rendering/widgets/text-control-flow-root-ref.html
vendored
Normal file
14
tests/wpt/tests/html/rendering/widgets/text-control-flow-root-ref.html
vendored
Normal file
|
@ -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
|
17
tests/wpt/tests/html/rendering/widgets/text-control-flow-root.html
vendored
Normal file
17
tests/wpt/tests/html/rendering/widgets/text-control-flow-root.html
vendored
Normal file
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue