servo: Merge #10612 - Implement HTMLTextArea.setSelectionRange (continuation of #10007) (from autrilla:textdir); r=emilio
authorAlberto Corona <ac@albertocorona.com>
Sun, 17 Apr 2016 21:29:01 +0500
changeset 338551 48c6a631f9f74e0236e4507cdd76089caa002df4
parent 338550 2f208c652b0a23b249bca78646eaa5ce268512f3
child 338552 23d10463cddd0e2eec16e426e293981fab961548
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
servo: Merge #10612 - Implement HTMLTextArea.setSelectionRange (continuation of #10007) (from autrilla:textdir); r=emilio Tests on `tests/wpt/web-platform-tests/html/semantics/forms/textfieldselection/textfieldselection-setSelectionRange.html` all pass and the other tests don't panic due to double borrows anymore. cc: @KiChjang Fixes #9994. Source-Repo: https://github.com/servo/servo Source-Revision: b00c2740e300fd7b8c18276d8d416a9f78c42674
servo/components/script/dom/htmlinputelement.rs
servo/components/script/dom/htmltextareaelement.rs
servo/components/script/dom/webidls/HTMLTextAreaElement.webidl
servo/components/script/textinput.rs
servo/tests/unit/script/textinput.rs
--- a/servo/components/script/dom/htmlinputelement.rs
+++ b/servo/components/script/dom/htmlinputelement.rs
@@ -37,17 +37,17 @@ use script_runtime::ScriptThreadEventCat
 use script_thread::Runnable;
 use script_traits::ScriptMsg as ConstellationMsg;
 use std::borrow::ToOwned;
 use std::cell::Cell;
 use string_cache::Atom;
 use style::element_state::*;
 use textinput::KeyReaction::{DispatchInput, Nothing, RedrawSelection, TriggerDefaultAction};
 use textinput::Lines::Single;
-use textinput::TextInput;
+use textinput::{TextInput, SelectionDirection};
 use util::str::{DOMString, search_index};
 
 const DEFAULT_SUBMIT_VALUE: &'static str = "Submit";
 const DEFAULT_RESET_VALUE: &'static str = "Reset";
 
 #[derive(JSTraceable, PartialEq, Copy, Clone)]
 #[allow(dead_code)]
 #[derive(HeapSizeOf)]
