mirror of
https://github.com/servo/servo.git
synced 2025-08-05 21:50:18 +01:00
Auto merge of #17147 - emilio:media-query-caching, r=heycam
Bug 1357461 - stylo: Cache media query results and only flush and restyle if they changed. <!-- Reviewable:start --> This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/17147) <!-- Reviewable:end -->
This commit is contained in:
commit
7b61d55421
9 changed files with 560 additions and 391 deletions
|
@ -9,7 +9,6 @@ use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
||||||
use dom::TElement;
|
use dom::TElement;
|
||||||
use fnv::FnvHashMap;
|
use fnv::FnvHashMap;
|
||||||
use gecko::rules::{CounterStyleRule, FontFaceRule};
|
use gecko::rules::{CounterStyleRule, FontFaceRule};
|
||||||
use gecko::wrapper::GeckoElement;
|
|
||||||
use gecko_bindings::bindings::RawServoStyleSet;
|
use gecko_bindings::bindings::RawServoStyleSet;
|
||||||
use gecko_bindings::structs::RawGeckoPresContextOwned;
|
use gecko_bindings::structs::RawGeckoPresContextOwned;
|
||||||
use gecko_bindings::structs::nsIDocument;
|
use gecko_bindings::structs::nsIDocument;
|
||||||
|
@ -70,15 +69,6 @@ impl PerDocumentStyleData {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PerDocumentStyleDataImpl {
|
impl PerDocumentStyleDataImpl {
|
||||||
/// Reset the device state because it may have changed.
|
|
||||||
///
|
|
||||||
/// Implies also a stylesheet flush.
|
|
||||||
pub fn reset_device(&mut self, guard: &SharedRwLockReadGuard) {
|
|
||||||
self.stylist.device_mut().reset();
|
|
||||||
self.stylesheets.force_dirty();
|
|
||||||
self.flush_stylesheets::<GeckoElement>(guard, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Recreate the style data if the stylesheets have changed.
|
/// Recreate the style data if the stylesheets have changed.
|
||||||
pub fn flush_stylesheets<E>(&mut self,
|
pub fn flush_stylesheets<E>(&mut self,
|
||||||
guard: &SharedRwLockReadGuard,
|
guard: &SharedRwLockReadGuard,
|
||||||
|
|
|
@ -99,6 +99,13 @@ impl Device {
|
||||||
self.root_font_size.store(size.0 as isize, Ordering::Relaxed)
|
self.root_font_size.store(size.0 as isize, Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Recreates the default computed values.
|
||||||
|
pub fn reset_computed_values(&mut self) {
|
||||||
|
// NB: A following stylesheet flush will populate this if appropriate.
|
||||||
|
self.viewport_override = None;
|
||||||
|
self.default_values = ComputedValues::default_values(unsafe { &*self.pres_context });
|
||||||
|
}
|
||||||
|
|
||||||
/// Recreates all the temporary state that the `Device` stores.
|
/// Recreates all the temporary state that the `Device` stores.
|
||||||
///
|
///
|
||||||
/// This includes the viewport override from `@viewport` rules, and also the
|
/// This includes the viewport override from `@viewport` rules, and also the
|
||||||
|
@ -106,7 +113,7 @@ impl Device {
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
// NB: A following stylesheet flush will populate this if appropriate.
|
// NB: A following stylesheet flush will populate this if appropriate.
|
||||||
self.viewport_override = None;
|
self.viewport_override = None;
|
||||||
self.default_values = ComputedValues::default_values(unsafe { &*self.pres_context });
|
self.reset_computed_values();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the current media type of the device.
|
/// Returns the current media type of the device.
|
||||||
|
|
133
components/style/invalidation/media_queries.rs
Normal file
133
components/style/invalidation/media_queries.rs
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
//! Code related to the invalidation of media-query-affected rules.
|
||||||
|
|
||||||
|
use context::QuirksMode;
|
||||||
|
use fnv::FnvHashSet;
|
||||||
|
use media_queries::Device;
|
||||||
|
use shared_lock::SharedRwLockReadGuard;
|
||||||
|
use stylesheets::{DocumentRule, ImportRule, MediaRule, SupportsRule};
|
||||||
|
use stylesheets::{NestedRuleIterationCondition, Stylesheet};
|
||||||
|
|
||||||
|
/// A key for a given media query result.
|
||||||
|
///
|
||||||
|
/// NOTE: It happens to be the case that all the media lists we care about
|
||||||
|
/// happen to have a stable address, so we can just use an opaque pointer to
|
||||||
|
/// represent them.
|
||||||
|
///
|
||||||
|
/// Also, note that right now when a rule or stylesheet is removed, we do a full
|
||||||
|
/// style flush, so there's no need to worry about other item created with the
|
||||||
|
/// same pointer address.
|
||||||
|
///
|
||||||
|
/// If this changes, though, we may need to remove the item from the cache if
|
||||||
|
/// present before it goes away.
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||||
|
pub struct MediaListKey(usize);
|
||||||
|
|
||||||
|
/// A trait to get a given `MediaListKey` for a given item that can hold a
|
||||||
|
/// `MediaList`.
|
||||||
|
pub trait ToMediaListKey : Sized {
|
||||||
|
/// Get a `MediaListKey` for this item. This key needs to uniquely identify
|
||||||
|
/// the item.
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
fn to_media_list_key(&self) -> MediaListKey {
|
||||||
|
use std::mem;
|
||||||
|
MediaListKey(unsafe { mem::transmute(self as *const Self) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToMediaListKey for Stylesheet {}
|
||||||
|
impl ToMediaListKey for ImportRule {}
|
||||||
|
impl ToMediaListKey for MediaRule {}
|
||||||
|
|
||||||
|
/// A struct that holds the result of a media query evaluation pass for the
|
||||||
|
/// media queries that evaluated successfully.
|
||||||
|
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||||
|
pub struct EffectiveMediaQueryResults {
|
||||||
|
/// The set of media lists that matched last time.
|
||||||
|
set: FnvHashSet<MediaListKey>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EffectiveMediaQueryResults {
|
||||||
|
/// Trivially constructs an empty `EffectiveMediaQueryResults`.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
set: FnvHashSet::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resets the results, using an empty key.
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.set.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether a given item was known to be effective when the results
|
||||||
|
/// were cached.
|
||||||
|
pub fn was_effective<T>(&self, item: &T) -> bool
|
||||||
|
where T: ToMediaListKey,
|
||||||
|
{
|
||||||
|
self.set.contains(&item.to_media_list_key())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notices that an effective item has been seen, and caches it as matching.
|
||||||
|
pub fn saw_effective<T>(&mut self, item: &T)
|
||||||
|
where T: ToMediaListKey,
|
||||||
|
{
|
||||||
|
// NOTE(emilio): We can't assert that we don't cache the same item twice
|
||||||
|
// because of stylesheet reusing... shrug.
|
||||||
|
self.set.insert(item.to_media_list_key());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A filter that filters over effective rules, but allowing all potentially
|
||||||
|
/// effective `@media` rules.
|
||||||
|
pub struct PotentiallyEffectiveMediaRules;
|
||||||
|
|
||||||
|
impl NestedRuleIterationCondition for PotentiallyEffectiveMediaRules {
|
||||||
|
fn process_import(
|
||||||
|
_: &SharedRwLockReadGuard,
|
||||||
|
_: &Device,
|
||||||
|
_: QuirksMode,
|
||||||
|
_: &ImportRule)
|
||||||
|
-> bool
|
||||||
|
{
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_media(
|
||||||
|
_: &SharedRwLockReadGuard,
|
||||||
|
_: &Device,
|
||||||
|
_: QuirksMode,
|
||||||
|
_: &MediaRule)
|
||||||
|
-> bool
|
||||||
|
{
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether we should process the nested rules in a given `@-moz-document` rule.
|
||||||
|
fn process_document(
|
||||||
|
guard: &SharedRwLockReadGuard,
|
||||||
|
device: &Device,
|
||||||
|
quirks_mode: QuirksMode,
|
||||||
|
rule: &DocumentRule)
|
||||||
|
-> bool
|
||||||
|
{
|
||||||
|
use stylesheets::EffectiveRules;
|
||||||
|
EffectiveRules::process_document(guard, device, quirks_mode, rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether we should process the nested rules in a given `@supports` rule.
|
||||||
|
fn process_supports(
|
||||||
|
guard: &SharedRwLockReadGuard,
|
||||||
|
device: &Device,
|
||||||
|
quirks_mode: QuirksMode,
|
||||||
|
rule: &SupportsRule)
|
||||||
|
-> bool
|
||||||
|
{
|
||||||
|
use stylesheets::EffectiveRules;
|
||||||
|
EffectiveRules::process_supports(guard, device, quirks_mode, rule)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,295 +2,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
//! A collection of invalidations due to changes in which stylesheets affect a
|
//! Different bits of code related to invalidating style.
|
||||||
//! document.
|
|
||||||
|
|
||||||
#![deny(unsafe_code)]
|
pub mod media_queries;
|
||||||
|
pub mod stylesheets;
|
||||||
use Atom;
|
|
||||||
use data::StoredRestyleHint;
|
|
||||||
use dom::{TElement, TNode};
|
|
||||||
use fnv::FnvHashSet;
|
|
||||||
use selector_parser::SelectorImpl;
|
|
||||||
use selectors::parser::{Component, Selector};
|
|
||||||
use shared_lock::SharedRwLockReadGuard;
|
|
||||||
use stylesheets::{CssRule, Stylesheet};
|
|
||||||
use stylist::Stylist;
|
|
||||||
|
|
||||||
/// An invalidation scope represents a kind of subtree that may need to be
|
|
||||||
/// restyled.
|
|
||||||
#[derive(Debug, Hash, Eq, PartialEq)]
|
|
||||||
enum InvalidationScope {
|
|
||||||
/// All the descendants of an element with a given id.
|
|
||||||
ID(Atom),
|
|
||||||
/// All the descendants of an element with a given class name.
|
|
||||||
Class(Atom),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InvalidationScope {
|
|
||||||
fn is_id(&self) -> bool {
|
|
||||||
matches!(*self, InvalidationScope::ID(..))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn matches<E>(&self, element: E) -> bool
|
|
||||||
where E: TElement,
|
|
||||||
{
|
|
||||||
match *self {
|
|
||||||
InvalidationScope::Class(ref class) => {
|
|
||||||
element.has_class(class)
|
|
||||||
}
|
|
||||||
InvalidationScope::ID(ref id) => {
|
|
||||||
match element.get_id() {
|
|
||||||
Some(element_id) => element_id == *id,
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A set of invalidations due to stylesheet additions.
|
|
||||||
///
|
|
||||||
/// TODO(emilio): We might be able to do the same analysis for removals and
|
|
||||||
/// media query changes too?
|
|
||||||
pub struct StylesheetInvalidationSet {
|
|
||||||
/// The style scopes we know we have to restyle so far.
|
|
||||||
invalid_scopes: FnvHashSet<InvalidationScope>,
|
|
||||||
/// Whether the whole document should be invalid.
|
|
||||||
fully_invalid: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StylesheetInvalidationSet {
|
|
||||||
/// Create an empty `StylesheetInvalidationSet`.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
invalid_scopes: FnvHashSet::default(),
|
|
||||||
fully_invalid: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mark the DOM tree styles' as fully invalid.
|
|
||||||
pub fn invalidate_fully(&mut self) {
|
|
||||||
debug!("StylesheetInvalidationSet::invalidate_fully");
|
|
||||||
self.invalid_scopes.clear();
|
|
||||||
self.fully_invalid = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Analyze the given stylesheet, and collect invalidations from their
|
|
||||||
/// rules, in order to avoid doing a full restyle when we style the document
|
|
||||||
/// next time.
|
|
||||||
pub fn collect_invalidations_for(
|
|
||||||
&mut self,
|
|
||||||
stylist: &Stylist,
|
|
||||||
stylesheet: &Stylesheet,
|
|
||||||
guard: &SharedRwLockReadGuard)
|
|
||||||
{
|
|
||||||
debug!("StylesheetInvalidationSet::collect_invalidations_for");
|
|
||||||
if self.fully_invalid {
|
|
||||||
debug!(" > Fully invalid already");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if stylesheet.disabled() ||
|
|
||||||
!stylesheet.is_effective_for_device(stylist.device(), guard) {
|
|
||||||
debug!(" > Stylesheet was not effective");
|
|
||||||
return; // Nothing to do here.
|
|
||||||
}
|
|
||||||
|
|
||||||
for rule in stylesheet.effective_rules(stylist.device(), guard) {
|
|
||||||
self.collect_invalidations_for_rule(rule, guard);
|
|
||||||
if self.fully_invalid {
|
|
||||||
self.invalid_scopes.clear();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!(" > resulting invalidations: {:?}", self.invalid_scopes);
|
|
||||||
debug!(" > fully_invalid: {}", self.fully_invalid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clears the invalidation set, invalidating elements as needed if
|
|
||||||
/// `document_element` is provided.
|
|
||||||
pub fn flush<E>(&mut self, document_element: Option<E>)
|
|
||||||
where E: TElement,
|
|
||||||
{
|
|
||||||
if let Some(e) = document_element {
|
|
||||||
self.process_invalidations_in_subtree(e);
|
|
||||||
}
|
|
||||||
self.invalid_scopes.clear();
|
|
||||||
self.fully_invalid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Process style invalidations in a given subtree, that is, look for all
|
|
||||||
/// the relevant scopes in the subtree, and mark as dirty only the relevant
|
|
||||||
/// ones.
|
|
||||||
///
|
|
||||||
/// Returns whether it invalidated at least one element's style.
|
|
||||||
#[allow(unsafe_code)]
|
|
||||||
fn process_invalidations_in_subtree<E>(&self, element: E) -> bool
|
|
||||||
where E: TElement,
|
|
||||||
{
|
|
||||||
let mut data = match element.mutate_data() {
|
|
||||||
Some(data) => data,
|
|
||||||
None => return false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if !data.has_styles() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ref r) = data.get_restyle() {
|
|
||||||
if r.hint.contains_subtree() {
|
|
||||||
debug!("process_invalidations_in_subtree: {:?} was already invalid",
|
|
||||||
element);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.fully_invalid {
|
|
||||||
debug!("process_invalidations_in_subtree: fully_invalid({:?})",
|
|
||||||
element);
|
|
||||||
data.ensure_restyle().hint.insert(StoredRestyleHint::subtree());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for scope in &self.invalid_scopes {
|
|
||||||
if scope.matches(element) {
|
|
||||||
debug!("process_invalidations_in_subtree: {:?} matched {:?}",
|
|
||||||
element, scope);
|
|
||||||
data.ensure_restyle().hint.insert(StoredRestyleHint::subtree());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let mut any_children_invalid = false;
|
|
||||||
|
|
||||||
for child in element.as_node().children() {
|
|
||||||
let child = match child.as_element() {
|
|
||||||
Some(e) => e,
|
|
||||||
None => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
any_children_invalid |= self.process_invalidations_in_subtree(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
if any_children_invalid {
|
|
||||||
debug!("Children of {:?} changed, setting dirty descendants",
|
|
||||||
element);
|
|
||||||
unsafe { element.set_dirty_descendants() }
|
|
||||||
}
|
|
||||||
|
|
||||||
return any_children_invalid
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scan_component(
|
|
||||||
component: &Component<SelectorImpl>,
|
|
||||||
scope: &mut Option<InvalidationScope>)
|
|
||||||
{
|
|
||||||
match *component {
|
|
||||||
Component::Class(ref class) => {
|
|
||||||
if scope.as_ref().map_or(true, |s| !s.is_id()) {
|
|
||||||
*scope = Some(InvalidationScope::Class(class.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Component::ID(ref id) => {
|
|
||||||
if scope.is_none() {
|
|
||||||
*scope = Some(InvalidationScope::ID(id.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// Ignore everything else, at least for now.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Collect a style scopes for a given selector.
|
|
||||||
///
|
|
||||||
/// We look at the outermost class or id selector to the left of an ancestor
|
|
||||||
/// combinator, in order to restyle only a given subtree.
|
|
||||||
///
|
|
||||||
/// We prefer id scopes to class scopes, and outermost scopes to innermost
|
|
||||||
/// scopes (to reduce the amount of traversal we need to do).
|
|
||||||
fn collect_scopes(&mut self, selector: &Selector<SelectorImpl>) {
|
|
||||||
debug!("StylesheetInvalidationSet::collect_scopes({:?})", selector);
|
|
||||||
|
|
||||||
let mut scope: Option<InvalidationScope> = None;
|
|
||||||
|
|
||||||
let mut scan = true;
|
|
||||||
let mut iter = selector.inner.complex.iter();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
for component in &mut iter {
|
|
||||||
if scan {
|
|
||||||
Self::scan_component(component, &mut scope);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match iter.next_sequence() {
|
|
||||||
None => break,
|
|
||||||
Some(combinator) => {
|
|
||||||
scan = combinator.is_ancestor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match scope {
|
|
||||||
Some(s) => {
|
|
||||||
debug!(" > Found scope: {:?}", s);
|
|
||||||
self.invalid_scopes.insert(s);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
debug!(" > Scope not found");
|
|
||||||
|
|
||||||
// If we didn't find a scope, any element could match this, so
|
|
||||||
// let's just bail out.
|
|
||||||
self.fully_invalid = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Collects invalidations for a given CSS rule.
|
|
||||||
fn collect_invalidations_for_rule(
|
|
||||||
&mut self,
|
|
||||||
rule: &CssRule,
|
|
||||||
guard: &SharedRwLockReadGuard)
|
|
||||||
{
|
|
||||||
use stylesheets::CssRule::*;
|
|
||||||
debug!("StylesheetInvalidationSet::collect_invalidations_for_rule");
|
|
||||||
debug_assert!(!self.fully_invalid, "Not worth to be here!");
|
|
||||||
|
|
||||||
match *rule {
|
|
||||||
Style(ref lock) => {
|
|
||||||
let style_rule = lock.read_with(guard);
|
|
||||||
for selector in &style_rule.selectors.0 {
|
|
||||||
self.collect_scopes(selector);
|
|
||||||
if self.fully_invalid {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Document(..) |
|
|
||||||
Namespace(..) |
|
|
||||||
Import(..) |
|
|
||||||
Media(..) |
|
|
||||||
Supports(..) => {
|
|
||||||
// Do nothing, relevant nested rules are visited as part of the
|
|
||||||
// iteration.
|
|
||||||
}
|
|
||||||
FontFace(..) |
|
|
||||||
CounterStyle(..) |
|
|
||||||
Keyframes(..) |
|
|
||||||
Page(..) |
|
|
||||||
Viewport(..) => {
|
|
||||||
debug!(" > Found unsupported rule, marking the whole subtree \
|
|
||||||
invalid.");
|
|
||||||
|
|
||||||
// TODO(emilio): Can we do better here?
|
|
||||||
//
|
|
||||||
// At least in `@page`, we could check the relevant media, I
|
|
||||||
// guess.
|
|
||||||
self.fully_invalid = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
296
components/style/invalidation/stylesheets.rs
Normal file
296
components/style/invalidation/stylesheets.rs
Normal file
|
@ -0,0 +1,296 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
//! A collection of invalidations due to changes in which stylesheets affect a
|
||||||
|
//! document.
|
||||||
|
|
||||||
|
#![deny(unsafe_code)]
|
||||||
|
|
||||||
|
use Atom;
|
||||||
|
use data::StoredRestyleHint;
|
||||||
|
use dom::{TElement, TNode};
|
||||||
|
use fnv::FnvHashSet;
|
||||||
|
use selector_parser::SelectorImpl;
|
||||||
|
use selectors::parser::{Component, Selector};
|
||||||
|
use shared_lock::SharedRwLockReadGuard;
|
||||||
|
use stylesheets::{CssRule, Stylesheet};
|
||||||
|
use stylist::Stylist;
|
||||||
|
|
||||||
|
/// An invalidation scope represents a kind of subtree that may need to be
|
||||||
|
/// restyled.
|
||||||
|
#[derive(Debug, Hash, Eq, PartialEq)]
|
||||||
|
enum InvalidationScope {
|
||||||
|
/// All the descendants of an element with a given id.
|
||||||
|
ID(Atom),
|
||||||
|
/// All the descendants of an element with a given class name.
|
||||||
|
Class(Atom),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InvalidationScope {
|
||||||
|
fn is_id(&self) -> bool {
|
||||||
|
matches!(*self, InvalidationScope::ID(..))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches<E>(&self, element: E) -> bool
|
||||||
|
where E: TElement,
|
||||||
|
{
|
||||||
|
match *self {
|
||||||
|
InvalidationScope::Class(ref class) => {
|
||||||
|
element.has_class(class)
|
||||||
|
}
|
||||||
|
InvalidationScope::ID(ref id) => {
|
||||||
|
match element.get_id() {
|
||||||
|
Some(element_id) => element_id == *id,
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A set of invalidations due to stylesheet additions.
|
||||||
|
///
|
||||||
|
/// TODO(emilio): We might be able to do the same analysis for removals and
|
||||||
|
/// media query changes too?
|
||||||
|
pub struct StylesheetInvalidationSet {
|
||||||
|
/// The style scopes we know we have to restyle so far.
|
||||||
|
invalid_scopes: FnvHashSet<InvalidationScope>,
|
||||||
|
/// Whether the whole document should be invalid.
|
||||||
|
fully_invalid: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StylesheetInvalidationSet {
|
||||||
|
/// Create an empty `StylesheetInvalidationSet`.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
invalid_scopes: FnvHashSet::default(),
|
||||||
|
fully_invalid: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark the DOM tree styles' as fully invalid.
|
||||||
|
pub fn invalidate_fully(&mut self) {
|
||||||
|
debug!("StylesheetInvalidationSet::invalidate_fully");
|
||||||
|
self.invalid_scopes.clear();
|
||||||
|
self.fully_invalid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Analyze the given stylesheet, and collect invalidations from their
|
||||||
|
/// rules, in order to avoid doing a full restyle when we style the document
|
||||||
|
/// next time.
|
||||||
|
pub fn collect_invalidations_for(
|
||||||
|
&mut self,
|
||||||
|
stylist: &Stylist,
|
||||||
|
stylesheet: &Stylesheet,
|
||||||
|
guard: &SharedRwLockReadGuard)
|
||||||
|
{
|
||||||
|
debug!("StylesheetInvalidationSet::collect_invalidations_for");
|
||||||
|
if self.fully_invalid {
|
||||||
|
debug!(" > Fully invalid already");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if stylesheet.disabled() ||
|
||||||
|
!stylesheet.is_effective_for_device(stylist.device(), guard) {
|
||||||
|
debug!(" > Stylesheet was not effective");
|
||||||
|
return; // Nothing to do here.
|
||||||
|
}
|
||||||
|
|
||||||
|
for rule in stylesheet.effective_rules(stylist.device(), guard) {
|
||||||
|
self.collect_invalidations_for_rule(rule, guard);
|
||||||
|
if self.fully_invalid {
|
||||||
|
self.invalid_scopes.clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!(" > resulting invalidations: {:?}", self.invalid_scopes);
|
||||||
|
debug!(" > fully_invalid: {}", self.fully_invalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the invalidation set, invalidating elements as needed if
|
||||||
|
/// `document_element` is provided.
|
||||||
|
pub fn flush<E>(&mut self, document_element: Option<E>)
|
||||||
|
where E: TElement,
|
||||||
|
{
|
||||||
|
if let Some(e) = document_element {
|
||||||
|
self.process_invalidations_in_subtree(e);
|
||||||
|
}
|
||||||
|
self.invalid_scopes.clear();
|
||||||
|
self.fully_invalid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process style invalidations in a given subtree, that is, look for all
|
||||||
|
/// the relevant scopes in the subtree, and mark as dirty only the relevant
|
||||||
|
/// ones.
|
||||||
|
///
|
||||||
|
/// Returns whether it invalidated at least one element's style.
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
fn process_invalidations_in_subtree<E>(&self, element: E) -> bool
|
||||||
|
where E: TElement,
|
||||||
|
{
|
||||||
|
let mut data = match element.mutate_data() {
|
||||||
|
Some(data) => data,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if !data.has_styles() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref r) = data.get_restyle() {
|
||||||
|
if r.hint.contains_subtree() {
|
||||||
|
debug!("process_invalidations_in_subtree: {:?} was already invalid",
|
||||||
|
element);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.fully_invalid {
|
||||||
|
debug!("process_invalidations_in_subtree: fully_invalid({:?})",
|
||||||
|
element);
|
||||||
|
data.ensure_restyle().hint.insert(StoredRestyleHint::subtree());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for scope in &self.invalid_scopes {
|
||||||
|
if scope.matches(element) {
|
||||||
|
debug!("process_invalidations_in_subtree: {:?} matched {:?}",
|
||||||
|
element, scope);
|
||||||
|
data.ensure_restyle().hint.insert(StoredRestyleHint::subtree());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let mut any_children_invalid = false;
|
||||||
|
|
||||||
|
for child in element.as_node().children() {
|
||||||
|
let child = match child.as_element() {
|
||||||
|
Some(e) => e,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
any_children_invalid |= self.process_invalidations_in_subtree(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
if any_children_invalid {
|
||||||
|
debug!("Children of {:?} changed, setting dirty descendants",
|
||||||
|
element);
|
||||||
|
unsafe { element.set_dirty_descendants() }
|
||||||
|
}
|
||||||
|
|
||||||
|
return any_children_invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scan_component(
|
||||||
|
component: &Component<SelectorImpl>,
|
||||||
|
scope: &mut Option<InvalidationScope>)
|
||||||
|
{
|
||||||
|
match *component {
|
||||||
|
Component::Class(ref class) => {
|
||||||
|
if scope.as_ref().map_or(true, |s| !s.is_id()) {
|
||||||
|
*scope = Some(InvalidationScope::Class(class.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Component::ID(ref id) => {
|
||||||
|
if scope.is_none() {
|
||||||
|
*scope = Some(InvalidationScope::ID(id.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Ignore everything else, at least for now.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collect a style scopes for a given selector.
|
||||||
|
///
|
||||||
|
/// We look at the outermost class or id selector to the left of an ancestor
|
||||||
|
/// combinator, in order to restyle only a given subtree.
|
||||||
|
///
|
||||||
|
/// We prefer id scopes to class scopes, and outermost scopes to innermost
|
||||||
|
/// scopes (to reduce the amount of traversal we need to do).
|
||||||
|
fn collect_scopes(&mut self, selector: &Selector<SelectorImpl>) {
|
||||||
|
debug!("StylesheetInvalidationSet::collect_scopes({:?})", selector);
|
||||||
|
|
||||||
|
let mut scope: Option<InvalidationScope> = None;
|
||||||
|
|
||||||
|
let mut scan = true;
|
||||||
|
let mut iter = selector.inner.complex.iter();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
for component in &mut iter {
|
||||||
|
if scan {
|
||||||
|
Self::scan_component(component, &mut scope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match iter.next_sequence() {
|
||||||
|
None => break,
|
||||||
|
Some(combinator) => {
|
||||||
|
scan = combinator.is_ancestor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match scope {
|
||||||
|
Some(s) => {
|
||||||
|
debug!(" > Found scope: {:?}", s);
|
||||||
|
self.invalid_scopes.insert(s);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
debug!(" > Scope not found");
|
||||||
|
|
||||||
|
// If we didn't find a scope, any element could match this, so
|
||||||
|
// let's just bail out.
|
||||||
|
self.fully_invalid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collects invalidations for a given CSS rule.
|
||||||
|
fn collect_invalidations_for_rule(
|
||||||
|
&mut self,
|
||||||
|
rule: &CssRule,
|
||||||
|
guard: &SharedRwLockReadGuard)
|
||||||
|
{
|
||||||
|
use stylesheets::CssRule::*;
|
||||||
|
debug!("StylesheetInvalidationSet::collect_invalidations_for_rule");
|
||||||
|
debug_assert!(!self.fully_invalid, "Not worth to be here!");
|
||||||
|
|
||||||
|
match *rule {
|
||||||
|
Style(ref lock) => {
|
||||||
|
let style_rule = lock.read_with(guard);
|
||||||
|
for selector in &style_rule.selectors.0 {
|
||||||
|
self.collect_scopes(selector);
|
||||||
|
if self.fully_invalid {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Document(..) |
|
||||||
|
Namespace(..) |
|
||||||
|
Import(..) |
|
||||||
|
Media(..) |
|
||||||
|
Supports(..) => {
|
||||||
|
// Do nothing, relevant nested rules are visited as part of the
|
||||||
|
// iteration.
|
||||||
|
}
|
||||||
|
FontFace(..) |
|
||||||
|
CounterStyle(..) |
|
||||||
|
Keyframes(..) |
|
||||||
|
Page(..) |
|
||||||
|
Viewport(..) => {
|
||||||
|
debug!(" > Found unsupported rule, marking the whole subtree \
|
||||||
|
invalid.");
|
||||||
|
|
||||||
|
// TODO(emilio): Can we do better here?
|
||||||
|
//
|
||||||
|
// At least in `@page`, we could check the relevant media, I
|
||||||
|
// guess.
|
||||||
|
self.fully_invalid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@
|
||||||
//! A centralized set of stylesheets for a document.
|
//! A centralized set of stylesheets for a document.
|
||||||
|
|
||||||
use dom::TElement;
|
use dom::TElement;
|
||||||
use invalidation::StylesheetInvalidationSet;
|
use invalidation::stylesheets::StylesheetInvalidationSet;
|
||||||
use shared_lock::SharedRwLockReadGuard;
|
use shared_lock::SharedRwLockReadGuard;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
use stylearc::Arc;
|
use stylearc::Arc;
|
||||||
|
@ -175,6 +175,11 @@ impl StylesheetSet {
|
||||||
self.dirty = false;
|
self.dirty = false;
|
||||||
self.invalidations.flush(document_element);
|
self.invalidations.flush(document_element);
|
||||||
|
|
||||||
|
self.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over the current list of stylesheets.
|
||||||
|
pub fn iter(&self) -> StylesheetIterator {
|
||||||
StylesheetIterator(self.entries.iter())
|
StylesheetIterator(self.entries.iter())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1048,7 +1048,6 @@ impl NestedRuleIterationCondition for AllRules {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether we should process the nested rules in a given `@media` rule.
|
|
||||||
fn process_media(
|
fn process_media(
|
||||||
_: &SharedRwLockReadGuard,
|
_: &SharedRwLockReadGuard,
|
||||||
_: &Device,
|
_: &Device,
|
||||||
|
@ -1059,7 +1058,6 @@ impl NestedRuleIterationCondition for AllRules {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether we should process the nested rules in a given `@-moz-document` rule.
|
|
||||||
fn process_document(
|
fn process_document(
|
||||||
_: &SharedRwLockReadGuard,
|
_: &SharedRwLockReadGuard,
|
||||||
_: &Device,
|
_: &Device,
|
||||||
|
@ -1070,7 +1068,6 @@ impl NestedRuleIterationCondition for AllRules {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether we should process the nested rules in a given `@supports` rule.
|
|
||||||
fn process_supports(
|
fn process_supports(
|
||||||
_: &SharedRwLockReadGuard,
|
_: &SharedRwLockReadGuard,
|
||||||
_: &Device,
|
_: &Device,
|
||||||
|
@ -1158,36 +1155,31 @@ impl<'a, 'b, C> Iterator for RulesIterator<'a, 'b, C>
|
||||||
sub_iter = match *rule {
|
sub_iter = match *rule {
|
||||||
CssRule::Import(ref import_rule) => {
|
CssRule::Import(ref import_rule) => {
|
||||||
let import_rule = import_rule.read_with(self.guard);
|
let import_rule = import_rule.read_with(self.guard);
|
||||||
|
if !C::process_import(self.guard, self.device, self.quirks_mode, import_rule) {
|
||||||
if C::process_import(self.guard, self.device, self.quirks_mode, import_rule) {
|
continue;
|
||||||
Some(import_rule.stylesheet.rules.read_with(self.guard).0.iter())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
Some(import_rule.stylesheet.rules.read_with(self.guard).0.iter())
|
||||||
}
|
}
|
||||||
CssRule::Document(ref doc_rule) => {
|
CssRule::Document(ref doc_rule) => {
|
||||||
let doc_rule = doc_rule.read_with(self.guard);
|
let doc_rule = doc_rule.read_with(self.guard);
|
||||||
if C::process_document(self.guard, self.device, self.quirks_mode, doc_rule) {
|
if !C::process_document(self.guard, self.device, self.quirks_mode, doc_rule) {
|
||||||
Some(doc_rule.rules.read_with(self.guard).0.iter())
|
continue;
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
Some(doc_rule.rules.read_with(self.guard).0.iter())
|
||||||
}
|
}
|
||||||
CssRule::Media(ref lock) => {
|
CssRule::Media(ref lock) => {
|
||||||
let media_rule = lock.read_with(self.guard);
|
let media_rule = lock.read_with(self.guard);
|
||||||
if C::process_media(self.guard, self.device, self.quirks_mode, media_rule) {
|
if !C::process_media(self.guard, self.device, self.quirks_mode, media_rule) {
|
||||||
Some(media_rule.rules.read_with(self.guard).0.iter())
|
continue;
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
Some(media_rule.rules.read_with(self.guard).0.iter())
|
||||||
}
|
}
|
||||||
CssRule::Supports(ref lock) => {
|
CssRule::Supports(ref lock) => {
|
||||||
let supports_rule = lock.read_with(self.guard);
|
let supports_rule = lock.read_with(self.guard);
|
||||||
if C::process_supports(self.guard, self.device, self.quirks_mode, supports_rule) {
|
if !C::process_supports(self.guard, self.device, self.quirks_mode, supports_rule) {
|
||||||
Some(supports_rule.rules.read_with(self.guard).0.iter())
|
continue;
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
Some(supports_rule.rules.read_with(self.guard).0.iter())
|
||||||
}
|
}
|
||||||
CssRule::Namespace(_) |
|
CssRule::Namespace(_) |
|
||||||
CssRule::Style(_) |
|
CssRule::Style(_) |
|
||||||
|
|
|
@ -14,6 +14,7 @@ use error_reporting::RustLogReporter;
|
||||||
use font_metrics::FontMetricsProvider;
|
use font_metrics::FontMetricsProvider;
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
use gecko_bindings::structs::{nsIAtom, StyleRuleInclusion};
|
use gecko_bindings::structs::{nsIAtom, StyleRuleInclusion};
|
||||||
|
use invalidation::media_queries::EffectiveMediaQueryResults;
|
||||||
use keyframes::KeyframesAnimation;
|
use keyframes::KeyframesAnimation;
|
||||||
use media_queries::Device;
|
use media_queries::Device;
|
||||||
use properties::{self, CascadeFlags, ComputedValues};
|
use properties::{self, CascadeFlags, ComputedValues};
|
||||||
|
@ -39,9 +40,8 @@ use style_traits::viewport::ViewportConstraints;
|
||||||
use stylearc::Arc;
|
use stylearc::Arc;
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
use stylesheets::{CounterStyleRule, FontFaceRule};
|
use stylesheets::{CounterStyleRule, FontFaceRule};
|
||||||
use stylesheets::{CssRule, DocumentRule, ImportRule, MediaRule, StyleRule, SupportsRule};
|
use stylesheets::{CssRule, StyleRule};
|
||||||
use stylesheets::{Stylesheet, Origin, UserAgentStylesheets};
|
use stylesheets::{Stylesheet, Origin, UserAgentStylesheets};
|
||||||
use stylesheets::NestedRuleIterationCondition;
|
|
||||||
use thread_state;
|
use thread_state;
|
||||||
use viewport::{self, MaybeNew, ViewportRule};
|
use viewport::{self, MaybeNew, ViewportRule};
|
||||||
|
|
||||||
|
@ -83,6 +83,9 @@ pub struct Stylist {
|
||||||
/// Viewport constraints based on the current device.
|
/// Viewport constraints based on the current device.
|
||||||
viewport_constraints: Option<ViewportConstraints>,
|
viewport_constraints: Option<ViewportConstraints>,
|
||||||
|
|
||||||
|
/// Effective media query results cached from the last rebuild.
|
||||||
|
effective_media_query_results: EffectiveMediaQueryResults,
|
||||||
|
|
||||||
/// If true, the quirks-mode stylesheet is applied.
|
/// If true, the quirks-mode stylesheet is applied.
|
||||||
quirks_mode: QuirksMode,
|
quirks_mode: QuirksMode,
|
||||||
|
|
||||||
|
@ -222,57 +225,6 @@ impl From<StyleRuleInclusion> for RuleInclusion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A filter that filters over effective rules, but allowing all potentially
|
|
||||||
/// effective `@media` rules.
|
|
||||||
pub struct PotentiallyEffectiveMediaRules;
|
|
||||||
|
|
||||||
impl NestedRuleIterationCondition for PotentiallyEffectiveMediaRules {
|
|
||||||
fn process_import(
|
|
||||||
_: &SharedRwLockReadGuard,
|
|
||||||
_: &Device,
|
|
||||||
_: QuirksMode,
|
|
||||||
_: &ImportRule)
|
|
||||||
-> bool
|
|
||||||
{
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_media(
|
|
||||||
_: &SharedRwLockReadGuard,
|
|
||||||
_: &Device,
|
|
||||||
_: QuirksMode,
|
|
||||||
_: &MediaRule)
|
|
||||||
-> bool
|
|
||||||
{
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether we should process the nested rules in a given `@-moz-document` rule.
|
|
||||||
fn process_document(
|
|
||||||
guard: &SharedRwLockReadGuard,
|
|
||||||
device: &Device,
|
|
||||||
quirks_mode: QuirksMode,
|
|
||||||
rule: &DocumentRule)
|
|
||||||
-> bool
|
|
||||||
{
|
|
||||||
use stylesheets::EffectiveRules;
|
|
||||||
EffectiveRules::process_document(guard, device, quirks_mode, rule)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether we should process the nested rules in a given `@supports` rule.
|
|
||||||
fn process_supports(
|
|
||||||
guard: &SharedRwLockReadGuard,
|
|
||||||
device: &Device,
|
|
||||||
quirks_mode: QuirksMode,
|
|
||||||
rule: &SupportsRule)
|
|
||||||
-> bool
|
|
||||||
{
|
|
||||||
use stylesheets::EffectiveRules;
|
|
||||||
EffectiveRules::process_supports(guard, device, quirks_mode, rule)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl Stylist {
|
impl Stylist {
|
||||||
/// Construct a new `Stylist`, using given `Device` and `QuirksMode`.
|
/// Construct a new `Stylist`, using given `Device` and `QuirksMode`.
|
||||||
/// If more members are added here, think about whether they should
|
/// If more members are added here, think about whether they should
|
||||||
|
@ -285,6 +237,7 @@ impl Stylist {
|
||||||
is_device_dirty: true,
|
is_device_dirty: true,
|
||||||
is_cleared: true,
|
is_cleared: true,
|
||||||
quirks_mode: quirks_mode,
|
quirks_mode: quirks_mode,
|
||||||
|
effective_media_query_results: EffectiveMediaQueryResults::new(),
|
||||||
|
|
||||||
element_map: PerPseudoElementSelectorMap::new(),
|
element_map: PerPseudoElementSelectorMap::new(),
|
||||||
pseudos_map: Default::default(),
|
pseudos_map: Default::default(),
|
||||||
|
@ -355,6 +308,7 @@ impl Stylist {
|
||||||
|
|
||||||
self.is_cleared = true;
|
self.is_cleared = true;
|
||||||
|
|
||||||
|
self.effective_media_query_results.clear();
|
||||||
self.viewport_constraints = None;
|
self.viewport_constraints = None;
|
||||||
// preserve current device
|
// preserve current device
|
||||||
self.is_device_dirty = true;
|
self.is_device_dirty = true;
|
||||||
|
@ -482,6 +436,8 @@ impl Stylist {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.effective_media_query_results.saw_effective(stylesheet);
|
||||||
|
|
||||||
for rule in stylesheet.effective_rules(&self.device, guard) {
|
for rule in stylesheet.effective_rules(&self.device, guard) {
|
||||||
match *rule {
|
match *rule {
|
||||||
CssRule::Style(ref locked) => {
|
CssRule::Style(ref locked) => {
|
||||||
|
@ -515,10 +471,17 @@ impl Stylist {
|
||||||
}
|
}
|
||||||
self.rules_source_order += 1;
|
self.rules_source_order += 1;
|
||||||
}
|
}
|
||||||
CssRule::Import(..) => {
|
CssRule::Import(ref lock) => {
|
||||||
// effective_rules visits the inner stylesheet if
|
let import_rule = lock.read_with(guard);
|
||||||
|
self.effective_media_query_results.saw_effective(import_rule);
|
||||||
|
|
||||||
|
// NOTE: effective_rules visits the inner stylesheet if
|
||||||
// appropriate.
|
// appropriate.
|
||||||
}
|
}
|
||||||
|
CssRule::Media(ref lock) => {
|
||||||
|
let media_rule = lock.read_with(guard);
|
||||||
|
self.effective_media_query_results.saw_effective(media_rule);
|
||||||
|
}
|
||||||
CssRule::Keyframes(ref keyframes_rule) => {
|
CssRule::Keyframes(ref keyframes_rule) => {
|
||||||
let keyframes_rule = keyframes_rule.read_with(guard);
|
let keyframes_rule = keyframes_rule.read_with(guard);
|
||||||
debug!("Found valid keyframes rule: {:?}", *keyframes_rule);
|
debug!("Found valid keyframes rule: {:?}", *keyframes_rule);
|
||||||
|
@ -816,16 +779,50 @@ impl Stylist {
|
||||||
device.account_for_viewport_rule(constraints);
|
device.account_for_viewport_rule(constraints);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.is_device_dirty |= stylesheets.iter().any(|stylesheet| {
|
self.device = device;
|
||||||
let mq = stylesheet.media.read_with(guard);
|
let features_changed = self.media_features_change_changed_style(
|
||||||
if mq.evaluate(&self.device, self.quirks_mode) != mq.evaluate(&device, self.quirks_mode) {
|
stylesheets.iter(),
|
||||||
|
guard
|
||||||
|
);
|
||||||
|
self.is_device_dirty |= features_changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether, given a media feature change, any previously-applicable
|
||||||
|
/// style has become non-applicable, or vice-versa.
|
||||||
|
pub fn media_features_change_changed_style<'a, I>(
|
||||||
|
&self,
|
||||||
|
stylesheets: I,
|
||||||
|
guard: &SharedRwLockReadGuard,
|
||||||
|
) -> bool
|
||||||
|
where I: Iterator<Item = &'a Arc<Stylesheet>>
|
||||||
|
{
|
||||||
|
use invalidation::media_queries::PotentiallyEffectiveMediaRules;
|
||||||
|
|
||||||
|
debug!("Stylist::media_features_change_changed_style");
|
||||||
|
|
||||||
|
for stylesheet in stylesheets {
|
||||||
|
let effective_now =
|
||||||
|
stylesheet.media.read_with(guard)
|
||||||
|
.evaluate(&self.device, self.quirks_mode);
|
||||||
|
|
||||||
|
let effective_then =
|
||||||
|
self.effective_media_query_results.was_effective(&**stylesheet);
|
||||||
|
|
||||||
|
if effective_now != effective_then {
|
||||||
|
debug!(" > Stylesheet changed -> {}, {}",
|
||||||
|
effective_then, effective_now);
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !effective_now {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let mut iter =
|
let mut iter =
|
||||||
stylesheet.iter_rules::<PotentiallyEffectiveMediaRules>(
|
stylesheet.iter_rules::<PotentiallyEffectiveMediaRules>(
|
||||||
&self.device,
|
&self.device,
|
||||||
guard);
|
guard
|
||||||
|
);
|
||||||
|
|
||||||
while let Some(rule) = iter.next() {
|
while let Some(rule) = iter.next() {
|
||||||
match *rule {
|
match *rule {
|
||||||
|
@ -844,8 +841,13 @@ impl Stylist {
|
||||||
CssRule::Import(ref lock) => {
|
CssRule::Import(ref lock) => {
|
||||||
let import_rule = lock.read_with(guard);
|
let import_rule = lock.read_with(guard);
|
||||||
let mq = import_rule.stylesheet.media.read_with(guard);
|
let mq = import_rule.stylesheet.media.read_with(guard);
|
||||||
let effective_now = mq.evaluate(&self.device, self.quirks_mode);
|
let effective_now =
|
||||||
if effective_now != mq.evaluate(&device, self.quirks_mode) {
|
mq.evaluate(&self.device, self.quirks_mode);
|
||||||
|
let effective_then =
|
||||||
|
self.effective_media_query_results.was_effective(import_rule);
|
||||||
|
if effective_now != effective_then {
|
||||||
|
debug!(" > @import rule changed {} -> {}",
|
||||||
|
effective_then, effective_now);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -856,8 +858,13 @@ impl Stylist {
|
||||||
CssRule::Media(ref lock) => {
|
CssRule::Media(ref lock) => {
|
||||||
let media_rule = lock.read_with(guard);
|
let media_rule = lock.read_with(guard);
|
||||||
let mq = media_rule.media_queries.read_with(guard);
|
let mq = media_rule.media_queries.read_with(guard);
|
||||||
let effective_now = mq.evaluate(&self.device, self.quirks_mode);
|
let effective_now =
|
||||||
if effective_now != mq.evaluate(&device, self.quirks_mode) {
|
mq.evaluate(&self.device, self.quirks_mode);
|
||||||
|
let effective_then =
|
||||||
|
self.effective_media_query_results.was_effective(media_rule);
|
||||||
|
if effective_now != effective_then {
|
||||||
|
debug!(" > @media rule changed {} -> {}",
|
||||||
|
effective_then, effective_now);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -867,11 +874,9 @@ impl Stylist {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
|
||||||
|
|
||||||
self.device = device;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the viewport constraints that apply to this document because of
|
/// Returns the viewport constraints that apply to this document because of
|
||||||
|
|
|
@ -747,6 +747,32 @@ pub extern "C" fn Servo_StyleSet_AppendStyleSheet(raw_data: RawServoStyleSetBorr
|
||||||
data.clear_stylist();
|
data.clear_stylist();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn Servo_StyleSet_MediumFeaturesChanged(
|
||||||
|
raw_data: RawServoStyleSetBorrowed,
|
||||||
|
) -> bool {
|
||||||
|
let global_style_data = &*GLOBAL_STYLE_DATA;
|
||||||
|
let guard = global_style_data.shared_lock.read();
|
||||||
|
|
||||||
|
// NOTE(emilio): We don't actually need to flush the stylist here and ensure
|
||||||
|
// it's up to date.
|
||||||
|
//
|
||||||
|
// In case it isn't we would trigger a rebuild + restyle as needed too.
|
||||||
|
//
|
||||||
|
// We need to ensure the default computed values are up to date though,
|
||||||
|
// because those can influence the result of media query evaluation.
|
||||||
|
//
|
||||||
|
// FIXME(emilio, bug 1369984): do the computation conditionally, to do it
|
||||||
|
// less often.
|
||||||
|
let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
|
||||||
|
|
||||||
|
data.stylist.device_mut().reset_computed_values();
|
||||||
|
data.stylist.media_features_change_changed_style(
|
||||||
|
data.stylesheets.iter(),
|
||||||
|
&guard,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn Servo_StyleSet_PrependStyleSheet(raw_data: RawServoStyleSetBorrowed,
|
pub extern "C" fn Servo_StyleSet_PrependStyleSheet(raw_data: RawServoStyleSetBorrowed,
|
||||||
raw_sheet: RawServoStyleSheetBorrowed,
|
raw_sheet: RawServoStyleSheetBorrowed,
|
||||||
|
@ -1408,8 +1434,11 @@ pub extern "C" fn Servo_StyleSet_Init(pres_context: RawGeckoPresContextOwned)
|
||||||
pub extern "C" fn Servo_StyleSet_RebuildData(raw_data: RawServoStyleSetBorrowed) {
|
pub extern "C" fn Servo_StyleSet_RebuildData(raw_data: RawServoStyleSetBorrowed) {
|
||||||
let global_style_data = &*GLOBAL_STYLE_DATA;
|
let global_style_data = &*GLOBAL_STYLE_DATA;
|
||||||
let guard = global_style_data.shared_lock.read();
|
let guard = global_style_data.shared_lock.read();
|
||||||
|
|
||||||
let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
|
let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
|
||||||
data.reset_device(&guard);
|
data.stylist.device_mut().reset();
|
||||||
|
data.stylesheets.force_dirty();
|
||||||
|
data.flush_stylesheets::<GeckoElement>(&guard, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue