mirror of
https://github.com/servo/servo
synced 2026-05-13 18:37:30 +02:00
This implements LazyDOMString (from now on DOMString) as outlined in https://github.com/servo/servo/issues/39479. Constructing from a *mut JSString we keep the in a RootedTraceableBox<Heap<*mut JSString>> and transform the string into a rust string if necessary via the `make_rust_string` method. Methods used in script are implemented on this string. Currently we transform the string at all times. But in the future more efficient implementations are possible. We implement the safety critical sections in a separate module DOMStringInner which allows simple constructors, `make_rust_string` and the `bytes` method. This method returns the new type `EncodedBytes` which contains the reference to the underlying string in either format. Testing: WPT tests still seem to work, so this should test this functionality. --------- Signed-off-by: Narfinger <Narfinger@users.noreply.github.com>
816 lines
30 KiB
Rust
816 lines
30 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
use std::cell::Cell;
|
|
use std::default::Default;
|
|
use std::ops::Range;
|
|
|
|
use dom_struct::dom_struct;
|
|
use html5ever::{LocalName, Prefix, local_name, ns};
|
|
use js::rust::HandleObject;
|
|
use style::attr::AttrValue;
|
|
use stylo_dom::ElementState;
|
|
|
|
use crate::clipboard_provider::EmbedderClipboardProvider;
|
|
use crate::dom::attr::Attr;
|
|
use crate::dom::bindings::cell::DomRefCell;
|
|
use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
|
|
use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode;
|
|
use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
|
|
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
|
|
use crate::dom::bindings::error::ErrorResult;
|
|
use crate::dom::bindings::inheritance::Castable;
|
|
use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
|
|
use crate::dom::bindings::str::DOMString;
|
|
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::event::{Event, EventBubbles, EventCancelable};
|
|
use crate::dom::html::htmlelement::HTMLElement;
|
|
use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
|
|
use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
|
|
use crate::dom::html::htmlinputelement::HTMLInputElement;
|
|
use crate::dom::keyboardevent::KeyboardEvent;
|
|
use crate::dom::node::{
|
|
BindContext, ChildrenMutation, CloneChildrenFlag, Node, NodeDamage, NodeTraits, UnbindContext,
|
|
};
|
|
use crate::dom::nodelist::NodeList;
|
|
use crate::dom::textcontrol::{TextControlElement, TextControlSelection};
|
|
use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
|
|
use crate::dom::validitystate::{ValidationFlags, ValidityState};
|
|
use crate::dom::virtualmethods::VirtualMethods;
|
|
use crate::script_runtime::CanGc;
|
|
use crate::textinput::{
|
|
ClipboardEventReaction, Direction, KeyReaction, Lines, SelectionDirection, TextInput,
|
|
UTF8Bytes, UTF16CodeUnits,
|
|
};
|
|
|
|
#[dom_struct]
|
|
pub(crate) struct HTMLTextAreaElement {
|
|
htmlelement: HTMLElement,
|
|
#[no_trace]
|
|
textinput: DomRefCell<TextInput<EmbedderClipboardProvider>>,
|
|
placeholder: DomRefCell<String>,
|
|
// https://html.spec.whatwg.org/multipage/#concept-textarea-dirty
|
|
value_dirty: Cell<bool>,
|
|
form_owner: MutNullableDom<HTMLFormElement>,
|
|
labels_node_list: MutNullableDom<NodeList>,
|
|
validity_state: MutNullableDom<ValidityState>,
|
|
}
|
|
|
|
pub(crate) trait LayoutHTMLTextAreaElementHelpers {
|
|
fn value_for_layout(self) -> String;
|
|
fn selection_for_layout(self) -> Option<Range<usize>>;
|
|
fn get_cols(self) -> u32;
|
|
fn get_rows(self) -> u32;
|
|
}
|
|
|
|
#[allow(unsafe_code)]
|
|
impl<'dom> LayoutDom<'dom, HTMLTextAreaElement> {
|
|
fn textinput_content(self) -> DOMString {
|
|
unsafe {
|
|
self.unsafe_get()
|
|
.textinput
|
|
.borrow_for_layout()
|
|
.get_content()
|
|
}
|
|
}
|
|
|
|
fn textinput_sorted_selection_offsets_range(self) -> Range<UTF8Bytes> {
|
|
unsafe {
|
|
self.unsafe_get()
|
|
.textinput
|
|
.borrow_for_layout()
|
|
.sorted_selection_offsets_range()
|
|
}
|
|
}
|
|
|
|
fn placeholder(self) -> &'dom str {
|
|
unsafe { self.unsafe_get().placeholder.borrow_for_layout() }
|
|
}
|
|
}
|
|
|
|
impl LayoutHTMLTextAreaElementHelpers for LayoutDom<'_, HTMLTextAreaElement> {
|
|
fn value_for_layout(self) -> String {
|
|
let text = self.textinput_content();
|
|
if text.is_empty() {
|
|
// FIXME(nox): Would be cool to not allocate a new string if the
|
|
// placeholder is single line, but that's an unimportant detail.
|
|
self.placeholder().replace("\r\n", "\n").replace('\r', "\n")
|
|
} else {
|
|
text.into()
|
|
}
|
|
}
|
|
|
|
fn selection_for_layout(self) -> Option<Range<usize>> {
|
|
if !self.upcast::<Element>().focus_state() {
|
|
return None;
|
|
}
|
|
Some(UTF8Bytes::unwrap_range(
|
|
self.textinput_sorted_selection_offsets_range(),
|
|
))
|
|
}
|
|
|
|
fn get_cols(self) -> u32 {
|
|
self.upcast::<Element>()
|
|
.get_attr_for_layout(&ns!(), &local_name!("cols"))
|
|
.map_or(DEFAULT_COLS, AttrValue::as_uint)
|
|
}
|
|
|
|
fn get_rows(self) -> u32 {
|
|
self.upcast::<Element>()
|
|
.get_attr_for_layout(&ns!(), &local_name!("rows"))
|
|
.map_or(DEFAULT_ROWS, AttrValue::as_uint)
|
|
}
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#attr-textarea-cols-value
|
|
const DEFAULT_COLS: u32 = 20;
|
|
|
|
// https://html.spec.whatwg.org/multipage/#attr-textarea-rows-value
|
|
const DEFAULT_ROWS: u32 = 2;
|
|
|
|
const DEFAULT_MAX_LENGTH: i32 = -1;
|
|
const DEFAULT_MIN_LENGTH: i32 = -1;
|
|
|
|
impl HTMLTextAreaElement {
|
|
fn new_inherited(
|
|
local_name: LocalName,
|
|
prefix: Option<Prefix>,
|
|
document: &Document,
|
|
) -> HTMLTextAreaElement {
|
|
let embedder_sender = document
|
|
.window()
|
|
.as_global_scope()
|
|
.script_to_embedder_chan()
|
|
.clone();
|
|
HTMLTextAreaElement {
|
|
htmlelement: HTMLElement::new_inherited_with_state(
|
|
ElementState::ENABLED | ElementState::READWRITE,
|
|
local_name,
|
|
prefix,
|
|
document,
|
|
),
|
|
placeholder: DomRefCell::new(String::new()),
|
|
textinput: DomRefCell::new(TextInput::new(
|
|
Lines::Multiple,
|
|
DOMString::new(),
|
|
EmbedderClipboardProvider {
|
|
embedder_sender,
|
|
webview_id: document.webview_id(),
|
|
},
|
|
None,
|
|
None,
|
|
SelectionDirection::None,
|
|
)),
|
|
value_dirty: Cell::new(false),
|
|
form_owner: Default::default(),
|
|
labels_node_list: Default::default(),
|
|
validity_state: Default::default(),
|
|
}
|
|
}
|
|
|
|
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
|
pub(crate) fn new(
|
|
local_name: LocalName,
|
|
prefix: Option<Prefix>,
|
|
document: &Document,
|
|
proto: Option<HandleObject>,
|
|
can_gc: CanGc,
|
|
) -> DomRoot<HTMLTextAreaElement> {
|
|
Node::reflect_node_with_proto(
|
|
Box::new(HTMLTextAreaElement::new_inherited(
|
|
local_name, prefix, document,
|
|
)),
|
|
document,
|
|
proto,
|
|
can_gc,
|
|
)
|
|
}
|
|
|
|
pub(crate) fn auto_directionality(&self) -> String {
|
|
let value: String = self.Value().to_string();
|
|
HTMLInputElement::directionality_from_value(&value)
|
|
}
|
|
|
|
fn update_placeholder_shown_state(&self) {
|
|
let has_placeholder = !self.placeholder.borrow().is_empty();
|
|
let has_value = !self.textinput.borrow().is_empty();
|
|
let el = self.upcast::<Element>();
|
|
el.set_placeholder_shown_state(has_placeholder && !has_value);
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#concept-fe-mutable
|
|
pub(crate) fn is_mutable(&self) -> bool {
|
|
// https://html.spec.whatwg.org/multipage/#the-textarea-element%3Aconcept-fe-mutable
|
|
// https://html.spec.whatwg.org/multipage/#the-readonly-attribute:concept-fe-mutable
|
|
!(self.upcast::<Element>().disabled_state() || self.ReadOnly())
|
|
}
|
|
}
|
|
|
|
impl TextControlElement for HTMLTextAreaElement {
|
|
fn selection_api_applies(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
fn has_selectable_text(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
fn set_dirty_value_flag(&self, value: bool) {
|
|
self.value_dirty.set(value)
|
|
}
|
|
}
|
|
|
|
impl HTMLTextAreaElementMethods<crate::DomTypeHolder> for HTMLTextAreaElement {
|
|
// TODO A few of these attributes have default values and additional
|
|
// constraints
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea-cols
|
|
make_uint_getter!(Cols, "cols", DEFAULT_COLS);
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea-cols
|
|
make_limited_uint_setter!(SetCols, "cols", DEFAULT_COLS);
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-input-dirName
|
|
make_getter!(DirName, "dirname");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-input-dirName
|
|
make_setter!(SetDirName, "dirname");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-fe-disabled
|
|
make_bool_getter!(Disabled, "disabled");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-fe-disabled
|
|
make_bool_setter!(SetDisabled, "disabled");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-fae-form
|
|
fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
|
|
self.form_owner()
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#attr-fe-name
|
|
make_getter!(Name, "name");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#attr-fe-name
|
|
make_atomic_setter!(SetName, "name");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea-placeholder
|
|
make_getter!(Placeholder, "placeholder");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea-placeholder
|
|
make_setter!(SetPlaceholder, "placeholder");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#attr-textarea-maxlength
|
|
make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH);
|
|
|
|
// https://html.spec.whatwg.org/multipage/#attr-textarea-maxlength
|
|
make_limited_int_setter!(SetMaxLength, "maxlength", DEFAULT_MAX_LENGTH);
|
|
|
|
// https://html.spec.whatwg.org/multipage/#attr-textarea-minlength
|
|
make_int_getter!(MinLength, "minlength", DEFAULT_MIN_LENGTH);
|
|
|
|
// https://html.spec.whatwg.org/multipage/#attr-textarea-minlength
|
|
make_limited_int_setter!(SetMinLength, "minlength", DEFAULT_MIN_LENGTH);
|
|
|
|
// https://html.spec.whatwg.org/multipage/#attr-textarea-readonly
|
|
make_bool_getter!(ReadOnly, "readonly");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#attr-textarea-readonly
|
|
make_bool_setter!(SetReadOnly, "readonly");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea-required
|
|
make_bool_getter!(Required, "required");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea-required
|
|
make_bool_setter!(SetRequired, "required");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea-rows
|
|
make_uint_getter!(Rows, "rows", DEFAULT_ROWS);
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea-rows
|
|
make_limited_uint_setter!(SetRows, "rows", DEFAULT_ROWS);
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea-wrap
|
|
make_getter!(Wrap, "wrap");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea-wrap
|
|
make_setter!(SetWrap, "wrap");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea-type
|
|
fn Type(&self) -> DOMString {
|
|
DOMString::from("textarea")
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea-defaultvalue
|
|
fn DefaultValue(&self) -> DOMString {
|
|
self.upcast::<Node>().GetTextContent().unwrap()
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea-defaultvalue
|
|
fn SetDefaultValue(&self, value: DOMString, can_gc: CanGc) {
|
|
self.upcast::<Node>()
|
|
.set_text_content_for_element(Some(value), can_gc);
|
|
|
|
// if the element's dirty value flag is false, then the element's
|
|
// raw value must be set to the value of the element's textContent IDL attribute
|
|
if !self.value_dirty.get() {
|
|
self.reset();
|
|
}
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea-value
|
|
fn Value(&self) -> DOMString {
|
|
self.textinput.borrow().get_content()
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea-value
|
|
fn SetValue(&self, value: DOMString) {
|
|
{
|
|
let mut textinput = self.textinput.borrow_mut();
|
|
|
|
// Step 1
|
|
let old_value = textinput.get_content();
|
|
|
|
// Step 2
|
|
textinput.set_content(value);
|
|
|
|
// Step 3
|
|
self.value_dirty.set(true);
|
|
|
|
if old_value != textinput.get_content() {
|
|
// Step 4
|
|
textinput.clear_selection_to_limit(Direction::Forward);
|
|
}
|
|
}
|
|
|
|
self.validity_state()
|
|
.perform_validation_and_update(ValidationFlags::all(), CanGc::note());
|
|
self.upcast::<Node>().dirty(NodeDamage::Other);
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea-textlength
|
|
fn TextLength(&self) -> u32 {
|
|
let UTF16CodeUnits(num_units) = self.textinput.borrow().utf16_len();
|
|
num_units as u32
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-lfe-labels
|
|
make_labels_getter!(Labels, labels_node_list);
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea/input-select
|
|
fn Select(&self) {
|
|
self.selection().dom_select();
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart
|
|
fn GetSelectionStart(&self) -> Option<u32> {
|
|
self.selection().dom_start()
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart
|
|
fn SetSelectionStart(&self, start: Option<u32>) -> ErrorResult {
|
|
self.selection().set_dom_start(start)
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend
|
|
fn GetSelectionEnd(&self) -> Option<u32> {
|
|
self.selection().dom_end()
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend
|
|
fn SetSelectionEnd(&self, end: Option<u32>) -> ErrorResult {
|
|
self.selection().set_dom_end(end)
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection
|
|
fn GetSelectionDirection(&self) -> Option<DOMString> {
|
|
self.selection().dom_direction()
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection
|
|
fn SetSelectionDirection(&self, direction: Option<DOMString>) -> ErrorResult {
|
|
self.selection().set_dom_direction(direction)
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea/input-setselectionrange
|
|
fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult {
|
|
self.selection().set_dom_range(start, end, direction)
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext
|
|
fn SetRangeText(&self, replacement: DOMString) -> ErrorResult {
|
|
self.selection()
|
|
.set_dom_range_text(replacement, None, None, Default::default())
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-textarea/input-setrangetext
|
|
fn SetRangeText_(
|
|
&self,
|
|
replacement: DOMString,
|
|
start: u32,
|
|
end: u32,
|
|
selection_mode: SelectionMode,
|
|
) -> ErrorResult {
|
|
self.selection()
|
|
.set_dom_range_text(replacement, Some(start), Some(end), selection_mode)
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate
|
|
fn WillValidate(&self) -> bool {
|
|
self.is_instance_validatable()
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-cva-validity
|
|
fn Validity(&self) -> DomRoot<ValidityState> {
|
|
self.validity_state()
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity
|
|
fn CheckValidity(&self, can_gc: CanGc) -> bool {
|
|
self.check_validity(can_gc)
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity
|
|
fn ReportValidity(&self, can_gc: CanGc) -> bool {
|
|
self.report_validity(can_gc)
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage
|
|
fn ValidationMessage(&self) -> DOMString {
|
|
self.validation_message()
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity
|
|
fn SetCustomValidity(&self, error: DOMString) {
|
|
self.validity_state().set_custom_error_message(error);
|
|
}
|
|
}
|
|
|
|
impl HTMLTextAreaElement {
|
|
/// <https://w3c.github.io/webdriver/#ref-for-dfn-clear-algorithm-4>
|
|
/// Used by WebDriver to clear the textarea element.
|
|
pub(crate) fn clear(&self) {
|
|
self.value_dirty.set(false);
|
|
self.textinput.borrow_mut().set_content(DOMString::from(""));
|
|
}
|
|
|
|
pub(crate) fn reset(&self) {
|
|
// https://html.spec.whatwg.org/multipage/#the-textarea-element:concept-form-reset-control
|
|
let mut textinput = self.textinput.borrow_mut();
|
|
textinput.set_content(self.DefaultValue());
|
|
self.value_dirty.set(false);
|
|
}
|
|
|
|
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
|
fn selection(&self) -> TextControlSelection<'_, Self> {
|
|
TextControlSelection::new(self, &self.textinput)
|
|
}
|
|
}
|
|
|
|
impl VirtualMethods for HTMLTextAreaElement {
|
|
fn super_type(&self) -> Option<&dyn VirtualMethods> {
|
|
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
|
|
}
|
|
|
|
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
|
|
self.super_type()
|
|
.unwrap()
|
|
.attribute_mutated(attr, mutation, can_gc);
|
|
match *attr.local_name() {
|
|
local_name!("disabled") => {
|
|
let el = self.upcast::<Element>();
|
|
match mutation {
|
|
AttributeMutation::Set(_) => {
|
|
el.set_disabled_state(true);
|
|
el.set_enabled_state(false);
|
|
|
|
el.set_read_write_state(false);
|
|
},
|
|
AttributeMutation::Removed => {
|
|
el.set_disabled_state(false);
|
|
el.set_enabled_state(true);
|
|
el.check_ancestors_disabled_state_for_form_control();
|
|
|
|
if !el.disabled_state() && !el.read_write_state() {
|
|
el.set_read_write_state(true);
|
|
}
|
|
},
|
|
}
|
|
el.update_sequentially_focusable_status(CanGc::note());
|
|
},
|
|
local_name!("maxlength") => match *attr.value() {
|
|
AttrValue::Int(_, value) => {
|
|
let mut textinput = self.textinput.borrow_mut();
|
|
|
|
if value < 0 {
|
|
textinput.set_max_length(None);
|
|
} else {
|
|
textinput.set_max_length(Some(UTF16CodeUnits(value as usize)))
|
|
}
|
|
},
|
|
_ => panic!("Expected an AttrValue::Int"),
|
|
},
|
|
local_name!("minlength") => match *attr.value() {
|
|
AttrValue::Int(_, value) => {
|
|
let mut textinput = self.textinput.borrow_mut();
|
|
|
|
if value < 0 {
|
|
textinput.set_min_length(None);
|
|
} else {
|
|
textinput.set_min_length(Some(UTF16CodeUnits(value as usize)))
|
|
}
|
|
},
|
|
_ => panic!("Expected an AttrValue::Int"),
|
|
},
|
|
local_name!("placeholder") => {
|
|
{
|
|
let mut placeholder = self.placeholder.borrow_mut();
|
|
placeholder.clear();
|
|
if let AttributeMutation::Set(_) = mutation {
|
|
placeholder.push_str(attr.value().as_ref());
|
|
}
|
|
}
|
|
self.update_placeholder_shown_state();
|
|
},
|
|
local_name!("readonly") => {
|
|
let el = self.upcast::<Element>();
|
|
match mutation {
|
|
AttributeMutation::Set(_) => {
|
|
el.set_read_write_state(false);
|
|
},
|
|
AttributeMutation::Removed => {
|
|
el.set_read_write_state(!el.disabled_state());
|
|
},
|
|
}
|
|
},
|
|
local_name!("form") => {
|
|
self.form_attribute_mutated(mutation, can_gc);
|
|
},
|
|
_ => {},
|
|
}
|
|
|
|
self.validity_state()
|
|
.perform_validation_and_update(ValidationFlags::all(), can_gc);
|
|
}
|
|
|
|
fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
|
|
if let Some(s) = self.super_type() {
|
|
s.bind_to_tree(context, can_gc);
|
|
}
|
|
|
|
self.upcast::<Element>()
|
|
.check_ancestors_disabled_state_for_form_control();
|
|
|
|
self.validity_state()
|
|
.perform_validation_and_update(ValidationFlags::all(), can_gc);
|
|
}
|
|
|
|
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
|
|
match *name {
|
|
local_name!("cols") => AttrValue::from_limited_u32(value.into(), DEFAULT_COLS),
|
|
local_name!("rows") => AttrValue::from_limited_u32(value.into(), DEFAULT_ROWS),
|
|
local_name!("maxlength") => {
|
|
AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH)
|
|
},
|
|
local_name!("minlength") => {
|
|
AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH)
|
|
},
|
|
_ => self
|
|
.super_type()
|
|
.unwrap()
|
|
.parse_plain_attribute(name, value),
|
|
}
|
|
}
|
|
|
|
fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
|
|
self.super_type().unwrap().unbind_from_tree(context, can_gc);
|
|
|
|
let node = self.upcast::<Node>();
|
|
let el = self.upcast::<Element>();
|
|
if node
|
|
.ancestors()
|
|
.any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
|
|
{
|
|
el.check_ancestors_disabled_state_for_form_control();
|
|
} else {
|
|
el.check_disabled_attribute();
|
|
}
|
|
|
|
self.validity_state()
|
|
.perform_validation_and_update(ValidationFlags::all(), can_gc);
|
|
}
|
|
|
|
// The cloning steps for textarea elements must propagate the raw value
|
|
// and dirty value flag from the node being cloned to the copy.
|
|
fn cloning_steps(
|
|
&self,
|
|
copy: &Node,
|
|
maybe_doc: Option<&Document>,
|
|
clone_children: CloneChildrenFlag,
|
|
can_gc: CanGc,
|
|
) {
|
|
if let Some(s) = self.super_type() {
|
|
s.cloning_steps(copy, maybe_doc, clone_children, can_gc);
|
|
}
|
|
let el = copy.downcast::<HTMLTextAreaElement>().unwrap();
|
|
el.value_dirty.set(self.value_dirty.get());
|
|
{
|
|
let mut textinput = el.textinput.borrow_mut();
|
|
textinput.set_content(self.textinput.borrow().get_content());
|
|
}
|
|
el.validity_state()
|
|
.perform_validation_and_update(ValidationFlags::all(), can_gc);
|
|
}
|
|
|
|
fn children_changed(&self, mutation: &ChildrenMutation) {
|
|
if let Some(s) = self.super_type() {
|
|
s.children_changed(mutation);
|
|
}
|
|
if !self.value_dirty.get() {
|
|
self.reset();
|
|
}
|
|
}
|
|
|
|
// copied and modified from htmlinputelement.rs
|
|
fn handle_event(&self, event: &Event, can_gc: CanGc) {
|
|
if let Some(s) = self.super_type() {
|
|
s.handle_event(event, can_gc);
|
|
}
|
|
|
|
if event.type_() == atom!("click") && !event.DefaultPrevented() {
|
|
// TODO: set the editing position for text inputs
|
|
} else if event.type_() == atom!("keydown") && !event.DefaultPrevented() {
|
|
if let Some(kevent) = event.downcast::<KeyboardEvent>() {
|
|
// This can't be inlined, as holding on to textinput.borrow_mut()
|
|
// during self.implicit_submission will cause a panic.
|
|
let action = self.textinput.borrow_mut().handle_keydown(kevent);
|
|
match action {
|
|
KeyReaction::TriggerDefaultAction => (),
|
|
KeyReaction::DispatchInput => {
|
|
if event.IsTrusted() {
|
|
self.owner_global()
|
|
.task_manager()
|
|
.user_interaction_task_source()
|
|
.queue_event(
|
|
self.upcast(),
|
|
atom!("input"),
|
|
EventBubbles::Bubbles,
|
|
EventCancelable::NotCancelable,
|
|
);
|
|
}
|
|
self.value_dirty.set(true);
|
|
self.update_placeholder_shown_state();
|
|
self.upcast::<Node>().dirty(NodeDamage::Other);
|
|
event.mark_as_handled();
|
|
},
|
|
KeyReaction::RedrawSelection => {
|
|
self.upcast::<Node>().dirty(NodeDamage::Other);
|
|
event.mark_as_handled();
|
|
},
|
|
KeyReaction::Nothing => (),
|
|
}
|
|
}
|
|
} else if event.type_() == atom!("keypress") && !event.DefaultPrevented() {
|
|
// keypress should be deprecated and replaced by beforeinput.
|
|
// keypress was supposed to fire "blur" and "focus" events
|
|
// but already done in `document.rs`
|
|
} else if event.type_() == atom!("compositionstart") ||
|
|
event.type_() == atom!("compositionupdate") ||
|
|
event.type_() == atom!("compositionend")
|
|
{
|
|
if let Some(compositionevent) = event.downcast::<CompositionEvent>() {
|
|
if event.type_() == atom!("compositionend") {
|
|
let _ = self
|
|
.textinput
|
|
.borrow_mut()
|
|
.handle_compositionend(compositionevent);
|
|
self.upcast::<Node>().dirty(NodeDamage::Other);
|
|
} else if event.type_() == atom!("compositionupdate") {
|
|
let _ = self
|
|
.textinput
|
|
.borrow_mut()
|
|
.handle_compositionupdate(compositionevent);
|
|
self.upcast::<Node>().dirty(NodeDamage::Other);
|
|
}
|
|
event.mark_as_handled();
|
|
}
|
|
} else if let Some(clipboard_event) = event.downcast::<ClipboardEvent>() {
|
|
let reaction = self
|
|
.textinput
|
|
.borrow_mut()
|
|
.handle_clipboard_event(clipboard_event);
|
|
if reaction.contains(ClipboardEventReaction::FireClipboardChangedEvent) {
|
|
self.owner_document()
|
|
.event_handler()
|
|
.fire_clipboardchange_event(can_gc);
|
|
}
|
|
if reaction.contains(ClipboardEventReaction::QueueInputEvent) {
|
|
self.owner_global()
|
|
.task_manager()
|
|
.user_interaction_task_source()
|
|
.queue_event(
|
|
self.upcast(),
|
|
atom!("input"),
|
|
EventBubbles::Bubbles,
|
|
EventCancelable::NotCancelable,
|
|
);
|
|
}
|
|
if !reaction.is_empty() {
|
|
self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
|
|
}
|
|
}
|
|
|
|
self.validity_state()
|
|
.perform_validation_and_update(ValidationFlags::all(), can_gc);
|
|
}
|
|
|
|
fn pop(&self) {
|
|
self.super_type().unwrap().pop();
|
|
|
|
// https://html.spec.whatwg.org/multipage/#the-textarea-element:stack-of-open-elements
|
|
self.reset();
|
|
}
|
|
}
|
|
|
|
impl FormControl for HTMLTextAreaElement {
|
|
fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
|
|
self.form_owner.get()
|
|
}
|
|
|
|
fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
|
|
self.form_owner.set(form);
|
|
}
|
|
|
|
fn to_element(&self) -> &Element {
|
|
self.upcast::<Element>()
|
|
}
|
|
}
|
|
|
|
impl Validatable for HTMLTextAreaElement {
|
|
fn as_element(&self) -> &Element {
|
|
self.upcast()
|
|
}
|
|
|
|
fn validity_state(&self) -> DomRoot<ValidityState> {
|
|
self.validity_state
|
|
.or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), CanGc::note()))
|
|
}
|
|
|
|
fn is_instance_validatable(&self) -> bool {
|
|
// https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation
|
|
// https://html.spec.whatwg.org/multipage/#the-textarea-element%3Abarred-from-constraint-validation
|
|
// https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation
|
|
!self.upcast::<Element>().disabled_state() &&
|
|
!self.ReadOnly() &&
|
|
!is_barred_by_datalist_ancestor(self.upcast())
|
|
}
|
|
|
|
fn perform_validation(
|
|
&self,
|
|
validate_flags: ValidationFlags,
|
|
_can_gc: CanGc,
|
|
) -> ValidationFlags {
|
|
let mut failed_flags = ValidationFlags::empty();
|
|
|
|
let textinput = self.textinput.borrow();
|
|
let UTF16CodeUnits(value_len) = textinput.utf16_len();
|
|
let last_edit_by_user = !textinput.was_last_change_by_set_content();
|
|
let value_dirty = self.value_dirty.get();
|
|
|
|
// https://html.spec.whatwg.org/multipage/#suffering-from-being-missing
|
|
// https://html.spec.whatwg.org/multipage/#the-textarea-element%3Asuffering-from-being-missing
|
|
if validate_flags.contains(ValidationFlags::VALUE_MISSING) &&
|
|
self.Required() &&
|
|
self.is_mutable() &&
|
|
value_len == 0
|
|
{
|
|
failed_flags.insert(ValidationFlags::VALUE_MISSING);
|
|
}
|
|
|
|
if value_dirty && last_edit_by_user && value_len > 0 {
|
|
// https://html.spec.whatwg.org/multipage/#suffering-from-being-too-long
|
|
// https://html.spec.whatwg.org/multipage/#limiting-user-input-length%3A-the-maxlength-attribute%3Asuffering-from-being-too-long
|
|
if validate_flags.contains(ValidationFlags::TOO_LONG) {
|
|
let max_length = self.MaxLength();
|
|
if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) {
|
|
failed_flags.insert(ValidationFlags::TOO_LONG);
|
|
}
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#suffering-from-being-too-short
|
|
// https://html.spec.whatwg.org/multipage/#setting-minimum-input-length-requirements%3A-the-minlength-attribute%3Asuffering-from-being-too-short
|
|
if validate_flags.contains(ValidationFlags::TOO_SHORT) {
|
|
let min_length = self.MinLength();
|
|
if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) {
|
|
failed_flags.insert(ValidationFlags::TOO_SHORT);
|
|
}
|
|
}
|
|
}
|
|
|
|
failed_flags
|
|
}
|
|
}
|