diff options
Diffstat (limited to 'components')
-rw-r--r-- | components/compositing/constellation.rs | 20 | ||||
-rw-r--r-- | components/script/dom/htmlinputelement.rs | 16 | ||||
-rw-r--r-- | components/script/dom/htmltextareaelement.rs | 13 | ||||
-rw-r--r-- | components/script/lib.rs | 1 | ||||
-rw-r--r-- | components/script/textinput.rs | 132 | ||||
-rw-r--r-- | components/style/selector_matching.rs | 61 |
6 files changed, 169 insertions, 74 deletions
diff --git a/components/compositing/constellation.rs b/components/compositing/constellation.rs index dbb22b84fc6..b41f75c7b6e 100644 --- a/components/compositing/constellation.rs +++ b/components/compositing/constellation.rs @@ -536,6 +536,17 @@ impl<LTF: LayoutThreadFactory, STF: ScriptThreadFactory> Constellation<LTF, STF> Paint(FromPaintMsg) } + // Get one incoming request. + // This is one of the few places where the compositor is + // allowed to panic. If one of the receiver.recv() calls + // fails, it is because the matching sender has been + // reclaimed, but this can't happen in normal execution + // because the constellation keeps a pointer to the sender, + // so it should never be reclaimed. A possible scenario in + // which receiver.recv() fails is if some unsafe code + // produces undefined behaviour, resulting in the destructor + // being called. If this happens, there's not much we can do + // other than panic. let request = { let receiver_from_script = &self.script_receiver; let receiver_from_compositor = &self.compositor_receiver; @@ -543,16 +554,17 @@ impl<LTF: LayoutThreadFactory, STF: ScriptThreadFactory> Constellation<LTF, STF> let receiver_from_paint = &self.painter_receiver; select! { msg = receiver_from_script.recv() => - Request::Script(msg.unwrap()), + Request::Script(msg.expect("Unexpected script failure in constellation")), msg = receiver_from_compositor.recv() => - Request::Compositor(msg.unwrap()), + Request::Compositor(msg.expect("Unexpected compositor failure in constellation")), msg = receiver_from_layout.recv() => - Request::Layout(msg.unwrap()), + Request::Layout(msg.expect("Unexpected layout failure in constellation")), msg = receiver_from_paint.recv() => - Request::Paint(msg.unwrap()) + Request::Paint(msg.expect("Unexpected paint failure in constellation")) } }; + // Process the request. match request { // Messages from compositor diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index e14a91ae9d7..e2434d60759 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -200,6 +200,7 @@ impl HTMLInputElement { 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); } } @@ -401,7 +402,7 @@ impl HTMLInputElementMethods for HTMLInputElement { } self.value_changed.set(true); - self.force_relayout(); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); Ok(()) } @@ -586,11 +587,6 @@ fn in_same_group(other: &HTMLInputElement, owner: Option<&HTMLFormElement>, } impl HTMLInputElement { - fn force_relayout(&self) { - let doc = document_from_node(self); - doc.content_changed(self.upcast(), NodeDamage::OtherNodeDamage) - } - fn radio_group_updated(&self, group: Option<&Atom>) { if self.Checked() { broadcast_radio_checked(self, group); @@ -654,7 +650,7 @@ impl HTMLInputElement { self.get_radio_group_name().as_ref()); } - self.force_relayout(); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); //TODO: dispatch change event } @@ -684,7 +680,7 @@ impl HTMLInputElement { .expect("Failed to reset input value to default."); self.value_dirty.set(false); self.value_changed.set(false); - self.force_relayout(); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); } } @@ -889,11 +885,11 @@ impl VirtualMethods for HTMLInputElement { ChangeEventRunnable::send(self.upcast::<Node>()); } - self.force_relayout(); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); event.PreventDefault(); } RedrawSelection => { - self.force_relayout(); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); event.PreventDefault(); } Nothing => (), diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index 8b57d638adb..50f34c9ff85 100644 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -209,7 +209,7 @@ impl HTMLTextAreaElementMethods for HTMLTextAreaElement { self.textinput.borrow_mut().set_content(value); self.value_changed.set(true); - self.force_relayout(); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); } // https://html.spec.whatwg.org/multipage/#dom-lfe-labels @@ -233,13 +233,6 @@ impl HTMLTextAreaElement { } -impl HTMLTextAreaElement { - fn force_relayout(&self) { - let doc = document_from_node(self); - doc.content_changed(self.upcast(), NodeDamage::OtherNodeDamage) - } -} - impl VirtualMethods for HTMLTextAreaElement { fn super_type(&self) -> Option<&VirtualMethods> { Some(self.upcast::<HTMLElement>() as &VirtualMethods) @@ -324,11 +317,11 @@ impl VirtualMethods for HTMLTextAreaElement { ChangeEventRunnable::send(self.upcast::<Node>()); } - self.force_relayout(); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); event.PreventDefault(); } KeyReaction::RedrawSelection => { - self.force_relayout(); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); event.PreventDefault(); } KeyReaction::Nothing => (), diff --git a/components/script/lib.rs b/components/script/lib.rs index fa6dc8a385e..3ab51fc954e 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -11,6 +11,7 @@ #![feature(custom_attribute)] #![feature(custom_derive)] #![feature(fnbox)] +#![feature(iter_arith)] #![feature(mpsc_select)] #![feature(nonzero)] #![feature(on_unimplemented)] diff --git a/components/script/textinput.rs b/components/script/textinput.rs index cc5936d42b0..1be7962003c 100644 --- a/components/script/textinput.rs +++ b/components/script/textinput.rs @@ -42,6 +42,9 @@ pub struct TextInput<T: ClipboardProvider> { 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> } @@ -107,6 +110,32 @@ fn is_printable_key(key: Key) -> bool { } } +/// The length in bytes of the first n characters in a UTF-8 string. +/// +/// If the string has fewer than n characters, returns the length of the whole string. +fn len_of_first_n_chars(text: &str, n: usize) -> usize { + match text.char_indices().take(n).last() { + Some((index, ch)) => index + ch.len_utf8(), + None => 0 + } +} + +/// The length in bytes of the first n code units a string when encoded in UTF-16. +/// +/// If the string is fewer than n code units, returns the length of the whole string. +fn len_of_first_n_code_units(text: &str, n: usize) -> usize { + let mut utf8_len = 0; + let mut utf16_len = 0; + for c in text.chars() { + utf16_len += c.len_utf16(); + if utf16_len > n { + break; + } + 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> { @@ -155,6 +184,9 @@ impl<T: ClipboardProvider> TextInput<T> { }) } + /// Return the selection range as UTF-8 byte offsets from the start of the content. + /// + /// If there is no selection, returns an empty range at the insertion point. pub fn get_absolute_selection_range(&self) -> Range<usize> { match self.get_sorted_selection() { Some((begin, _end)) => @@ -165,44 +197,51 @@ impl<T: ClipboardProvider> TextInput<T> { } pub fn get_selection_text(&self) -> Option<String> { - self.get_sorted_selection().map(|(begin, end)| { - if begin.line != end.line { - let mut s = String::new(); - s.push_str(&self.lines[begin.line][begin.index..]); - for (_, line) in self.lines.iter().enumerate().filter(|&(i, _)| begin.line < i && i < end.line) { - s.push_str("\n"); - s.push_str(line); - } - s.push_str("\n"); - s.push_str(&self.lines[end.line][..end.index]); - s - } else { - self.lines[begin.line][begin.index..end.index].to_owned() - } - }) + let text = self.fold_selection_slices(String::new(), |s, slice| s.push_str(slice)); + if text.is_empty() { + return None + } + Some(text) } + /// The length of the selected text in UTF-8 bytes. fn selection_len(&self) -> usize { - if let Some((begin, end)) = self.get_sorted_selection() { - let prefix = &self.lines[begin.line][0..begin.index]; - let suffix = &self.lines[end.line][end.index..]; - let lines_prefix = &self.lines[..begin.line]; - let lines_suffix = &self.lines[end.line + 1..]; - - self.len() - (prefix.chars().count() + - suffix.chars().count() + - lines_prefix.iter().fold(0, |m, i| m + i.chars().count() + 1) + - lines_suffix.iter().fold(0, |m, i| m + i.chars().count() + 1)) - } else { - 0 + self.fold_selection_slices(0, |len, slice| *len += slice.len()) + } + + /// The length of the selected text in UTF-16 code units. + fn selection_utf16_len(&self) -> usize { + self.fold_selection_slices(0usize, + |len, slice| *len += slice.chars().map(char::len_utf16).sum()) + } + + /// Run the callback on a series of slices that, concatenated, make up the selected text. + /// + /// The accumulator `acc` can be mutated by the callback, and will be returned at the end. + fn fold_selection_slices<B, F: FnMut(&mut B, &str)>(&self, mut acc: B, mut f: F) -> B { + match self.get_sorted_selection() { + Some((begin, end)) if begin.line == end.line => { + f(&mut acc, &self.lines[begin.line][begin.index..end.index]) + } + Some((begin, end)) => { + f(&mut acc, &self.lines[begin.line][begin.index..]); + for line in &self.lines[begin.line + 1 .. end.line] { + f(&mut acc, "\n"); + f(&mut acc, line); + } + f(&mut acc, "\n"); + f(&mut acc, &self.lines[end.line][..end.index]) + } + None => {} } + acc } pub fn replace_selection(&mut self, insert: DOMString) { if let Some((begin, end)) = self.get_sorted_selection() { let allowed_to_insert_count = if let Some(max_length) = self.max_length { - let len_after_selection_replaced = self.len() - self.selection_len(); - if len_after_selection_replaced > max_length { + let len_after_selection_replaced = self.utf16_len() - self.selection_utf16_len(); + if len_after_selection_replaced >= max_length { // If, after deleting the selection, the len is still greater than the max // length, then don't delete/insert anything return @@ -213,8 +252,8 @@ impl<T: ClipboardProvider> TextInput<T> { usize::MAX }; - let last_char_to_insert = min(allowed_to_insert_count, insert.chars().count()); - let chars_to_insert = (&insert[0 .. last_char_to_insert]).to_owned(); + let last_char_index = len_of_first_n_code_units(&*insert, allowed_to_insert_count); + let chars_to_insert = &insert[..last_char_index]; self.clear_selection(); @@ -225,7 +264,7 @@ impl<T: ClipboardProvider> TextInput<T> { let lines_suffix = &self.lines[end.line + 1..]; let mut insert_lines = if self.multiline { - chars_to_insert.split('\n').map(|s| DOMString::from(s.to_owned())).collect() + chars_to_insert.split('\n').map(|s| DOMString::from(s)).collect() } else { vec!(DOMString::from(chars_to_insert)) }; @@ -288,11 +327,14 @@ impl<T: ClipboardProvider> TextInput<T> { return; } + + let col = self.lines[self.edit_point.line][..self.edit_point.index].chars().count(); + self.edit_point.line = target_line as usize; - self.edit_point.index = min(self.current_line_length(), self.edit_point.index); + self.edit_point.index = len_of_first_n_chars(&self.lines[self.edit_point.line], col); } - /// Adjust the editing point position by a given number of columns. If the adjustment + /// Adjust the editing point position by a given number of bytes. If the adjustment /// requested is larger than is available in the current line, the editing point is /// adjusted vertically and the process repeats with the remaining adjustment requested. pub fn adjust_horizontal(&mut self, adjust: isize, select: Selection) { @@ -329,7 +371,7 @@ impl<T: ClipboardProvider> TextInput<T> { self.perform_horizontal_adjustment(adjust, select); } - // Return whether to cancel the caret move + /// Return whether to cancel the caret move fn adjust_selection_for_horizontal_change(&mut self, adjust: Direction, select: Selection) -> bool { if select == Selection::Selected { @@ -479,9 +521,24 @@ impl<T: ClipboardProvider> TextInput<T> { } } + /// The length of the content in bytes. pub fn len(&self) -> usize { self.lines.iter().fold(0, |m, l| { - m + l.len() + 1 + m + l.len() + 1 // + 1 for the '\n' + }) - 1 + } + + /// The length of the content in bytes. + pub fn utf16_len(&self) -> usize { + self.lines.iter().fold(0, |m, l| { + m + l.chars().map(char::len_utf16).sum::<usize>() + 1 // + 1 for the '\n' + }) - 1 + } + + /// The length of the content in chars. + pub fn char_count(&self) -> usize { + self.lines.iter().fold(0, |m, l| { + m + l.chars().count() + 1 // + 1 for the '\n' }) - 1 } @@ -510,10 +567,12 @@ impl<T: ClipboardProvider> TextInput<T> { self.selection_begin = None; } + /// Get the insertion point as a byte offset from the start of the content. pub fn get_absolute_insertion_point(&self) -> usize { self.get_absolute_point_for_text_point(&self.edit_point) } + /// Convert a TextPoint into a byte offset from the start of the content. pub fn get_absolute_point_for_text_point(&self, text_point: &TextPoint) -> usize { self.lines.iter().enumerate().fold(0, |acc, (i, val)| { if i < text_point.line { @@ -524,6 +583,7 @@ impl<T: ClipboardProvider> TextInput<T> { }) + text_point.index } + /// Convert a byte offset from the start of the content into a TextPoint. pub fn get_text_point_for_absolute_point(&self, abs_point: usize) -> TextPoint { let mut index = abs_point; let mut line = 0; diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs index bb063c805e3..d210f888e39 100644 --- a/components/style/selector_matching.rs +++ b/components/style/selector_matching.rs @@ -83,27 +83,47 @@ lazy_static! { }; } +/// This structure holds all the selectors and device characteristics +/// for a given document. The selectors are converted into `Rule`s +/// (defined in rust-selectors), and introduced in a `SelectorMap` +/// depending on the pseudo-element (see `PerPseudoElementSelectorMap`), +/// stylesheet origin (see `PerOriginSelectorMap`), and priority +/// (see the `normal` and `important` fields in `PerOriginSelectorMap`). +/// +/// This structure is effectively created once per pipeline, in the +/// LayoutThread corresponding to that pipeline. +/// +/// The stylist is parameterized on `SelectorImplExt`, a trait that extends +/// `selectors::parser::SelectorImpl`, and that allows to customise what +/// pseudo-classes and pseudo-elements are parsed. This is actually either +/// `ServoSelectorImpl`, the implementation used by Servo's layout system in +/// regular builds, or `GeckoSelectorImpl`, the implementation used in the +/// geckolib port. #[derive(HeapSizeOf)] pub struct Stylist<Impl: SelectorImplExt> { - // Device that the stylist is currently evaluating against. + /// Device that the stylist is currently evaluating against. pub device: Device, - // Viewport constraints based on the current device. + /// Viewport constraints based on the current device. viewport_constraints: Option<ViewportConstraints>, - // If true, the quirks-mode stylesheet is applied. + /// If true, the quirks-mode stylesheet is applied. quirks_mode: bool, - // If true, the device has changed, and the stylist needs to be updated. + /// If true, the device has changed, and the stylist needs to be updated. is_device_dirty: bool, - // The current selector maps, after evaluating media - // rules against the current device. + /// The current selector maps, after evaluating media + /// rules against the current device. element_map: PerPseudoElementSelectorMap<Impl>, - pseudos_map: HashMap<Impl::PseudoElement, PerPseudoElementSelectorMap<Impl>, BuildHasherDefault<::fnv::FnvHasher>>, + /// The selector maps corresponding to a given pseudo-element + /// (depending on the implementation) + pseudos_map: HashMap<Impl::PseudoElement, + PerPseudoElementSelectorMap<Impl>, + BuildHasherDefault<::fnv::FnvHasher>>, rules_source_order: usize, - // Selector dependencies used to compute restyle hints. + /// Selector dependencies used to compute restyle hints. state_deps: DependencySet<Impl>, } @@ -137,6 +157,7 @@ impl<Impl: SelectorImplExt> Stylist<Impl> { if !(self.is_device_dirty || stylesheets_changed) { return false; } + self.element_map = PerPseudoElementSelectorMap::new(); self.pseudos_map = HashMap::with_hasher(Default::default()); self.rules_source_order = 0; @@ -171,7 +192,7 @@ impl<Impl: SelectorImplExt> Stylist<Impl> { // them into the SelectorMap of that priority. macro_rules! append( ($style_rule: ident, $priority: ident) => { - if $style_rule.declarations.$priority.len() > 0 { + if !$style_rule.declarations.$priority.is_empty() { for selector in &$style_rule.selectors { let map = if let Some(ref pseudo) = selector.pseudo_element { self.pseudos_map.entry(pseudo.clone()) @@ -243,12 +264,13 @@ impl<Impl: SelectorImplExt> Stylist<Impl> { self.quirks_mode = enabled; } - /// Returns the applicable CSS declarations for the given element. This corresponds to - /// `ElementRuleCollector` in WebKit. + /// Returns the applicable CSS declarations for the given element. + /// This corresponds to `ElementRuleCollector` in WebKit. /// - /// The returned boolean indicates whether the style is *shareable*; that is, whether the - /// matched selectors are simple enough to allow the matching logic to be reduced to the logic - /// in `css::matching::PrivateMatchMethods::candidate_element_allows_for_style_sharing`. + /// The returned boolean indicates whether the style is *shareable*; + /// that is, whether the matched selectors are simple enough to allow the + /// matching logic to be reduced to the logic in + /// `css::matching::PrivateMatchMethods::candidate_element_allows_for_style_sharing`. pub fn push_applicable_declarations<E, V>( &self, element: &E, @@ -333,14 +355,20 @@ impl<Impl: SelectorImplExt> Stylist<Impl> { shareable } + #[inline] pub fn is_device_dirty(&self) -> bool { self.is_device_dirty } } +/// Map that contains the CSS rules for a given origin. #[derive(HeapSizeOf)] struct PerOriginSelectorMap<Impl: SelectorImpl> { + /// Rules that contains at least one property declararion with + /// normal importance. normal: SelectorMap<Vec<PropertyDeclaration>, Impl>, + /// Rules that contains at least one property declararion with + /// !important. important: SelectorMap<Vec<PropertyDeclaration>, Impl>, } @@ -354,10 +382,15 @@ impl<Impl: SelectorImpl> PerOriginSelectorMap<Impl> { } } +/// Map that contains the CSS rules for a specific PseudoElement +/// (or lack of PseudoElement). #[derive(HeapSizeOf)] struct PerPseudoElementSelectorMap<Impl: SelectorImpl> { + /// Rules from user agent stylesheets user_agent: PerOriginSelectorMap<Impl>, + /// Rules from author stylesheets author: PerOriginSelectorMap<Impl>, + /// Rules from user stylesheets user: PerOriginSelectorMap<Impl>, } |