diff options
47 files changed, 1051 insertions, 386 deletions
diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs index 514e3e71e66..603a08491d0 100644 --- a/components/layout/layout_task.rs +++ b/components/layout/layout_task.rs @@ -1082,21 +1082,45 @@ impl LayoutTask { let document = unsafe { LayoutNode::new(&data.document) }; let document = document.as_document().unwrap(); + + debug!("layout: received layout request for: {}", self.url.serialize()); + + let mut rw_data = possibly_locked_rw_data.lock(); + let node: LayoutNode = match document.root_node() { - None => return, + None => { + // Since we cannot compute anything, give spec-required placeholders. + debug!("layout: No root node: bailing"); + match data.query_type { + ReflowQueryType::ContentBoxQuery(_) => { + rw_data.content_box_response = Rect::zero(); + }, + ReflowQueryType::ContentBoxesQuery(_) => { + rw_data.content_boxes_response = Vec::new(); + }, + ReflowQueryType::NodeGeometryQuery(_) => { + rw_data.client_rect_response = Rect::zero(); + }, + ReflowQueryType::ResolvedStyleQuery(_, _, _) => { + rw_data.resolved_style_response = None; + }, + ReflowQueryType::OffsetParentQuery(_) => { + rw_data.offset_parent_response = OffsetParentResponse::empty(); + }, + ReflowQueryType::NoQuery => {} + } + return; + }, Some(x) => x, }; - debug!("layout: received layout request for: {}", self.url.serialize()); if log_enabled!(log::LogLevel::Debug) { node.dump(); } - let mut rw_data = possibly_locked_rw_data.lock(); let stylesheets: Vec<&Stylesheet> = data.document_stylesheets.iter().map(|entry| &**entry) .collect(); let stylesheets_changed = data.stylesheets_changed; - let initial_viewport = data.window_size.initial_viewport; let old_viewport_size = self.viewport_size; let current_screen_size = Size2D::new(Au::from_f32_px(initial_viewport.width.get()), @@ -1145,10 +1169,8 @@ impl LayoutTask { let modified_elements = document.drain_modified_elements(); if !needs_dirtying { - for &(el, old_state) in modified_elements.iter() { - let hint = rw_data.stylist.restyle_hint_for_state_change(&el, - el.get_state(), - old_state); + for (el, snapshot) in modified_elements { + let hint = rw_data.stylist.compute_restyle_hint(&el, &snapshot, el.get_state()); el.note_restyle_hint(hint); } } diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs index ae8462426e8..57b0008c842 100644 --- a/components/layout/wrapper.rs +++ b/components/layout/wrapper.rs @@ -58,7 +58,6 @@ use selectors::states::*; use smallvec::VecLike; use std::borrow::ToOwned; use std::cell::{Ref, RefMut}; -use std::iter::FromIterator; use std::marker::PhantomData; use std::mem; use std::sync::Arc; @@ -69,7 +68,7 @@ use style::legacy::UnsignedIntegerAttribute; use style::node::TElementAttributes; use style::properties::ComputedValues; 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 util::str::{is_whitespace, search_index}; @@ -92,14 +91,17 @@ impl<'a> PartialEq for LayoutNode<'a> { } impl<'ln> LayoutNode<'ln> { - pub unsafe fn new(address: &TrustedNodeAddress) -> LayoutNode { - let node = LayoutJS::from_trusted_node_address(*address); + pub fn from_layout_js(n: LayoutJS<Node>) -> LayoutNode<'ln> { LayoutNode { - node: node, + node: n, 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. pub unsafe fn new_with_this_lifetime(&self, node: &LayoutJS<Node>) -> LayoutNode<'ln> { LayoutNode { @@ -216,12 +218,7 @@ impl<'ln> LayoutNode<'ln> { } pub fn as_document(&self) -> Option<LayoutDocument<'ln>> { - self.node.downcast().map(|document| { - LayoutDocument { - document: document, - chain: PhantomData, - } - }) + self.node.downcast().map(|document| LayoutDocument::from_layout_js(document)) } fn parent_node(&self) -> Option<LayoutNode<'ln>> { @@ -364,26 +361,24 @@ pub struct LayoutDocument<'le> { } impl<'le> LayoutDocument<'le> { - pub fn as_node(&self) -> LayoutNode<'le> { - LayoutNode { - node: self.document.upcast(), + pub fn from_layout_js(doc: LayoutJS<Document>) -> LayoutDocument<'le> { + LayoutDocument { + document: doc, chain: PhantomData, } } + pub fn as_node(&self) -> LayoutNode<'le> { + LayoutNode::from_layout_js(self.document.upcast()) + } + pub fn root_node(&self) -> Option<LayoutNode<'le>> { self.as_node().children().find(LayoutNode::is_element) } - pub fn drain_modified_elements(&self) -> Vec<(LayoutElement, ElementState)> { - unsafe { - let elements = self.document.drain_modified_elements(); - Vec::from_iter(elements.iter().map(|&(el, state)| - (LayoutElement { - element: el, - chain: PhantomData, - }, state))) - } + pub fn drain_modified_elements(&self) -> Vec<(LayoutElement, ElementSnapshot)> { + let elements = unsafe { self.document.drain_modified_elements() }; + elements.into_iter().map(|(el, snapshot)| (LayoutElement::from_layout_js(el), snapshot)).collect() } } @@ -395,6 +390,13 @@ pub struct 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> { unsafe { &*self.element.style_attribute() @@ -402,10 +404,7 @@ impl<'le> LayoutElement<'le> { } pub fn as_node(&self) -> LayoutNode<'le> { - LayoutNode { - node: self.element.upcast(), - chain: PhantomData, - } + LayoutNode::from_layout_js(self.element.upcast()) } pub fn get_state(&self) -> ElementState { @@ -413,7 +412,7 @@ impl<'le> LayoutElement<'le> { } /// 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. if hint.is_empty() { return; @@ -446,6 +445,11 @@ impl<'le> LayoutElement<'le> { // Process hints. if hint.contains(RESTYLE_SELF) { 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) { unsafe { node.set_dirty_descendants(true); } @@ -464,12 +468,7 @@ impl<'le> LayoutElement<'le> { } fn as_element<'le>(node: LayoutJS<Node>) -> Option<LayoutElement<'le>> { - node.downcast().map(|element| { - LayoutElement { - element: element, - chain: PhantomData, - } - }) + node.downcast().map(|element| LayoutElement::from_layout_js(element)) } macro_rules! state_getter { diff --git a/components/script/dom/attr.rs b/components/script/dom/attr.rs index 315f26a3b7c..02ef5e4451e 100644 --- a/components/script/dom/attr.rs +++ b/components/script/dom/attr.rs @@ -2,7 +2,6 @@ * 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 devtools_traits::AttrInfo; use dom::bindings::cell::DOMRefCell; 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::reflector::{Reflector, reflect_dom_object}; use dom::element::{AttributeMutation, Element}; -use dom::values::UNSIGNED_LONG_MAX; use dom::virtualmethods::vtable_for; use dom::window::Window; use std::borrow::ToOwned; use std::cell::Ref; use std::mem; -use std::ops::Deref; use string_cache::{Atom, Namespace}; -use style::values::specified::Length; -use util::str::{DOMString, LengthOrPercentageOrAuto, parse_unsigned_integer, parse_legacy_color, parse_length}; -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, - } - } -} +pub use style::attr::{AttrIdentifier, AttrValue}; +use util::str::DOMString; // https://dom.spec.whatwg.org/#interface-attr #[dom_struct] pub struct Attr { reflector_: Reflector, - local_name: Atom, + identifier: AttrIdentifier, value: DOMRefCell<AttrValue>, - name: Atom, - namespace: Namespace, - prefix: Option<Atom>, /// the element that owns this attribute. owner: MutNullableHeap<JS<Element>>, @@ -200,11 +36,13 @@ impl Attr { prefix: Option<Atom>, owner: Option<&Element>) -> Attr { Attr { reflector_: Reflector::new(), - local_name: local_name, + identifier: AttrIdentifier { + local_name: local_name, + name: name, + namespace: namespace, + prefix: prefix, + }, value: DOMRefCell::new(value), - name: name, - namespace: namespace, - prefix: prefix, owner: MutNullableHeap::new(owner), } } @@ -220,17 +58,17 @@ impl Attr { #[inline] pub fn name(&self) -> &Atom { - &self.name + &self.identifier.name } #[inline] pub fn namespace(&self) -> &Namespace { - &self.namespace + &self.identifier.namespace } #[inline] pub fn prefix(&self) -> &Option<Atom> { - &self.prefix + &self.identifier.prefix } } @@ -250,7 +88,7 @@ impl AttrMethods for Attr { match self.owner() { None => *self.value.borrow_mut() = AttrValue::String(value), 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()); } } @@ -278,12 +116,12 @@ impl AttrMethods for Attr { // https://dom.spec.whatwg.org/#dom-attr-name fn Name(&self) -> DOMString { - DOMString((*self.name).to_owned()) + DOMString((*self.identifier.name).to_owned()) } // https://dom.spec.whatwg.org/#dom-attr-namespaceuri fn GetNamespaceURI(&self) -> Option<DOMString> { - let Namespace(ref atom) = self.namespace; + let Namespace(ref atom) = self.identifier.namespace; match &**atom { "" => None, url => Some(DOMString(url.to_owned())), @@ -310,33 +148,38 @@ impl AttrMethods for Attr { impl Attr { pub fn set_value(&self, mut value: AttrValue, owner: &Element) { assert!(Some(owner) == self.owner().r()); + owner.will_mutate_attr(); mem::swap(&mut *self.value.borrow_mut(), &mut value); - if self.namespace == ns!("") { + if self.identifier.namespace == ns!("") { vtable_for(owner.upcast()).attribute_mutated( self, AttributeMutation::Set(Some(&value))); } } + pub fn identifier(&self) -> &AttrIdentifier { + &self.identifier + } + pub fn value(&self) -> Ref<AttrValue> { self.value.borrow() } 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 /// or removed from its older parent. pub fn set_owner(&self, owner: Option<&Element>) { - let ref ns = self.namespace; + let ref ns = self.identifier.namespace; match (self.owner().r(), owner) { (None, Some(new)) => { // 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) => { // 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) } @@ -348,7 +191,7 @@ impl Attr { } pub fn summarize(&self) -> AttrInfo { - let Namespace(ref ns) = self.namespace; + let Namespace(ref ns) = self.identifier.namespace; AttrInfo { namespace: (**ns).to_owned(), name: self.Name().0, @@ -400,7 +243,7 @@ impl AttrHelpersForLayout for LayoutJS<Attr> { #[inline] unsafe fn local_name_atom_forever(&self) -> Atom { - (*self.unsafe_get()).local_name.clone() + (*self.unsafe_get()).identifier.local_name.clone() } #[inline] diff --git a/components/script/dom/bindings/global.rs b/components/script/dom/bindings/global.rs index d89c1c21f32..f8f24d862df 100644 --- a/components/script/dom/bindings/global.rs +++ b/components/script/dom/bindings/global.rs @@ -22,8 +22,9 @@ use msg::constellation_msg::{ConstellationChan, PipelineId, WorkerId}; use net_traits::ResourceTask; use profile_traits::mem; use script_task::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptTask}; -use script_traits::TimerEventRequest; +use script_traits::{MsDuration, TimerEventRequest}; use std::sync::mpsc::Sender; +use timers::{ScheduledCallback, TimerHandle}; use url::Url; use util::mem::HeapSizeOf; @@ -197,6 +198,23 @@ impl<'a> GlobalRef<'a> { } } + /// Schedule the given `callback` to be invoked after at least `duration` milliseconds have + /// passed. + pub fn schedule_callback(&self, callback: Box<ScheduledCallback>, duration: MsDuration) -> TimerHandle { + match *self { + GlobalRef::Window(window) => window.schedule_callback(callback, duration), + GlobalRef::Worker(worker) => worker.schedule_callback(callback, duration), + } + } + + /// Unschedule a previously-scheduled callback. + pub fn unschedule_callback(&self, handle: TimerHandle) { + match *self { + GlobalRef::Window(window) => window.unschedule_callback(handle), + GlobalRef::Worker(worker) => worker.unschedule_callback(handle), + } + } + /// Returns the receiver's reflector. pub fn reflector(&self) -> &Reflector { match *self { diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 9b592e731f7..cd8fab7e56f 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -82,7 +82,9 @@ use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::mpsc::{Receiver, Sender}; use string_cache::{Atom, Namespace, QualName}; +use style::attr::{AttrIdentifier, AttrValue}; use style::properties::PropertyDeclarationBlock; +use style::restyle_hints::ElementSnapshot; use style::values::specified::Length; use url::Url; use util::str::{DOMString, LengthOrPercentageOrAuto}; @@ -289,6 +291,9 @@ no_jsmanaged_fields!(Length); no_jsmanaged_fields!(ElementState); no_jsmanaged_fields!(DOMString); no_jsmanaged_fields!(Mime); +no_jsmanaged_fields!(AttrIdentifier); +no_jsmanaged_fields!(AttrValue); +no_jsmanaged_fields!(ElementSnapshot); impl JSTraceable for Box<ScriptChan + Send> { #[inline] diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index c077a4f5769..262e5a8b4d5 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -88,7 +88,6 @@ use net_traits::{AsyncResponseTarget, PendingAsyncLoad}; use num::ToPrimitive; use script_task::{MainThreadScriptMsg, Runnable}; use script_traits::{MouseButton, TouchEventType, TouchId, UntrustedNodeAddress}; -use selectors::states::*; use std::ascii::AsciiExt; use std::borrow::ToOwned; use std::boxed::FnBox; @@ -102,6 +101,7 @@ use std::rc::Rc; use std::sync::Arc; use std::sync::mpsc::channel; use string_cache::{Atom, QualName}; +use style::restyle_hints::ElementSnapshot; use style::stylesheets::Stylesheet; use time; use url::Url; @@ -188,8 +188,9 @@ pub struct Document { /// This field is set to the document itself for inert documents. /// https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-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. - modified_elements: DOMRefCell<HashMap<JS<Element>, ElementState>>, + /// For each element that has had a state or attribute change since the last restyle, + /// track the original condition of the element. + modified_elements: DOMRefCell<HashMap<JS<Element>, ElementSnapshot>>, /// http://w3c.github.io/touch-events/#dfn-active-touch-point active_touch_points: DOMRefCell<Vec<JS<Touch>>>, } @@ -1275,7 +1276,7 @@ pub enum DocumentSource { #[allow(unsafe_code)] pub trait LayoutDocumentHelpers { 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)] @@ -1287,7 +1288,7 @@ impl LayoutDocumentHelpers for LayoutJS<Document> { #[inline] #[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 drain = elements.drain(); 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) { 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); + } } } diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 6a59c74529b..6449825082f 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -65,6 +65,7 @@ use html5ever::serialize::TraversalScope; use html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode}; use html5ever::tree_builder::{LimitedQuirks, NoQuirks, Quirks}; 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::states::*; use smallvec::VecLike; @@ -851,6 +852,7 @@ impl Element { name: Atom, namespace: Namespace, prefix: Option<Atom>) { + self.will_mutate_attr(); let window = window_from_node(self); let in_empty_ns = namespace == ns!(""); 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)); idx.map(|idx| { + self.will_mutate_attr(); let attr = Root::from_ref(&*(*self.attrs.borrow())[idx]); self.attrs.borrow_mut().remove(idx); attr.set_owner(None); @@ -1075,6 +1078,11 @@ impl Element { assert!(&**local_name == local_name.to_ascii_lowercase()); 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 { @@ -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 { fn super_type(&self) -> Option<&VirtualMethods> { Some(self.upcast::<Node>() as &VirtualMethods) @@ -1468,18 +1480,16 @@ impl VirtualMethods for Element { self.super_type().unwrap().attribute_mutated(attr, mutation); let node = self.upcast::<Node>(); let doc = node.owner_doc(); - let damage = match attr.local_name() { + match attr.local_name() { &atom!(style) => { // Modifying the `style` attribute might change style. *self.style_attribute.borrow_mut() = mutation.new_value(attr).map(|value| { parse_style_attribute(&value, &doc.base_url()) }); - NodeDamage::NodeStyleDamaged - }, - &atom!(class) => { - // Modifying a class can change style. - NodeDamage::NodeStyleDamaged + if node.is_in_doc() { + doc.content_changed(node, NodeDamage::NodeStyleDamaged); + } }, &atom!(id) => { *self.id_attribute.borrow_mut() = @@ -1510,16 +1520,22 @@ impl VirtualMethods for Element { } } } - NodeDamage::NodeStyleDamaged }, - _ => { - // Modifying any other attribute might change arbitrary things. - NodeDamage::OtherNodeDamage + _ if attr.namespace() == &ns!("") => { + if fragment_affecting_attributes().iter().any(|a| a == attr.local_name()) || + 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 { diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index 49bb441e21e..3a8baa399d2 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -488,13 +488,7 @@ impl Node { self.dirty_impl(damage, true) } - 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 + pub fn rev_version(&self) { // 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 // 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); } 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. match damage { diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index e50674c222c..4da6bd76645 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -55,7 +55,7 @@ use profile_traits::mem; use rustc_serialize::base64::{FromBase64, STANDARD, ToBase64}; use script_task::{ScriptChan, ScriptPort, MainThreadScriptMsg, RunnableWrapper}; use script_task::{SendableMainThreadScriptChan, MainThreadScriptChan, MainThreadTimerEventChan}; -use script_traits::{TimerEventChan, TimerEventId, TimerEventRequest, TimerSource}; +use script_traits::{MsDuration, TimerEventChan, TimerEventId, TimerEventRequest, TimerSource}; use selectors::parser::PseudoElement; use std::ascii::AsciiExt; use std::borrow::ToOwned; @@ -71,7 +71,7 @@ use std::sync::mpsc::TryRecvError::{Disconnected, Empty}; use std::sync::mpsc::{Sender, channel}; use string_cache::Atom; use time; -use timers::{ActiveTimers, IsInterval, TimerCallback}; +use timers::{ActiveTimers, IsInterval, ScheduledCallback, TimerCallback, TimerHandle}; use url::Url; use util::geometry::{self, MAX_RECT}; use util::str::{DOMString, HTML_SPACE_CHARACTERS}; @@ -1083,6 +1083,16 @@ impl Window { self.scheduler_chan.clone() } + pub fn schedule_callback(&self, callback: Box<ScheduledCallback>, duration: MsDuration) -> TimerHandle { + self.timers.schedule_callback(callback, + duration, + TimerSource::FromWindow(self.id.clone())) + } + + pub fn unschedule_callback(&self, handle: TimerHandle) { + self.timers.unschedule_callback(handle); + } + pub fn windowproxy_handler(&self) -> WindowProxyHandler { WindowProxyHandler(self.dom_static.windowproxy_handler.0) } diff --git a/components/script/dom/workerglobalscope.rs b/components/script/dom/workerglobalscope.rs index 8a63df7905c..019d526b48f 100644 --- a/components/script/dom/workerglobalscope.rs +++ b/components/script/dom/workerglobalscope.rs @@ -24,12 +24,12 @@ use msg::constellation_msg::{ConstellationChan, PipelineId, WorkerId}; use net_traits::{ResourceTask, load_whole_resource}; use profile_traits::mem; use script_task::{CommonScriptMsg, ScriptChan, ScriptPort}; -use script_traits::{TimerEventChan, TimerEventId, TimerEventRequest, TimerSource}; +use script_traits::{MsDuration, TimerEventChan, TimerEventId, TimerEventRequest, TimerSource}; use std::cell::Cell; use std::default::Default; use std::rc::Rc; use std::sync::mpsc::{Receiver, Sender}; -use timers::{ActiveTimers, IsInterval, TimerCallback}; +use timers::{ActiveTimers, IsInterval, ScheduledCallback, TimerCallback, TimerHandle}; use url::{Url, UrlParser}; use util::str::DOMString; @@ -143,6 +143,16 @@ impl WorkerGlobalScope { self.scheduler_chan.clone() } + pub fn schedule_callback(&self, callback: Box<ScheduledCallback>, duration: MsDuration) -> TimerHandle { + self.timers.schedule_callback(callback, + duration, + TimerSource::FromWorker) + } + + pub fn unschedule_callback(&self, handle: TimerHandle) { + self.timers.unschedule_callback(handle); + } + pub fn get_cx(&self) -> *mut JSContext { self.runtime.cx() } diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index e8025880f2e..770bcaedf78 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -30,6 +30,7 @@ use dom::xmlhttprequestupload::XMLHttpRequestUpload; use encoding::all::UTF_8; use encoding::label::encoding_from_whatwg_label; use encoding::types::{DecoderTrap, EncoderTrap, Encoding, EncodingRef}; +use euclid::length::Length; use hyper::header::Headers; use hyper::header::{Accept, ContentLength, ContentType, qitem}; use hyper::http::RawStatus; @@ -50,14 +51,13 @@ use std::ascii::AsciiExt; use std::borrow::ToOwned; use std::cell::{Cell, RefCell}; use std::default::Default; -use std::sync::mpsc::{Sender, TryRecvError, channel}; +use std::sync::mpsc::channel; use std::sync::{Arc, Mutex}; -use std::thread::sleep_ms; use time; +use timers::{ScheduledCallback, TimerHandle}; use url::{Url, UrlParser}; use util::mem::HeapSizeOf; use util::str::DOMString; -use util::task::spawn_named; pub type SendParam = StringOrURLSearchParams; @@ -137,8 +137,7 @@ pub struct XMLHttpRequest { send_flag: Cell<bool>, global: GlobalField, - #[ignore_heap_size_of = "Defined in std"] - timeout_cancel: DOMRefCell<Option<Sender<()>>>, + timeout_cancel: DOMRefCell<Option<TimerHandle>>, fetch_time: Cell<i64>, #[ignore_heap_size_of = "Cannot calculate Heap size"] timeout_target: DOMRefCell<Option<Box<ScriptChan + Send>>>, @@ -974,36 +973,49 @@ impl XMLHttpRequest { } } - // Sets up the object to timeout in a given number of milliseconds - // This will cancel all previous timeouts - let timeout_target = (*self.timeout_target.borrow().as_ref().unwrap()).clone(); - let global = self.global.root(); - let xhr = Trusted::new(global.r().get_cx(), self, global.r().script_chan()); - let gen_id = self.generation_id.get(); - let (cancel_tx, cancel_rx) = channel(); - *self.timeout_cancel.borrow_mut() = Some(cancel_tx); - spawn_named("XHR:Timer".to_owned(), move || { - sleep_ms(duration_ms); - match cancel_rx.try_recv() { - Err(TryRecvError::Empty) => { - timeout_target.send(CommonScriptMsg::RunnableMsg(XhrEvent, box XHRTimeout { - xhr: xhr, - gen_id: gen_id, - })).unwrap(); - }, - Err(TryRecvError::Disconnected) | Ok(()) => { - // This occurs if xhr.timeout_cancel (the sender) goes out of scope (i.e, xhr went out of scope) - // or if the oneshot timer was overwritten. The former case should not happen due to pinning. - debug!("XHR timeout was overwritten or canceled") + #[derive(JSTraceable, HeapSizeOf)] + struct ScheduledXHRTimeout { + #[ignore_heap_size_of = "Cannot calculate Heap size"] + target: Box<ScriptChan + Send>, + #[ignore_heap_size_of = "Because it is non-owning"] + xhr: Trusted<XMLHttpRequest>, + generation_id: GenerationId, + } + + impl ScheduledCallback for ScheduledXHRTimeout { + fn invoke(self: Box<Self>) { + let s = *self; + s.target.send(CommonScriptMsg::RunnableMsg(XhrEvent, box XHRTimeout { + xhr: s.xhr, + gen_id: s.generation_id, + })).unwrap(); + } + + fn box_clone(&self) -> Box<ScheduledCallback> { + box ScheduledXHRTimeout { + target: self.target.clone(), + xhr: self.xhr.clone(), + generation_id: self.generation_id, } } } - ); + + // Sets up the object to timeout in a given number of milliseconds + // This will cancel all previous timeouts + let global = self.global.root(); + let callback = ScheduledXHRTimeout { + target: (*self.timeout_target.borrow().as_ref().unwrap()).clone(), + xhr: Trusted::new(global.r().get_cx(), self, global.r().script_chan()), + generation_id: self.generation_id.get(), + }; + let duration = Length::new(duration_ms as u64); + *self.timeout_cancel.borrow_mut() = Some(global.r().schedule_callback(box callback, duration)); } fn cancel_timeout(&self) { - if let Some(cancel_tx) = self.timeout_cancel.borrow_mut().take() { - let _ = cancel_tx.send(()); + if let Some(handle) = self.timeout_cancel.borrow_mut().take() { + let global = self.global.root(); + global.r().unschedule_callback(handle); } } diff --git a/components/script/timers.rs b/components/script/timers.rs index ad6547b0341..2fd39c189f8 100644 --- a/components/script/timers.rs +++ b/components/script/timers.rs @@ -7,6 +7,7 @@ use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::FunctionBinding::Function; use dom::bindings::global::global_object_for_js_object; use dom::bindings::reflector::Reflectable; +use dom::bindings::trace::JSTraceable; use dom::window::ScriptHelpers; use euclid::length::Length; use js::jsapi::{HandleValue, Heap, RootedValue}; @@ -106,6 +107,7 @@ pub enum TimerCallback { enum InternalTimerCallback { StringTimerCallback(DOMString), FunctionTimerCallback(Rc<Function>, Rc<Vec<Heap<JSVal>>>), + InternalCallback(Box<ScheduledCallback>), } impl HeapSizeOf for InternalTimerCallback { @@ -115,6 +117,18 @@ impl HeapSizeOf for InternalTimerCallback { } } +pub trait ScheduledCallback: JSTraceable + HeapSizeOf { + fn invoke(self: Box<Self>); + + fn box_clone(&self) -> Box<ScheduledCallback>; +} + +impl Clone for Box<ScheduledCallback> { + fn clone(&self) -> Box<ScheduledCallback> { + self.box_clone() + } +} + impl ActiveTimers { pub fn new(timer_event_chan: Box<TimerEventChan + Send>, scheduler_chan: Sender<TimerEventRequest>) @@ -139,15 +153,6 @@ impl ActiveTimers { is_interval: IsInterval, source: TimerSource) -> i32 { - // step 3 - let TimerHandle(new_handle) = self.next_timer_handle.get(); - self.next_timer_handle.set(TimerHandle(new_handle + 1)); - - let timeout = cmp::max(0, timeout); - // step 7 - let duration = self.clamp_duration(Length::new(timeout as u64)); - let next_call = self.base_time() + duration; - let callback = match callback { TimerCallback::StringTimerCallback(code_str) => InternalTimerCallback::StringTimerCallback(code_str), @@ -165,6 +170,38 @@ impl ActiveTimers { } }; + let timeout = cmp::max(0, timeout); + // step 7 + let duration = self.clamp_duration(Length::new(timeout as u64)); + + let TimerHandle(handle) = self.schedule_internal_callback(callback, duration, is_interval, source); + handle + } + + pub fn schedule_callback(&self, + callback: Box<ScheduledCallback>, + duration: MsDuration, + source: TimerSource) -> TimerHandle { + self.schedule_internal_callback(InternalTimerCallback::InternalCallback(callback), + duration, + IsInterval::NonInterval, + source) + } + + // see https://html.spec.whatwg.org/multipage/#timer-initialisation-steps + fn schedule_internal_callback(&self, + callback: InternalTimerCallback, + duration: MsDuration, + is_interval: IsInterval, + source: TimerSource) -> TimerHandle { + assert!(self.suspended_since.get().is_none()); + + // step 3 + let TimerHandle(new_handle) = self.next_timer_handle.get(); + self.next_timer_handle.set(TimerHandle(new_handle + 1)); + + let next_call = self.base_time() + duration; + let timer = Timer { handle: TimerHandle(new_handle), source: source, @@ -184,11 +221,14 @@ impl ActiveTimers { } // step 10 - new_handle + TimerHandle(new_handle) } pub fn clear_timeout_or_interval(&self, handle: i32) { - let handle = TimerHandle(handle); + self.unschedule_callback(TimerHandle(handle)); + } + + pub fn unschedule_callback(&self, handle: TimerHandle) { let was_next = self.is_next_timer(handle); self.timers.borrow_mut().retain(|t| t.handle != handle); @@ -258,7 +298,10 @@ impl ActiveTimers { }).collect(); let _ = function.Call_(this, arguments, Report); - } + }, + InternalTimerCallback::InternalCallback(callback) => { + callback.invoke(); + }, }; self.nesting_level.set(0); diff --git a/components/style/attr.rs b/components/style/attr.rs new file mode 100644 index 00000000000..e9fc50e0845 --- /dev/null +++ b/components/style/attr.rs @@ -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>, +} diff --git a/components/style/lib.rs b/components/style/lib.rs index 997cd90b79c..a30274233d9 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -42,6 +42,7 @@ extern crate url; extern crate util; pub mod animation; +pub mod attr; mod custom_properties; pub mod font_face; pub mod legacy; diff --git a/components/style/restyle_hints.rs b/components/style/restyle_hints.rs index c180c13dcde..cc96f3f89dc 100644 --- a/components/style/restyle_hints.rs +++ b/components/style/restyle_hints.rs @@ -2,9 +2,10 @@ * 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 attr::{AttrIdentifier, AttrValue}; use selectors::Element; 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 std::clone::Clone; 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 /// pseudo-classes (like :hover) may require us to restyle that element, its -/// siblings, and/or its descendants. Doing this conservatively is expensive, -/// and so we RestyleHints to short-circuit work we know is unnecessary. -/// -/// NB: We should extent restyle hints to check for attribute-dependent style -/// in addition to state-dependent style (Gecko does this). - +/// siblings, and/or its descendants. Similarly, when various attributes of an +/// 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 +/// short-circuit work we know is unnecessary. bitflags! { 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 /// now to reduce complexity, but it's worth measuring the performance impact (if any) of the /// 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, - state_override: ElementState, + snapshot: &'a ElementSnapshot, } -impl<'a, E> ElementWrapper<E> where E: Element { - pub fn new(el: E) -> ElementWrapper<E> { - ElementWrapper { element: el, state_override: ElementState::empty() } +impl<'a, E> ElementWrapper<'a, E> where E: Element { + pub fn new(el: E) -> ElementWrapper<'a, E> { + ElementWrapper { element: el, snapshot: &EMPTY_SNAPSHOT } } - pub fn new_with_override(el: E, state: ElementState) -> ElementWrapper<E> { - ElementWrapper { element: el, state_override: state } + pub fn new_with_snapshot(el: E, snapshot: &'a ElementSnapshot) -> ElementWrapper<'a, E> { + ElementWrapper { element: el, snapshot: snapshot } } } -macro_rules! overridden_state_accessors { +macro_rules! snapshot_state_accessors { ($( $(#[$Flag_attr: meta])* state $css: expr => $variant: ident / $method: ident / $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. - state_pseudo_classes!(overridden_state_accessors); + // Implement the state accessors on Element to use the snapshot state if it exists. + state_pseudo_classes!(snapshot_state_accessors); fn parent_element(&self) -> Option<Self> { 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() } 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 { - 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 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 { self.element.is_empty() @@ -127,8 +177,15 @@ impl<E> Element for ElementWrapper<E> where E: Element { fn is_unvisited_link(&self) -> bool { self.element.is_unvisited_link() } - fn each_class<F>(&self, callback: F) where F: FnMut(&Atom) { - self.element.each_class(callback) + fn each_class<F>(&self, mut callback: F) where F: FnMut(&Atom) { + 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 { match combinator { 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) -// 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: // -// |s _ s:X _ s _ s:Y _ s| +// |a _ b _ c _ d _ e| // // Where: -// * Each |s| is an arbitrary simple selector. -// * Each |s| is an arbitrary combinator (or nothing). -// * X and Y are state-dependent pseudo-classes like :hover. +// * |b| and |d| are simple selectors that depend on state (like :hover) or +// attributes (like [attr...], .foo, or #foo). +// * |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 -// us to quickly scan through the operation points of pseudo-classes and determine the -// maximum effect their associated state changes may have on the style of elements in -// the document. +// us to quickly scan through the dependency sites of all style rules and determine the +// maximum effect that a given state or attribute change may have on the style of +// elements in the document. #[derive(Debug)] -struct StateDependency { +struct Dependency { selector: Arc<CompoundSelector>, combinator: Option<Combinator>, - state: ElementState, + sensitivities: Sensitivities, } #[derive(Debug)] -pub struct StateDependencySet { - deps: Vec<StateDependency>, +pub struct DependencySet { + deps: Vec<Dependency>, } -impl StateDependencySet { - pub fn new() -> StateDependencySet { - StateDependencySet { deps: Vec::new() } +impl DependencySet { + pub fn new() -> DependencySet { + 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 { + 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 state_changes = current_state ^ old_state; for dep in &self.deps { - if state_changes.intersects(dep.state) { - let old_el: ElementWrapper<E> = ElementWrapper::new_with_override(el.clone(), old_state); + if state_changes.intersects(dep.sensitivities.states) || (attrs_changed && dep.sensitivities.attrs) { + 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 matches_now = matches_compound_selector(&*dep.selector, el, None, &mut false); if matched_then != matches_now { @@ -219,15 +312,18 @@ impl StateDependencySet { let mut cur = selector; let mut combinator: Option<Combinator> = None; loop { - let mut deps = ElementState::empty(); + let mut sensitivities = Sensitivities::new(); 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() { - self.deps.push(StateDependency { + if !sensitivities.is_empty() { + self.deps.push(Dependency { selector: cur.clone(), combinator: combinator, - state: deps, + sensitivities: sensitivities, }); } diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs index 85dadac97d1..5a4b1734917 100644 --- a/components/style/selector_matching.rs +++ b/components/style/selector_matching.rs @@ -6,7 +6,7 @@ use legacy::PresentationalHintSynthesis; use media_queries::{Device, MediaType}; use node::TElementAttributes; use properties::{PropertyDeclaration, PropertyDeclarationBlock}; -use restyle_hints::{RestyleHint, StateDependencySet}; +use restyle_hints::{ElementSnapshot, RestyleHint, DependencySet}; use selectors::Element; use selectors::bloom::BloomFilter; use selectors::matching::DeclarationBlock as GenericDeclarationBlock; @@ -95,8 +95,8 @@ pub struct Stylist { after_map: PerPseudoElementSelectorMap, rules_source_order: usize, - // Selector state dependencies used to compute restyle hints. - state_deps: StateDependencySet, + // Selector dependencies used to compute restyle hints. + state_deps: DependencySet, } impl Stylist { @@ -112,7 +112,7 @@ impl Stylist { before_map: PerPseudoElementSelectorMap::new(), after_map: PerPseudoElementSelectorMap::new(), 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. stylist @@ -204,12 +204,16 @@ impl Stylist { self.rules_source_order = rules_source_order; } - pub fn restyle_hint_for_state_change<E>(&self, element: &E, - current_state: ElementState, - old_state: ElementState) - -> RestyleHint - where E: Element + Clone { - self.state_deps.compute_hint(element, current_state, old_state) + pub fn compute_restyle_hint<E>(&self, element: &E, + snapshot: &ElementSnapshot, + // NB: We need to pass current_state as an argument because + // selectors::Element doesn't provide access to ElementState + // directly, and computing it from the ElementState would be + // 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]) { diff --git a/python/tidy.py b/python/tidy.py index 44f7ba45b63..dd2ef9d29cc 100644 --- a/python/tidy.py +++ b/python/tidy.py @@ -25,6 +25,8 @@ ignored_files = [ os.path.join(".", "support", "*"), os.path.join(".", "tests", "wpt", "css-tests", "*"), os.path.join(".", "tests", "wpt", "harness", "*"), + os.path.join(".", "tests", "wpt", "sync", "*"), + os.path.join(".", "tests", "wpt", "sync_css", "*"), os.path.join(".", "tests", "wpt", "update", "*"), os.path.join(".", "tests", "wpt", "web-platform-tests", "*"), os.path.join(".", "python", "mach", "*"), diff --git a/tests/ref/basic.list b/tests/ref/basic.list index ead25bc53d7..ce35db3cf9a 100644 --- a/tests/ref/basic.list +++ b/tests/ref/basic.list @@ -34,17 +34,7 @@ prefs:"layout.writing-mode.enabled" == iframe/size_attributes_vertical_writing_m == iframe/stacking_context.html iframe/stacking_context_ref.html == iframe/stacking_context_position_a.html iframe/stacking_context_position_ref.html -!= inline_background_a.html inline_background_ref.html -== inline_block_margin_auto_a.html inline_block_margin_auto_ref.html -== inline_block_parent_width.html inline_block_parent_width_ref.html -== inline_block_parent_width_percentage.html inline_block_parent_width_ref.html # inline_border_a.html inline_border_b.html -== inline_whitespace_a.html inline_whitespace_ref.html -== inline_whitespace_b.html inline_whitespace_ref.html -!= input_height_a.html input_height_ref.html -!= inset_blackborder.html blackborder_ref.html -== jpeg_normal.html jpeg_ref.html -== jpeg_progressive.html jpeg_ref.html == legacy_cellspacing_attribute_a.html border_spacing_ref.html == legacy_td_width_attribute_a.html legacy_td_width_attribute_ref.html == legacy_th_width_attribute_a.html legacy_td_width_attribute_ref.html @@ -62,7 +52,6 @@ flaky_cpu == linebreak_simple_a.html linebreak_simple_b.html != octicons_a.html octicons_ref.html == ol_japanese_iroha_a.html ol_japanese_iroha_ref.html != ol_japanese_iroha_bullet_styles.html ol_japanese_iroha_ref.html -!= outset_blackborder.html blackborder_ref.html # Should be == with expected failure. See #2797 != overconstrained_block.html overconstrained_block_ref.html == overflow_auto.html overflow_simple_b.html diff --git a/tests/wpt/grouping_formatter.py b/tests/wpt/grouping_formatter.py index c97101ca86c..75ec5d4c674 100644 --- a/tests/wpt/grouping_formatter.py +++ b/tests/wpt/grouping_formatter.py @@ -17,11 +17,16 @@ class GroupingFormatter(base.BaseFormatter): self.need_to_erase_last_line = False self.current_display = "" self.running_tests = {} + self.last_test_finished = "Running tests..." self.test_output = collections.defaultdict(str) self.subtest_failures = collections.defaultdict(list) self.tests_with_failing_subtests = [] self.interactive = os.isatty(sys.stdout.fileno()) + # iTerm2 doesn't support the terminal codes used to erase previous lines, + # so only print one line and rely only on backspace characters. + self.one_line = os.environ.get("TERM_PROGRAM", "") == "iTerm.app" + self.expected = { 'OK': 0, 'PASS': 0, @@ -44,8 +49,13 @@ class GroupingFormatter(base.BaseFormatter): def text_to_erase_display(self): if not self.interactive or not self.current_display: return "" + # TODO(mrobinson, 8313): We need to add support for Windows terminals here. - return ("\033[F" + "\033[K") * len(self.current_display.splitlines()) + erase_length = len(self.current_display) + if self.one_line: + return "\b \b" * erase_length + else: + return ("\033[F" + "\033[K") * len(self.current_display.splitlines()) def generate_output(self, text=None, new_display=None): if not self.interactive: @@ -64,6 +74,9 @@ class GroupingFormatter(base.BaseFormatter): else: new_display = " [%i/%i] " % (self.completed_tests, self.number_of_tests) + if self.one_line: + return new_display + self.last_test_finished + if self.running_tests: indent = " " * len(new_display) return new_display + ("\n%s" % indent).join( @@ -161,11 +174,16 @@ class GroupingFormatter(base.BaseFormatter): subtest_failures = self.subtest_failures.pop(test_name, []) del self.running_tests[data['thread']] + self.last_test_finished = test_name new_display = self.build_status_line() if not had_unexpected_test_result and not subtest_failures: self.expected[test_status] += 1 - return self.generate_output(text=None, new_display=new_display) + if self.interactive: + return self.generate_output(text=None, new_display=new_display) + else: + return self.generate_output(text=" %s\n\n" % test_name, + new_display=new_display) # If the test crashed or timed out, we also include any process output, # because there is a good chance that the test produced a stack trace diff --git a/tests/wpt/metadata-css/css21_dev/html4/background-root-101.htm.ini b/tests/wpt/metadata-css/css21_dev/html4/background-root-101.htm.ini deleted file mode 100644 index 004cd45bf51..00000000000 --- a/tests/wpt/metadata-css/css21_dev/html4/background-root-101.htm.ini +++ /dev/null @@ -1,3 +0,0 @@ -[background-root-101.htm] - type: reftest - expected: FAIL diff --git a/tests/wpt/metadata-css/css21_dev/html4/dynamic-sibling-combinator-001.htm.ini b/tests/wpt/metadata-css/css21_dev/html4/dynamic-sibling-combinator-001.htm.ini deleted file mode 100644 index 3f785687b72..00000000000 --- a/tests/wpt/metadata-css/css21_dev/html4/dynamic-sibling-combinator-001.htm.ini +++ /dev/null @@ -1,4 +0,0 @@ -[dynamic-sibling-combinator-001.htm] - type: reftest - bug: https://github.com/servo/servo/issues/7890 - expected: FAIL diff --git a/tests/wpt/metadata/XMLHttpRequest/event-timeout.htm.ini b/tests/wpt/metadata/XMLHttpRequest/event-timeout.htm.ini deleted file mode 100644 index bbc554a338c..00000000000 --- a/tests/wpt/metadata/XMLHttpRequest/event-timeout.htm.ini +++ /dev/null @@ -1,3 +0,0 @@ -[event-timeout.htm] - type: testharness - disabled: issue 3396 diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 340cbde980b..81eab3bdd73 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -1763,6 +1763,18 @@ "url": "/_mozilla/css/inline_absolute_out_of_flow_a.html" } ], + "css/inline_background_a.html": [ + { + "path": "css/inline_background_a.html", + "references": [ + [ + "/_mozilla/css/inline_background_ref.html", + "!=" + ] + ], + "url": "/_mozilla/css/inline_background_a.html" + } + ], "css/inline_block_baseline_a.html": [ { "path": "css/inline_block_baseline_a.html", @@ -1835,6 +1847,18 @@ "url": "/_mozilla/css/inline_block_margin_a.html" } ], + "css/inline_block_margin_auto_a.html": [ + { + "path": "css/inline_block_margin_auto_a.html", + "references": [ + [ + "/_mozilla/css/inline_block_margin_auto_ref.html", + "==" + ] + ], + "url": "/_mozilla/css/inline_block_margin_auto_a.html" + } + ], "css/inline_block_margin_auto_zero_a.html": [ { "path": "css/inline_block_margin_auto_zero_a.html", @@ -1895,6 +1919,30 @@ "url": "/_mozilla/css/inline_block_parent_padding_a.html" } ], + "css/inline_block_parent_width.html": [ + { + "path": "css/inline_block_parent_width.html", + "references": [ + [ + "/_mozilla/css/inline_block_parent_width_ref.html", + "==" + ] + ], + "url": "/_mozilla/css/inline_block_parent_width.html" + } + ], + "css/inline_block_parent_width_percentage.html": [ + { + "path": "css/inline_block_parent_width_percentage.html", + "references": [ + [ + "/_mozilla/css/inline_block_parent_width_ref.html", + "==" + ] + ], + "url": "/_mozilla/css/inline_block_parent_width_percentage.html" + } + ], "css/inline_block_percentage_height_a.html": [ { "path": "css/inline_block_percentage_height_a.html", @@ -2027,6 +2075,30 @@ "url": "/_mozilla/css/inline_text_align_a.html" } ], + "css/inline_whitespace_a.html": [ + { + "path": "css/inline_whitespace_a.html", + "references": [ + [ + "/_mozilla/css/inline_whitespace_ref.html", + "==" + ] + ], + "url": "/_mozilla/css/inline_whitespace_a.html" + } + ], + "css/inline_whitespace_b.html": [ + { + "path": "css/inline_whitespace_b.html", + "references": [ + [ + "/_mozilla/css/inline_whitespace_ref.html", + "==" + ] + ], + "url": "/_mozilla/css/inline_whitespace_b.html" + } + ], "css/input_button_margins_a.html": [ { "path": "css/input_button_margins_a.html", @@ -2051,6 +2123,18 @@ "url": "/_mozilla/css/input_button_size_a.html" } ], + "css/input_height_a.html": [ + { + "path": "css/input_height_a.html", + "references": [ + [ + "/_mozilla/css/input_height_ref.html", + "!=" + ] + ], + "url": "/_mozilla/css/input_height_a.html" + } + ], "css/inset.html": [ { "path": "css/inset.html", @@ -2063,6 +2147,18 @@ "url": "/_mozilla/css/inset.html" } ], + "css/inset_blackborder.html": [ + { + "path": "css/inset_blackborder.html", + "references": [ + [ + "/_mozilla/css/blackborder_ref.html", + "!=" + ] + ], + "url": "/_mozilla/css/inset_blackborder.html" + } + ], "css/issue-1324.html": [ { "path": "css/issue-1324.html", @@ -2075,6 +2171,30 @@ "url": "/_mozilla/css/issue-1324.html" } ], + "css/jpeg_normal.html": [ + { + "path": "css/jpeg_normal.html", + "references": [ + [ + "/_mozilla/css/jpeg_ref.html", + "==" + ] + ], + "url": "/_mozilla/css/jpeg_normal.html" + } + ], + "css/jpeg_progressive.html": [ + { + "path": "css/jpeg_progressive.html", + "references": [ + [ + "/_mozilla/css/jpeg_ref.html", + "==" + ] + ], + "url": "/_mozilla/css/jpeg_progressive.html" + } + ], "css/jumpiness_a.html": [ { "path": "css/jumpiness_a.html", @@ -2675,6 +2795,18 @@ "url": "/_mozilla/css/outset.html" } ], + "css/outset_blackborder.html": [ + { + "path": "css/outset_blackborder.html", + "references": [ + [ + "/_mozilla/css/blackborder_ref.html", + "!=" + ] + ], + "url": "/_mozilla/css/outset_blackborder.html" + } + ], "css/overflow_auto_stacking_order_a.html": [ { "path": "css/overflow_auto_stacking_order_a.html", @@ -3119,6 +3251,18 @@ "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": [ { "path": "css/restyle_hints_state.html", @@ -6696,6 +6840,18 @@ "url": "/_mozilla/css/inline_absolute_out_of_flow_a.html" } ], + "css/inline_background_a.html": [ + { + "path": "css/inline_background_a.html", + "references": [ + [ + "/_mozilla/css/inline_background_ref.html", + "!=" + ] + ], + "url": "/_mozilla/css/inline_background_a.html" + } + ], "css/inline_block_baseline_a.html": [ { "path": "css/inline_block_baseline_a.html", @@ -6768,6 +6924,18 @@ "url": "/_mozilla/css/inline_block_margin_a.html" } ], + "css/inline_block_margin_auto_a.html": [ + { + "path": "css/inline_block_margin_auto_a.html", + "references": [ + [ + "/_mozilla/css/inline_block_margin_auto_ref.html", + "==" + ] + ], + "url": "/_mozilla/css/inline_block_margin_auto_a.html" + } + ], "css/inline_block_margin_auto_zero_a.html": [ { "path": "css/inline_block_margin_auto_zero_a.html", @@ -6828,6 +6996,30 @@ "url": "/_mozilla/css/inline_block_parent_padding_a.html" } ], + "css/inline_block_parent_width.html": [ + { + "path": "css/inline_block_parent_width.html", + "references": [ + [ + "/_mozilla/css/inline_block_parent_width_ref.html", + "==" + ] + ], + "url": "/_mozilla/css/inline_block_parent_width.html" + } + ], + "css/inline_block_parent_width_percentage.html": [ + { + "path": "css/inline_block_parent_width_percentage.html", + "references": [ + [ + "/_mozilla/css/inline_block_parent_width_ref.html", + "==" + ] + ], + "url": "/_mozilla/css/inline_block_parent_width_percentage.html" + } + ], "css/inline_block_percentage_height_a.html": [ { "path": "css/inline_block_percentage_height_a.html", @@ -6960,6 +7152,30 @@ "url": "/_mozilla/css/inline_text_align_a.html" } ], + "css/inline_whitespace_a.html": [ + { + "path": "css/inline_whitespace_a.html", + "references": [ + [ + "/_mozilla/css/inline_whitespace_ref.html", + "==" + ] + ], + "url": "/_mozilla/css/inline_whitespace_a.html" + } + ], + "css/inline_whitespace_b.html": [ + { + "path": "css/inline_whitespace_b.html", + "references": [ + [ + "/_mozilla/css/inline_whitespace_ref.html", + "==" + ] + ], + "url": "/_mozilla/css/inline_whitespace_b.html" + } + ], "css/input_button_margins_a.html": [ { "path": "css/input_button_margins_a.html", @@ -6984,6 +7200,18 @@ "url": "/_mozilla/css/input_button_size_a.html" } ], + "css/input_height_a.html": [ + { + "path": "css/input_height_a.html", + "references": [ + [ + "/_mozilla/css/input_height_ref.html", + "!=" + ] + ], + "url": "/_mozilla/css/input_height_a.html" + } + ], "css/inset.html": [ { "path": "css/inset.html", @@ -6996,6 +7224,18 @@ "url": "/_mozilla/css/inset.html" } ], + "css/inset_blackborder.html": [ + { + "path": "css/inset_blackborder.html", + "references": [ + [ + "/_mozilla/css/blackborder_ref.html", + "!=" + ] + ], + "url": "/_mozilla/css/inset_blackborder.html" + } + ], "css/issue-1324.html": [ { "path": "css/issue-1324.html", @@ -7008,6 +7248,30 @@ "url": "/_mozilla/css/issue-1324.html" } ], + "css/jpeg_normal.html": [ + { + "path": "css/jpeg_normal.html", + "references": [ + [ + "/_mozilla/css/jpeg_ref.html", + "==" + ] + ], + "url": "/_mozilla/css/jpeg_normal.html" + } + ], + "css/jpeg_progressive.html": [ + { + "path": "css/jpeg_progressive.html", + "references": [ + [ + "/_mozilla/css/jpeg_ref.html", + "==" + ] + ], + "url": "/_mozilla/css/jpeg_progressive.html" + } + ], "css/jumpiness_a.html": [ { "path": "css/jumpiness_a.html", @@ -7608,6 +7872,18 @@ "url": "/_mozilla/css/outset.html" } ], + "css/outset_blackborder.html": [ + { + "path": "css/outset_blackborder.html", + "references": [ + [ + "/_mozilla/css/blackborder_ref.html", + "!=" + ] + ], + "url": "/_mozilla/css/outset_blackborder.html" + } + ], "css/overflow_auto_stacking_order_a.html": [ { "path": "css/overflow_auto_stacking_order_a.html", @@ -8052,6 +8328,18 @@ "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": [ { "path": "css/restyle_hints_state.html", diff --git a/tests/ref/blackborder_ref.html b/tests/wpt/mozilla/tests/css/blackborder_ref.html index 266c7f058c5..266c7f058c5 100644 --- a/tests/ref/blackborder_ref.html +++ b/tests/wpt/mozilla/tests/css/blackborder_ref.html diff --git a/tests/ref/inline_background_a.html b/tests/wpt/mozilla/tests/css/inline_background_a.html index 58a34046d5d..31b45aabe15 100644 --- a/tests/ref/inline_background_a.html +++ b/tests/wpt/mozilla/tests/css/inline_background_a.html @@ -1,6 +1,7 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> + <link rel=mismatch href=inline_background_ref.html> <style type="text/css"> .white { color: white; diff --git a/tests/ref/inline_background_ref.html b/tests/wpt/mozilla/tests/css/inline_background_ref.html index e2ecc75beba..e2ecc75beba 100644 --- a/tests/ref/inline_background_ref.html +++ b/tests/wpt/mozilla/tests/css/inline_background_ref.html diff --git a/tests/ref/inline_block_margin_auto_a.html b/tests/wpt/mozilla/tests/css/inline_block_margin_auto_a.html index 3fa23c34563..cba325fd717 100644 --- a/tests/ref/inline_block_margin_auto_a.html +++ b/tests/wpt/mozilla/tests/css/inline_block_margin_auto_a.html @@ -1,4 +1,5 @@ <!DOCTYPE html> +<link rel=match href=inline_block_margin_auto_ref.html> <style> html, body { margin: 0; diff --git a/tests/ref/inline_block_margin_auto_ref.html b/tests/wpt/mozilla/tests/css/inline_block_margin_auto_ref.html index 1ab56dd83fb..1ab56dd83fb 100644 --- a/tests/ref/inline_block_margin_auto_ref.html +++ b/tests/wpt/mozilla/tests/css/inline_block_margin_auto_ref.html diff --git a/tests/ref/inline_block_parent_width.html b/tests/wpt/mozilla/tests/css/inline_block_parent_width.html index 520339a5a51..4a878a32d6a 100644 --- a/tests/ref/inline_block_parent_width.html +++ b/tests/wpt/mozilla/tests/css/inline_block_parent_width.html @@ -1,5 +1,6 @@ <!DOCTYPE html> <html> +<link rel=match href=inline_block_parent_width_ref.html> <body> <div style="display: block; width: 300px;"> <div style="display: inline-block"> diff --git a/tests/ref/inline_block_parent_width_percentage.html b/tests/wpt/mozilla/tests/css/inline_block_parent_width_percentage.html index ed6bcf1ee89..53a04ad8382 100644 --- a/tests/ref/inline_block_parent_width_percentage.html +++ b/tests/wpt/mozilla/tests/css/inline_block_parent_width_percentage.html @@ -1,5 +1,6 @@ <!DOCTYPE html> <html> +<link rel=match href=inline_block_parent_width_ref.html> <body> <div style="display: block; width: 600px;"> <div style="width: 50%; display: inline-block;"> diff --git a/tests/ref/inline_block_parent_width_ref.html b/tests/wpt/mozilla/tests/css/inline_block_parent_width_ref.html index d9d9fff3b90..d9d9fff3b90 100644 --- a/tests/ref/inline_block_parent_width_ref.html +++ b/tests/wpt/mozilla/tests/css/inline_block_parent_width_ref.html diff --git a/tests/ref/inline_whitespace_a.html b/tests/wpt/mozilla/tests/css/inline_whitespace_a.html index f454de56674..930f3b3b016 100644 --- a/tests/ref/inline_whitespace_a.html +++ b/tests/wpt/mozilla/tests/css/inline_whitespace_a.html @@ -2,6 +2,7 @@ <html> <head> <link rel="stylesheet" type="text/css" href="css/ahem.css"> + <link rel=match href=inline_whitespace_ref.html> <style type="text/css"> body { background-color: #f6f6f6; diff --git a/tests/ref/inline_whitespace_b.html b/tests/wpt/mozilla/tests/css/inline_whitespace_b.html index 9e1b40ba08e..a9b1bbaf290 100644 --- a/tests/ref/inline_whitespace_b.html +++ b/tests/wpt/mozilla/tests/css/inline_whitespace_b.html @@ -2,6 +2,7 @@ <html> <head> <link rel="stylesheet" type="text/css" href="css/ahem.css"> + <link rel=match href=inline_whitespace_ref.html> <style type="text/css"> body { background-color: #f6f6f6; diff --git a/tests/ref/inline_whitespace_ref.html b/tests/wpt/mozilla/tests/css/inline_whitespace_ref.html index 7c88e293d46..7c88e293d46 100644 --- a/tests/ref/inline_whitespace_ref.html +++ b/tests/wpt/mozilla/tests/css/inline_whitespace_ref.html diff --git a/tests/ref/input_height_a.html b/tests/wpt/mozilla/tests/css/input_height_a.html index 7c6f7dbfa8a..9375d302eae 100644 --- a/tests/ref/input_height_a.html +++ b/tests/wpt/mozilla/tests/css/input_height_a.html @@ -1,5 +1,6 @@ <!DOCTYPE html> <html> +<link rel=mismatch href=input_height_ref.html> <body> <!-- We assume that inputs are not 100px high on any platform, since that would be absurd --> <input type=text style="height: 100px;"></input> diff --git a/tests/ref/input_height_ref.html b/tests/wpt/mozilla/tests/css/input_height_ref.html index c3d507e768e..c3d507e768e 100644 --- a/tests/ref/input_height_ref.html +++ b/tests/wpt/mozilla/tests/css/input_height_ref.html diff --git a/tests/ref/inset_blackborder.html b/tests/wpt/mozilla/tests/css/inset_blackborder.html index ffacd8a7855..565760bedc2 100644 --- a/tests/ref/inset_blackborder.html +++ b/tests/wpt/mozilla/tests/css/inset_blackborder.html @@ -1,4 +1,5 @@ <html> +<link rel=mismatch href=blackborder_ref.html> <style> body { margin: 0px; diff --git a/tests/ref/jpeg_normal.html b/tests/wpt/mozilla/tests/css/jpeg_normal.html index 51b5231f7a2..512731a03ca 100644 --- a/tests/ref/jpeg_normal.html +++ b/tests/wpt/mozilla/tests/css/jpeg_normal.html @@ -1,8 +1,7 @@ <!DOCTYPE html> <html> <head> - <style type="text/css"> - </style> + <link rel=match href=jpeg_ref.html> </head> <body> <img src="jpeg_normal.jpg"> diff --git a/tests/ref/jpeg_normal.jpg b/tests/wpt/mozilla/tests/css/jpeg_normal.jpg Binary files differindex aac501b2a02..aac501b2a02 100644 --- a/tests/ref/jpeg_normal.jpg +++ b/tests/wpt/mozilla/tests/css/jpeg_normal.jpg diff --git a/tests/ref/jpeg_progressive.html b/tests/wpt/mozilla/tests/css/jpeg_progressive.html index fe2bda0c2e2..447c78877fe 100644 --- a/tests/ref/jpeg_progressive.html +++ b/tests/wpt/mozilla/tests/css/jpeg_progressive.html @@ -1,8 +1,7 @@ <!DOCTYPE html> <html> <head> - <style type="text/css"> - </style> + <link rel=match href=jpeg_ref.html> </head> <body> <img src="jpeg_progressive.jpg"> diff --git a/tests/ref/jpeg_progressive.jpg b/tests/wpt/mozilla/tests/css/jpeg_progressive.jpg Binary files differindex c2561d8fe50..c2561d8fe50 100644 --- a/tests/ref/jpeg_progressive.jpg +++ b/tests/wpt/mozilla/tests/css/jpeg_progressive.jpg diff --git a/tests/ref/jpeg_ref.html b/tests/wpt/mozilla/tests/css/jpeg_ref.html index 37417b7647b..37417b7647b 100644 --- a/tests/ref/jpeg_ref.html +++ b/tests/wpt/mozilla/tests/css/jpeg_ref.html diff --git a/tests/ref/outset_blackborder.html b/tests/wpt/mozilla/tests/css/outset_blackborder.html index 35cda879793..15fedb63a7d 100644 --- a/tests/ref/outset_blackborder.html +++ b/tests/wpt/mozilla/tests/css/outset_blackborder.html @@ -1,4 +1,5 @@ <html> +<link rel=mismatch href=blackborder_ref.html> <style> body { margin: 0px; diff --git a/tests/wpt/mozilla/tests/css/restyle_hints_attr.css b/tests/wpt/mozilla/tests/css/restyle_hints_attr.css new file mode 100644 index 00000000000..64d332c31f7 --- /dev/null +++ b/tests/wpt/mozilla/tests/css/restyle_hints_attr.css @@ -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; +} diff --git a/tests/wpt/mozilla/tests/css/restyle_hints_attr.html b/tests/wpt/mozilla/tests/css/restyle_hints_attr.html new file mode 100644 index 00000000000..d9c55498f8f --- /dev/null +++ b/tests/wpt/mozilla/tests/css/restyle_hints_attr.html @@ -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> diff --git a/tests/wpt/mozilla/tests/css/restyle_hints_attr_ref.html b/tests/wpt/mozilla/tests/css/restyle_hints_attr_ref.html new file mode 100644 index 00000000000..2d87a0f4526 --- /dev/null +++ b/tests/wpt/mozilla/tests/css/restyle_hints_attr_ref.html @@ -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> + diff --git a/tests/wpt/mozilla/tests/css/restyle_hints_state.html b/tests/wpt/mozilla/tests/css/restyle_hints_state.html index 52f82bc905d..480d1675bb5 100644 --- a/tests/wpt/mozilla/tests/css/restyle_hints_state.html +++ b/tests/wpt/mozilla/tests/css/restyle_hints_state.html @@ -30,8 +30,9 @@ * implement attribute-based restyle hints, we can stop dirtying the subtree * on attribute modifications, and these tests will start to be more useful. */ + window.dummy = 0; var $ = document.getElementById.bind(document); - function syncRestyle() { window.dummy != $("fs2").offsetTop; } + function syncRestyle() { window.dummy += $("fs2").offsetTop; } syncRestyle(); $('fs1').disabled = true; syncRestyle(); |