@@ -66,41 +66,31 @@ enum InputType {
 #[derive(Debug, PartialEq)]
 enum ValueMode {
     Value,
     Default,
     DefaultOn,
     Filename,
 }
 
-#[derive(JSTraceable, PartialEq, Copy, Clone)]
-#[derive(HeapSizeOf)]
-enum SelectionDirection {
-    Forward,
-    Backward,
-    None
-}
-
 #[dom_struct]
 pub struct HTMLInputElement {
     htmlelement: HTMLElement,
     input_type: Cell<InputType>,
     checked_changed: Cell<bool>,
     placeholder: DOMRefCell<DOMString>,
     value_changed: Cell<bool>,
     size: Cell<u32>,
     maxlength: Cell<i32>,
     #[ignore_heap_size_of = "#7193"]
     textinput: DOMRefCell<TextInput<ConstellationChan<ConstellationMsg>>>,
     activation_state: DOMRefCell<InputActivationState>,
     // https://html.spec.whatwg.org/multipage/#concept-input-value-dirty-flag
     value_dirty: Cell<bool>,
 
-    selection_direction: Cell<SelectionDirection>,
-
     // TODO: selected files for file input
 }
 
 #[derive(JSTraceable)]
 #[must_root]
 #[derive(HeapSizeOf)]
 struct InputActivationState {
     indeterminate: bool,
@@ -137,20 +127,19 @@ impl HTMLInputElement {
                 HTMLElement::new_inherited_with_state(IN_ENABLED_STATE,
                                                       localName, prefix, document),
             input_type: Cell::new(InputType::InputText),
             placeholder: DOMRefCell::new(DOMString::new()),
             checked_changed: Cell::new(false),
             value_changed: Cell::new(false),
             maxlength: Cell::new(DEFAULT_MAX_LENGTH),
             size: Cell::new(DEFAULT_INPUT_SIZE),
-            textinput: DOMRefCell::new(TextInput::new(Single, DOMString::new(), chan, None)),
+            textinput: DOMRefCell::new(TextInput::new(Single, DOMString::new(), chan, None, SelectionDirection::None)),
             activation_state: DOMRefCell::new(InputActivationState::new()),
             value_dirty: Cell::new(false),
-            selection_direction: Cell::new(SelectionDirection::None)
         }
     }
 
     #[allow(unrooted_must_root)]
     pub fn new(localName: Atom,
                prefix: Option<DOMString>,
                document: &Document) -> Root<HTMLInputElement> {
         let element = HTMLInputElement::new_inherited(localName, prefix, document);
@@ -172,43 +161,16 @@ impl HTMLInputElement {
             InputType::InputImage => ValueMode::Default,
             InputType::InputCheckbox |
             InputType::InputRadio => ValueMode::DefaultOn,
             InputType::InputPassword |
             InputType::InputText => ValueMode::Value,
             InputType::InputFile => ValueMode::Filename,
         }
     }
-
-    // this method exists so that the functions SetSelectionStart() and SetSelectionEnd()
-    // don't needlessly allocate strings
-    fn set_selection_range(&self, start: u32, end: u32, direction: &SelectionDirection) {
-        let mut text_input = self.textinput.borrow_mut();
-
-        let mut start = start as usize;
-        let mut end = end as usize;
-
-        let text_end = text_input.get_content().len();
-        if start > text_end {
-            start = text_end;
-        }
-        if end > text_end {
-            end = text_end;
-        }
-
-        if start >= end {
-            start = end;
-        }
-
-        text_input.selection_begin = Some(text_input.get_text_point_for_absolute_point(start));
-        text_input.edit_point = text_input.get_text_point_for_absolute_point(end);
-        self.selection_direction.set(*direction);
-        self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
-    }
-
 }
 
 pub trait LayoutHTMLInputElementHelpers {
     #[allow(unsafe_code)]
     unsafe fn value_for_layout(self) -> String;
     #[allow(unsafe_code)]
     unsafe fn size_for_layout(self) -> u32;
     #[allow(unsafe_code)]
@@ -542,70 +504,54 @@ impl HTMLInputElementMethods for HTMLInp
             NodeList::empty(&window)
         } else {
             self.upcast::<HTMLElement>().labels()
         }
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-input-selectionstart
     fn SelectionStart(&self) -> u32 {
-        let text_input = self.textinput.borrow();
-        let selection_start = match text_input.selection_begin {
-            Some(selection_begin_point) => {
-                text_input.get_absolute_point_for_text_point(&selection_begin_point)
-            },
-            None => text_input.get_absolute_insertion_point()
-        };
-
-        selection_start as u32
+        self.textinput.borrow().get_selection_start()
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart
     fn SetSelectionStart(&self, start: u32) {
-        self.set_selection_range(start, self.SelectionEnd(), &self.selection_direction.get());
+        let selection_end = self.SelectionEnd();
+        self.textinput.borrow_mut().set_selection_range(start, selection_end);
+        self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend
     fn SelectionEnd(&self) -> u32 {
-        let text_input = self.textinput.borrow();
-        text_input.get_absolute_insertion_point() as u32
+        self.textinput.borrow().get_absolute_insertion_point() as u32
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend
     fn SetSelectionEnd(&self, end: u32) {
-        self.set_selection_range(self.SelectionStart(), end, &self.selection_direction.get());
+        let selection_start = self.SelectionStart();
+        self.textinput.borrow_mut().set_selection_range(selection_start, end);
+        self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection
     fn SelectionDirection(&self) -> DOMString {
-        match self.selection_direction.get() {
-            SelectionDirection::Forward => DOMString::from("forward"),
-            SelectionDirection::Backward => DOMString::from("backward"),
-            SelectionDirection::None => DOMString::from("none"),
-        }
+        DOMString::from(self.textinput.borrow().selection_direction)
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection
     fn SetSelectionDirection(&self, direction: DOMString) {
-        self.SetSelectionRange(self.SelectionStart(), self.SelectionEnd(), Some(direction));
+        self.textinput.borrow_mut().selection_direction = SelectionDirection::from(direction);
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-textarea/input-setselectionrange
     fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) {
-        let selection_direction = match direction {
-            Some(selection_direction) => {
-                match &*selection_direction {
-                    "forward" => SelectionDirection::Forward,
-                    "backward" => SelectionDirection::Backward,
-                    _ => SelectionDirection::None,
-                }
-            },
-            None => SelectionDirection::None,
-        };
-        self.set_selection_range(start, end, &selection_direction);
+        let direction = direction.map_or(SelectionDirection::None, |d| SelectionDirection::from(d));
+        self.textinput.borrow_mut().selection_direction = direction;
+        self.textinput.borrow_mut().set_selection_range(start, end);
+        self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
     }
 }
 
 
 #[allow(unsafe_code)]
 fn broadcast_radio_checked(broadcaster: &HTMLInputElement, group: Option<&Atom>) {
     match group {
         None | Some(&atom!("")) => {
--- a/servo/components/script/dom/htmltextareaelement.rs
+++ b/servo/components/script/dom/htmltextareaelement.rs
@@ -26,17 +26,17 @@ use dom::nodelist::NodeList;
 use dom::validation::Validatable;
 use dom::virtualmethods::VirtualMethods;
 use msg::constellation_msg::ConstellationChan;
 use range::Range;
 use script_traits::ScriptMsg as ConstellationMsg;
 use std::cell::Cell;
 use string_cache::Atom;
 use style::element_state::*;
-use textinput::{KeyReaction, Lines, TextInput};
+use textinput::{KeyReaction, Lines, TextInput, SelectionDirection};
 use util::str::DOMString;
 
 #[dom_struct]
 pub struct HTMLTextAreaElement {
     htmlelement: HTMLElement,
     #[ignore_heap_size_of = "#7193"]
     textinput: DOMRefCell<TextInput<ConstellationChan<ConstellationMsg>>>,
     // https://html.spec.whatwg.org/multipage/#concept-textarea-dirty
@@ -101,17 +101,18 @@ impl HTMLTextAreaElement {
     fn new_inherited(localName: Atom,
                      prefix: Option<DOMString>,
                      document: &Document) -> HTMLTextAreaElement {
         let chan = document.window().constellation_chan();
         HTMLTextAreaElement {
             htmlelement:
                 HTMLElement::new_inherited_with_state(IN_ENABLED_STATE,
                                                       localName, prefix, document),
-            textinput: DOMRefCell::new(TextInput::new(Lines::Multiple, DOMString::new(), chan, None)),
+            textinput: DOMRefCell::new(TextInput::new(
+                    Lines::Multiple, DOMString::new(), chan, None, SelectionDirection::None)),
             value_changed: Cell::new(false),
         }
     }
 
     #[allow(unrooted_must_root)]
     pub fn new(localName: Atom,
                prefix: Option<DOMString>,
                document: &Document) -> Root<HTMLTextAreaElement> {
@@ -211,16 +212,58 @@ impl HTMLTextAreaElementMethods for HTML
 
         self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-lfe-labels
     fn Labels(&self) -> Root<NodeList> {
         self.upcast::<HTMLElement>().labels()
     }
+
+    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection
+    fn SetSelectionDirection(&self, direction: DOMString) {
+        self.textinput.borrow_mut().selection_direction = SelectionDirection::from(direction);
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectiondirection
+    fn SelectionDirection(&self) -> DOMString {
+        DOMString::from(self.textinput.borrow().selection_direction)
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend
+    fn SetSelectionEnd(&self, end: u32) {
+        let selection_start = self.SelectionStart();
+        self.textinput.borrow_mut().set_selection_range(selection_start, end);
+        self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionend
+    fn SelectionEnd(&self) -> u32 {
+        self.textinput.borrow().get_absolute_insertion_point() as u32
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart
+    fn SetSelectionStart(&self, start: u32) {
+        let selection_end = self.SelectionEnd();
+        self.textinput.borrow_mut().set_selection_range(start, selection_end);
+        self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-selectionstart
+    fn SelectionStart(&self) -> u32 {
+        self.textinput.borrow().get_selection_start()
+    }
+
+    // https://html.spec.whatwg.org/multipage/#dom-textarea/input-setselectionrange
+    fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) {
+        let direction = direction.map_or(SelectionDirection::None, |d| SelectionDirection::from(d));
+        self.textinput.borrow_mut().selection_direction = direction;
+        self.textinput.borrow_mut().set_selection_range(start, end);
+        self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
+    }
 }
 
 
 impl HTMLTextAreaElement {
     // https://html.spec.whatwg.org/multipage/#concept-fe-mutable
     pub fn mutable(&self) -> bool {
         // https://html.spec.whatwg.org/multipage/#the-textarea-element:concept-fe-mutable
         !(self.Disabled() || self.ReadOnly())
--- a/servo/components/script/dom/webidls/HTMLTextAreaElement.webidl
+++ b/servo/components/script/dom/webidls/HTMLTextAreaElement.webidl
@@ -33,16 +33,16 @@ interface HTMLTextAreaElement : HTMLElem
   //readonly attribute DOMString validationMessage;
   //boolean checkValidity();
   //boolean reportValidity();
   //void setCustomValidity(DOMString error);
 
   readonly attribute NodeList labels;
 
   //void select();
-  //         attribute unsigned long selectionStart;
-  //         attribute unsigned long selectionEnd;
-  //         attribute DOMString selectionDirection;
+           attribute unsigned long selectionStart;
+           attribute unsigned long selectionEnd;
+           attribute DOMString selectionDirection;
   //void setRangeText(DOMString replacement);
   //void setRangeText(DOMString replacement, unsigned long start, unsigned long end,
   //                  optional SelectionMode selectionMode = "preserve");
-  //void setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction);
+  void setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction);
 };
--- a/servo/components/script/textinput.rs
+++ b/servo/components/script/textinput.rs
@@ -16,16 +16,43 @@ use std::usize;
 use util::str::DOMString;
 
 #[derive(Copy, Clone, PartialEq)]
 pub enum Selection {
     Selected,
     NotSelected
 }
 
+#[derive(JSTraceable, PartialEq, Copy, Clone, HeapSizeOf)]
+pub enum SelectionDirection {
+    Forward,
+    Backward,
+    None,
+}
+
+impl From<DOMString> for SelectionDirection {
+    fn from(direction: DOMString) -> SelectionDirection {
+        match direction.as_ref() {
+            "forward" => SelectionDirection::Forward,
+            "backward" => SelectionDirection::Backward,
+            _ => SelectionDirection::None,
+        }
+    }
+}
+
+impl From<SelectionDirection> for DOMString {
+    fn from(direction: SelectionDirection) -> DOMString {
+        match direction {
+            SelectionDirection::Forward => DOMString::from("forward"),
+            SelectionDirection::Backward => DOMString::from("backward"),
+            SelectionDirection::None => DOMString::from("none"),
+        }
+    }
+}
+
 #[derive(JSTraceable, Copy, Clone, HeapSizeOf, PartialEq)]
 pub struct TextPoint {
     /// 0-based line number
     pub line: usize,
     /// 0-based column number in UTF-8 bytes
     pub index: usize,
 }
 
@@ -40,17 +67,18 @@ pub struct TextInput<T: ClipboardProvide
     pub selection_begin: Option<TextPoint>,
     /// Is this a multiline input?
     multiline: bool,
     #[ignore_heap_size_of = "Can't easily measure this generic type"]
     clipboard_provider: T,
     /// The maximum number of UTF-16 code units this text input is allowed to hold.
     ///
     /// https://html.spec.whatwg.org/multipage/#attr-fe-maxlength
-    pub max_length: Option<usize>
+    pub max_length: Option<usize>,
+    pub selection_direction: SelectionDirection,
 }
 
 /// Resulting action to be taken by the owner of a text input that is handling an event.
 pub enum KeyReaction {
     TriggerDefaultAction,
     DispatchInput,
     RedrawSelection,
     Nothing,
@@ -133,24 +161,27 @@ fn len_of_first_n_code_units(text: &str,
         }
         utf8_len += c.len_utf8();
     }
     utf8_len
 }
 
 impl<T: ClipboardProvider> TextInput<T> {
     /// Instantiate a new text input control
-    pub fn new(lines: Lines, initial: DOMString, clipboard_provider: T, max_length: Option<usize>) -> TextInput<T> {
+    pub fn new(lines: Lines, initial: DOMString,
+               clipboard_provider: T, max_length: Option<usize>,
+               selection_direction: SelectionDirection) -> TextInput<T> {
         let mut i = TextInput {
             lines: vec!(),
             edit_point: Default::default(),
             selection_begin: None,
             multiline: lines == Lines::Multiple,
             clipboard_provider: clipboard_provider,
-            max_length: max_length
+            max_length: max_length,
+            selection_direction: selection_direction,
         };
         i.set_content(initial);
         i
     }
 
     /// Remove a character at the current editing point
     pub fn delete_char(&mut self, dir: Direction) {
         if self.selection_begin.is_none() || self.selection_begin == Some(self.edit_point) {
@@ -602,9 +633,37 @@ impl<T: ClipboardProvider> TextInput<T> 
                 acc
             }
         });
 
         TextPoint {
             line: line, index: index
         }
     }
+
+    pub fn set_selection_range(&mut self, start: u32, end: u32) {
+        let mut start = start as usize;
+        let mut end = end as usize;
+        let text_end = self.get_content().len();
+
+        if start > text_end {
+            start = text_end;
+        } else if end > text_end {
+            end = text_end;
+        } else if start >= end {
+            start = end;
+        }
+
+        self.selection_begin = Some(self.get_text_point_for_absolute_point(start));
+        self.edit_point = self.get_text_point_for_absolute_point(end);
+    }
+
+    pub fn get_selection_start(&self) -> u32 {
+        let selection_start = match self.selection_begin {
+            Some(selection_begin_point) => {
+                self.get_absolute_point_for_text_point(&selection_begin_point)
+            },
+            None => self.get_absolute_insertion_point()
+        };
+
+        selection_start as u32
+    }
 }
--- a/servo/tests/unit/script/textinput.rs
+++ b/servo/tests/unit/script/textinput.rs
@@ -8,40 +8,41 @@
 // except according to those terms.
 
 #[cfg(not(target_os = "macos"))]
 use msg::constellation_msg::CONTROL;
 #[cfg(target_os = "macos")]
 use msg::constellation_msg::SUPER;
 use msg::constellation_msg::{Key, KeyModifiers};
 use script::clipboard_provider::DummyClipboardContext;
-use script::textinput::{TextInput, TextPoint, Selection, Lines, Direction};
+use script::textinput::{TextInput, TextPoint, Selection, Lines, Direction, SelectionDirection};
 use util::str::DOMString;
 
 fn text_input(lines: Lines, s: &str) -> TextInput<DummyClipboardContext> {
-    TextInput::new(lines, DOMString::from(s), DummyClipboardContext::new(""), None)
+    TextInput::new(lines, DOMString::from(s), DummyClipboardContext::new(""), None, SelectionDirection::None)
 }
 
 #[test]
 fn test_set_content_ignores_max_length() {
     let mut textinput = TextInput::new(
-        Lines::Single, DOMString::from(""), DummyClipboardContext::new(""), Some(1)
+        Lines::Single, DOMString::from(""), DummyClipboardContext::new(""), Some(1), SelectionDirection::None
     );
 
     textinput.set_content(DOMString::from("mozilla rocks"));
     assert_eq!(textinput.get_content(), DOMString::from("mozilla rocks"));
 }
 
 #[test]
 fn test_textinput_when_inserting_multiple_lines_over_a_selection_respects_max_length() {
     let mut textinput = TextInput::new(
         Lines::Multiple,
         DOMString::from("hello\nworld"),
         DummyClipboardContext::new(""),
-        Some(17)
+        Some(17),
+        SelectionDirection::None,
     );
 
     textinput.edit_point = TextPoint { line: 0, index: 1 };
     textinput.adjust_horizontal(3, Selection::Selected);
     textinput.adjust_vertical(1, Selection::Selected);
 
     // Selection is now "hello\n
     //                    ------
@@ -54,61 +55,65 @@ fn test_textinput_when_inserting_multipl
 }
 
 #[test]
 fn test_textinput_when_inserting_multiple_lines_still_respects_max_length() {
     let mut textinput = TextInput::new(
         Lines::Multiple,
         DOMString::from("hello\nworld"),
         DummyClipboardContext::new(""),
-        Some(17)
+        Some(17),
+        SelectionDirection::None
     );
 
     textinput.edit_point = TextPoint { line: 1, index: 0 };
 
     textinput.insert_string("cruel\nterrible".to_string());
 
     assert_eq!(textinput.get_content(), "hello\ncruel\nworld");
 }
 
 #[test]
 fn test_textinput_when_content_is_already_longer_than_max_length_and_theres_no_selection_dont_insert_anything() {
     let mut textinput = TextInput::new(
         Lines::Single,
         DOMString::from("abc"),
         DummyClipboardContext::new(""),
-        Some(1)
+        Some(1),
+        SelectionDirection::None,
     );
 
     textinput.insert_char('a');
 
     assert_eq!(textinput.get_content(), "abc");
 }
 
 #[test]
 fn test_multi_line_textinput_with_maxlength_doesnt_allow_appending_characters_when_input_spans_lines() {
     let mut textinput = TextInput::new(
         Lines::Multiple,
         DOMString::from("abc\nd"),
         DummyClipboardContext::new(""),
-        Some(5)
+        Some(5),
+        SelectionDirection::None,
     );
 
     textinput.insert_char('a');
 
     assert_eq!(textinput.get_content(), "abc\nd");
 }
 
 #[test]
 fn test_single_line_textinput_with_max_length_doesnt_allow_appending_characters_when_replacing_a_selection() {
     let mut textinput = TextInput::new(
         Lines::Single,
         DOMString::from("abcde"),
         DummyClipboardContext::new(""),
-        Some(5)
+        Some(5),
+        SelectionDirection::None,
     );
 
     textinput.edit_point = TextPoint { line: 0, index: 1 };
     textinput.adjust_horizontal(3, Selection::Selected);
 
     // Selection is now "abcde"
     //                    ---
 
@@ -118,34 +123,36 @@ fn test_single_line_textinput_with_max_l
 }
 
 #[test]
 fn test_single_line_textinput_with_max_length_multibyte() {
     let mut textinput = TextInput::new(
         Lines::Single,
         DOMString::from(""),
         DummyClipboardContext::new(""),
-        Some(2)
+        Some(2),
+        SelectionDirection::None,
     );
 
     textinput.insert_char('á');
     assert_eq!(textinput.get_content(), "á");
     textinput.insert_char('é');
     assert_eq!(textinput.get_content(), "áé");
     textinput.insert_char('i');
     assert_eq!(textinput.get_content(), "áé");
 }
 
 #[test]
 fn test_single_line_textinput_with_max_length_multi_code_unit() {
     let mut textinput = TextInput::new(
         Lines::Single,
         DOMString::from(""),
         DummyClipboardContext::new(""),
-        Some(3)
+        Some(3),
+        SelectionDirection::None,
     );
 
     textinput.insert_char('\u{10437}');
     assert_eq!(textinput.get_content(), "\u{10437}");
     textinput.insert_char('\u{10437}');
     assert_eq!(textinput.get_content(), "\u{10437}");
     textinput.insert_char('x');
     assert_eq!(textinput.get_content(), "\u{10437}x");
@@ -154,30 +161,32 @@ fn test_single_line_textinput_with_max_l
 }
 
 #[test]
 fn test_single_line_textinput_with_max_length_inside_char() {
     let mut textinput = TextInput::new(
         Lines::Single,
         DOMString::from("\u{10437}"),
         DummyClipboardContext::new(""),
-        Some(1)
+        Some(1),
+        SelectionDirection::None,
     );
 
     textinput.insert_char('x');
     assert_eq!(textinput.get_content(), "\u{10437}");
 }
 
 #[test]
 fn test_single_line_textinput_with_max_length_doesnt_allow_appending_characters_after_max_length_is_reached() {
     let mut textinput = TextInput::new(
         Lines::Single,
         DOMString::from("a"),
         DummyClipboardContext::new(""),
-        Some(1)
+        Some(1),
+        SelectionDirection::None,
     );
 
     textinput.insert_char('b');
     assert_eq!(textinput.get_content(), "a");
 }
 
 #[test]
 fn test_textinput_delete_char() {
@@ -383,17 +392,18 @@ fn test_clipboard_paste() {
     #[cfg(target_os = "macos")]
     const MODIFIERS: KeyModifiers = SUPER;
     #[cfg(not(target_os = "macos"))]
     const MODIFIERS: KeyModifiers = CONTROL;
 
     let mut textinput = TextInput::new(Lines::Single,
                                        DOMString::from("defg"),
                                        DummyClipboardContext::new("abc"),
-                                       None);
+                                       None,
+                                       SelectionDirection::None);
     assert_eq!(textinput.get_content(), "defg");
     assert_eq!(textinput.edit_point.index, 0);
     textinput.handle_keydown_aux(Key::V, MODIFIERS);
     assert_eq!(textinput.get_content(), "abcdefg");
 }
 
 #[test]
 fn test_textinput_cursor_position_correct_after_clearing_selection() {