aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
Diffstat (limited to 'components')
-rw-r--r--components/compositing/constellation.rs20
-rw-r--r--components/script/dom/htmlinputelement.rs16
-rw-r--r--components/script/dom/htmltextareaelement.rs13
-rw-r--r--components/script/lib.rs1
-rw-r--r--components/script/textinput.rs132
-rw-r--r--components/style/selector_matching.rs61
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>,
}