mirror of
https://github.com/servo/servo.git
synced 2025-06-04 07:35:36 +00:00
Implement Input type=text
UA Shadow DOM (#37065)
Implement Shadow Tree construction for input `type=text`, adding a text control inner editor container and placeholder container. Subsequently, due to the changes of the DOM tree structure, the changes will add a new NodeFlag `IS_TEXT_CONTROL_INNER_EDITOR` to handle the following cases. - If a mouse click button event hits a text control inner editor, it will redirect the focus target to its shadow host. - In text run's construction, the text control inner editor container queries the selection from its shadow host. This is later used to resolve caret and selection painting in the display list. This will be the first step of fixing input `type=text` and other single-line text input element widgets. Such as, implementing `::placeholder` selector. Testing: Existing WPT test and new Servo specific appearance WPT. Fixes: #36307 --------- Signed-off-by: stevennovaryo <steven.novaryo@gmail.com>
This commit is contained in:
parent
578c52fe2b
commit
5580704438
24 changed files with 635 additions and 36 deletions
|
@ -59,8 +59,14 @@ impl<'dom> NodeAndStyleInfo<'dom> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Whether this is a container for the editable text within a single-line text input.
|
||||
/// This is used to solve the special case of line height for a text editor.
|
||||
/// <https://html.spec.whatwg.org/multipage/#the-input-element-as-a-text-entry-widget>
|
||||
// FIXME(stevennovaryo): Now, this would also refer to HTMLInputElement, to handle input
|
||||
// elements without shadow DOM.
|
||||
pub(crate) fn is_single_line_text_input(&self) -> bool {
|
||||
self.node.type_id() == LayoutNodeType::Element(LayoutElementType::HTMLInputElement)
|
||||
self.node.type_id() == LayoutNodeType::Element(LayoutElementType::HTMLInputElement) ||
|
||||
self.node.is_text_control_inner_editor()
|
||||
}
|
||||
|
||||
pub(crate) fn pseudo(
|
||||
|
|
|
@ -1556,11 +1556,14 @@ impl Document {
|
|||
return;
|
||||
}
|
||||
|
||||
// For a node within a text input UA shadow DOM, delegate the focus target into its shadow host.
|
||||
// TODO: This focus delegation should be done with shadow DOM delegateFocus attribute.
|
||||
let target_el = el.find_focusable_shadow_host_if_necessary();
|
||||
|
||||
self.begin_focus_transaction();
|
||||
// Try to focus `el`. If it's not focusable, focus the document
|
||||
// instead.
|
||||
// Try to focus `el`. If it's not focusable, focus the document instead.
|
||||
self.request_focus(None, FocusInitiator::Local, can_gc);
|
||||
self.request_focus(Some(&*el), FocusInitiator::Local, can_gc);
|
||||
self.request_focus(target_el.as_deref(), FocusInitiator::Local, can_gc);
|
||||
}
|
||||
|
||||
let dom_event = DomRoot::upcast::<Event>(MouseEvent::for_platform_mouse_event(
|
||||
|
|
|
@ -1662,6 +1662,27 @@ impl Element {
|
|||
)
|
||||
}
|
||||
|
||||
/// Returns the focusable shadow host if this is a text control inner editor.
|
||||
/// This is a workaround for the focus delegation of shadow DOM and should be
|
||||
/// used only to delegate focusable inner editor of [HTMLInputElement] and
|
||||
/// [HTMLTextAreaElement].
|
||||
pub(crate) fn find_focusable_shadow_host_if_necessary(&self) -> Option<DomRoot<Element>> {
|
||||
if self.is_focusable_area() {
|
||||
Some(DomRoot::from_ref(self))
|
||||
} else if self.upcast::<Node>().is_text_control_inner_editor() {
|
||||
let containing_shadow_host = self.containing_shadow_root().map(|root| root.Host());
|
||||
assert!(
|
||||
containing_shadow_host
|
||||
.as_ref()
|
||||
.is_some_and(|e| e.is_focusable_area()),
|
||||
"Containing shadow host is not focusable"
|
||||
);
|
||||
containing_shadow_host
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_actually_disabled(&self) -> bool {
|
||||
let node = self.upcast::<Node>();
|
||||
match node.type_id() {
|
||||
|
|
|
@ -101,6 +101,29 @@ const DEFAULT_RESET_VALUE: &str = "Reset";
|
|||
const PASSWORD_REPLACEMENT_CHAR: char = '●';
|
||||
const DEFAULT_FILE_INPUT_VALUE: &str = "No file chosen";
|
||||
|
||||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||||
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
|
||||
/// Contains reference to text control inner editor and placeholder container element in the UA
|
||||
/// shadow tree for `<input type=text>`. The following is the structure of the shadow tree.
|
||||
///
|
||||
/// ```
|
||||
/// <input type="text">
|
||||
/// #shadow-root
|
||||
/// <div id="inner-container">
|
||||
/// <div id="input-editor"></div>
|
||||
/// <div id="input-placeholder"></div>
|
||||
/// </div>
|
||||
/// </input>
|
||||
/// ```
|
||||
// TODO(stevennovaryo): We are trying to use CSS to mimic Chrome and Firefox's layout for the <input> element.
|
||||
// But, this could be slower in performance and does have some discrepancies. For example,
|
||||
// they would try to vertically align <input> text baseline with the baseline of other
|
||||
// TextNode within an inline flow. Another example is the horizontal scroll.
|
||||
struct InputTypeTextShadowTree {
|
||||
text_container: Dom<HTMLDivElement>,
|
||||
placeholder_container: Dom<HTMLDivElement>,
|
||||
}
|
||||
|
||||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||||
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
|
||||
/// Contains references to the elements in the shadow tree for `<input type=range>`.
|
||||
|
@ -111,10 +134,49 @@ struct InputTypeColorShadowTree {
|
|||
color_value: Dom<HTMLDivElement>,
|
||||
}
|
||||
|
||||
// FIXME: These styles should be inside UA stylesheet, but it is not possible without internal pseudo element support.
|
||||
const TEXT_TREE_STYLE: &str = "
|
||||
#input-editor::selection {
|
||||
background: rgba(176, 214, 255, 1.0);
|
||||
color: black;
|
||||
}
|
||||
|
||||
:host:not(:placeholder-shown) #input-placeholder {
|
||||
visibility: hidden !important
|
||||
}
|
||||
|
||||
#input-editor {
|
||||
overflow-wrap: normal;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
#input-container {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#input-editor, #input-placeholder {
|
||||
white-space: pre;
|
||||
margin-block: auto !important;
|
||||
inset-block: 0 !important;
|
||||
block-size: fit-content !important;
|
||||
}
|
||||
|
||||
#input-placeholder {
|
||||
overflow: hidden !important;
|
||||
position: absolute !important;
|
||||
color: grey;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
";
|
||||
|
||||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||||
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
|
||||
#[non_exhaustive]
|
||||
enum ShadowTree {
|
||||
Text(InputTypeTextShadowTree),
|
||||
Color(InputTypeColorShadowTree),
|
||||
// TODO: Add shadow trees for other input types (range etc) here
|
||||
}
|
||||
|
@ -1071,7 +1133,7 @@ impl HTMLInputElement {
|
|||
ShadowRootMode::Closed,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
SlotAssignmentMode::Manual,
|
||||
can_gc,
|
||||
)
|
||||
|
@ -1079,6 +1141,92 @@ impl HTMLInputElement {
|
|||
})
|
||||
}
|
||||
|
||||
fn create_text_shadow_tree(&self, can_gc: CanGc) {
|
||||
let document = self.owner_document();
|
||||
let shadow_root = self.shadow_root(can_gc);
|
||||
Node::replace_all(None, shadow_root.upcast::<Node>(), can_gc);
|
||||
|
||||
let inner_container =
|
||||
HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc);
|
||||
inner_container
|
||||
.upcast::<Element>()
|
||||
.SetId(DOMString::from("input-container"), can_gc);
|
||||
shadow_root
|
||||
.upcast::<Node>()
|
||||
.AppendChild(inner_container.upcast::<Node>(), can_gc)
|
||||
.unwrap();
|
||||
|
||||
let placeholder_container =
|
||||
HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc);
|
||||
placeholder_container
|
||||
.upcast::<Element>()
|
||||
.SetId(DOMString::from("input-placeholder"), can_gc);
|
||||
inner_container
|
||||
.upcast::<Node>()
|
||||
.AppendChild(placeholder_container.upcast::<Node>(), can_gc)
|
||||
.unwrap();
|
||||
|
||||
let text_container = HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc);
|
||||
text_container
|
||||
.upcast::<Element>()
|
||||
.SetId(DOMString::from("input-editor"), can_gc);
|
||||
text_container
|
||||
.upcast::<Node>()
|
||||
.set_text_control_inner_editor();
|
||||
inner_container
|
||||
.upcast::<Node>()
|
||||
.AppendChild(text_container.upcast::<Node>(), can_gc)
|
||||
.unwrap();
|
||||
|
||||
let style = HTMLStyleElement::new(
|
||||
local_name!("style"),
|
||||
None,
|
||||
&document,
|
||||
None,
|
||||
ElementCreator::ScriptCreated,
|
||||
can_gc,
|
||||
);
|
||||
// TODO(stevennovaryo): Either use UA stylesheet with internal pseudo element or preemptively parse
|
||||
// the stylesheet to reduce the costly operation and avoid CSP related error.
|
||||
style
|
||||
.upcast::<Node>()
|
||||
.SetTextContent(Some(DOMString::from(TEXT_TREE_STYLE)), can_gc);
|
||||
shadow_root
|
||||
.upcast::<Node>()
|
||||
.AppendChild(style.upcast::<Node>(), can_gc)
|
||||
.unwrap();
|
||||
|
||||
let _ = self
|
||||
.shadow_tree
|
||||
.borrow_mut()
|
||||
.insert(ShadowTree::Text(InputTypeTextShadowTree {
|
||||
text_container: text_container.as_traced(),
|
||||
placeholder_container: placeholder_container.as_traced(),
|
||||
}));
|
||||
}
|
||||
|
||||
fn text_shadow_tree(&self, can_gc: CanGc) -> Ref<InputTypeTextShadowTree> {
|
||||
let has_text_shadow_tree = self
|
||||
.shadow_tree
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.is_some_and(|shadow_tree| matches!(shadow_tree, ShadowTree::Text(_)));
|
||||
if !has_text_shadow_tree {
|
||||
self.create_text_shadow_tree(can_gc);
|
||||
}
|
||||
|
||||
let shadow_tree = self.shadow_tree.borrow();
|
||||
Ref::filter_map(shadow_tree, |shadow_tree| {
|
||||
let shadow_tree = shadow_tree.as_ref()?;
|
||||
match shadow_tree {
|
||||
ShadowTree::Text(text_tree) => Some(text_tree),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.ok()
|
||||
.expect("UA shadow tree was not created")
|
||||
}
|
||||
|
||||
fn create_color_shadow_tree(&self, can_gc: CanGc) {
|
||||
let document = self.owner_document();
|
||||
let shadow_root = self.shadow_root(can_gc);
|
||||
|
@ -1136,27 +1284,53 @@ impl HTMLInputElement {
|
|||
let shadow_tree = self.shadow_tree.borrow();
|
||||
Ref::filter_map(shadow_tree, |shadow_tree| {
|
||||
let shadow_tree = shadow_tree.as_ref()?;
|
||||
let ShadowTree::Color(color_tree) = shadow_tree;
|
||||
Some(color_tree)
|
||||
match shadow_tree {
|
||||
ShadowTree::Color(color_tree) => Some(color_tree),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.ok()
|
||||
.expect("UA shadow tree was not created")
|
||||
}
|
||||
|
||||
fn update_shadow_tree_if_needed(&self, can_gc: CanGc) {
|
||||
if self.input_type() == InputType::Color {
|
||||
let color_shadow_tree = self.color_shadow_tree(can_gc);
|
||||
let mut value = self.Value();
|
||||
if value.str().is_valid_simple_color_string() {
|
||||
value.make_ascii_lowercase();
|
||||
} else {
|
||||
value = DOMString::from("#000000");
|
||||
}
|
||||
let style = format!("background-color: {value}");
|
||||
color_shadow_tree
|
||||
.color_value
|
||||
.upcast::<Element>()
|
||||
.set_string_attribute(&local_name!("style"), style.into(), can_gc);
|
||||
match self.input_type() {
|
||||
InputType::Text => {
|
||||
let text_shadow_tree = self.text_shadow_tree(can_gc);
|
||||
let value = self.Value();
|
||||
|
||||
// The addition of zero-width space here forces the text input to have an inline formatting
|
||||
// context that might otherwise be trimmed if there's no text. This is important to ensure
|
||||
// that the input element is at least as tall as the line gap of the caret:
|
||||
// <https://drafts.csswg.org/css-ui/#element-with-default-preferred-size>.
|
||||
//
|
||||
// This is also used to ensure that the caret will still be rendered when the input is empty.
|
||||
// TODO: Is there a less hacky way to do this?
|
||||
let value_text = match value.is_empty() {
|
||||
false => value,
|
||||
true => "\u{200B}".into(),
|
||||
};
|
||||
|
||||
text_shadow_tree
|
||||
.text_container
|
||||
.upcast::<Node>()
|
||||
.SetTextContent(Some(value_text), can_gc);
|
||||
},
|
||||
InputType::Color => {
|
||||
let color_shadow_tree = self.color_shadow_tree(can_gc);
|
||||
let mut value = self.Value();
|
||||
if value.str().is_valid_simple_color_string() {
|
||||
value.make_ascii_lowercase();
|
||||
} else {
|
||||
value = DOMString::from("#000000");
|
||||
}
|
||||
let style = format!("background-color: {value}");
|
||||
color_shadow_tree
|
||||
.color_value
|
||||
.upcast::<Element>()
|
||||
.set_string_attribute(&local_name!("style"), style.into(), can_gc);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1465,22 +1639,29 @@ impl HTMLInputElementMethods<crate::DomTypeHolder> for HTMLInputElement {
|
|||
fn SetValue(&self, mut value: DOMString, can_gc: CanGc) -> ErrorResult {
|
||||
match self.value_mode() {
|
||||
ValueMode::Value => {
|
||||
// Step 3.
|
||||
self.value_dirty.set(true);
|
||||
{
|
||||
// Step 3.
|
||||
self.value_dirty.set(true);
|
||||
|
||||
// Step 4.
|
||||
self.sanitize_value(&mut value);
|
||||
// Step 4.
|
||||
self.sanitize_value(&mut value);
|
||||
|
||||
let mut textinput = self.textinput.borrow_mut();
|
||||
|
||||
// Step 5.
|
||||
if *textinput.single_line_content() != value {
|
||||
// Steps 1-2
|
||||
textinput.set_content(value);
|
||||
let mut textinput = self.textinput.borrow_mut();
|
||||
|
||||
// Step 5.
|
||||
textinput.clear_selection_to_limit(Direction::Forward);
|
||||
if *textinput.single_line_content() != value {
|
||||
// Steps 1-2
|
||||
textinput.set_content(value);
|
||||
|
||||
// Step 5.
|
||||
textinput.clear_selection_to_limit(Direction::Forward);
|
||||
}
|
||||
}
|
||||
|
||||
// Additionaly, update the placeholder shown state. This is
|
||||
// normally being done in the attributed mutated. And, being
|
||||
// done in another scope to prevent borrow checker issues.
|
||||
self.update_placeholder_shown_state();
|
||||
},
|
||||
ValueMode::Default | ValueMode::DefaultOn => {
|
||||
self.upcast::<Element>()
|
||||
|
@ -2063,6 +2244,19 @@ impl HTMLInputElement {
|
|||
el.set_placeholder_shown_state(has_placeholder && !has_value);
|
||||
}
|
||||
|
||||
// Update the placeholder text in the text shadow tree.
|
||||
// To increase the performance, we would only do this when it is necessary.
|
||||
fn update_text_shadow_tree_placeholder(&self, can_gc: CanGc) {
|
||||
if self.input_type() != InputType::Text {
|
||||
return;
|
||||
}
|
||||
|
||||
self.text_shadow_tree(can_gc)
|
||||
.placeholder_container
|
||||
.upcast::<Node>()
|
||||
.SetTextContent(Some(self.placeholder.borrow().clone()), can_gc);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#file-upload-state-(type=file)
|
||||
// Select files by invoking UI or by passed in argument
|
||||
fn select_files(&self, opt_test_paths: Option<Vec<DOMString>>, can_gc: CanGc) {
|
||||
|
@ -2688,8 +2882,11 @@ impl VirtualMethods for HTMLInputElement {
|
|||
},
|
||||
}
|
||||
|
||||
self.update_text_shadow_tree_placeholder(can_gc);
|
||||
self.update_placeholder_shown_state();
|
||||
},
|
||||
// FIXME(stevennovaryo): This is only reachable by Default and DefaultOn value mode. While others
|
||||
// are being handled in [Self::SetValue]. Should we merge this two together?
|
||||
local_name!("value") if !self.value_dirty.get() => {
|
||||
let value = mutation.new_value(attr).map(|value| (**value).to_owned());
|
||||
let mut value = value.map_or(DOMString::new(), DOMString::from);
|
||||
|
@ -2738,6 +2935,7 @@ impl VirtualMethods for HTMLInputElement {
|
|||
.extend(attr.value().chars().filter(|&c| c != '\n' && c != '\r'));
|
||||
}
|
||||
}
|
||||
self.update_text_shadow_tree_placeholder(can_gc);
|
||||
self.update_placeholder_shown_state();
|
||||
},
|
||||
local_name!("readonly") => {
|
||||
|
|
|
@ -230,6 +230,10 @@ bitflags! {
|
|||
/// Whether this node has a weird parser insertion mode. i.e whether setting innerHTML
|
||||
/// needs extra work or not
|
||||
const HAS_WEIRD_PARSER_INSERTION_MODE = 1 << 11;
|
||||
|
||||
/// Whether this node serves as the text container for editable content of
|
||||
/// <input> or <textarea> element.
|
||||
const IS_TEXT_CONTROL_INNER_EDITOR = 1 << 12;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -697,6 +701,16 @@ impl Node {
|
|||
self.flags.get().contains(NodeFlags::IS_CONNECTED)
|
||||
}
|
||||
|
||||
pub(crate) fn set_text_control_inner_editor(&self) {
|
||||
self.set_flag(NodeFlags::IS_TEXT_CONTROL_INNER_EDITOR, true)
|
||||
}
|
||||
|
||||
pub(crate) fn is_text_control_inner_editor(&self) -> bool {
|
||||
self.flags
|
||||
.get()
|
||||
.contains(NodeFlags::IS_TEXT_CONTROL_INNER_EDITOR)
|
||||
}
|
||||
|
||||
/// Returns the type ID of this node.
|
||||
pub(crate) fn type_id(&self) -> NodeTypeId {
|
||||
match *self.eventtarget.type_id() {
|
||||
|
@ -1579,6 +1593,7 @@ pub(crate) trait LayoutNodeHelpers<'dom> {
|
|||
fn assigned_slot_for_layout(self) -> Option<LayoutDom<'dom, HTMLSlotElement>>;
|
||||
|
||||
fn is_element_for_layout(&self) -> bool;
|
||||
fn is_text_node_for_layout(&self) -> bool;
|
||||
unsafe fn get_flag(self, flag: NodeFlags) -> bool;
|
||||
unsafe fn set_flag(self, flag: NodeFlags, value: bool);
|
||||
|
||||
|
@ -1614,6 +1629,9 @@ pub(crate) trait LayoutNodeHelpers<'dom> {
|
|||
|
||||
/// Whether this element is a `<input>` rendered as text or a `<textarea>`.
|
||||
fn is_text_input(&self) -> bool;
|
||||
|
||||
/// Whether this element serve as a container of editable text for a text input.
|
||||
fn is_text_control_inner_editor(&self) -> bool;
|
||||
fn text_content(self) -> Cow<'dom, str>;
|
||||
fn selection(self) -> Option<Range<usize>>;
|
||||
fn image_url(self) -> Option<ServoUrl>;
|
||||
|
@ -1646,6 +1664,11 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
|
|||
(*self).is::<Element>()
|
||||
}
|
||||
|
||||
fn is_text_node_for_layout(&self) -> bool {
|
||||
self.type_id_for_layout() ==
|
||||
NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn composed_parent_node_ref(self) -> Option<LayoutDom<'dom, Node>> {
|
||||
let parent = self.parent_node_ref();
|
||||
|
@ -1787,8 +1810,8 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
|
|||
{
|
||||
let input = self.unsafe_get().downcast::<HTMLInputElement>().unwrap();
|
||||
|
||||
// FIXME: All the non-color input types currently render as text
|
||||
input.input_type() != InputType::Color
|
||||
// FIXME: All the non-color and non-text input types currently render as text
|
||||
!matches!(input.input_type(), InputType::Color | InputType::Text)
|
||||
} else {
|
||||
type_id ==
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(
|
||||
|
@ -1797,6 +1820,10 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_text_control_inner_editor(&self) -> bool {
|
||||
self.unsafe_get().is_text_control_inner_editor()
|
||||
}
|
||||
|
||||
fn text_content(self) -> Cow<'dom, str> {
|
||||
if let Some(text) = self.downcast::<Text>() {
|
||||
return text.upcast().data_for_layout().into();
|
||||
|
@ -1814,6 +1841,25 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
|
|||
}
|
||||
|
||||
fn selection(self) -> Option<Range<usize>> {
|
||||
// This node is a text node of a text control inner editor in a <input> or <textarea> element.
|
||||
// So we should find those corresponding element, and get its selection.
|
||||
if self.is_text_node_for_layout() &&
|
||||
self.parent_node_ref()
|
||||
.is_some_and(|parent| parent.is_text_control_inner_editor())
|
||||
{
|
||||
let shadow_root = self.containing_shadow_root_for_layout();
|
||||
if let Some(containing_shadow_host) = shadow_root.map(|root| root.get_host_for_layout())
|
||||
{
|
||||
if let Some(area) = containing_shadow_host.downcast::<HTMLTextAreaElement>() {
|
||||
return area.selection_for_layout();
|
||||
}
|
||||
|
||||
if let Some(input) = containing_shadow_host.downcast::<HTMLInputElement>() {
|
||||
return input.selection_for_layout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
|
||||
return area.selection_for_layout();
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ use style::selector_parser::PseudoElement;
|
|||
use super::{
|
||||
ServoLayoutDocument, ServoLayoutElement, ServoShadowRoot, ServoThreadSafeLayoutElement,
|
||||
};
|
||||
use crate::dom::bindings::inheritance::{CharacterDataTypeId, NodeTypeId, TextTypeId};
|
||||
use crate::dom::bindings::inheritance::NodeTypeId;
|
||||
use crate::dom::bindings::root::LayoutDom;
|
||||
use crate::dom::element::{Element, LayoutElementHelpers};
|
||||
use crate::dom::node::{LayoutNodeHelpers, Node, NodeFlags, NodeTypeIdWrapper};
|
||||
|
@ -100,6 +100,10 @@ impl<'dom> ServoLayoutNode<'dom> {
|
|||
pub fn is_text_input(&self) -> bool {
|
||||
self.node.is_text_input()
|
||||
}
|
||||
|
||||
pub fn is_text_control_inner_editor(&self) -> bool {
|
||||
self.node.is_text_control_inner_editor()
|
||||
}
|
||||
}
|
||||
|
||||
impl style::dom::NodeInfo for ServoLayoutNode<'_> {
|
||||
|
@ -108,8 +112,7 @@ impl style::dom::NodeInfo for ServoLayoutNode<'_> {
|
|||
}
|
||||
|
||||
fn is_text_node(&self) -> bool {
|
||||
self.script_type_id() ==
|
||||
NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text))
|
||||
self.node.is_text_node_for_layout()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[form-action-src-default-ignored.sub.html]
|
||||
[Expecting logs: ["PASS","TEST COMPLETE"\]]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[spelling-markers-009.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[spelling-markers-010.html]
|
||||
expected: FAIL
|
112
tests/wpt/mozilla/meta/MANIFEST.json
vendored
112
tests/wpt/mozilla/meta/MANIFEST.json
vendored
|
@ -91,6 +91,86 @@
|
|||
}
|
||||
},
|
||||
"reftest": {
|
||||
"appearance": {
|
||||
"input-text-definite-width.html": [
|
||||
"fda46f8af9c14cef3911ec809054624204848b9d",
|
||||
[
|
||||
"appearance/input-text-definite-width.html",
|
||||
[
|
||||
[
|
||||
"/_mozilla/appearance/input-text-definite-width-ref.html",
|
||||
"=="
|
||||
]
|
||||
],
|
||||
{}
|
||||
]
|
||||
],
|
||||
"input-text-empty.html": [
|
||||
"bd5f5f5a21ec0ce028922a6764de41dc904a1eb1",
|
||||
[
|
||||
"appearance/input-text-empty.html",
|
||||
[
|
||||
[
|
||||
"/_mozilla/appearance/input-text-empty-ref.html",
|
||||
"=="
|
||||
]
|
||||
],
|
||||
{}
|
||||
]
|
||||
],
|
||||
"input-text-nonempty-placeholder.html": [
|
||||
"e075663cb6ae708b313b3cd5cd69f78c51b4bc1f",
|
||||
[
|
||||
"appearance/input-text-nonempty-placeholder.html",
|
||||
[
|
||||
[
|
||||
"/_mozilla/appearance/input-text-nonempty-placeholder-ref.html",
|
||||
"=="
|
||||
]
|
||||
],
|
||||
{}
|
||||
]
|
||||
],
|
||||
"input-text-overflow.html": [
|
||||
"52db07c0f0274d2b7b086d7017982145c25918da",
|
||||
[
|
||||
"appearance/input-text-overflow.html",
|
||||
[
|
||||
[
|
||||
"/_mozilla/appearance/input-text-overflow-ref.html",
|
||||
"=="
|
||||
]
|
||||
],
|
||||
{}
|
||||
]
|
||||
],
|
||||
"input-text-placeholder-overflow.html": [
|
||||
"c4d77ae2a22a5b7972f2798b8ca78742b81bacc4",
|
||||
[
|
||||
"appearance/input-text-placeholder-overflow.html",
|
||||
[
|
||||
[
|
||||
"/_mozilla/appearance/input-text-placeholder-overflow-ref.html",
|
||||
"=="
|
||||
]
|
||||
],
|
||||
{}
|
||||
]
|
||||
],
|
||||
"input-text-placeholder.html": [
|
||||
"d75acade78038b14529135b1d63c0ac5a168a87b",
|
||||
[
|
||||
"appearance/input-text-placeholder.html",
|
||||
[
|
||||
[
|
||||
"/_mozilla/appearance/input-text-placeholder-ref.html",
|
||||
"=="
|
||||
]
|
||||
],
|
||||
{}
|
||||
]
|
||||
]
|
||||
},
|
||||
"css": {
|
||||
"abs-overflow-stackingcontext.html": [
|
||||
"264df01aa64e0abe9ea3a75e57452c27d53a904f",
|
||||
|
@ -8075,6 +8155,38 @@
|
|||
"b485d435a63ada28eabe976b49a8a580725e7508",
|
||||
[]
|
||||
],
|
||||
"appearance": {
|
||||
"input-text-definite-width-ref.html": [
|
||||
"86f7937755750261ed3b06dfe11e78a251b9d175",
|
||||
[]
|
||||
],
|
||||
"input-text-empty-ref.html": [
|
||||
"437c9988a13e094d870f67c8de0dd0becdeece76",
|
||||
[]
|
||||
],
|
||||
"input-text-nonempty-placeholder-ref.html": [
|
||||
"5415dfb2a4a88dc3bfed6ad04e23f288534351e4",
|
||||
[]
|
||||
],
|
||||
"input-text-overflow-ref.html": [
|
||||
"4cece657a2a09cfe3f1d91d49f0c9d76f5714516",
|
||||
[]
|
||||
],
|
||||
"input-text-placeholder-overflow-ref.html": [
|
||||
"0cccfff638c0d8687a3582310c73233b7d883b1a",
|
||||
[]
|
||||
],
|
||||
"input-text-placeholder-ref.html": [
|
||||
"fa5b60bdabdf2b9b818ebe66bfc7f2711173b88b",
|
||||
[]
|
||||
],
|
||||
"supports": {
|
||||
"input-text-ref.css": [
|
||||
"8cf00d493138285e50aa510273abae98c099ae8b",
|
||||
[]
|
||||
]
|
||||
}
|
||||
},
|
||||
"bluetooth": {
|
||||
"bluetooth-helpers.js": [
|
||||
"16a280cca298bcaa5796b36b48d331bfd15baae8",
|
||||
|
|
2
tests/wpt/mozilla/meta/css/input_placeholder.html.ini
vendored
Normal file
2
tests/wpt/mozilla/meta/css/input_placeholder.html.ini
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
[input_placeholder.html]
|
||||
expected: FAIL
|
15
tests/wpt/mozilla/tests/appearance/input-text-definite-width-ref.html
vendored
Normal file
15
tests/wpt/mozilla/tests/appearance/input-text-definite-width-ref.html
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Appearance of an Input type=text With a Definite Width</title>
|
||||
<link rel="stylesheet" href="./supports/input-text-ref.css">
|
||||
</head>
|
||||
<body>
|
||||
Display of an input type=text should match the display generated by the CSS reference.
|
||||
<div>
|
||||
<div id="input" class="definite-width">
|
||||
Foo
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
14
tests/wpt/mozilla/tests/appearance/input-text-definite-width.html
vendored
Normal file
14
tests/wpt/mozilla/tests/appearance/input-text-definite-width.html
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Appearance of an Input type=text With a Definite Width</title>
|
||||
<link rel="match" href="input-text-definite-width-ref.html">
|
||||
<link rel="help" href="https://github.com/servo/servo/pull/37065">
|
||||
</head>
|
||||
<body>
|
||||
Display of an input type=text should match the display generated by the CSS reference.
|
||||
<div>
|
||||
<input type="text" value="Foo" style="font-size: 1em !important; width: 100px;"></input>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
15
tests/wpt/mozilla/tests/appearance/input-text-empty-ref.html
vendored
Normal file
15
tests/wpt/mozilla/tests/appearance/input-text-empty-ref.html
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Appearance of an Empty Input type=text With a Definite Width</title>
|
||||
<link rel="stylesheet" href="./supports/input-text-ref.css">
|
||||
</head>
|
||||
<body>
|
||||
Display of an input type=text should match the display generated by the CSS reference.
|
||||
<div>
|
||||
<div id="input" class="definite-width">
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
14
tests/wpt/mozilla/tests/appearance/input-text-empty.html
vendored
Normal file
14
tests/wpt/mozilla/tests/appearance/input-text-empty.html
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Appearance of an Empty Input type=text With a Definite Width</title>
|
||||
<link rel="match" href="input-text-empty-ref.html">
|
||||
<link rel="help" href="https://github.com/servo/servo/pull/37065">
|
||||
</head>
|
||||
<body>
|
||||
Display of an input type=text should match the display generated by the CSS reference.
|
||||
<div>
|
||||
<input type="text" style="font-size: 1em !important; width: 100px;"></input>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
15
tests/wpt/mozilla/tests/appearance/input-text-nonempty-placeholder-ref.html
vendored
Normal file
15
tests/wpt/mozilla/tests/appearance/input-text-nonempty-placeholder-ref.html
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Appearance of a Non-empty Input type=text With a Definite Width and a Placeholder</title>
|
||||
<link rel="stylesheet" href="./supports/input-text-ref.css">
|
||||
</head>
|
||||
<body>
|
||||
Display of an input type=text should match the display generated by the CSS reference.
|
||||
<div>
|
||||
<div id="input" class="definite-width">
|
||||
Foo
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
14
tests/wpt/mozilla/tests/appearance/input-text-nonempty-placeholder.html
vendored
Normal file
14
tests/wpt/mozilla/tests/appearance/input-text-nonempty-placeholder.html
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Appearance of a Non-empty Input type=text With a Definite Width and a Placeholder</title>
|
||||
<link rel="match" href="input-text-nonempty-placeholder-ref.html">
|
||||
<link rel="help" href="https://github.com/servo/servo/pull/37065">
|
||||
</head>
|
||||
<body>
|
||||
Display of an input type=text should match the display generated by the CSS reference.
|
||||
<div>
|
||||
<input type="text" value="Foo" placeholder="Bar" style="font-size: 1em !important; width: 100px;"></input>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
15
tests/wpt/mozilla/tests/appearance/input-text-overflow-ref.html
vendored
Normal file
15
tests/wpt/mozilla/tests/appearance/input-text-overflow-ref.html
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Appearance of an Overflowing Input type=text With a Definite Width</title>
|
||||
<link rel="stylesheet" href="./supports/input-text-ref.css">
|
||||
</head>
|
||||
<body>
|
||||
Display of an input type=text should match the display generated by the CSS reference.
|
||||
<div>
|
||||
<div id="input" class="definite-width">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
14
tests/wpt/mozilla/tests/appearance/input-text-overflow.html
vendored
Normal file
14
tests/wpt/mozilla/tests/appearance/input-text-overflow.html
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Appearance of an Overflowing Input type=text With a Definite Width</title>
|
||||
<link rel="match" href="input-text-overflow-ref.html">
|
||||
<link rel="help" href="https://github.com/servo/servo/pull/37065">
|
||||
</head>
|
||||
<body>
|
||||
Display of an input type=text should match the display generated by the CSS reference.
|
||||
<div>
|
||||
<input type="text" value="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." style="font-size: 1em !important; width: 100px;"></input>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
15
tests/wpt/mozilla/tests/appearance/input-text-placeholder-overflow-ref.html
vendored
Normal file
15
tests/wpt/mozilla/tests/appearance/input-text-placeholder-overflow-ref.html
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Appearance of an Input type=text With a Definite Width and an Overflowing Placeholder</title>
|
||||
<link rel="stylesheet" href="./supports/input-text-ref.css">
|
||||
</head>
|
||||
<body>
|
||||
Display of an input type=text should match the display generated by the CSS reference.
|
||||
<div>
|
||||
<div id="input" class="definite-width placeholder-color">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
14
tests/wpt/mozilla/tests/appearance/input-text-placeholder-overflow.html
vendored
Normal file
14
tests/wpt/mozilla/tests/appearance/input-text-placeholder-overflow.html
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Appearance of an Input type=text With a Definite Width and an Overflowing Placeholder</title>
|
||||
<link rel="match" href="input-text-placeholder-overflow-ref.html">
|
||||
<link rel="help" href="https://github.com/servo/servo/pull/37065">
|
||||
</head>
|
||||
<body>
|
||||
Display of an input type=text should match the display generated by the CSS reference.
|
||||
<div>
|
||||
<input type="text" placeholder="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." style="font-size: 1em !important; width: 100px;"></input>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
15
tests/wpt/mozilla/tests/appearance/input-text-placeholder-ref.html
vendored
Normal file
15
tests/wpt/mozilla/tests/appearance/input-text-placeholder-ref.html
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Appearance of an Input type=text With a Definite Width and a Placeholder</title>
|
||||
<link rel="stylesheet" href="./supports/input-text-ref.css">
|
||||
</head>
|
||||
<body>
|
||||
Display of an input type=text should match the display generated by the CSS reference.
|
||||
<div>
|
||||
<div id="input" class="definite-width placeholder-color">
|
||||
Bar
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
14
tests/wpt/mozilla/tests/appearance/input-text-placeholder.html
vendored
Normal file
14
tests/wpt/mozilla/tests/appearance/input-text-placeholder.html
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Appearance of an Input type=text With a Definite Width and a Placeholder</title>
|
||||
<link rel="match" href="input-text-placeholder-ref.html">
|
||||
<link rel="help" href="https://github.com/servo/servo/pull/37065">
|
||||
</head>
|
||||
<body>
|
||||
Display of an input type=text should match the display generated by the CSS reference.
|
||||
<div>
|
||||
<input type="text" placeholder="Bar" style="font-size: 1em !important; width: 100px;"></input>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
27
tests/wpt/mozilla/tests/appearance/supports/input-text-ref.css
vendored
Normal file
27
tests/wpt/mozilla/tests/appearance/supports/input-text-ref.css
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
/* Minimal stylesheet to mimic the appearence of an input type=text specific to Servo.
|
||||
* This stylesheet is expected to be modified following the development of the
|
||||
* Shadow DOM input type=text in Servo.
|
||||
*/
|
||||
|
||||
#input {
|
||||
display: inline-block;
|
||||
background: white;
|
||||
border: solid lightgrey 1px;
|
||||
font-family: sans-serif;
|
||||
font-size: 1em; /* We are using 1em here to reduce the effect of inconsistencies in layout */
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* We are using definite width for most of the test to reduce the effect if calculating inline
|
||||
* size of the input element. Which, will depends on the average character width of a font.
|
||||
*
|
||||
* <https://html.spec.whatwg.org/#converting-a-character-width-to-pixels>
|
||||
*/
|
||||
.definite-width {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.placeholder-color {
|
||||
color: grey;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue