mirror of
https://github.com/servo/servo.git
synced 2025-08-08 23:15:33 +01:00
Auto merge of #8381 - bholley:attr_restyle_hints, r=pcwalton
Implement attribute restyle hints <!-- Reviewable:start --> [<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/servo/8381) <!-- Reviewable:end -->
This commit is contained in:
commit
13226f8472
18 changed files with 587 additions and 316 deletions
|
@ -1145,10 +1145,8 @@ impl LayoutTask {
|
||||||
|
|
||||||
let modified_elements = document.drain_modified_elements();
|
let modified_elements = document.drain_modified_elements();
|
||||||
if !needs_dirtying {
|
if !needs_dirtying {
|
||||||
for &(el, old_state) in modified_elements.iter() {
|
for (el, snapshot) in modified_elements {
|
||||||
let hint = rw_data.stylist.restyle_hint_for_state_change(&el,
|
let hint = rw_data.stylist.compute_restyle_hint(&el, &snapshot, el.get_state());
|
||||||
el.get_state(),
|
|
||||||
old_state);
|
|
||||||
el.note_restyle_hint(hint);
|
el.note_restyle_hint(hint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,6 @@ use selectors::states::*;
|
||||||
use smallvec::VecLike;
|
use smallvec::VecLike;
|
||||||
use std::borrow::ToOwned;
|
use std::borrow::ToOwned;
|
||||||
use std::cell::{Ref, RefMut};
|
use std::cell::{Ref, RefMut};
|
||||||
use std::iter::FromIterator;
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -69,7 +68,7 @@ use style::legacy::UnsignedIntegerAttribute;
|
||||||
use style::node::TElementAttributes;
|
use style::node::TElementAttributes;
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
use style::properties::{PropertyDeclaration, PropertyDeclarationBlock};
|
use style::properties::{PropertyDeclaration, PropertyDeclarationBlock};
|
||||||
use style::restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint};
|
use style::restyle_hints::{ElementSnapshot, RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use util::str::{is_whitespace, search_index};
|
use util::str::{is_whitespace, search_index};
|
||||||
|
|
||||||
|
@ -92,14 +91,17 @@ impl<'a> PartialEq for LayoutNode<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'ln> LayoutNode<'ln> {
|
impl<'ln> LayoutNode<'ln> {
|
||||||
pub unsafe fn new(address: &TrustedNodeAddress) -> LayoutNode {
|
pub fn from_layout_js(n: LayoutJS<Node>) -> LayoutNode<'ln> {
|
||||||
let node = LayoutJS::from_trusted_node_address(*address);
|
|
||||||
LayoutNode {
|
LayoutNode {
|
||||||
node: node,
|
node: n,
|
||||||
chain: PhantomData,
|
chain: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub unsafe fn new(address: &TrustedNodeAddress) -> LayoutNode {
|
||||||
|
LayoutNode::from_layout_js(LayoutJS::from_trusted_node_address(*address))
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new layout node with the same lifetime as this layout node.
|
/// Creates a new layout node with the same lifetime as this layout node.
|
||||||
pub unsafe fn new_with_this_lifetime(&self, node: &LayoutJS<Node>) -> LayoutNode<'ln> {
|
pub unsafe fn new_with_this_lifetime(&self, node: &LayoutJS<Node>) -> LayoutNode<'ln> {
|
||||||
LayoutNode {
|
LayoutNode {
|
||||||
|
@ -216,12 +218,7 @@ impl<'ln> LayoutNode<'ln> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_document(&self) -> Option<LayoutDocument<'ln>> {
|
pub fn as_document(&self) -> Option<LayoutDocument<'ln>> {
|
||||||
self.node.downcast().map(|document| {
|
self.node.downcast().map(|document| LayoutDocument::from_layout_js(document))
|
||||||
LayoutDocument {
|
|
||||||
document: document,
|
|
||||||
chain: PhantomData,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parent_node(&self) -> Option<LayoutNode<'ln>> {
|
fn parent_node(&self) -> Option<LayoutNode<'ln>> {
|
||||||
|
@ -364,26 +361,24 @@ pub struct LayoutDocument<'le> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'le> LayoutDocument<'le> {
|
impl<'le> LayoutDocument<'le> {
|
||||||
pub fn as_node(&self) -> LayoutNode<'le> {
|
pub fn from_layout_js(doc: LayoutJS<Document>) -> LayoutDocument<'le> {
|
||||||
LayoutNode {
|
LayoutDocument {
|
||||||
node: self.document.upcast(),
|
document: doc,
|
||||||
chain: PhantomData,
|
chain: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_node(&self) -> LayoutNode<'le> {
|
||||||
|
LayoutNode::from_layout_js(self.document.upcast())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn root_node(&self) -> Option<LayoutNode<'le>> {
|
pub fn root_node(&self) -> Option<LayoutNode<'le>> {
|
||||||
self.as_node().children().find(LayoutNode::is_element)
|
self.as_node().children().find(LayoutNode::is_element)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drain_modified_elements(&self) -> Vec<(LayoutElement, ElementState)> {
|
pub fn drain_modified_elements(&self) -> Vec<(LayoutElement, ElementSnapshot)> {
|
||||||
unsafe {
|
let elements = unsafe { self.document.drain_modified_elements() };
|
||||||
let elements = self.document.drain_modified_elements();
|
elements.into_iter().map(|(el, snapshot)| (LayoutElement::from_layout_js(el), snapshot)).collect()
|
||||||
Vec::from_iter(elements.iter().map(|&(el, state)|
|
|
||||||
(LayoutElement {
|
|
||||||
element: el,
|
|
||||||
chain: PhantomData,
|
|
||||||
}, state)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,6 +390,13 @@ pub struct LayoutElement<'le> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'le> LayoutElement<'le> {
|
impl<'le> LayoutElement<'le> {
|
||||||
|
pub fn from_layout_js(el: LayoutJS<Element>) -> LayoutElement<'le> {
|
||||||
|
LayoutElement {
|
||||||
|
element: el,
|
||||||
|
chain: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn style_attribute(&self) -> &'le Option<PropertyDeclarationBlock> {
|
pub fn style_attribute(&self) -> &'le Option<PropertyDeclarationBlock> {
|
||||||
unsafe {
|
unsafe {
|
||||||
&*self.element.style_attribute()
|
&*self.element.style_attribute()
|
||||||
|
@ -402,10 +404,7 @@ impl<'le> LayoutElement<'le> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_node(&self) -> LayoutNode<'le> {
|
pub fn as_node(&self) -> LayoutNode<'le> {
|
||||||
LayoutNode {
|
LayoutNode::from_layout_js(self.element.upcast())
|
||||||
node: self.element.upcast(),
|
|
||||||
chain: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_state(&self) -> ElementState {
|
pub fn get_state(&self) -> ElementState {
|
||||||
|
@ -413,7 +412,7 @@ impl<'le> LayoutElement<'le> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Properly marks nodes as dirty in response to restyle hints.
|
/// Properly marks nodes as dirty in response to restyle hints.
|
||||||
pub fn note_restyle_hint(&self, hint: RestyleHint) {
|
pub fn note_restyle_hint(&self, mut hint: RestyleHint) {
|
||||||
// Bail early if there's no restyling to do.
|
// Bail early if there's no restyling to do.
|
||||||
if hint.is_empty() {
|
if hint.is_empty() {
|
||||||
return;
|
return;
|
||||||
|
@ -446,6 +445,11 @@ impl<'le> LayoutElement<'le> {
|
||||||
// Process hints.
|
// Process hints.
|
||||||
if hint.contains(RESTYLE_SELF) {
|
if hint.contains(RESTYLE_SELF) {
|
||||||
dirty_node(&node);
|
dirty_node(&node);
|
||||||
|
|
||||||
|
// FIXME(bholley, #8438): We currently need to RESTYLE_DESCENDANTS in the
|
||||||
|
// RESTYLE_SELF case in order to make sure "inherit" style structs propagate
|
||||||
|
// properly. See the explanation in the github issue.
|
||||||
|
hint.insert(RESTYLE_DESCENDANTS);
|
||||||
}
|
}
|
||||||
if hint.contains(RESTYLE_DESCENDANTS) {
|
if hint.contains(RESTYLE_DESCENDANTS) {
|
||||||
unsafe { node.set_dirty_descendants(true); }
|
unsafe { node.set_dirty_descendants(true); }
|
||||||
|
@ -464,12 +468,7 @@ impl<'le> LayoutElement<'le> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_element<'le>(node: LayoutJS<Node>) -> Option<LayoutElement<'le>> {
|
fn as_element<'le>(node: LayoutJS<Node>) -> Option<LayoutElement<'le>> {
|
||||||
node.downcast().map(|element| {
|
node.downcast().map(|element| LayoutElement::from_layout_js(element))
|
||||||
LayoutElement {
|
|
||||||
element: element,
|
|
||||||
chain: PhantomData,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! state_getter {
|
macro_rules! state_getter {
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use cssparser::RGBA;
|
|
||||||
use devtools_traits::AttrInfo;
|
use devtools_traits::AttrInfo;
|
||||||
use dom::bindings::cell::DOMRefCell;
|
use dom::bindings::cell::DOMRefCell;
|
||||||
use dom::bindings::codegen::Bindings::AttrBinding::{self, AttrMethods};
|
use dom::bindings::codegen::Bindings::AttrBinding::{self, AttrMethods};
|
||||||
|
@ -12,184 +11,21 @@ use dom::bindings::js::{JS, MutNullableHeap};
|
||||||
use dom::bindings::js::{LayoutJS, Root, RootedReference};
|
use dom::bindings::js::{LayoutJS, Root, RootedReference};
|
||||||
use dom::bindings::reflector::{Reflector, reflect_dom_object};
|
use dom::bindings::reflector::{Reflector, reflect_dom_object};
|
||||||
use dom::element::{AttributeMutation, Element};
|
use dom::element::{AttributeMutation, Element};
|
||||||
use dom::values::UNSIGNED_LONG_MAX;
|
|
||||||
use dom::virtualmethods::vtable_for;
|
use dom::virtualmethods::vtable_for;
|
||||||
use dom::window::Window;
|
use dom::window::Window;
|
||||||
use std::borrow::ToOwned;
|
use std::borrow::ToOwned;
|
||||||
use std::cell::Ref;
|
use std::cell::Ref;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::Deref;
|
|
||||||
use string_cache::{Atom, Namespace};
|
use string_cache::{Atom, Namespace};
|
||||||
use style::values::specified::Length;
|
pub use style::attr::{AttrIdentifier, AttrValue};
|
||||||
use util::str::{DOMString, LengthOrPercentageOrAuto, parse_unsigned_integer, parse_legacy_color, parse_length};
|
use util::str::DOMString;
|
||||||
use util::str::{split_html_space_chars, str_join};
|
|
||||||
|
|
||||||
#[derive(JSTraceable, PartialEq, Clone, HeapSizeOf)]
|
|
||||||
pub enum AttrValue {
|
|
||||||
String(DOMString),
|
|
||||||
TokenList(DOMString, Vec<Atom>),
|
|
||||||
UInt(DOMString, u32),
|
|
||||||
Atom(Atom),
|
|
||||||
Length(DOMString, Option<Length>),
|
|
||||||
Color(DOMString, Option<RGBA>),
|
|
||||||
Dimension(DOMString, LengthOrPercentageOrAuto),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AttrValue {
|
|
||||||
pub fn from_serialized_tokenlist(tokens: DOMString) -> AttrValue {
|
|
||||||
let atoms =
|
|
||||||
split_html_space_chars(&tokens)
|
|
||||||
.map(Atom::from_slice)
|
|
||||||
.fold(vec![], |mut acc, atom| {
|
|
||||||
if !acc.contains(&atom) { acc.push(atom) }
|
|
||||||
acc
|
|
||||||
});
|
|
||||||
AttrValue::TokenList(tokens, atoms)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_atomic_tokens(atoms: Vec<Atom>) -> AttrValue {
|
|
||||||
let tokens = DOMString(str_join(&atoms, "\x20"));
|
|
||||||
AttrValue::TokenList(tokens, atoms)
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#reflecting-content-attributes-in-idl-attributes:idl-unsigned-long
|
|
||||||
pub fn from_u32(string: DOMString, default: u32) -> AttrValue {
|
|
||||||
let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
|
|
||||||
let result = if result > UNSIGNED_LONG_MAX {
|
|
||||||
default
|
|
||||||
} else {
|
|
||||||
result
|
|
||||||
};
|
|
||||||
AttrValue::UInt(string, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#limited-to-only-non-negative-numbers-greater-than-zero
|
|
||||||
pub fn from_limited_u32(string: DOMString, default: u32) -> AttrValue {
|
|
||||||
let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
|
|
||||||
let result = if result == 0 || result > UNSIGNED_LONG_MAX {
|
|
||||||
default
|
|
||||||
} else {
|
|
||||||
result
|
|
||||||
};
|
|
||||||
AttrValue::UInt(string, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_atomic(string: DOMString) -> AttrValue {
|
|
||||||
let value = Atom::from_slice(&string);
|
|
||||||
AttrValue::Atom(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_legacy_color(string: DOMString) -> AttrValue {
|
|
||||||
let parsed = parse_legacy_color(&string).ok();
|
|
||||||
AttrValue::Color(string, parsed)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_dimension(string: DOMString) -> AttrValue {
|
|
||||||
let parsed = parse_length(&string);
|
|
||||||
AttrValue::Dimension(string, parsed)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assumes the `AttrValue` is a `TokenList` and returns its tokens
|
|
||||||
///
|
|
||||||
/// ## Panics
|
|
||||||
///
|
|
||||||
/// Panics if the `AttrValue` is not a `TokenList`
|
|
||||||
pub fn as_tokens(&self) -> &[Atom] {
|
|
||||||
match *self {
|
|
||||||
AttrValue::TokenList(_, ref tokens) => tokens,
|
|
||||||
_ => panic!("Tokens not found"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assumes the `AttrValue` is an `Atom` and returns its value
|
|
||||||
///
|
|
||||||
/// ## Panics
|
|
||||||
///
|
|
||||||
/// Panics if the `AttrValue` is not an `Atom`
|
|
||||||
pub fn as_atom(&self) -> &Atom {
|
|
||||||
match *self {
|
|
||||||
AttrValue::Atom(ref value) => value,
|
|
||||||
_ => panic!("Atom not found"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assumes the `AttrValue` is a `Color` and returns its value
|
|
||||||
///
|
|
||||||
/// ## Panics
|
|
||||||
///
|
|
||||||
/// Panics if the `AttrValue` is not a `Color`
|
|
||||||
pub fn as_color(&self) -> Option<&RGBA> {
|
|
||||||
match *self {
|
|
||||||
AttrValue::Color(_, ref color) => color.as_ref(),
|
|
||||||
_ => panic!("Color not found"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assumes the `AttrValue` is a `Length` and returns its value
|
|
||||||
///
|
|
||||||
/// ## Panics
|
|
||||||
///
|
|
||||||
/// Panics if the `AttrValue` is not a `Length`
|
|
||||||
pub fn as_length(&self) -> Option<&Length> {
|
|
||||||
match *self {
|
|
||||||
AttrValue::Length(_, ref length) => length.as_ref(),
|
|
||||||
_ => panic!("Length not found"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assumes the `AttrValue` is a `Dimension` and returns its value
|
|
||||||
///
|
|
||||||
/// ## Panics
|
|
||||||
///
|
|
||||||
/// Panics if the `AttrValue` is not a `Dimension`
|
|
||||||
pub fn as_dimension(&self) -> &LengthOrPercentageOrAuto {
|
|
||||||
match *self {
|
|
||||||
AttrValue::Dimension(_, ref l) => l,
|
|
||||||
_ => panic!("Dimension not found"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the AttrValue as its integer representation, if any.
|
|
||||||
/// This corresponds to attribute values returned as `AttrValue::UInt(_)`
|
|
||||||
/// by `VirtualMethods::parse_plain_attribute()`.
|
|
||||||
///
|
|
||||||
/// ## Panics
|
|
||||||
///
|
|
||||||
/// Panics if the `AttrValue` is not a `UInt`
|
|
||||||
pub fn as_uint(&self) -> u32 {
|
|
||||||
if let AttrValue::UInt(_, value) = *self {
|
|
||||||
value
|
|
||||||
} else {
|
|
||||||
panic!("Uint not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for AttrValue {
|
|
||||||
type Target = str;
|
|
||||||
|
|
||||||
fn deref(&self) -> &str {
|
|
||||||
match *self {
|
|
||||||
AttrValue::String(ref value) |
|
|
||||||
AttrValue::TokenList(ref value, _) |
|
|
||||||
AttrValue::UInt(ref value, _) |
|
|
||||||
AttrValue::Length(ref value, _) |
|
|
||||||
AttrValue::Color(ref value, _) |
|
|
||||||
AttrValue::Dimension(ref value, _) => &value,
|
|
||||||
AttrValue::Atom(ref value) => &value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://dom.spec.whatwg.org/#interface-attr
|
// https://dom.spec.whatwg.org/#interface-attr
|
||||||
#[dom_struct]
|
#[dom_struct]
|
||||||
pub struct Attr {
|
pub struct Attr {
|
||||||
reflector_: Reflector,
|
reflector_: Reflector,
|
||||||
local_name: Atom,
|
identifier: AttrIdentifier,
|
||||||
value: DOMRefCell<AttrValue>,
|
value: DOMRefCell<AttrValue>,
|
||||||
name: Atom,
|
|
||||||
namespace: Namespace,
|
|
||||||
prefix: Option<Atom>,
|
|
||||||
|
|
||||||
/// the element that owns this attribute.
|
/// the element that owns this attribute.
|
||||||
owner: MutNullableHeap<JS<Element>>,
|
owner: MutNullableHeap<JS<Element>>,
|
||||||
|
@ -200,11 +36,13 @@ impl Attr {
|
||||||
prefix: Option<Atom>, owner: Option<&Element>) -> Attr {
|
prefix: Option<Atom>, owner: Option<&Element>) -> Attr {
|
||||||
Attr {
|
Attr {
|
||||||
reflector_: Reflector::new(),
|
reflector_: Reflector::new(),
|
||||||
local_name: local_name,
|
identifier: AttrIdentifier {
|
||||||
|
local_name: local_name,
|
||||||
|
name: name,
|
||||||
|
namespace: namespace,
|
||||||
|
prefix: prefix,
|
||||||
|
},
|
||||||
value: DOMRefCell::new(value),
|
value: DOMRefCell::new(value),
|
||||||
name: name,
|
|
||||||
namespace: namespace,
|
|
||||||
prefix: prefix,
|
|
||||||
owner: MutNullableHeap::new(owner),
|
owner: MutNullableHeap::new(owner),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -220,17 +58,17 @@ impl Attr {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn name(&self) -> &Atom {
|
pub fn name(&self) -> &Atom {
|
||||||
&self.name
|
&self.identifier.name
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn namespace(&self) -> &Namespace {
|
pub fn namespace(&self) -> &Namespace {
|
||||||
&self.namespace
|
&self.identifier.namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn prefix(&self) -> &Option<Atom> {
|
pub fn prefix(&self) -> &Option<Atom> {
|
||||||
&self.prefix
|
&self.identifier.prefix
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +88,7 @@ impl AttrMethods for Attr {
|
||||||
match self.owner() {
|
match self.owner() {
|
||||||
None => *self.value.borrow_mut() = AttrValue::String(value),
|
None => *self.value.borrow_mut() = AttrValue::String(value),
|
||||||
Some(owner) => {
|
Some(owner) => {
|
||||||
let value = owner.parse_attribute(&self.namespace, self.local_name(), value);
|
let value = owner.parse_attribute(&self.identifier.namespace, self.local_name(), value);
|
||||||
self.set_value(value, owner.r());
|
self.set_value(value, owner.r());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -278,12 +116,12 @@ impl AttrMethods for Attr {
|
||||||
|
|
||||||
// https://dom.spec.whatwg.org/#dom-attr-name
|
// https://dom.spec.whatwg.org/#dom-attr-name
|
||||||
fn Name(&self) -> DOMString {
|
fn Name(&self) -> DOMString {
|
||||||
DOMString((*self.name).to_owned())
|
DOMString((*self.identifier.name).to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://dom.spec.whatwg.org/#dom-attr-namespaceuri
|
// https://dom.spec.whatwg.org/#dom-attr-namespaceuri
|
||||||
fn GetNamespaceURI(&self) -> Option<DOMString> {
|
fn GetNamespaceURI(&self) -> Option<DOMString> {
|
||||||
let Namespace(ref atom) = self.namespace;
|
let Namespace(ref atom) = self.identifier.namespace;
|
||||||
match &**atom {
|
match &**atom {
|
||||||
"" => None,
|
"" => None,
|
||||||
url => Some(DOMString(url.to_owned())),
|
url => Some(DOMString(url.to_owned())),
|
||||||
|
@ -310,33 +148,38 @@ impl AttrMethods for Attr {
|
||||||
impl Attr {
|
impl Attr {
|
||||||
pub fn set_value(&self, mut value: AttrValue, owner: &Element) {
|
pub fn set_value(&self, mut value: AttrValue, owner: &Element) {
|
||||||
assert!(Some(owner) == self.owner().r());
|
assert!(Some(owner) == self.owner().r());
|
||||||
|
owner.will_mutate_attr();
|
||||||
mem::swap(&mut *self.value.borrow_mut(), &mut value);
|
mem::swap(&mut *self.value.borrow_mut(), &mut value);
|
||||||
if self.namespace == ns!("") {
|
if self.identifier.namespace == ns!("") {
|
||||||
vtable_for(owner.upcast()).attribute_mutated(
|
vtable_for(owner.upcast()).attribute_mutated(
|
||||||
self, AttributeMutation::Set(Some(&value)));
|
self, AttributeMutation::Set(Some(&value)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn identifier(&self) -> &AttrIdentifier {
|
||||||
|
&self.identifier
|
||||||
|
}
|
||||||
|
|
||||||
pub fn value(&self) -> Ref<AttrValue> {
|
pub fn value(&self) -> Ref<AttrValue> {
|
||||||
self.value.borrow()
|
self.value.borrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn local_name(&self) -> &Atom {
|
pub fn local_name(&self) -> &Atom {
|
||||||
&self.local_name
|
&self.identifier.local_name
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the owner element. Should be called after the attribute is added
|
/// Sets the owner element. Should be called after the attribute is added
|
||||||
/// or removed from its older parent.
|
/// or removed from its older parent.
|
||||||
pub fn set_owner(&self, owner: Option<&Element>) {
|
pub fn set_owner(&self, owner: Option<&Element>) {
|
||||||
let ref ns = self.namespace;
|
let ref ns = self.identifier.namespace;
|
||||||
match (self.owner().r(), owner) {
|
match (self.owner().r(), owner) {
|
||||||
(None, Some(new)) => {
|
(None, Some(new)) => {
|
||||||
// Already in the list of attributes of new owner.
|
// Already in the list of attributes of new owner.
|
||||||
assert!(new.get_attribute(&ns, &self.local_name) == Some(Root::from_ref(self)))
|
assert!(new.get_attribute(&ns, &self.identifier.local_name) == Some(Root::from_ref(self)))
|
||||||
}
|
}
|
||||||
(Some(old), None) => {
|
(Some(old), None) => {
|
||||||
// Already gone from the list of attributes of old owner.
|
// Already gone from the list of attributes of old owner.
|
||||||
assert!(old.get_attribute(&ns, &self.local_name).is_none())
|
assert!(old.get_attribute(&ns, &self.identifier.local_name).is_none())
|
||||||
}
|
}
|
||||||
(old, new) => assert!(old == new)
|
(old, new) => assert!(old == new)
|
||||||
}
|
}
|
||||||
|
@ -348,7 +191,7 @@ impl Attr {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn summarize(&self) -> AttrInfo {
|
pub fn summarize(&self) -> AttrInfo {
|
||||||
let Namespace(ref ns) = self.namespace;
|
let Namespace(ref ns) = self.identifier.namespace;
|
||||||
AttrInfo {
|
AttrInfo {
|
||||||
namespace: (**ns).to_owned(),
|
namespace: (**ns).to_owned(),
|
||||||
name: self.Name().0,
|
name: self.Name().0,
|
||||||
|
@ -400,7 +243,7 @@ impl AttrHelpersForLayout for LayoutJS<Attr> {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn local_name_atom_forever(&self) -> Atom {
|
unsafe fn local_name_atom_forever(&self) -> Atom {
|
||||||
(*self.unsafe_get()).local_name.clone()
|
(*self.unsafe_get()).identifier.local_name.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -82,7 +82,9 @@ use std::sync::Arc;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::mpsc::{Receiver, Sender};
|
use std::sync::mpsc::{Receiver, Sender};
|
||||||
use string_cache::{Atom, Namespace, QualName};
|
use string_cache::{Atom, Namespace, QualName};
|
||||||
|
use style::attr::{AttrIdentifier, AttrValue};
|
||||||
use style::properties::PropertyDeclarationBlock;
|
use style::properties::PropertyDeclarationBlock;
|
||||||
|
use style::restyle_hints::ElementSnapshot;
|
||||||
use style::values::specified::Length;
|
use style::values::specified::Length;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use util::str::{DOMString, LengthOrPercentageOrAuto};
|
use util::str::{DOMString, LengthOrPercentageOrAuto};
|
||||||
|
@ -289,6 +291,9 @@ no_jsmanaged_fields!(Length);
|
||||||
no_jsmanaged_fields!(ElementState);
|
no_jsmanaged_fields!(ElementState);
|
||||||
no_jsmanaged_fields!(DOMString);
|
no_jsmanaged_fields!(DOMString);
|
||||||
no_jsmanaged_fields!(Mime);
|
no_jsmanaged_fields!(Mime);
|
||||||
|
no_jsmanaged_fields!(AttrIdentifier);
|
||||||
|
no_jsmanaged_fields!(AttrValue);
|
||||||
|
no_jsmanaged_fields!(ElementSnapshot);
|
||||||
|
|
||||||
impl JSTraceable for Box<ScriptChan + Send> {
|
impl JSTraceable for Box<ScriptChan + Send> {
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -88,7 +88,6 @@ use net_traits::{AsyncResponseTarget, PendingAsyncLoad};
|
||||||
use num::ToPrimitive;
|
use num::ToPrimitive;
|
||||||
use script_task::{MainThreadScriptMsg, Runnable};
|
use script_task::{MainThreadScriptMsg, Runnable};
|
||||||
use script_traits::{MouseButton, TouchEventType, TouchId, UntrustedNodeAddress};
|
use script_traits::{MouseButton, TouchEventType, TouchId, UntrustedNodeAddress};
|
||||||
use selectors::states::*;
|
|
||||||
use std::ascii::AsciiExt;
|
use std::ascii::AsciiExt;
|
||||||
use std::borrow::ToOwned;
|
use std::borrow::ToOwned;
|
||||||
use std::boxed::FnBox;
|
use std::boxed::FnBox;
|
||||||
|
@ -102,6 +101,7 @@ use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::mpsc::channel;
|
use std::sync::mpsc::channel;
|
||||||
use string_cache::{Atom, QualName};
|
use string_cache::{Atom, QualName};
|
||||||
|
use style::restyle_hints::ElementSnapshot;
|
||||||
use style::stylesheets::Stylesheet;
|
use style::stylesheets::Stylesheet;
|
||||||
use time;
|
use time;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -188,8 +188,9 @@ pub struct Document {
|
||||||
/// This field is set to the document itself for inert documents.
|
/// This field is set to the document itself for inert documents.
|
||||||
/// https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document
|
/// https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document
|
||||||
appropriate_template_contents_owner_document: MutNullableHeap<JS<Document>>,
|
appropriate_template_contents_owner_document: MutNullableHeap<JS<Document>>,
|
||||||
/// For each element that has had a state change since the last restyle, track the original state.
|
/// For each element that has had a state or attribute change since the last restyle,
|
||||||
modified_elements: DOMRefCell<HashMap<JS<Element>, ElementState>>,
|
/// track the original condition of the element.
|
||||||
|
modified_elements: DOMRefCell<HashMap<JS<Element>, ElementSnapshot>>,
|
||||||
/// http://w3c.github.io/touch-events/#dfn-active-touch-point
|
/// http://w3c.github.io/touch-events/#dfn-active-touch-point
|
||||||
active_touch_points: DOMRefCell<Vec<JS<Touch>>>,
|
active_touch_points: DOMRefCell<Vec<JS<Touch>>>,
|
||||||
}
|
}
|
||||||
|
@ -1275,7 +1276,7 @@ pub enum DocumentSource {
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
pub trait LayoutDocumentHelpers {
|
pub trait LayoutDocumentHelpers {
|
||||||
unsafe fn is_html_document_for_layout(&self) -> bool;
|
unsafe fn is_html_document_for_layout(&self) -> bool;
|
||||||
unsafe fn drain_modified_elements(&self) -> Vec<(LayoutJS<Element>, ElementState)>;
|
unsafe fn drain_modified_elements(&self) -> Vec<(LayoutJS<Element>, ElementSnapshot)>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
|
@ -1287,7 +1288,7 @@ impl LayoutDocumentHelpers for LayoutJS<Document> {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[allow(unrooted_must_root)]
|
#[allow(unrooted_must_root)]
|
||||||
unsafe fn drain_modified_elements(&self) -> Vec<(LayoutJS<Element>, ElementState)> {
|
unsafe fn drain_modified_elements(&self) -> Vec<(LayoutJS<Element>, ElementSnapshot)> {
|
||||||
let mut elements = (*self.unsafe_get()).modified_elements.borrow_mut_for_layout();
|
let mut elements = (*self.unsafe_get()).modified_elements.borrow_mut_for_layout();
|
||||||
let drain = elements.drain();
|
let drain = elements.drain();
|
||||||
let layout_drain = drain.map(|(k, v)| (k.to_layout(), v));
|
let layout_drain = drain.map(|(k, v)| (k.to_layout(), v));
|
||||||
|
@ -1457,7 +1458,21 @@ impl Document {
|
||||||
|
|
||||||
pub fn element_state_will_change(&self, el: &Element) {
|
pub fn element_state_will_change(&self, el: &Element) {
|
||||||
let mut map = self.modified_elements.borrow_mut();
|
let mut map = self.modified_elements.borrow_mut();
|
||||||
map.entry(JS::from_ref(el)).or_insert(el.get_state());
|
let snapshot = map.entry(JS::from_ref(el)).or_insert(ElementSnapshot::new());
|
||||||
|
if snapshot.state.is_none() {
|
||||||
|
snapshot.state = Some(el.get_state());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn element_attr_will_change(&self, el: &Element) {
|
||||||
|
let mut map = self.modified_elements.borrow_mut();
|
||||||
|
let mut snapshot = map.entry(JS::from_ref(el)).or_insert(ElementSnapshot::new());
|
||||||
|
if snapshot.attrs.is_none() {
|
||||||
|
let attrs = el.attrs().iter()
|
||||||
|
.map(|attr| (attr.identifier().clone(), attr.value().clone()))
|
||||||
|
.collect();
|
||||||
|
snapshot.attrs = Some(attrs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,7 @@ use html5ever::serialize::TraversalScope;
|
||||||
use html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode};
|
use html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode};
|
||||||
use html5ever::tree_builder::{LimitedQuirks, NoQuirks, Quirks};
|
use html5ever::tree_builder::{LimitedQuirks, NoQuirks, Quirks};
|
||||||
use selectors::matching::{DeclarationBlock, matches};
|
use selectors::matching::{DeclarationBlock, matches};
|
||||||
|
use selectors::matching::{common_style_affecting_attributes, rare_style_affecting_attributes};
|
||||||
use selectors::parser::{AttrSelector, NamespaceConstraint, parse_author_origin_selector_list_from_str};
|
use selectors::parser::{AttrSelector, NamespaceConstraint, parse_author_origin_selector_list_from_str};
|
||||||
use selectors::states::*;
|
use selectors::states::*;
|
||||||
use smallvec::VecLike;
|
use smallvec::VecLike;
|
||||||
|
@ -851,6 +852,7 @@ impl Element {
|
||||||
name: Atom,
|
name: Atom,
|
||||||
namespace: Namespace,
|
namespace: Namespace,
|
||||||
prefix: Option<Atom>) {
|
prefix: Option<Atom>) {
|
||||||
|
self.will_mutate_attr();
|
||||||
let window = window_from_node(self);
|
let window = window_from_node(self);
|
||||||
let in_empty_ns = namespace == ns!("");
|
let in_empty_ns = namespace == ns!("");
|
||||||
let attr = Attr::new(&window, local_name, value, name, namespace, prefix, Some(self));
|
let attr = Attr::new(&window, local_name, value, name, namespace, prefix, Some(self));
|
||||||
|
@ -963,6 +965,7 @@ impl Element {
|
||||||
let idx = self.attrs.borrow().iter().position(|attr| find(&attr));
|
let idx = self.attrs.borrow().iter().position(|attr| find(&attr));
|
||||||
|
|
||||||
idx.map(|idx| {
|
idx.map(|idx| {
|
||||||
|
self.will_mutate_attr();
|
||||||
let attr = Root::from_ref(&*(*self.attrs.borrow())[idx]);
|
let attr = Root::from_ref(&*(*self.attrs.borrow())[idx]);
|
||||||
self.attrs.borrow_mut().remove(idx);
|
self.attrs.borrow_mut().remove(idx);
|
||||||
attr.set_owner(None);
|
attr.set_owner(None);
|
||||||
|
@ -1075,6 +1078,11 @@ impl Element {
|
||||||
assert!(&**local_name == local_name.to_ascii_lowercase());
|
assert!(&**local_name == local_name.to_ascii_lowercase());
|
||||||
self.set_attribute(local_name, AttrValue::UInt(DOMString(value.to_string()), value));
|
self.set_attribute(local_name, AttrValue::UInt(DOMString(value.to_string()), value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn will_mutate_attr(&self) {
|
||||||
|
let node = self.upcast::<Node>();
|
||||||
|
node.owner_doc().element_attr_will_change(self);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ElementMethods for Element {
|
impl ElementMethods for Element {
|
||||||
|
@ -1459,6 +1467,10 @@ impl ElementMethods for Element {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn fragment_affecting_attributes() -> [Atom; 3] {
|
||||||
|
[atom!("width"), atom!("height"), atom!("src")]
|
||||||
|
}
|
||||||
|
|
||||||
impl VirtualMethods for Element {
|
impl VirtualMethods for Element {
|
||||||
fn super_type(&self) -> Option<&VirtualMethods> {
|
fn super_type(&self) -> Option<&VirtualMethods> {
|
||||||
Some(self.upcast::<Node>() as &VirtualMethods)
|
Some(self.upcast::<Node>() as &VirtualMethods)
|
||||||
|
@ -1468,18 +1480,16 @@ impl VirtualMethods for Element {
|
||||||
self.super_type().unwrap().attribute_mutated(attr, mutation);
|
self.super_type().unwrap().attribute_mutated(attr, mutation);
|
||||||
let node = self.upcast::<Node>();
|
let node = self.upcast::<Node>();
|
||||||
let doc = node.owner_doc();
|
let doc = node.owner_doc();
|
||||||
let damage = match attr.local_name() {
|
match attr.local_name() {
|
||||||
&atom!(style) => {
|
&atom!(style) => {
|
||||||
// Modifying the `style` attribute might change style.
|
// Modifying the `style` attribute might change style.
|
||||||
*self.style_attribute.borrow_mut() =
|
*self.style_attribute.borrow_mut() =
|
||||||
mutation.new_value(attr).map(|value| {
|
mutation.new_value(attr).map(|value| {
|
||||||
parse_style_attribute(&value, &doc.base_url())
|
parse_style_attribute(&value, &doc.base_url())
|
||||||
});
|
});
|
||||||
NodeDamage::NodeStyleDamaged
|
if node.is_in_doc() {
|
||||||
},
|
doc.content_changed(node, NodeDamage::NodeStyleDamaged);
|
||||||
&atom!(class) => {
|
}
|
||||||
// Modifying a class can change style.
|
|
||||||
NodeDamage::NodeStyleDamaged
|
|
||||||
},
|
},
|
||||||
&atom!(id) => {
|
&atom!(id) => {
|
||||||
*self.id_attribute.borrow_mut() =
|
*self.id_attribute.borrow_mut() =
|
||||||
|
@ -1510,16 +1520,22 @@ impl VirtualMethods for Element {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NodeDamage::NodeStyleDamaged
|
|
||||||
},
|
},
|
||||||
_ => {
|
_ if attr.namespace() == &ns!("") => {
|
||||||
// Modifying any other attribute might change arbitrary things.
|
if fragment_affecting_attributes().iter().any(|a| a == attr.local_name()) ||
|
||||||
NodeDamage::OtherNodeDamage
|
common_style_affecting_attributes().iter().any(|a| &a.atom == attr.local_name()) ||
|
||||||
|
rare_style_affecting_attributes().iter().any(|a| a == attr.local_name())
|
||||||
|
{
|
||||||
|
doc.content_changed(node, NodeDamage::OtherNodeDamage);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
_ => {},
|
||||||
};
|
};
|
||||||
if node.is_in_doc() {
|
|
||||||
doc.content_changed(node, damage);
|
// Make sure we rev the version even if we didn't dirty the node. If we
|
||||||
}
|
// don't do this, various attribute-dependent htmlcollections (like those
|
||||||
|
// generated by getElementsByClassName) might become stale.
|
||||||
|
node.rev_version();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_plain_attribute(&self, name: &Atom, value: DOMString) -> AttrValue {
|
fn parse_plain_attribute(&self, name: &Atom, value: DOMString) -> AttrValue {
|
||||||
|
|
|
@ -488,13 +488,7 @@ impl Node {
|
||||||
self.dirty_impl(damage, true)
|
self.dirty_impl(damage, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dirty(&self, damage: NodeDamage) {
|
pub fn rev_version(&self) {
|
||||||
self.dirty_impl(damage, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dirty_impl(&self, damage: NodeDamage, force_ancestors: bool) {
|
|
||||||
|
|
||||||
// 0. Set version counter
|
|
||||||
// The new version counter is 1 plus the max of the node's current version counter,
|
// The new version counter is 1 plus the max of the node's current version counter,
|
||||||
// its descendants version, and the document's version. Normally, this will just be
|
// its descendants version, and the document's version. Normally, this will just be
|
||||||
// the document's version, but we do have to deal with the case where the node has moved
|
// the document's version, but we do have to deal with the case where the node has moved
|
||||||
|
@ -505,6 +499,15 @@ impl Node {
|
||||||
ancestor.inclusive_descendants_version.set(version);
|
ancestor.inclusive_descendants_version.set(version);
|
||||||
}
|
}
|
||||||
doc.inclusive_descendants_version.set(version);
|
doc.inclusive_descendants_version.set(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dirty(&self, damage: NodeDamage) {
|
||||||
|
self.dirty_impl(damage, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dirty_impl(&self, damage: NodeDamage, force_ancestors: bool) {
|
||||||
|
// 0. Set version counter
|
||||||
|
self.rev_version();
|
||||||
|
|
||||||
// 1. Dirty self.
|
// 1. Dirty self.
|
||||||
match damage {
|
match damage {
|
||||||
|
|
178
components/style/attr.rs
Normal file
178
components/style/attr.rs
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
/* 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 http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use cssparser::RGBA;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use string_cache::{Atom, Namespace};
|
||||||
|
use util::str::{DOMString, LengthOrPercentageOrAuto, parse_unsigned_integer, parse_legacy_color, parse_length};
|
||||||
|
use util::str::{split_html_space_chars, str_join};
|
||||||
|
use values::specified::{Length};
|
||||||
|
|
||||||
|
// Duplicated from script::dom::values.
|
||||||
|
const UNSIGNED_LONG_MAX: u32 = 2147483647;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, HeapSizeOf)]
|
||||||
|
pub enum AttrValue {
|
||||||
|
String(DOMString),
|
||||||
|
TokenList(DOMString, Vec<Atom>),
|
||||||
|
UInt(DOMString, u32),
|
||||||
|
Atom(Atom),
|
||||||
|
Length(DOMString, Option<Length>),
|
||||||
|
Color(DOMString, Option<RGBA>),
|
||||||
|
Dimension(DOMString, LengthOrPercentageOrAuto),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AttrValue {
|
||||||
|
pub fn from_serialized_tokenlist(tokens: DOMString) -> AttrValue {
|
||||||
|
let atoms =
|
||||||
|
split_html_space_chars(&tokens)
|
||||||
|
.map(Atom::from_slice)
|
||||||
|
.fold(vec![], |mut acc, atom| {
|
||||||
|
if !acc.contains(&atom) { acc.push(atom) }
|
||||||
|
acc
|
||||||
|
});
|
||||||
|
AttrValue::TokenList(tokens, atoms)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_atomic_tokens(atoms: Vec<Atom>) -> AttrValue {
|
||||||
|
let tokens = DOMString(str_join(&atoms, "\x20"));
|
||||||
|
AttrValue::TokenList(tokens, atoms)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/#reflecting-content-attributes-in-idl-attributes:idl-unsigned-long
|
||||||
|
pub fn from_u32(string: DOMString, default: u32) -> AttrValue {
|
||||||
|
let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
|
||||||
|
let result = if result > UNSIGNED_LONG_MAX {
|
||||||
|
default
|
||||||
|
} else {
|
||||||
|
result
|
||||||
|
};
|
||||||
|
AttrValue::UInt(string, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/#limited-to-only-non-negative-numbers-greater-than-zero
|
||||||
|
pub fn from_limited_u32(string: DOMString, default: u32) -> AttrValue {
|
||||||
|
let result = parse_unsigned_integer(string.chars()).unwrap_or(default);
|
||||||
|
let result = if result == 0 || result > UNSIGNED_LONG_MAX {
|
||||||
|
default
|
||||||
|
} else {
|
||||||
|
result
|
||||||
|
};
|
||||||
|
AttrValue::UInt(string, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_atomic(string: DOMString) -> AttrValue {
|
||||||
|
let value = Atom::from_slice(&string);
|
||||||
|
AttrValue::Atom(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_legacy_color(string: DOMString) -> AttrValue {
|
||||||
|
let parsed = parse_legacy_color(&string).ok();
|
||||||
|
AttrValue::Color(string, parsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_dimension(string: DOMString) -> AttrValue {
|
||||||
|
let parsed = parse_length(&string);
|
||||||
|
AttrValue::Dimension(string, parsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assumes the `AttrValue` is a `TokenList` and returns its tokens
|
||||||
|
///
|
||||||
|
/// ## Panics
|
||||||
|
///
|
||||||
|
/// Panics if the `AttrValue` is not a `TokenList`
|
||||||
|
pub fn as_tokens(&self) -> &[Atom] {
|
||||||
|
match *self {
|
||||||
|
AttrValue::TokenList(_, ref tokens) => tokens,
|
||||||
|
_ => panic!("Tokens not found"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assumes the `AttrValue` is an `Atom` and returns its value
|
||||||
|
///
|
||||||
|
/// ## Panics
|
||||||
|
///
|
||||||
|
/// Panics if the `AttrValue` is not an `Atom`
|
||||||
|
pub fn as_atom(&self) -> &Atom {
|
||||||
|
match *self {
|
||||||
|
AttrValue::Atom(ref value) => value,
|
||||||
|
_ => panic!("Atom not found"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assumes the `AttrValue` is a `Color` and returns its value
|
||||||
|
///
|
||||||
|
/// ## Panics
|
||||||
|
///
|
||||||
|
/// Panics if the `AttrValue` is not a `Color`
|
||||||
|
pub fn as_color(&self) -> Option<&RGBA> {
|
||||||
|
match *self {
|
||||||
|
AttrValue::Color(_, ref color) => color.as_ref(),
|
||||||
|
_ => panic!("Color not found"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assumes the `AttrValue` is a `Length` and returns its value
|
||||||
|
///
|
||||||
|
/// ## Panics
|
||||||
|
///
|
||||||
|
/// Panics if the `AttrValue` is not a `Length`
|
||||||
|
pub fn as_length(&self) -> Option<&Length> {
|
||||||
|
match *self {
|
||||||
|
AttrValue::Length(_, ref length) => length.as_ref(),
|
||||||
|
_ => panic!("Length not found"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assumes the `AttrValue` is a `Dimension` and returns its value
|
||||||
|
///
|
||||||
|
/// ## Panics
|
||||||
|
///
|
||||||
|
/// Panics if the `AttrValue` is not a `Dimension`
|
||||||
|
pub fn as_dimension(&self) -> &LengthOrPercentageOrAuto {
|
||||||
|
match *self {
|
||||||
|
AttrValue::Dimension(_, ref l) => l,
|
||||||
|
_ => panic!("Dimension not found"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the AttrValue as its integer representation, if any.
|
||||||
|
/// This corresponds to attribute values returned as `AttrValue::UInt(_)`
|
||||||
|
/// by `VirtualMethods::parse_plain_attribute()`.
|
||||||
|
///
|
||||||
|
/// ## Panics
|
||||||
|
///
|
||||||
|
/// Panics if the `AttrValue` is not a `UInt`
|
||||||
|
pub fn as_uint(&self) -> u32 {
|
||||||
|
if let AttrValue::UInt(_, value) = *self {
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
panic!("Uint not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for AttrValue {
|
||||||
|
type Target = str;
|
||||||
|
|
||||||
|
fn deref(&self) -> &str {
|
||||||
|
match *self {
|
||||||
|
AttrValue::String(ref value) |
|
||||||
|
AttrValue::TokenList(ref value, _) |
|
||||||
|
AttrValue::UInt(ref value, _) |
|
||||||
|
AttrValue::Length(ref value, _) |
|
||||||
|
AttrValue::Color(ref value, _) |
|
||||||
|
AttrValue::Dimension(ref value, _) => &value,
|
||||||
|
AttrValue::Atom(ref value) => &value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, HeapSizeOf, Debug)]
|
||||||
|
pub struct AttrIdentifier {
|
||||||
|
pub local_name: Atom,
|
||||||
|
pub name: Atom,
|
||||||
|
pub namespace: Namespace,
|
||||||
|
pub prefix: Option<Atom>,
|
||||||
|
}
|
|
@ -42,6 +42,7 @@ extern crate url;
|
||||||
extern crate util;
|
extern crate util;
|
||||||
|
|
||||||
pub mod animation;
|
pub mod animation;
|
||||||
|
pub mod attr;
|
||||||
mod custom_properties;
|
mod custom_properties;
|
||||||
pub mod font_face;
|
pub mod font_face;
|
||||||
pub mod legacy;
|
pub mod legacy;
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use attr::{AttrIdentifier, AttrValue};
|
||||||
use selectors::Element;
|
use selectors::Element;
|
||||||
use selectors::matching::matches_compound_selector;
|
use selectors::matching::matches_compound_selector;
|
||||||
use selectors::parser::{AttrSelector, Combinator, CompoundSelector, SimpleSelector};
|
use selectors::parser::{AttrSelector, Combinator, CompoundSelector, NamespaceConstraint, SimpleSelector};
|
||||||
use selectors::states::*;
|
use selectors::states::*;
|
||||||
use std::clone::Clone;
|
use std::clone::Clone;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -12,12 +13,10 @@ use string_cache::{Atom, Namespace};
|
||||||
|
|
||||||
/// When the ElementState of an element (like IN_HOVER_STATE) changes, certain
|
/// When the ElementState of an element (like IN_HOVER_STATE) changes, certain
|
||||||
/// pseudo-classes (like :hover) may require us to restyle that element, its
|
/// pseudo-classes (like :hover) may require us to restyle that element, its
|
||||||
/// siblings, and/or its descendants. Doing this conservatively is expensive,
|
/// siblings, and/or its descendants. Similarly, when various attributes of an
|
||||||
/// and so we RestyleHints to short-circuit work we know is unnecessary.
|
/// element change, we may also need to restyle things with id, class, and attribute
|
||||||
///
|
/// selectors. Doing this conservatively is expensive, and so we use RestyleHints to
|
||||||
/// NB: We should extent restyle hints to check for attribute-dependent style
|
/// short-circuit work we know is unnecessary.
|
||||||
/// in addition to state-dependent style (Gecko does this).
|
|
||||||
|
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
flags RestyleHint: u8 {
|
flags RestyleHint: u8 {
|
||||||
|
@ -49,34 +48,68 @@ bitflags! {
|
||||||
/// take the ElementWrapper approach for attribute-dependent style. So we do it the same both ways for
|
/// take the ElementWrapper approach for attribute-dependent style. So we do it the same both ways for
|
||||||
/// now to reduce complexity, but it's worth measuring the performance impact (if any) of the
|
/// now to reduce complexity, but it's worth measuring the performance impact (if any) of the
|
||||||
/// mStateMask approach.
|
/// mStateMask approach.
|
||||||
struct ElementWrapper<E> where E: Element {
|
|
||||||
|
#[derive(HeapSizeOf, Clone)]
|
||||||
|
pub struct ElementSnapshot {
|
||||||
|
pub state: Option<ElementState>,
|
||||||
|
pub attrs: Option<Vec<(AttrIdentifier, AttrValue)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElementSnapshot {
|
||||||
|
pub fn new() -> ElementSnapshot {
|
||||||
|
EMPTY_SNAPSHOT.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets an attribute matching |namespace| and |name|, if any. Panics if |attrs| is None.
|
||||||
|
pub fn get_attr(&self, namespace: &Namespace, name: &Atom) -> Option<&AttrValue> {
|
||||||
|
self.attrs.as_ref().unwrap().iter()
|
||||||
|
.find(|&&(ref ident, _)| ident.local_name == *name && ident.namespace == *namespace)
|
||||||
|
.map(|&(_, ref v)| v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets an attribute matching |name| if any, ignoring namespace. Panics if |attrs| is None.
|
||||||
|
pub fn get_attr_ignore_ns(&self, name: &Atom) -> Option<&AttrValue> {
|
||||||
|
self.attrs.as_ref().unwrap().iter()
|
||||||
|
.find(|&&(ref ident, _)| ident.local_name == *name)
|
||||||
|
.map(|&(_, ref v)| v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static EMPTY_SNAPSHOT: ElementSnapshot = ElementSnapshot { state: None, attrs: None };
|
||||||
|
|
||||||
|
struct ElementWrapper<'a, E> where E: Element {
|
||||||
element: E,
|
element: E,
|
||||||
state_override: ElementState,
|
snapshot: &'a ElementSnapshot,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, E> ElementWrapper<E> where E: Element {
|
impl<'a, E> ElementWrapper<'a, E> where E: Element {
|
||||||
pub fn new(el: E) -> ElementWrapper<E> {
|
pub fn new(el: E) -> ElementWrapper<'a, E> {
|
||||||
ElementWrapper { element: el, state_override: ElementState::empty() }
|
ElementWrapper { element: el, snapshot: &EMPTY_SNAPSHOT }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_with_override(el: E, state: ElementState) -> ElementWrapper<E> {
|
pub fn new_with_snapshot(el: E, snapshot: &'a ElementSnapshot) -> ElementWrapper<'a, E> {
|
||||||
ElementWrapper { element: el, state_override: state }
|
ElementWrapper { element: el, snapshot: snapshot }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! overridden_state_accessors {
|
macro_rules! snapshot_state_accessors {
|
||||||
($(
|
($(
|
||||||
$(#[$Flag_attr: meta])*
|
$(#[$Flag_attr: meta])*
|
||||||
state $css: expr => $variant: ident / $method: ident /
|
state $css: expr => $variant: ident / $method: ident /
|
||||||
$flag: ident = $value: expr,
|
$flag: ident = $value: expr,
|
||||||
)+) => { $( fn $method(&self) -> bool { self.state_override.contains($flag) } )+
|
)+) => { $( fn $method(&self) -> bool {
|
||||||
|
match self.snapshot.state {
|
||||||
|
Some(s) => s.contains($flag),
|
||||||
|
None => self.element.$method()
|
||||||
|
}
|
||||||
|
} )+
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E> Element for ElementWrapper<E> where E: Element {
|
impl<'a, E> Element for ElementWrapper<'a, E> where E: Element {
|
||||||
|
|
||||||
// Implement the state accessors on Element to use our overridden state.
|
// Implement the state accessors on Element to use the snapshot state if it exists.
|
||||||
state_pseudo_classes!(overridden_state_accessors);
|
state_pseudo_classes!(snapshot_state_accessors);
|
||||||
|
|
||||||
fn parent_element(&self) -> Option<Self> {
|
fn parent_element(&self) -> Option<Self> {
|
||||||
self.element.parent_element().map(|el| ElementWrapper::new(el))
|
self.element.parent_element().map(|el| ElementWrapper::new(el))
|
||||||
|
@ -103,14 +136,31 @@ impl<E> Element for ElementWrapper<E> where E: Element {
|
||||||
self.element.get_namespace()
|
self.element.get_namespace()
|
||||||
}
|
}
|
||||||
fn get_id(&self) -> Option<Atom> {
|
fn get_id(&self) -> Option<Atom> {
|
||||||
self.element.get_id()
|
match self.snapshot.attrs {
|
||||||
|
Some(_) => self.snapshot.get_attr(&ns!(""), &atom!("id")).map(|value| value.as_atom().clone()),
|
||||||
|
None => self.element.get_id(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn has_class(&self, name: &Atom) -> bool {
|
fn has_class(&self, name: &Atom) -> bool {
|
||||||
self.element.has_class(name)
|
match self.snapshot.attrs {
|
||||||
|
Some(_) => self.snapshot.get_attr(&ns!(""), &atom!("class"))
|
||||||
|
.map_or(false, |v| { v.as_tokens().iter().any(|atom| atom == name) }),
|
||||||
|
None => self.element.has_class(name),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn match_attr<F>(&self, attr: &AttrSelector, test: F) -> bool
|
fn match_attr<F>(&self, attr: &AttrSelector, test: F) -> bool
|
||||||
where F: Fn(&str) -> bool {
|
where F: Fn(&str) -> bool {
|
||||||
self.element.match_attr(attr, test)
|
match self.snapshot.attrs {
|
||||||
|
Some(_) => {
|
||||||
|
let html = self.is_html_element_in_html_document();
|
||||||
|
let local_name = if html { &attr.lower_name } else { &attr.name };
|
||||||
|
match attr.namespace {
|
||||||
|
NamespaceConstraint::Specific(ref ns) => self.snapshot.get_attr(ns, local_name),
|
||||||
|
NamespaceConstraint::Any => self.snapshot.get_attr_ignore_ns(local_name),
|
||||||
|
}.map_or(false, |v| test(v))
|
||||||
|
},
|
||||||
|
None => self.element.match_attr(attr, test)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn is_empty(&self) -> bool {
|
fn is_empty(&self) -> bool {
|
||||||
self.element.is_empty()
|
self.element.is_empty()
|
||||||
|
@ -127,8 +177,15 @@ impl<E> Element for ElementWrapper<E> where E: Element {
|
||||||
fn is_unvisited_link(&self) -> bool {
|
fn is_unvisited_link(&self) -> bool {
|
||||||
self.element.is_unvisited_link()
|
self.element.is_unvisited_link()
|
||||||
}
|
}
|
||||||
fn each_class<F>(&self, callback: F) where F: FnMut(&Atom) {
|
fn each_class<F>(&self, mut callback: F) where F: FnMut(&Atom) {
|
||||||
self.element.each_class(callback)
|
match self.snapshot.attrs {
|
||||||
|
Some(_) => {
|
||||||
|
if let Some(v) = self.snapshot.get_attr(&ns!(""), &atom!("class")) {
|
||||||
|
for c in v.as_tokens() { callback(c) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => self.element.each_class(callback),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,6 +204,23 @@ macro_rules! gen_selector_to_state {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state_pseudo_classes!(gen_selector_to_state);
|
||||||
|
|
||||||
|
fn is_attr_selector(sel: &SimpleSelector) -> bool {
|
||||||
|
match *sel {
|
||||||
|
SimpleSelector::ID(_) |
|
||||||
|
SimpleSelector::Class(_) |
|
||||||
|
SimpleSelector::AttrExists(_) |
|
||||||
|
SimpleSelector::AttrEqual(_, _, _) |
|
||||||
|
SimpleSelector::AttrIncludes(_, _) |
|
||||||
|
SimpleSelector::AttrDashMatch(_, _, _) |
|
||||||
|
SimpleSelector::AttrPrefixMatch(_, _) |
|
||||||
|
SimpleSelector::AttrSubstringMatch(_, _) |
|
||||||
|
SimpleSelector::AttrSuffixMatch(_, _) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn combinator_to_restyle_hint(combinator: Option<Combinator>) -> RestyleHint {
|
fn combinator_to_restyle_hint(combinator: Option<Combinator>) -> RestyleHint {
|
||||||
match combinator {
|
match combinator {
|
||||||
None => RESTYLE_SELF,
|
None => RESTYLE_SELF,
|
||||||
|
@ -159,49 +233,68 @@ fn combinator_to_restyle_hint(combinator: Option<Combinator>) -> RestyleHint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state_pseudo_classes!(gen_selector_to_state);
|
#[derive(Debug)]
|
||||||
|
struct Sensitivities {
|
||||||
|
pub states: ElementState,
|
||||||
|
pub attrs: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sensitivities {
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.states.is_empty() && !self.attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new() -> Sensitivities {
|
||||||
|
Sensitivities {
|
||||||
|
states: ElementState::empty(),
|
||||||
|
attrs: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Mapping between (partial) CompoundSelectors (and the combinator to their right)
|
// Mapping between (partial) CompoundSelectors (and the combinator to their right)
|
||||||
// and the states they depend on.
|
// and the states and attributes they depend on.
|
||||||
//
|
//
|
||||||
// In general, for all selectors in all applicable stylesheets of the form:
|
// In general, for all selectors in all applicable stylesheets of the form:
|
||||||
//
|
//
|
||||||
// |s _ s:X _ s _ s:Y _ s|
|
// |a _ b _ c _ d _ e|
|
||||||
//
|
//
|
||||||
// Where:
|
// Where:
|
||||||
// * Each |s| is an arbitrary simple selector.
|
// * |b| and |d| are simple selectors that depend on state (like :hover) or
|
||||||
// * Each |s| is an arbitrary combinator (or nothing).
|
// attributes (like [attr...], .foo, or #foo).
|
||||||
// * X and Y are state-dependent pseudo-classes like :hover.
|
// * |a|, |c|, and |e| are arbitrary simple selectors that do not depend on
|
||||||
|
// state or attributes.
|
||||||
//
|
//
|
||||||
// We generate a StateDependency for both |s _ s:X _| and |s _ s:X _ s _ s:Y _|, even
|
// We generate a Dependency for both |a _ b:X _| and |a _ b:X _ c _ d:Y _|, even
|
||||||
// though those selectors may not appear on their own in any stylesheet. This allows
|
// though those selectors may not appear on their own in any stylesheet. This allows
|
||||||
// us to quickly scan through the operation points of pseudo-classes and determine the
|
// us to quickly scan through the dependency sites of all style rules and determine the
|
||||||
// maximum effect their associated state changes may have on the style of elements in
|
// maximum effect that a given state or attribute change may have on the style of
|
||||||
// the document.
|
// elements in the document.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct StateDependency {
|
struct Dependency {
|
||||||
selector: Arc<CompoundSelector>,
|
selector: Arc<CompoundSelector>,
|
||||||
combinator: Option<Combinator>,
|
combinator: Option<Combinator>,
|
||||||
state: ElementState,
|
sensitivities: Sensitivities,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StateDependencySet {
|
pub struct DependencySet {
|
||||||
deps: Vec<StateDependency>,
|
deps: Vec<Dependency>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StateDependencySet {
|
impl DependencySet {
|
||||||
pub fn new() -> StateDependencySet {
|
pub fn new() -> DependencySet {
|
||||||
StateDependencySet { deps: Vec::new() }
|
DependencySet { deps: Vec::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compute_hint<E>(&self, el: &E, current_state: ElementState, old_state: ElementState)
|
pub fn compute_hint<E>(&self, el: &E, snapshot: &ElementSnapshot, current_state: ElementState)
|
||||||
-> RestyleHint where E: Element, E: Clone {
|
-> RestyleHint where E: Element, E: Clone {
|
||||||
|
let state_changes = snapshot.state.map_or(ElementState::empty(), |old_state| current_state ^ old_state);
|
||||||
|
let attrs_changed = snapshot.attrs.is_some();
|
||||||
let mut hint = RestyleHint::empty();
|
let mut hint = RestyleHint::empty();
|
||||||
let state_changes = current_state ^ old_state;
|
|
||||||
for dep in &self.deps {
|
for dep in &self.deps {
|
||||||
if state_changes.intersects(dep.state) {
|
if state_changes.intersects(dep.sensitivities.states) || (attrs_changed && dep.sensitivities.attrs) {
|
||||||
let old_el: ElementWrapper<E> = ElementWrapper::new_with_override(el.clone(), old_state);
|
let old_el: ElementWrapper<E> = ElementWrapper::new_with_snapshot(el.clone(), snapshot);
|
||||||
let matched_then = matches_compound_selector(&*dep.selector, &old_el, None, &mut false);
|
let matched_then = matches_compound_selector(&*dep.selector, &old_el, None, &mut false);
|
||||||
let matches_now = matches_compound_selector(&*dep.selector, el, None, &mut false);
|
let matches_now = matches_compound_selector(&*dep.selector, el, None, &mut false);
|
||||||
if matched_then != matches_now {
|
if matched_then != matches_now {
|
||||||
|
@ -219,15 +312,18 @@ impl StateDependencySet {
|
||||||
let mut cur = selector;
|
let mut cur = selector;
|
||||||
let mut combinator: Option<Combinator> = None;
|
let mut combinator: Option<Combinator> = None;
|
||||||
loop {
|
loop {
|
||||||
let mut deps = ElementState::empty();
|
let mut sensitivities = Sensitivities::new();
|
||||||
for s in &cur.simple_selectors {
|
for s in &cur.simple_selectors {
|
||||||
deps.insert(selector_to_state(s));
|
sensitivities.states.insert(selector_to_state(s));
|
||||||
|
if !sensitivities.attrs {
|
||||||
|
sensitivities.attrs = is_attr_selector(s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !deps.is_empty() {
|
if !sensitivities.is_empty() {
|
||||||
self.deps.push(StateDependency {
|
self.deps.push(Dependency {
|
||||||
selector: cur.clone(),
|
selector: cur.clone(),
|
||||||
combinator: combinator,
|
combinator: combinator,
|
||||||
state: deps,
|
sensitivities: sensitivities,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ use legacy::PresentationalHintSynthesis;
|
||||||
use media_queries::{Device, MediaType};
|
use media_queries::{Device, MediaType};
|
||||||
use node::TElementAttributes;
|
use node::TElementAttributes;
|
||||||
use properties::{PropertyDeclaration, PropertyDeclarationBlock};
|
use properties::{PropertyDeclaration, PropertyDeclarationBlock};
|
||||||
use restyle_hints::{RestyleHint, StateDependencySet};
|
use restyle_hints::{ElementSnapshot, RestyleHint, DependencySet};
|
||||||
use selectors::Element;
|
use selectors::Element;
|
||||||
use selectors::bloom::BloomFilter;
|
use selectors::bloom::BloomFilter;
|
||||||
use selectors::matching::DeclarationBlock as GenericDeclarationBlock;
|
use selectors::matching::DeclarationBlock as GenericDeclarationBlock;
|
||||||
|
@ -95,8 +95,8 @@ pub struct Stylist {
|
||||||
after_map: PerPseudoElementSelectorMap,
|
after_map: PerPseudoElementSelectorMap,
|
||||||
rules_source_order: usize,
|
rules_source_order: usize,
|
||||||
|
|
||||||
// Selector state dependencies used to compute restyle hints.
|
// Selector dependencies used to compute restyle hints.
|
||||||
state_deps: StateDependencySet,
|
state_deps: DependencySet,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stylist {
|
impl Stylist {
|
||||||
|
@ -112,7 +112,7 @@ impl Stylist {
|
||||||
before_map: PerPseudoElementSelectorMap::new(),
|
before_map: PerPseudoElementSelectorMap::new(),
|
||||||
after_map: PerPseudoElementSelectorMap::new(),
|
after_map: PerPseudoElementSelectorMap::new(),
|
||||||
rules_source_order: 0,
|
rules_source_order: 0,
|
||||||
state_deps: StateDependencySet::new(),
|
state_deps: DependencySet::new(),
|
||||||
};
|
};
|
||||||
// FIXME: Add iso-8859-9.css when the document’s encoding is ISO-8859-8.
|
// FIXME: Add iso-8859-9.css when the document’s encoding is ISO-8859-8.
|
||||||
stylist
|
stylist
|
||||||
|
@ -204,12 +204,16 @@ impl Stylist {
|
||||||
self.rules_source_order = rules_source_order;
|
self.rules_source_order = rules_source_order;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restyle_hint_for_state_change<E>(&self, element: &E,
|
pub fn compute_restyle_hint<E>(&self, element: &E,
|
||||||
current_state: ElementState,
|
snapshot: &ElementSnapshot,
|
||||||
old_state: ElementState)
|
// NB: We need to pass current_state as an argument because
|
||||||
-> RestyleHint
|
// selectors::Element doesn't provide access to ElementState
|
||||||
where E: Element + Clone {
|
// directly, and computing it from the ElementState would be
|
||||||
self.state_deps.compute_hint(element, current_state, old_state)
|
// more expensive than getting it directly from the caller.
|
||||||
|
current_state: ElementState)
|
||||||
|
-> RestyleHint
|
||||||
|
where E: Element + Clone {
|
||||||
|
self.state_deps.compute_hint(element, snapshot, current_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_device(&mut self, mut device: Device, stylesheets: &[&Stylesheet]) {
|
pub fn set_device(&mut self, mut device: Device, stylesheets: &[&Stylesheet]) {
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[background-root-101.htm]
|
|
||||||
type: reftest
|
|
||||||
expected: FAIL
|
|
|
@ -1,4 +0,0 @@
|
||||||
[dynamic-sibling-combinator-001.htm]
|
|
||||||
type: reftest
|
|
||||||
bug: https://github.com/servo/servo/issues/7890
|
|
||||||
expected: FAIL
|
|
|
@ -3251,6 +3251,18 @@
|
||||||
"url": "/_mozilla/css/quotes_simple_a.html"
|
"url": "/_mozilla/css/quotes_simple_a.html"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"css/restyle_hints_attr.html": [
|
||||||
|
{
|
||||||
|
"path": "css/restyle_hints_attr.html",
|
||||||
|
"references": [
|
||||||
|
[
|
||||||
|
"/_mozilla/css/restyle_hints_attr_ref.html",
|
||||||
|
"=="
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"url": "/_mozilla/css/restyle_hints_attr.html"
|
||||||
|
}
|
||||||
|
],
|
||||||
"css/restyle_hints_state.html": [
|
"css/restyle_hints_state.html": [
|
||||||
{
|
{
|
||||||
"path": "css/restyle_hints_state.html",
|
"path": "css/restyle_hints_state.html",
|
||||||
|
@ -8316,6 +8328,18 @@
|
||||||
"url": "/_mozilla/css/quotes_simple_a.html"
|
"url": "/_mozilla/css/quotes_simple_a.html"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"css/restyle_hints_attr.html": [
|
||||||
|
{
|
||||||
|
"path": "css/restyle_hints_attr.html",
|
||||||
|
"references": [
|
||||||
|
[
|
||||||
|
"/_mozilla/css/restyle_hints_attr_ref.html",
|
||||||
|
"=="
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"url": "/_mozilla/css/restyle_hints_attr.html"
|
||||||
|
}
|
||||||
|
],
|
||||||
"css/restyle_hints_state.html": [
|
"css/restyle_hints_state.html": [
|
||||||
{
|
{
|
||||||
"path": "css/restyle_hints_state.html",
|
"path": "css/restyle_hints_state.html",
|
||||||
|
|
44
tests/wpt/mozilla/tests/css/restyle_hints_attr.css
Normal file
44
tests/wpt/mozilla/tests/css/restyle_hints_attr.css
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
div {
|
||||||
|
background-color: black;
|
||||||
|
padding: 10px;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[foo=kokosnuss] {
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[foo][bar] {
|
||||||
|
background: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[foo=kokosnuss][bar~=fett] {
|
||||||
|
background: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[bar] > div[baz] div {
|
||||||
|
color: cyan;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[foo][bar~=butter] div {
|
||||||
|
background: purple;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[foo][bar~=butter] > div {
|
||||||
|
background: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[bar~=butter][foo=kokosnuss] + div {
|
||||||
|
background: pink;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[foo=kokosnuss] ~ div {
|
||||||
|
background: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
div + div[foo=keine][bar=ahnung] {
|
||||||
|
background: lavender;
|
||||||
|
}
|
35
tests/wpt/mozilla/tests/css/restyle_hints_attr.html
Normal file
35
tests/wpt/mozilla/tests/css/restyle_hints_attr.html
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<!doctype html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title></title>
|
||||||
|
<link rel="match" href="restyle_hints_attr_ref.html">
|
||||||
|
<link rel="stylesheet" href="restyle_hints_attr.css">
|
||||||
|
<body>
|
||||||
|
<div id="d1">
|
||||||
|
<div id="d2">
|
||||||
|
<div>some text</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="d3" foo="keine" bar="ahnung"></div>
|
||||||
|
<div id="d4"></div>
|
||||||
|
<div></div>
|
||||||
|
<script>
|
||||||
|
window.dummy = 0;
|
||||||
|
var $ = document.getElementById.bind(document);
|
||||||
|
function syncRestyle() { window.dummy += document.body.lastElementChild.offsetTop; }
|
||||||
|
$('d1').setAttribute("foo", "kokosnuss");
|
||||||
|
syncRestyle();
|
||||||
|
$('d1').setAttribute("bar", "butter");
|
||||||
|
syncRestyle();
|
||||||
|
$('d4').setAttribute("foo", true);
|
||||||
|
syncRestyle();
|
||||||
|
$('d4').setAttribute("bar", true);
|
||||||
|
syncRestyle();
|
||||||
|
$('d2').setAttribute("baz", "lecker");
|
||||||
|
syncRestyle();
|
||||||
|
$('d1').setAttribute("bar", "fett butter");
|
||||||
|
syncRestyle();
|
||||||
|
$('d3').removeAttribute("bar");
|
||||||
|
syncRestyle();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
16
tests/wpt/mozilla/tests/css/restyle_hints_attr_ref.html
Normal file
16
tests/wpt/mozilla/tests/css/restyle_hints_attr_ref.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<!doctype html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="stylesheet" href="restyle_hints_attr.css">
|
||||||
|
<title></title>
|
||||||
|
<body>
|
||||||
|
<div foo="kokosnuss" bar="butter fett">
|
||||||
|
<div baz="lecker">
|
||||||
|
<div>some text</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
<div bar foo></div>
|
||||||
|
<div></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
|
@ -30,8 +30,9 @@
|
||||||
* implement attribute-based restyle hints, we can stop dirtying the subtree
|
* implement attribute-based restyle hints, we can stop dirtying the subtree
|
||||||
* on attribute modifications, and these tests will start to be more useful.
|
* on attribute modifications, and these tests will start to be more useful.
|
||||||
*/
|
*/
|
||||||
|
window.dummy = 0;
|
||||||
var $ = document.getElementById.bind(document);
|
var $ = document.getElementById.bind(document);
|
||||||
function syncRestyle() { window.dummy != $("fs2").offsetTop; }
|
function syncRestyle() { window.dummy += $("fs2").offsetTop; }
|
||||||
syncRestyle();
|
syncRestyle();
|
||||||
$('fs1').disabled = true;
|
$('fs1').disabled = true;
|
||||||
syncRestyle();
|
syncRestyle();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue