aboutsummaryrefslogtreecommitdiffstats
path: root/components/script
diff options
context:
space:
mode:
Diffstat (limited to 'components/script')
-rw-r--r--components/script/dom/htmlinputelement.rs304
-rw-r--r--components/script/dom/htmlselectelement.rs8
-rw-r--r--components/script/dom/node.rs25
-rw-r--r--components/script/layout_dom/node.rs4
4 files changed, 286 insertions, 55 deletions
diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs
index aff8168b8fe..3c78ba67772 100644
--- a/components/script/dom/htmlinputelement.rs
+++ b/components/script/dom/htmlinputelement.rs
@@ -12,9 +12,13 @@ use std::str::FromStr;
use std::{f64, ptr};
use dom_struct::dom_struct;
-use embedder_traits::{FilterPattern, InputMethodType};
+use embedder_traits::{
+ EmbedderMsg, FilterPattern, FormControl as EmbedderFormControl, InputMethodType, RgbColor,
+};
use encoding_rs::Encoding;
+use euclid::{Point2D, Rect, Size2D};
use html5ever::{LocalName, Prefix, local_name, ns};
+use ipc_channel::ipc;
use js::jsapi::{
ClippedTime, DateGetMsecSinceEpoch, Handle, JS_ClearPendingException, JSObject, NewDateObject,
NewUCRegExpObject, ObjectIsDate, RegExpFlag_UnicodeSets, RegExpFlags,
@@ -25,7 +29,9 @@ use js::rust::{HandleObject, MutableHandleObject};
use net_traits::blob_url_store::get_blob_origin;
use net_traits::filemanager_thread::FileManagerThreadMsg;
use net_traits::{CoreResourceMsg, IpcSend};
-use profile_traits::ipc;
+use script_bindings::codegen::GenericBindings::ShadowRootBinding::{
+ ShadowRootMode, SlotAssignmentMode,
+};
use style::attr::AttrValue;
use style::str::{split_commas, str_join};
use stylo_atoms::Atom;
@@ -33,12 +39,12 @@ use stylo_dom::ElementState;
use time::{Month, OffsetDateTime, Time};
use unicode_bidi::{BidiClass, bidi_class};
use url::Url;
+use webrender_api::units::DeviceIntRect;
-use super::bindings::str::{FromInputValueString, ToInputValueString};
use crate::clipboard_provider::EmbedderClipboardProvider;
use crate::dom::activation::Activatable;
use crate::dom::attr::Attr;
-use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
use crate::dom::bindings::codegen::Bindings::FileListBinding::FileListMethods;
@@ -48,30 +54,33 @@ use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, N
use crate::dom::bindings::error::{Error, ErrorResult};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::DomGlobal;
-use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
-use crate::dom::bindings::str::{DOMString, USVString};
+use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom};
+use crate::dom::bindings::str::{DOMString, FromInputValueString, ToInputValueString, USVString};
use crate::dom::clipboardevent::ClipboardEvent;
use crate::dom::compositionevent::CompositionEvent;
use crate::dom::document::Document;
-use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers};
+use crate::dom::element::{AttributeMutation, Element, ElementCreator, LayoutElementHelpers};
use crate::dom::event::{Event, EventBubbles, EventCancelable};
use crate::dom::eventtarget::EventTarget;
use crate::dom::file::File;
use crate::dom::filelist::{FileList, LayoutFileListHelpers};
use crate::dom::globalscope::GlobalScope;
use crate::dom::htmldatalistelement::HTMLDataListElement;
+use crate::dom::htmldivelement::HTMLDivElement;
use crate::dom::htmlelement::HTMLElement;
use crate::dom::htmlfieldsetelement::HTMLFieldSetElement;
use crate::dom::htmlformelement::{
FormControl, FormDatum, FormDatumValue, FormSubmitterElement, HTMLFormElement, ResetFrom,
SubmittedFrom,
};
+use crate::dom::htmlstyleelement::HTMLStyleElement;
use crate::dom::keyboardevent::KeyboardEvent;
use crate::dom::mouseevent::MouseEvent;
use crate::dom::node::{
BindContext, CloneChildrenFlag, Node, NodeDamage, NodeTraits, ShadowIncluding, UnbindContext,
};
use crate::dom::nodelist::NodeList;
+use crate::dom::shadowroot::{IsUserAgentWidget, ShadowRoot};
use crate::dom::textcontrol::{TextControlElement, TextControlSelection};
use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
use crate::dom::validitystate::{ValidationFlags, ValidityState};
@@ -92,6 +101,34 @@ 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 references to the elements in the shadow tree for `<input type=range>`.
+///
+/// The shadow tree consists of a single div with the currently selected color as
+/// the background.
+struct InputTypeColorShadowTree {
+ color_value: Dom<HTMLDivElement>,
+}
+
+#[derive(Clone, JSTraceable, MallocSizeOf)]
+#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
+#[non_exhaustive]
+enum ShadowTree {
+ Color(InputTypeColorShadowTree),
+ // TODO: Add shadow trees for other input types (range etc) here
+}
+
+const COLOR_TREE_STYLE: &str = "
+#color-value {
+ width: 100%;
+ height: 100%;
+ box-sizing: border-box;
+ border: 1px solid gray;
+ border-radius: 2px;
+}
+";
+
/// <https://html.spec.whatwg.org/multipage/#attr-input-type>
#[derive(Clone, Copy, Default, JSTraceable, PartialEq)]
#[allow(dead_code)]
@@ -172,8 +209,7 @@ impl InputType {
fn is_textual(&self) -> bool {
matches!(
*self,
- InputType::Color |
- InputType::Date |
+ InputType::Date |
InputType::DatetimeLocal |
InputType::Email |
InputType::Hidden |
@@ -277,9 +313,16 @@ impl From<&Atom> for InputType {
#[derive(Debug, PartialEq)]
enum ValueMode {
+ /// <https://html.spec.whatwg.org/multipage/#dom-input-value-value>
Value,
+
+ /// <https://html.spec.whatwg.org/multipage/#dom-input-value-default>
Default,
+
+ /// <https://html.spec.whatwg.org/multipage/#dom-input-value-default-on>
DefaultOn,
+
+ /// <https://html.spec.whatwg.org/multipage/#dom-input-value-filename>
Filename,
}
@@ -314,6 +357,7 @@ pub(crate) struct HTMLInputElement {
form_owner: MutNullableDom<HTMLFormElement>,
labels_node_list: MutNullableDom<NodeList>,
validity_state: MutNullableDom<ValidityState>,
+ shadow_tree: DomRefCell<Option<ShadowTree>>,
}
#[derive(JSTraceable)]
@@ -372,6 +416,7 @@ impl HTMLInputElement {
form_owner: Default::default(),
labels_node_list: MutNullableDom::new(None),
validity_state: Default::default(),
+ shadow_tree: Default::default(),
}
}
@@ -803,7 +848,7 @@ impl HTMLInputElement {
.map(DomRoot::from_ref)
}
- // https://html.spec.whatwg.org/multipage/#suffering-from-being-missing
+ /// <https://html.spec.whatwg.org/multipage/#suffering-from-being-missing>
fn suffers_from_being_missing(&self, value: &DOMString) -> bool {
match self.input_type() {
// https://html.spec.whatwg.org/multipage/#checkbox-state-(type%3Dcheckbox)%3Asuffering-from-being-missing
@@ -958,9 +1003,9 @@ impl HTMLInputElement {
failed_flags
}
- // https://html.spec.whatwg.org/multipage/#suffering-from-an-underflow
- // https://html.spec.whatwg.org/multipage/#suffering-from-an-overflow
- // https://html.spec.whatwg.org/multipage/#suffering-from-a-step-mismatch
+ /// * <https://html.spec.whatwg.org/multipage/#suffering-from-an-underflow>
+ /// * <https://html.spec.whatwg.org/multipage/#suffering-from-an-overflow>
+ /// * <https://html.spec.whatwg.org/multipage/#suffering-from-a-step-mismatch>
fn suffers_from_range_issues(&self, value: &DOMString) -> ValidationFlags {
if value.is_empty() || !self.does_value_as_number_apply() {
return ValidationFlags::empty();
@@ -1014,9 +1059,109 @@ impl HTMLInputElement {
failed_flags
}
+
+ /// Return a reference to the ShadowRoot that this element is a host of,
+ /// or create one if none exists.
+ fn shadow_root(&self, can_gc: CanGc) -> DomRoot<ShadowRoot> {
+ self.upcast::<Element>().shadow_root().unwrap_or_else(|| {
+ self.upcast::<Element>()
+ .attach_shadow(
+ IsUserAgentWidget::Yes,
+ ShadowRootMode::Closed,
+ false,
+ false,
+ false,
+ SlotAssignmentMode::Manual,
+ can_gc,
+ )
+ .expect("Attaching UA shadow root failed")
+ })
+ }
+
+ fn create_color_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 color_value = HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc);
+ color_value
+ .upcast::<Element>()
+ .SetId(DOMString::from("color-value"), can_gc);
+ shadow_root
+ .upcast::<Node>()
+ .AppendChild(color_value.upcast::<Node>(), can_gc)
+ .unwrap();
+
+ let style = HTMLStyleElement::new(
+ local_name!("style"),
+ None,
+ &document,
+ None,
+ ElementCreator::ScriptCreated,
+ can_gc,
+ );
+ style
+ .upcast::<Node>()
+ .SetTextContent(Some(DOMString::from(COLOR_TREE_STYLE)), can_gc);
+ shadow_root
+ .upcast::<Node>()
+ .AppendChild(style.upcast::<Node>(), can_gc)
+ .unwrap();
+
+ let _ = self
+ .shadow_tree
+ .borrow_mut()
+ .insert(ShadowTree::Color(InputTypeColorShadowTree {
+ color_value: color_value.as_traced(),
+ }));
+ }
+
+ /// Get a handle to the shadow tree for this input, assuming it's [InputType] is `Color`.
+ ///
+ /// If the input is not currently a shadow host, a new shadow tree will be created.
+ ///
+ /// If the input is a shadow host for a different kind of shadow tree then the old
+ /// tree will be removed and a new one will be created.
+ fn color_shadow_tree(&self, can_gc: CanGc) -> Ref<InputTypeColorShadowTree> {
+ let has_color_shadow_tree = self
+ .shadow_tree
+ .borrow()
+ .as_ref()
+ .is_some_and(|shadow_tree| matches!(shadow_tree, ShadowTree::Color(_)));
+ if !has_color_shadow_tree {
+ self.create_color_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()?;
+ let ShadowTree::Color(color_tree) = shadow_tree;
+ Some(color_tree)
+ })
+ .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);
+ }
+ }
}
pub(crate) trait LayoutHTMLInputElementHelpers<'dom> {
+ /// Return a string that represents the contents of the element for layout.
fn value_for_layout(self) -> Cow<'dom, str>;
fn size_for_layout(self) -> u32;
fn selection_for_layout(self) -> Option<Range<usize>>;
@@ -1075,16 +1220,15 @@ impl<'dom> LayoutHTMLInputElementHelpers<'dom> for LayoutDom<'dom, HTMLInputElem
Some(filelist) => {
let length = filelist.len();
if length == 0 {
- return DEFAULT_FILE_INPUT_VALUE.into();
- }
- if length == 1 {
+ DEFAULT_FILE_INPUT_VALUE.into()
+ } else if length == 1 {
match filelist.file_for_layout(0) {
- Some(file) => return file.name().to_string().into(),
- None => return DEFAULT_FILE_INPUT_VALUE.into(),
+ Some(file) => file.name().to_string().into(),
+ None => DEFAULT_FILE_INPUT_VALUE.into(),
}
+ } else {
+ format!("{} files", length).into()
}
-
- format!("{} files", length).into()
},
None => DEFAULT_FILE_INPUT_VALUE.into(),
}
@@ -1103,6 +1247,9 @@ impl<'dom> LayoutHTMLInputElementHelpers<'dom> for LayoutDom<'dom, HTMLInputElem
self.placeholder().into()
}
},
+ InputType::Color => {
+ unreachable!("Input type color is explicitly not rendered as text");
+ },
_ => {
let text = self.get_raw_textinput_value();
if !text.is_empty() {
@@ -1178,11 +1325,11 @@ impl TextControlElement for HTMLInputElement {
InputType::Week |
InputType::Time |
InputType::DatetimeLocal |
- InputType::Number |
- InputType::Color => true,
+ InputType::Number => true,
InputType::Button |
InputType::Checkbox |
+ InputType::Color |
InputType::File |
InputType::Hidden |
InputType::Image |
@@ -1257,7 +1404,7 @@ impl HTMLInputElementMethods<crate::DomTypeHolder> for HTMLInputElement {
// https://html.spec.whatwg.org/multipage/#dom-input-checked
fn SetChecked(&self, checked: bool) {
self.update_checked_state(checked, true);
- update_related_validity_states(self, CanGc::note())
+ self.value_changed(CanGc::note());
}
// https://html.spec.whatwg.org/multipage/#dom-input-readonly
@@ -1349,7 +1496,7 @@ impl HTMLInputElementMethods<crate::DomTypeHolder> for HTMLInputElement {
},
}
- update_related_validity_states(self, can_gc);
+ self.value_changed(can_gc);
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
Ok(())
}
@@ -1724,18 +1871,6 @@ fn perform_radio_group_validation(elem: &HTMLInputElement, group: Option<&Atom>,
}
}
-fn update_related_validity_states(elem: &HTMLInputElement, can_gc: CanGc) {
- match elem.input_type() {
- InputType::Radio => {
- perform_radio_group_validation(elem, elem.radio_group_name().as_ref(), can_gc)
- },
- _ => {
- elem.validity_state()
- .perform_validation_and_update(ValidationFlags::all(), can_gc);
- },
- }
-}
-
// https://html.spec.whatwg.org/multipage/#radio-button-group
fn in_same_group(
other: &HTMLInputElement,
@@ -1905,7 +2040,7 @@ impl HTMLInputElement {
InputType::Radio | InputType::Checkbox => {
self.update_checked_state(self.DefaultChecked(), false);
self.checked_changed.set(false);
- update_related_validity_states(self, can_gc);
+ self.value_changed(can_gc);
},
InputType::Image => (),
_ => (),
@@ -1949,8 +2084,9 @@ impl HTMLInputElement {
.collect()
});
- let (chan, recv) = ipc::channel(self.global().time_profiler_chan().clone())
- .expect("Error initializing channel");
+ let (chan, recv) =
+ profile_traits::ipc::channel(self.global().time_profiler_chan().clone())
+ .expect("Error initializing channel");
let msg =
FileManagerThreadMsg::SelectFiles(webview_id, filter, chan, origin, opt_test_paths);
resource_threads
@@ -1977,8 +2113,9 @@ impl HTMLInputElement {
None => None,
};
- let (chan, recv) = ipc::channel(self.global().time_profiler_chan().clone())
- .expect("Error initializing channel");
+ let (chan, recv) =
+ profile_traits::ipc::channel(self.global().time_profiler_chan().clone())
+ .expect("Error initializing channel");
let msg =
FileManagerThreadMsg::SelectFile(webview_id, filter, chan, origin, opt_test_path);
resource_threads
@@ -2348,6 +2485,70 @@ impl HTMLInputElement {
},
})
}
+
+ fn update_related_validity_states(&self, can_gc: CanGc) {
+ match self.input_type() {
+ InputType::Radio => {
+ perform_radio_group_validation(self, self.radio_group_name().as_ref(), can_gc)
+ },
+ _ => {
+ self.validity_state()
+ .perform_validation_and_update(ValidationFlags::all(), can_gc);
+ },
+ }
+ }
+
+ fn value_changed(&self, can_gc: CanGc) {
+ self.update_related_validity_states(can_gc);
+ self.update_shadow_tree_if_needed(can_gc);
+ }
+
+ /// <https://html.spec.whatwg.org/multipage/#show-the-picker,-if-applicable>
+ fn show_the_picker_if_applicable(&self, can_gc: CanGc) {
+ // FIXME: Implement most of this algorithm
+
+ // Step 2. If element is not mutable, then return.
+ if !self.is_mutable() {
+ return;
+ }
+
+ // Step 6. Otherwise, the user agent should show the relevant user interface for selecting a value for element,
+ // in the way it normally would when the user interacts with the control.
+ if self.input_type() == InputType::Color {
+ let (ipc_sender, ipc_receiver) =
+ ipc::channel::<Option<RgbColor>>().expect("Failed to create IPC channel!");
+ let document = self.owner_document();
+ let rect = self.upcast::<Node>().bounding_content_box_or_zero(can_gc);
+ let rect = Rect::new(
+ Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()),
+ Size2D::new(rect.size.width.to_px(), rect.size.height.to_px()),
+ );
+ let current_value = self.Value();
+ let current_color = RgbColor {
+ red: u8::from_str_radix(&current_value[1..3], 16).unwrap(),
+ green: u8::from_str_radix(&current_value[3..5], 16).unwrap(),
+ blue: u8::from_str_radix(&current_value[5..7], 16).unwrap(),
+ };
+ document.send_to_embedder(EmbedderMsg::ShowFormControl(
+ document.webview_id(),
+ DeviceIntRect::from_untyped(&rect.to_box2d()),
+ EmbedderFormControl::ColorPicker(current_color, ipc_sender),
+ ));
+
+ let Ok(response) = ipc_receiver.recv() else {
+ log::error!("Failed to receive response");
+ return;
+ };
+
+ if let Some(selected_color) = response {
+ let formatted_color = format!(
+ "#{:0>2x}{:0>2x}{:0>2x}",
+ selected_color.red, selected_color.green, selected_color.blue
+ );
+ let _ = self.SetValue(formatted_color.into(), can_gc);
+ }
+ }
+ }
}
impl VirtualMethods for HTMLInputElement {
@@ -2359,6 +2560,7 @@ impl VirtualMethods for HTMLInputElement {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
+
match *attr.local_name() {
local_name!("disabled") => {
let disabled_state = match mutation {
@@ -2553,7 +2755,7 @@ impl VirtualMethods for HTMLInputElement {
_ => {},
}
- update_related_validity_states(self, can_gc);
+ self.value_changed(can_gc);
}
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
@@ -2584,7 +2786,8 @@ impl VirtualMethods for HTMLInputElement {
if self.input_type() == InputType::Radio {
self.radio_group_updated(self.radio_group_name().as_ref());
}
- update_related_validity_states(self, can_gc);
+
+ self.value_changed(can_gc);
}
fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
@@ -2730,7 +2933,7 @@ impl VirtualMethods for HTMLInputElement {
}
}
- update_related_validity_states(self, can_gc);
+ self.value_changed(can_gc);
}
// https://html.spec.whatwg.org/multipage/#the-input-element%3Aconcept-node-clone-ext
@@ -2752,7 +2955,7 @@ impl VirtualMethods for HTMLInputElement {
elem.textinput
.borrow_mut()
.set_content(self.textinput.borrow().get_content());
- update_related_validity_states(self, can_gc);
+ self.value_changed(can_gc);
}
}
@@ -2861,7 +3064,8 @@ impl Activatable for HTMLInputElement {
},
// https://html.spec.whatwg.org/multipage/#checkbox-state-(type=checkbox):input-activation-behavior
// https://html.spec.whatwg.org/multipage/#radio-button-state-(type=radio):input-activation-behavior
- InputType::Checkbox | InputType::Radio => true,
+ // https://html.spec.whatwg.org/multipage/#color-state-(type=color):input-activation-behavior
+ InputType::Checkbox | InputType::Radio | InputType::Color => true,
_ => false,
}
}
@@ -2907,7 +3111,7 @@ impl Activatable for HTMLInputElement {
};
if activation_state.is_some() {
- update_related_validity_states(self, can_gc);
+ self.value_changed(can_gc);
}
activation_state
@@ -2966,7 +3170,7 @@ impl Activatable for HTMLInputElement {
_ => (),
}
- update_related_validity_states(self, can_gc);
+ self.value_changed(can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#input-activation-behavior>
@@ -3028,6 +3232,10 @@ impl Activatable for HTMLInputElement {
},
// https://html.spec.whatwg.org/multipage/#file-upload-state-(type=file):input-activation-behavior
InputType::File => self.select_files(None, can_gc),
+ // https://html.spec.whatwg.org/multipage/#color-state-(type=color):input-activation-behavior
+ InputType::Color => {
+ self.show_the_picker_if_applicable(can_gc);
+ },
_ => (),
}
}
diff --git a/components/script/dom/htmlselectelement.rs b/components/script/dom/htmlselectelement.rs
index 56fac20e841..0f48c8e923f 100644
--- a/components/script/dom/htmlselectelement.rs
+++ b/components/script/dom/htmlselectelement.rs
@@ -14,7 +14,7 @@ use style::attr::AttrValue;
use stylo_dom::ElementState;
use embedder_traits::{SelectElementOptionOrOptgroup, SelectElementOption};
use euclid::{Size2D, Point2D, Rect};
-use embedder_traits::EmbedderMsg;
+use embedder_traits::{FormControl as EmbedderFormControl, EmbedderMsg};
use crate::dom::bindings::codegen::GenericBindings::HTMLOptGroupElementBinding::HTMLOptGroupElement_Binding::HTMLOptGroupElementMethods;
use crate::dom::activation::Activatable;
@@ -406,12 +406,10 @@ impl HTMLSelectElement {
let selected_index = self.list_of_options().position(|option| option.Selected());
let document = self.owner_document();
- document.send_to_embedder(EmbedderMsg::ShowSelectElementMenu(
+ document.send_to_embedder(EmbedderMsg::ShowFormControl(
document.webview_id(),
- options,
- selected_index,
DeviceIntRect::from_untyped(&rect.to_box2d()),
- ipc_sender,
+ EmbedderFormControl::SelectElement(options, selected_index, ipc_sender),
));
let Ok(response) = ipc_receiver.recv() else {
diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs
index 5f08abce354..17235543906 100644
--- a/components/script/dom/node.rs
+++ b/components/script/dom/node.rs
@@ -50,7 +50,6 @@ use style::stylesheets::{Stylesheet, UrlExtraData};
use uuid::Uuid;
use xml5ever::serialize as xml_serialize;
-use super::globalscope::GlobalScope;
use crate::conversions::Convert;
use crate::document_loader::DocumentLoader;
use crate::dom::attr::Attr;
@@ -92,13 +91,14 @@ use crate::dom::documenttype::DocumentType;
use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator, SelectorWrapper};
use crate::dom::event::{Event, EventBubbles, EventCancelable};
use crate::dom::eventtarget::EventTarget;
+use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlbodyelement::HTMLBodyElement;
use crate::dom::htmlcanvaselement::{HTMLCanvasElement, LayoutHTMLCanvasElementHelpers};
use crate::dom::htmlcollection::HTMLCollection;
use crate::dom::htmlelement::HTMLElement;
use crate::dom::htmliframeelement::{HTMLIFrameElement, HTMLIFrameElementLayoutMethods};
use crate::dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers};
-use crate::dom::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers};
+use crate::dom::htmlinputelement::{HTMLInputElement, InputType, LayoutHTMLInputElementHelpers};
use crate::dom::htmllinkelement::HTMLLinkElement;
use crate::dom::htmlslotelement::{HTMLSlotElement, Slottable};
use crate::dom::htmlstyleelement::HTMLStyleElement;
@@ -1612,6 +1612,8 @@ pub(crate) trait LayoutNodeHelpers<'dom> {
/// attempting to read or modify the opaque layout data of this node.
unsafe fn clear_style_and_layout_data(self);
+ /// Whether this element is a `<input>` rendered as text or a `<textarea>`.
+ fn is_text_input(&self) -> bool;
fn text_content(self) -> Cow<'dom, str>;
fn selection(self) -> Option<Range<usize>>;
fn image_url(self) -> Option<ServoUrl>;
@@ -1776,6 +1778,25 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
self.unsafe_get().layout_data.borrow_mut_for_layout().take();
}
+ fn is_text_input(&self) -> bool {
+ let type_id = self.type_id_for_layout();
+ if type_id ==
+ NodeTypeId::Element(ElementTypeId::HTMLElement(
+ HTMLElementTypeId::HTMLInputElement,
+ ))
+ {
+ let input = self.unsafe_get().downcast::<HTMLInputElement>().unwrap();
+
+ // FIXME: All the non-color input types currently render as text
+ input.input_type() != InputType::Color
+ } else {
+ type_id ==
+ NodeTypeId::Element(ElementTypeId::HTMLElement(
+ HTMLElementTypeId::HTMLTextAreaElement,
+ ))
+ }
+ }
+
fn text_content(self) -> Cow<'dom, str> {
if let Some(text) = self.downcast::<Text>() {
return text.upcast().data_for_layout().into();
diff --git a/components/script/layout_dom/node.rs b/components/script/layout_dom/node.rs
index 9d36f73bbc0..d60d9a673b1 100644
--- a/components/script/layout_dom/node.rs
+++ b/components/script/layout_dom/node.rs
@@ -96,6 +96,10 @@ impl<'dom> ServoLayoutNode<'dom> {
.map(LayoutDom::upcast)
.map(ServoLayoutElement::from_layout_js)
}
+
+ pub fn is_text_input(&self) -> bool {
+ self.node.is_text_input()
+ }
}
impl style::dom::NodeInfo for ServoLayoutNode<'_> {