aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/layout/layout_task.rs38
-rw-r--r--components/layout/wrapper.rs67
-rw-r--r--components/script/dom/attr.rs211
-rw-r--r--components/script/dom/bindings/global.rs20
-rw-r--r--components/script/dom/bindings/trace.rs5
-rw-r--r--components/script/dom/document.rs27
-rw-r--r--components/script/dom/element.rs42
-rw-r--r--components/script/dom/node.rs17
-rw-r--r--components/script/dom/window.rs14
-rw-r--r--components/script/dom/workerglobalscope.rs14
-rw-r--r--components/script/dom/xmlhttprequest.rs70
-rw-r--r--components/script/timers.rs67
-rw-r--r--components/style/attr.rs178
-rw-r--r--components/style/lib.rs1
-rw-r--r--components/style/restyle_hints.rs196
-rw-r--r--components/style/selector_matching.rs24
-rw-r--r--python/tidy.py2
-rw-r--r--tests/ref/basic.list11
-rw-r--r--tests/wpt/grouping_formatter.py22
-rw-r--r--tests/wpt/metadata-css/css21_dev/html4/background-root-101.htm.ini3
-rw-r--r--tests/wpt/metadata-css/css21_dev/html4/dynamic-sibling-combinator-001.htm.ini4
-rw-r--r--tests/wpt/metadata/XMLHttpRequest/event-timeout.htm.ini3
-rw-r--r--tests/wpt/mozilla/meta/MANIFEST.json288
-rw-r--r--tests/wpt/mozilla/tests/css/blackborder_ref.html (renamed from tests/ref/blackborder_ref.html)0
-rw-r--r--tests/wpt/mozilla/tests/css/inline_background_a.html (renamed from tests/ref/inline_background_a.html)1
-rw-r--r--tests/wpt/mozilla/tests/css/inline_background_ref.html (renamed from tests/ref/inline_background_ref.html)0
-rw-r--r--tests/wpt/mozilla/tests/css/inline_block_margin_auto_a.html (renamed from tests/ref/inline_block_margin_auto_a.html)1
-rw-r--r--tests/wpt/mozilla/tests/css/inline_block_margin_auto_ref.html (renamed from tests/ref/inline_block_margin_auto_ref.html)0
-rw-r--r--tests/wpt/mozilla/tests/css/inline_block_parent_width.html (renamed from tests/ref/inline_block_parent_width.html)1
-rw-r--r--tests/wpt/mozilla/tests/css/inline_block_parent_width_percentage.html (renamed from tests/ref/inline_block_parent_width_percentage.html)1
-rw-r--r--tests/wpt/mozilla/tests/css/inline_block_parent_width_ref.html (renamed from tests/ref/inline_block_parent_width_ref.html)0
-rw-r--r--tests/wpt/mozilla/tests/css/inline_whitespace_a.html (renamed from tests/ref/inline_whitespace_a.html)1
-rw-r--r--tests/wpt/mozilla/tests/css/inline_whitespace_b.html (renamed from tests/ref/inline_whitespace_b.html)1
-rw-r--r--tests/wpt/mozilla/tests/css/inline_whitespace_ref.html (renamed from tests/ref/inline_whitespace_ref.html)0
-rw-r--r--tests/wpt/mozilla/tests/css/input_height_a.html (renamed from tests/ref/input_height_a.html)1
-rw-r--r--tests/wpt/mozilla/tests/css/input_height_ref.html (renamed from tests/ref/input_height_ref.html)0
-rw-r--r--tests/wpt/mozilla/tests/css/inset_blackborder.html (renamed from tests/ref/inset_blackborder.html)1
-rw-r--r--tests/wpt/mozilla/tests/css/jpeg_normal.html (renamed from tests/ref/jpeg_normal.html)3
-rw-r--r--tests/wpt/mozilla/tests/css/jpeg_normal.jpg (renamed from tests/ref/jpeg_normal.jpg)bin336 -> 336 bytes
-rw-r--r--tests/wpt/mozilla/tests/css/jpeg_progressive.html (renamed from tests/ref/jpeg_progressive.html)3
-rw-r--r--tests/wpt/mozilla/tests/css/jpeg_progressive.jpg (renamed from tests/ref/jpeg_progressive.jpg)bin570 -> 570 bytes
-rw-r--r--tests/wpt/mozilla/tests/css/jpeg_ref.html (renamed from tests/ref/jpeg_ref.html)0
-rw-r--r--tests/wpt/mozilla/tests/css/outset_blackborder.html (renamed from tests/ref/outset_blackborder.html)1
-rw-r--r--tests/wpt/mozilla/tests/css/restyle_hints_attr.css44
-rw-r--r--tests/wpt/mozilla/tests/css/restyle_hints_attr.html35
-rw-r--r--tests/wpt/mozilla/tests/css/restyle_hints_attr_ref.html16
-rw-r--r--tests/wpt/mozilla/tests/css/restyle_hints_state.html3
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
index aac501b2a02..aac501b2a02 100644
--- a/tests/ref/jpeg_normal.jpg
+++ b/tests/wpt/mozilla/tests/css/jpeg_normal.jpg
Binary files differ
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
index c2561d8fe50..c2561d8fe50 100644
--- a/tests/ref/jpeg_progressive.jpg
+++ b/tests/wpt/mozilla/tests/css/jpeg_progressive.jpg
Binary files differ
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();