mirror of
https://github.com/servo/servo.git
synced 2025-06-06 00:25:37 +00:00
Auto merge of #12668 - emilio:style-cache, r=SimonSapin,pcwalton
Rewrite the style sharing candidate cache <!-- Please describe your changes on the following line: --> See the first commit's description for a bit of background. I'm making the PR in this state just to verify against try I've handled all cases of possibly incorrect sharing and, if not, fix it. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes fix #12534 <!-- Either: --> - [x] There are tests for these changes OR <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> <!-- 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/12668) <!-- Reviewable:end -->
This commit is contained in:
commit
ec7efff14b
25 changed files with 575 additions and 279 deletions
|
@ -33,7 +33,7 @@ range = {path = "../range"}
|
|||
rustc-serialize = "0.3"
|
||||
script_layout_interface = {path = "../script_layout_interface"}
|
||||
script_traits = {path = "../script_traits"}
|
||||
selectors = {version = "0.8", features = ["heap_size"]}
|
||||
selectors = {version = "0.9", features = ["heap_size"]}
|
||||
serde_macros = "0.8"
|
||||
smallvec = "0.1"
|
||||
string_cache = {version = "0.2.23", features = ["heap_size"]}
|
||||
|
|
|
@ -13,7 +13,7 @@ use gfx::display_list::OpaqueNode;
|
|||
use script_layout_interface::restyle_damage::{BUBBLE_ISIZES, REFLOW, REFLOW_OUT_OF_FLOW, REPAINT, RestyleDamage};
|
||||
use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
|
||||
use std::mem;
|
||||
use style::context::SharedStyleContext;
|
||||
use style::context::{LocalStyleContext, SharedStyleContext, StyleContext};
|
||||
use style::dom::TNode;
|
||||
use style::selector_impl::ServoSelectorImpl;
|
||||
use style::traversal::RestyleResult;
|
||||
|
@ -81,6 +81,10 @@ impl<'lc, N> DomTraversalContext<N> for RecalcStyleAndConstructFlows<'lc>
|
|||
fn process_postorder(&self, node: N) {
|
||||
construct_flows_at(&self.context, self.root, node);
|
||||
}
|
||||
|
||||
fn local_context(&self) -> &LocalStyleContext {
|
||||
self.context.local_context()
|
||||
}
|
||||
}
|
||||
|
||||
/// A bottom-up, parallelizable traversal.
|
||||
|
|
|
@ -65,7 +65,7 @@ regex = "0.1.43"
|
|||
rustc-serialize = "0.3"
|
||||
script_layout_interface = {path = "../script_layout_interface"}
|
||||
script_traits = {path = "../script_traits"}
|
||||
selectors = {version = "0.8", features = ["heap_size"]}
|
||||
selectors = {version = "0.9", features = ["heap_size"]}
|
||||
serde = "0.8"
|
||||
smallvec = "0.1"
|
||||
string_cache = {version = "0.2.23", features = ["heap_size", "unstable"]}
|
||||
|
|
|
@ -444,6 +444,11 @@ impl<'le> TElement for ServoLayoutElement<'le> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'le> PartialEq for ServoLayoutElement<'le> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.as_node() == other.as_node()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'le> ServoLayoutElement<'le> {
|
||||
fn from_layout_js(el: LayoutJS<Element>) -> ServoLayoutElement<'le> {
|
||||
|
|
|
@ -27,7 +27,7 @@ plugins = {path = "../plugins"}
|
|||
profile_traits = {path = "../profile_traits"}
|
||||
range = {path = "../range"}
|
||||
script_traits = {path = "../script_traits"}
|
||||
selectors = {version = "0.8", features = ["heap_size"]}
|
||||
selectors = {version = "0.9", features = ["heap_size"]}
|
||||
string_cache = {version = "0.2.23", features = ["heap_size"]}
|
||||
style = {path = "../style"}
|
||||
url = {version = "1.2", features = ["heap_size"]}
|
||||
|
|
12
components/servo/Cargo.lock
generated
12
components/servo/Cargo.lock
generated
|
@ -1162,7 +1162,7 @@ dependencies = [
|
|||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"script_layout_interface 0.0.1",
|
||||
"script_traits 0.0.1",
|
||||
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_macros 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1931,7 +1931,7 @@ dependencies = [
|
|||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"script_layout_interface 0.0.1",
|
||||
"script_traits 0.0.1",
|
||||
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1968,7 +1968,7 @@ dependencies = [
|
|||
"profile_traits 0.0.1",
|
||||
"range 0.0.1",
|
||||
"script_traits 0.0.1",
|
||||
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"style 0.0.1",
|
||||
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2017,7 +2017,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "selectors"
|
||||
version = "0.8.2"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2239,7 +2239,7 @@ dependencies = [
|
|||
"plugins 0.0.1",
|
||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_macros 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2259,7 +2259,7 @@ dependencies = [
|
|||
"cssparser 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"style 0.0.1",
|
||||
"style_traits 0.0.1",
|
||||
|
|
|
@ -38,7 +38,7 @@ num-traits = "0.1.32"
|
|||
ordered-float = "0.2.2"
|
||||
rand = "0.3"
|
||||
rustc-serialize = "0.3"
|
||||
selectors = "0.8.2"
|
||||
selectors = "0.9"
|
||||
serde = {version = "0.8", optional = true}
|
||||
serde_macros = {version = "0.8", optional = true}
|
||||
smallvec = "0.1"
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
use rand;
|
||||
use rand::Rng;
|
||||
use std::hash::{Hash, Hasher, SipHasher};
|
||||
use std::slice::Iter;
|
||||
use std::slice::{Iter, IterMut};
|
||||
|
||||
pub struct LRUCache<K, V> {
|
||||
entries: Vec<(K, V)>,
|
||||
|
@ -17,7 +17,7 @@ pub struct LRUCache<K, V> {
|
|||
impl<K: PartialEq, V: Clone> LRUCache<K, V> {
|
||||
pub fn new(size: usize) -> LRUCache<K, V> {
|
||||
LRUCache {
|
||||
entries: vec!(),
|
||||
entries: vec![],
|
||||
cache_size: size,
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,10 @@ impl<K: PartialEq, V: Clone> LRUCache<K, V> {
|
|||
self.entries.iter()
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> IterMut<(K, V)> {
|
||||
self.entries.iter_mut()
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, key: K, val: V) {
|
||||
if self.entries.len() == self.cache_size {
|
||||
self.entries.remove(0);
|
||||
|
|
|
@ -201,7 +201,7 @@ pub trait PresentationalHintsSynthetizer {
|
|||
where V: Push<DeclarationBlock<Vec<PropertyDeclaration>>>;
|
||||
}
|
||||
|
||||
pub trait TElement : Sized + Copy + Clone + ElementExt + PresentationalHintsSynthetizer {
|
||||
pub trait TElement : PartialEq + Sized + Copy + Clone + ElementExt + PresentationalHintsSynthetizer {
|
||||
type ConcreteNode: TNode<ConcreteElement = Self, ConcreteDocument = Self::ConcreteDocument>;
|
||||
type ConcreteDocument: TDocument<ConcreteNode = Self::ConcreteNode, ConcreteElement = Self>;
|
||||
|
||||
|
@ -236,6 +236,7 @@ pub trait TElement : Sized + Copy + Clone + ElementExt + PresentationalHintsSynt
|
|||
unsafe { node.set_dirty(true); }
|
||||
// XXX(emilio): For now, dirty implies dirty descendants if found.
|
||||
} else if hint.contains(RESTYLE_DESCENDANTS) {
|
||||
unsafe { node.set_dirty_descendants(true); }
|
||||
let mut current = node.first_child();
|
||||
while let Some(node) = current {
|
||||
unsafe { node.set_dirty(true); }
|
||||
|
|
|
@ -12,21 +12,21 @@ use cache::{LRUCache, SimpleHashCache};
|
|||
use cascade_info::CascadeInfo;
|
||||
use context::{StyleContext, SharedStyleContext};
|
||||
use data::PrivateStyleData;
|
||||
use dom::{TElement, TNode, TRestyleDamage};
|
||||
use dom::{TElement, TNode, TRestyleDamage, UnsafeNode};
|
||||
use properties::longhands::display::computed_value as display;
|
||||
use properties::{ComputedValues, PropertyDeclaration, cascade};
|
||||
use selector_impl::{ElementExt, TheSelectorImpl, PseudoElement};
|
||||
use selector_impl::{TheSelectorImpl, PseudoElement};
|
||||
use selector_matching::{DeclarationBlock, Stylist};
|
||||
use selectors::bloom::BloomFilter;
|
||||
use selectors::matching::{StyleRelations, AFFECTED_BY_PSEUDO_ELEMENTS};
|
||||
use selectors::{Element, MatchAttr};
|
||||
use sink::ForgetfulSink;
|
||||
use smallvec::SmallVec;
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::{BuildHasherDefault, Hash, Hasher};
|
||||
use std::slice::Iter;
|
||||
use std::slice::IterMut;
|
||||
use std::sync::Arc;
|
||||
use string_cache::{Atom, Namespace};
|
||||
use string_cache::Atom;
|
||||
use traversal::RestyleResult;
|
||||
use util::opts;
|
||||
|
||||
|
@ -178,155 +178,147 @@ impl ApplicableDeclarationsCache {
|
|||
}
|
||||
}
|
||||
|
||||
/// An LRU cache of the last few nodes seen, so that we can aggressively try to reuse their styles.
|
||||
pub struct StyleSharingCandidateCache {
|
||||
cache: LRUCache<StyleSharingCandidate, ()>,
|
||||
/// Information regarding a candidate.
|
||||
///
|
||||
/// TODO: We can stick a lot more info here.
|
||||
#[derive(Debug)]
|
||||
struct StyleSharingCandidate {
|
||||
/// The node, guaranteed to be an element.
|
||||
node: UnsafeNode,
|
||||
/// The cached computed style, here for convenience.
|
||||
style: Arc<ComputedValues>,
|
||||
/// The cached common style affecting attribute info.
|
||||
common_style_affecting_attributes: Option<CommonStyleAffectingAttributes>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StyleSharingCandidate {
|
||||
pub style: Arc<ComputedValues>,
|
||||
pub parent_style: Arc<ComputedValues>,
|
||||
pub local_name: Atom,
|
||||
pub classes: Vec<Atom>,
|
||||
pub namespace: Namespace,
|
||||
pub common_style_affecting_attributes: CommonStyleAffectingAttributes,
|
||||
pub link: bool,
|
||||
}
|
||||
|
||||
impl PartialEq for StyleSharingCandidate {
|
||||
impl PartialEq<StyleSharingCandidate> for StyleSharingCandidate {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
arc_ptr_eq(&self.style, &other.style) &&
|
||||
arc_ptr_eq(&self.parent_style, &other.parent_style) &&
|
||||
self.local_name == other.local_name &&
|
||||
self.classes == other.classes &&
|
||||
self.link == other.link &&
|
||||
self.namespace == other.namespace &&
|
||||
self.node == other.node &&
|
||||
arc_ptr_eq(&self.style, &other.style) &&
|
||||
self.common_style_affecting_attributes == other.common_style_affecting_attributes
|
||||
}
|
||||
}
|
||||
|
||||
impl StyleSharingCandidate {
|
||||
/// Attempts to create a style sharing candidate from this node. Returns
|
||||
/// the style sharing candidate or `None` if this node is ineligible for
|
||||
/// style sharing.
|
||||
#[allow(unsafe_code)]
|
||||
fn new<N: TNode>(element: &N::ConcreteElement) -> Option<Self> {
|
||||
let parent_element = match element.parent_element() {
|
||||
None => return None,
|
||||
Some(parent_element) => parent_element,
|
||||
};
|
||||
/// An LRU cache of the last few nodes seen, so that we can aggressively try to
|
||||
/// reuse their styles.
|
||||
///
|
||||
/// Note that this cache is flushed every time we steal work from the queue, so
|
||||
/// storing nodes here temporarily is safe.
|
||||
///
|
||||
/// NB: We store UnsafeNode's, but this is not unsafe. It's a shame being
|
||||
/// generic over elements is unfeasible (you can make compile style without much
|
||||
/// difficulty, but good luck with layout and all the types with assoc.
|
||||
/// lifetimes).
|
||||
pub struct StyleSharingCandidateCache {
|
||||
cache: LRUCache<StyleSharingCandidate, ()>,
|
||||
}
|
||||
|
||||
let style = unsafe {
|
||||
match element.as_node().borrow_data_unchecked() {
|
||||
None => return None,
|
||||
Some(data_ref) => {
|
||||
match (*data_ref).style {
|
||||
None => return None,
|
||||
Some(ref data) => (*data).clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let parent_style = unsafe {
|
||||
match parent_element.as_node().borrow_data_unchecked() {
|
||||
None => return None,
|
||||
Some(parent_data_ref) => {
|
||||
match (*parent_data_ref).style {
|
||||
None => return None,
|
||||
Some(ref data) => (*data).clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CacheMiss {
|
||||
Parent,
|
||||
LocalName,
|
||||
Namespace,
|
||||
Link,
|
||||
State,
|
||||
IdAttr,
|
||||
StyleAttr,
|
||||
Class,
|
||||
CommonStyleAffectingAttributes,
|
||||
PresHints,
|
||||
SiblingRules,
|
||||
NonCommonAttrRules,
|
||||
}
|
||||
|
||||
if element.style_attribute().is_some() {
|
||||
return None
|
||||
fn element_matches_candidate<E: TElement>(element: &E,
|
||||
candidate: &mut StyleSharingCandidate,
|
||||
candidate_element: &E,
|
||||
shared_context: &SharedStyleContext)
|
||||
-> Result<Arc<ComputedValues>, CacheMiss> {
|
||||
macro_rules! miss {
|
||||
($miss: ident) => {
|
||||
return Err(CacheMiss::$miss);
|
||||
}
|
||||
|
||||
let mut classes = Vec::new();
|
||||
element.each_class(|c| classes.push(c.clone()));
|
||||
Some(StyleSharingCandidate {
|
||||
style: style,
|
||||
parent_style: parent_style,
|
||||
local_name: element.get_local_name().clone(),
|
||||
classes: classes,
|
||||
link: element.is_link(),
|
||||
namespace: (*element.get_namespace()).clone(),
|
||||
common_style_affecting_attributes:
|
||||
create_common_style_affecting_attributes_from_element::<N::ConcreteElement>(&element)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn can_share_style_with<E: TElement>(&self, element: &E) -> bool {
|
||||
if element.get_local_name() != self.local_name.borrow() {
|
||||
return false
|
||||
}
|
||||
|
||||
let mut num_classes = 0;
|
||||
let mut classes_match = true;
|
||||
element.each_class(|c| {
|
||||
num_classes += 1;
|
||||
// Note that we could do this check more cheaply if we decided to
|
||||
// only consider class lists as equal if the orders match, since
|
||||
// we could then index by num_classes instead of using .contains().
|
||||
if classes_match && !self.classes.contains(c) {
|
||||
classes_match = false;
|
||||
}
|
||||
});
|
||||
if !classes_match || num_classes != self.classes.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if element.get_namespace() != self.namespace.borrow() {
|
||||
return false
|
||||
}
|
||||
|
||||
let mut matching_rules = ForgetfulSink::new();
|
||||
element.synthesize_presentational_hints_for_legacy_attributes(&mut matching_rules);
|
||||
if !matching_rules.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// FIXME(pcwalton): It's probably faster to iterate over all the element's attributes and
|
||||
// use the {common, rare}-style-affecting-attributes tables as lookup tables.
|
||||
|
||||
for attribute_info in &common_style_affecting_attributes() {
|
||||
match attribute_info.mode {
|
||||
CommonStyleAffectingAttributeMode::IsPresent(flag) => {
|
||||
if self.common_style_affecting_attributes.contains(flag) !=
|
||||
element.has_attr(&ns!(), &attribute_info.atom) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
CommonStyleAffectingAttributeMode::IsEqual(ref target_value, flag) => {
|
||||
let contains = self.common_style_affecting_attributes.contains(flag);
|
||||
if element.has_attr(&ns!(), &attribute_info.atom) {
|
||||
if !contains || !element.attr_equals(&ns!(), &attribute_info.atom, target_value) {
|
||||
return false
|
||||
}
|
||||
} else if contains {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for attribute_name in &rare_style_affecting_attributes() {
|
||||
if element.has_attr(&ns!(), attribute_name) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if element.is_link() != self.link {
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO(pcwalton): We don't support visited links yet, but when we do there will need to
|
||||
// be some logic here.
|
||||
|
||||
true
|
||||
if element.parent_element() != candidate_element.parent_element() {
|
||||
miss!(Parent)
|
||||
}
|
||||
|
||||
if *element.get_local_name() != *candidate_element.get_local_name() {
|
||||
miss!(LocalName)
|
||||
}
|
||||
|
||||
if *element.get_namespace() != *candidate_element.get_namespace() {
|
||||
miss!(Namespace)
|
||||
}
|
||||
|
||||
if element.is_link() != candidate_element.is_link() {
|
||||
miss!(Link)
|
||||
}
|
||||
|
||||
if element.get_state() != candidate_element.get_state() {
|
||||
miss!(State)
|
||||
}
|
||||
|
||||
if element.get_id().is_some() {
|
||||
miss!(IdAttr)
|
||||
}
|
||||
|
||||
if element.style_attribute().is_some() {
|
||||
miss!(StyleAttr)
|
||||
}
|
||||
|
||||
if !have_same_class(element, candidate_element) {
|
||||
miss!(Class)
|
||||
}
|
||||
|
||||
if !have_same_common_style_affecting_attributes(element,
|
||||
candidate,
|
||||
candidate_element) {
|
||||
miss!(CommonStyleAffectingAttributes)
|
||||
}
|
||||
|
||||
if !have_same_presentational_hints(element, candidate_element) {
|
||||
miss!(PresHints)
|
||||
}
|
||||
|
||||
if !match_same_sibling_affecting_rules(element,
|
||||
candidate_element,
|
||||
shared_context) {
|
||||
miss!(SiblingRules)
|
||||
}
|
||||
|
||||
if !match_same_not_common_style_affecting_attributes_rules(element,
|
||||
candidate_element,
|
||||
shared_context) {
|
||||
miss!(NonCommonAttrRules)
|
||||
}
|
||||
|
||||
Ok(candidate.style.clone())
|
||||
}
|
||||
|
||||
fn have_same_common_style_affecting_attributes<E: TElement>(element: &E,
|
||||
candidate: &mut StyleSharingCandidate,
|
||||
candidate_element: &E) -> bool {
|
||||
if candidate.common_style_affecting_attributes.is_none() {
|
||||
candidate.common_style_affecting_attributes =
|
||||
Some(create_common_style_affecting_attributes_from_element(candidate_element))
|
||||
}
|
||||
create_common_style_affecting_attributes_from_element(element) ==
|
||||
candidate.common_style_affecting_attributes.unwrap()
|
||||
}
|
||||
|
||||
fn have_same_presentational_hints<E: TElement>(element: &E, candidate: &E) -> bool {
|
||||
let mut first = ForgetfulSink::new();
|
||||
element.synthesize_presentational_hints_for_legacy_attributes(&mut first);
|
||||
if cfg!(debug_assertions) {
|
||||
let mut second = vec![];
|
||||
candidate.synthesize_presentational_hints_for_legacy_attributes(&mut second);
|
||||
debug_assert!(second.is_empty(),
|
||||
"Should never have inserted an element with preshints in the cache!");
|
||||
}
|
||||
|
||||
first.is_empty()
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
|
@ -384,7 +376,33 @@ pub fn rare_style_affecting_attributes() -> [Atom; 3] {
|
|||
[ atom!("bgcolor"), atom!("border"), atom!("colspan") ]
|
||||
}
|
||||
|
||||
static STYLE_SHARING_CANDIDATE_CACHE_SIZE: usize = 40;
|
||||
fn have_same_class<E: TElement>(element: &E, candidate: &E) -> bool {
|
||||
// XXX Efficiency here, I'm only validating ideas.
|
||||
let mut first = vec![];
|
||||
let mut second = vec![];
|
||||
|
||||
element.each_class(|c| first.push(c.clone()));
|
||||
candidate.each_class(|c| second.push(c.clone()));
|
||||
|
||||
first == second
|
||||
}
|
||||
|
||||
// TODO: These re-match the candidate every time, which is suboptimal.
|
||||
#[inline]
|
||||
fn match_same_not_common_style_affecting_attributes_rules<E: TElement>(element: &E,
|
||||
candidate: &E,
|
||||
ctx: &SharedStyleContext) -> bool {
|
||||
ctx.stylist.match_same_not_common_style_affecting_attributes_rules(element, candidate)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn match_same_sibling_affecting_rules<E: TElement>(element: &E,
|
||||
candidate: &E,
|
||||
ctx: &SharedStyleContext) -> bool {
|
||||
ctx.stylist.match_same_sibling_affecting_rules(element, candidate)
|
||||
}
|
||||
|
||||
static STYLE_SHARING_CANDIDATE_CACHE_SIZE: usize = 8;
|
||||
|
||||
impl StyleSharingCandidateCache {
|
||||
pub fn new() -> Self {
|
||||
|
@ -393,20 +411,62 @@ impl StyleSharingCandidateCache {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> Iter<(StyleSharingCandidate, ())> {
|
||||
self.cache.iter()
|
||||
fn iter_mut(&mut self) -> IterMut<(StyleSharingCandidate, ())> {
|
||||
self.cache.iter_mut()
|
||||
}
|
||||
|
||||
pub fn insert_if_possible<N: TNode>(&mut self, element: &N::ConcreteElement) {
|
||||
match StyleSharingCandidate::new::<N>(element) {
|
||||
None => {}
|
||||
Some(candidate) => self.cache.insert(candidate, ())
|
||||
pub fn insert_if_possible<E: TElement>(&mut self,
|
||||
element: &E,
|
||||
relations: StyleRelations) {
|
||||
use traversal::relations_are_shareable;
|
||||
|
||||
let parent = match element.parent_element() {
|
||||
Some(element) => element,
|
||||
None => {
|
||||
debug!("Failing to insert to the cache: no parent element");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// These are things we don't check in the candidate match because they
|
||||
// are either uncommon or expensive.
|
||||
if !relations_are_shareable(&relations) {
|
||||
debug!("Failing to insert to the cache: {:?}", relations);
|
||||
return;
|
||||
}
|
||||
|
||||
let node = element.as_node();
|
||||
let data = node.borrow_data().unwrap();
|
||||
let style = data.style.as_ref().unwrap();
|
||||
|
||||
let box_style = style.get_box();
|
||||
if box_style.transition_property_count() > 0 {
|
||||
debug!("Failing to insert to the cache: transitions");
|
||||
return;
|
||||
}
|
||||
|
||||
if box_style.animation_name_count() > 0 {
|
||||
debug!("Failing to insert to the cache: animations");
|
||||
return;
|
||||
}
|
||||
|
||||
debug!("Inserting into cache: {:?} with parent {:?}",
|
||||
element.as_node().to_unsafe(), parent.as_node().to_unsafe());
|
||||
|
||||
self.cache.insert(StyleSharingCandidate {
|
||||
node: node.to_unsafe(),
|
||||
style: style.clone(),
|
||||
common_style_affecting_attributes: None,
|
||||
}, ());
|
||||
}
|
||||
|
||||
pub fn touch(&mut self, index: usize) {
|
||||
self.cache.touch(index);
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.cache.evict_all()
|
||||
}
|
||||
}
|
||||
|
||||
/// The results of attempting to share a style.
|
||||
|
@ -562,31 +622,18 @@ impl<N: TNode> PrivateMatchMethods for N {}
|
|||
|
||||
trait PrivateElementMatchMethods: TElement {
|
||||
fn share_style_with_candidate_if_possible(&self,
|
||||
parent_node: Option<Self::ConcreteNode>,
|
||||
candidate: &StyleSharingCandidate)
|
||||
-> Option<Arc<ComputedValues>> {
|
||||
let parent_node = match parent_node {
|
||||
Some(ref parent_node) if parent_node.as_element().is_some() => parent_node,
|
||||
Some(_) | None => return None,
|
||||
parent_node: Self::ConcreteNode,
|
||||
shared_context: &SharedStyleContext,
|
||||
candidate: &mut StyleSharingCandidate)
|
||||
-> Result<Arc<ComputedValues>, CacheMiss> {
|
||||
debug_assert!(parent_node.is_element());
|
||||
|
||||
let candidate_element = unsafe {
|
||||
Self::ConcreteNode::from_unsafe(&candidate.node).as_element().unwrap()
|
||||
};
|
||||
|
||||
let parent_data: Option<&PrivateStyleData> = unsafe {
|
||||
parent_node.borrow_data_unchecked().map(|d| &*d)
|
||||
};
|
||||
|
||||
if let Some(parent_data_ref) = parent_data {
|
||||
// Check parent style.
|
||||
let parent_style = (*parent_data_ref).style.as_ref().unwrap();
|
||||
if !arc_ptr_eq(parent_style, &candidate.parent_style) {
|
||||
return None
|
||||
}
|
||||
// Check tag names, classes, etc.
|
||||
if !candidate.can_share_style_with(self) {
|
||||
return None
|
||||
}
|
||||
return Some(candidate.style.clone())
|
||||
}
|
||||
None
|
||||
element_matches_candidate(self, candidate, &candidate_element,
|
||||
shared_context)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -597,15 +644,19 @@ pub trait ElementMatchMethods : TElement {
|
|||
stylist: &Stylist,
|
||||
parent_bf: Option<&BloomFilter>,
|
||||
applicable_declarations: &mut ApplicableDeclarations)
|
||||
-> bool {
|
||||
-> StyleRelations {
|
||||
use traversal::relations_are_shareable;
|
||||
let style_attribute = self.style_attribute().as_ref();
|
||||
|
||||
applicable_declarations.normal_shareable =
|
||||
let mut relations =
|
||||
stylist.push_applicable_declarations(self,
|
||||
parent_bf,
|
||||
style_attribute,
|
||||
None,
|
||||
&mut applicable_declarations.normal);
|
||||
|
||||
applicable_declarations.normal_shareable = relations_are_shareable(&relations);
|
||||
|
||||
TheSelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
|
||||
stylist.push_applicable_declarations(self,
|
||||
parent_bf,
|
||||
|
@ -614,8 +665,14 @@ pub trait ElementMatchMethods : TElement {
|
|||
applicable_declarations.per_pseudo.entry(pseudo).or_insert(vec![]));
|
||||
});
|
||||
|
||||
applicable_declarations.normal_shareable &&
|
||||
applicable_declarations.per_pseudo.values().all(|v| v.is_empty())
|
||||
let has_pseudos =
|
||||
applicable_declarations.per_pseudo.values().any(|v| !v.is_empty());
|
||||
|
||||
if has_pseudos {
|
||||
relations |= AFFECTED_BY_PSEUDO_ELEMENTS;
|
||||
}
|
||||
|
||||
relations
|
||||
}
|
||||
|
||||
/// Attempts to share a style with another node. This method is unsafe because it depends on
|
||||
|
@ -624,6 +681,7 @@ pub trait ElementMatchMethods : TElement {
|
|||
unsafe fn share_style_if_possible(&self,
|
||||
style_sharing_candidate_cache:
|
||||
&mut StyleSharingCandidateCache,
|
||||
shared_context: &SharedStyleContext,
|
||||
parent: Option<Self::ConcreteNode>)
|
||||
-> StyleSharingResult<<Self::ConcreteNode as TNode>::ConcreteRestyleDamage> {
|
||||
if opts::get().disable_share_style_cache {
|
||||
|
@ -633,38 +691,67 @@ pub trait ElementMatchMethods : TElement {
|
|||
if self.style_attribute().is_some() {
|
||||
return StyleSharingResult::CannotShare
|
||||
}
|
||||
|
||||
if self.has_attr(&ns!(), &atom!("id")) {
|
||||
return StyleSharingResult::CannotShare
|
||||
}
|
||||
|
||||
for (i, &(ref candidate, ())) in style_sharing_candidate_cache.iter().enumerate() {
|
||||
if let Some(shared_style) = self.share_style_with_candidate_if_possible(parent.clone(), candidate) {
|
||||
// Yay, cache hit. Share the style.
|
||||
let node = self.as_node();
|
||||
let parent = match parent {
|
||||
Some(parent) if parent.is_element() => parent,
|
||||
_ => return StyleSharingResult::CannotShare,
|
||||
};
|
||||
|
||||
let style = &mut node.mutate_data().unwrap().style;
|
||||
for (i, &mut (ref mut candidate, ())) in style_sharing_candidate_cache.iter_mut().enumerate() {
|
||||
let sharing_result = self.share_style_with_candidate_if_possible(parent,
|
||||
shared_context,
|
||||
candidate);
|
||||
match sharing_result {
|
||||
Ok(shared_style) => {
|
||||
// Yay, cache hit. Share the style.
|
||||
let node = self.as_node();
|
||||
let style = &mut node.mutate_data().unwrap().style;
|
||||
|
||||
let damage =
|
||||
match node.existing_style_for_restyle_damage((*style).as_ref(), None) {
|
||||
Some(ref source) => {
|
||||
<<Self as TElement>::ConcreteNode as TNode>
|
||||
::ConcreteRestyleDamage::compute(source, &shared_style)
|
||||
}
|
||||
None => {
|
||||
<<Self as TElement>::ConcreteNode as TNode>
|
||||
::ConcreteRestyleDamage::rebuild_and_reflow()
|
||||
}
|
||||
// TODO: add the display: none optimisation here too! Even
|
||||
// better, factor it out/make it a bit more generic so Gecko
|
||||
// can decide more easily if it knows that it's a child of
|
||||
// replaced content, or similar stuff!
|
||||
let damage =
|
||||
match node.existing_style_for_restyle_damage((*style).as_ref(), None) {
|
||||
Some(ref source) => {
|
||||
<<Self as TElement>::ConcreteNode as TNode>
|
||||
::ConcreteRestyleDamage::compute(source, &shared_style)
|
||||
}
|
||||
None => {
|
||||
<<Self as TElement>::ConcreteNode as TNode>
|
||||
::ConcreteRestyleDamage::rebuild_and_reflow()
|
||||
}
|
||||
};
|
||||
|
||||
let restyle_result = if shared_style.get_box().clone_display() == display::T::none {
|
||||
RestyleResult::Stop
|
||||
} else {
|
||||
RestyleResult::Continue
|
||||
};
|
||||
|
||||
let restyle_result = if shared_style.get_box().clone_display() == display::T::none {
|
||||
RestyleResult::Stop
|
||||
} else {
|
||||
RestyleResult::Continue
|
||||
};
|
||||
*style = Some(shared_style);
|
||||
|
||||
*style = Some(shared_style);
|
||||
return StyleSharingResult::StyleWasShared(i, damage, restyle_result)
|
||||
}
|
||||
Err(miss) => {
|
||||
debug!("Cache miss: {:?}", miss);
|
||||
|
||||
return StyleSharingResult::StyleWasShared(i, damage, restyle_result)
|
||||
// Cache miss, let's see what kind of failure to decide
|
||||
// whether we keep trying or not.
|
||||
match miss {
|
||||
// Too expensive failure, give up, we don't want another
|
||||
// one of these.
|
||||
CacheMiss::CommonStyleAffectingAttributes |
|
||||
CacheMiss::PresHints |
|
||||
CacheMiss::SiblingRules |
|
||||
CacheMiss::NonCommonAttrRules => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@ use dom::{OpaqueNode, TNode, UnsafeNode};
|
|||
use std::mem;
|
||||
use std::sync::atomic::Ordering;
|
||||
use traversal::{RestyleResult, DomTraversalContext};
|
||||
use traversal::{STYLE_SHARING_CACHE_HITS, STYLE_SHARING_CACHE_MISSES};
|
||||
use util::opts;
|
||||
use workqueue::{WorkQueue, WorkUnit, WorkerProxy};
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
@ -42,13 +44,28 @@ pub fn run_queue_with_custom_work_data_type<To, F, SharedContext: Sync>(
|
|||
pub fn traverse_dom<N, C>(root: N,
|
||||
queue_data: &C::SharedContext,
|
||||
queue: &mut WorkQueue<C::SharedContext, WorkQueueData>)
|
||||
where N: TNode, C: DomTraversalContext<N> {
|
||||
where N: TNode,
|
||||
C: DomTraversalContext<N>
|
||||
{
|
||||
if opts::get().style_sharing_stats {
|
||||
STYLE_SHARING_CACHE_HITS.store(0, Ordering::SeqCst);
|
||||
STYLE_SHARING_CACHE_MISSES.store(0, Ordering::SeqCst);
|
||||
}
|
||||
run_queue_with_custom_work_data_type(queue, |queue| {
|
||||
queue.push(WorkUnit {
|
||||
fun: top_down_dom::<N, C>,
|
||||
data: (Box::new(vec![root.to_unsafe()]), root.opaque()),
|
||||
});
|
||||
}, queue_data);
|
||||
|
||||
if opts::get().style_sharing_stats {
|
||||
let hits = STYLE_SHARING_CACHE_HITS.load(Ordering::SeqCst);
|
||||
let misses = STYLE_SHARING_CACHE_MISSES.load(Ordering::SeqCst);
|
||||
|
||||
println!("Style sharing stats:");
|
||||
println!(" * Hits: {}", hits);
|
||||
println!(" * Misses: {}", misses);
|
||||
}
|
||||
}
|
||||
|
||||
/// A parallel top-down DOM traversal.
|
||||
|
@ -102,6 +119,10 @@ fn top_down_dom<N, C>(unsafe_nodes: UnsafeNodeList,
|
|||
}
|
||||
}
|
||||
|
||||
// NB: In parallel traversal mode we have to purge the LRU cache in order to
|
||||
// be able to access it without races.
|
||||
context.local_context().style_sharing_candidate_cache.borrow_mut().clear();
|
||||
|
||||
for chunk in discovered_child_nodes.chunks(CHUNK_SIZE) {
|
||||
proxy.push(WorkUnit {
|
||||
fun: top_down_dom::<N, C>,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
use element_state::*;
|
||||
use selector_impl::{ElementExt, TheSelectorImpl, NonTSPseudoClass, AttrValue};
|
||||
use selectors::matching::StyleRelations;
|
||||
use selectors::matching::matches_compound_selector;
|
||||
use selectors::parser::{AttrSelector, Combinator, CompoundSelector, SimpleSelector, SelectorImpl};
|
||||
use selectors::{Element, MatchAttr};
|
||||
|
@ -348,7 +349,11 @@ impl DependencySet {
|
|||
DependencySet { deps: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn note_selector(&mut self, selector: Arc<CompoundSelector<TheSelectorImpl>>) {
|
||||
pub fn len(&self) -> usize {
|
||||
self.deps.len()
|
||||
}
|
||||
|
||||
pub fn note_selector(&mut self, selector: &Arc<CompoundSelector<TheSelectorImpl>>) {
|
||||
let mut cur = selector;
|
||||
let mut combinator: Option<Combinator> = None;
|
||||
loop {
|
||||
|
@ -370,7 +375,7 @@ impl DependencySet {
|
|||
cur = match cur.next {
|
||||
Some((ref sel, comb)) => {
|
||||
combinator = Some(comb);
|
||||
sel.clone()
|
||||
sel
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
|
@ -389,14 +394,19 @@ impl DependencySet {
|
|||
-> RestyleHint
|
||||
where E: ElementExt + Clone
|
||||
{
|
||||
debug!("About to calculate restyle hint for element. Deps: {}",
|
||||
self.deps.len());
|
||||
|
||||
let state_changes = snapshot.state().map_or_else(ElementState::empty, |old_state| current_state ^ old_state);
|
||||
let attrs_changed = snapshot.has_attrs();
|
||||
let mut hint = RestyleHint::empty();
|
||||
for dep in &self.deps {
|
||||
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);
|
||||
let matched_then =
|
||||
matches_compound_selector(&*dep.selector, &old_el, None, &mut StyleRelations::empty());
|
||||
let matches_now =
|
||||
matches_compound_selector(&*dep.selector, el, None, &mut StyleRelations::empty());
|
||||
if matched_then != matches_now {
|
||||
hint.insert(combinator_to_restyle_hint(dep.combinator));
|
||||
if hint.is_all() {
|
||||
|
|
|
@ -15,7 +15,9 @@ use selector_impl::{ElementExt, TheSelectorImpl, PseudoElement};
|
|||
use selectors::Element;
|
||||
use selectors::bloom::BloomFilter;
|
||||
use selectors::matching::DeclarationBlock as GenericDeclarationBlock;
|
||||
use selectors::matching::{Rule, SelectorMap};
|
||||
use selectors::matching::{AFFECTED_BY_STYLE_ATTRIBUTE, AFFECTED_BY_PRESENTATIONAL_HINTS};
|
||||
use selectors::matching::{Rule, SelectorMap, StyleRelations};
|
||||
use selectors::parser::Selector;
|
||||
use sink::Push;
|
||||
use smallvec::VecLike;
|
||||
use std::collections::HashMap;
|
||||
|
@ -26,7 +28,6 @@ use style_traits::viewport::ViewportConstraints;
|
|||
use stylesheets::{CSSRule, CSSRuleIteratorExt, Origin, Stylesheet};
|
||||
use viewport::{MaybeNew, ViewportRuleCascade};
|
||||
|
||||
|
||||
pub type DeclarationBlock = GenericDeclarationBlock<Vec<PropertyDeclaration>>;
|
||||
|
||||
/// This structure holds all the selectors and device characteristics
|
||||
|
@ -76,6 +77,13 @@ pub struct Stylist {
|
|||
|
||||
/// Selector dependencies used to compute restyle hints.
|
||||
state_deps: DependencySet,
|
||||
|
||||
/// Selectors in the page affecting siblings
|
||||
sibling_affecting_selectors: Vec<Selector<TheSelectorImpl>>,
|
||||
|
||||
/// Selectors in the page matching elements with non-common style-affecting
|
||||
/// attributes.
|
||||
non_common_style_affecting_attributes_selectors: Vec<Selector<TheSelectorImpl>>,
|
||||
}
|
||||
|
||||
impl Stylist {
|
||||
|
@ -93,6 +101,10 @@ impl Stylist {
|
|||
precomputed_pseudo_element_decls: HashMap::with_hasher(Default::default()),
|
||||
rules_source_order: 0,
|
||||
state_deps: DependencySet::new(),
|
||||
|
||||
// XXX remember resetting them!
|
||||
sibling_affecting_selectors: vec![],
|
||||
non_common_style_affecting_attributes_selectors: vec![]
|
||||
};
|
||||
|
||||
TheSelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
|
||||
|
@ -120,6 +132,9 @@ impl Stylist {
|
|||
self.rules_source_order = 0;
|
||||
self.state_deps.clear();
|
||||
|
||||
self.sibling_affecting_selectors.clear();
|
||||
self.non_common_style_affecting_attributes_selectors.clear();
|
||||
|
||||
for ref stylesheet in TheSelectorImpl::get_user_or_user_agent_stylesheets().iter() {
|
||||
self.add_stylesheet(&stylesheet);
|
||||
}
|
||||
|
@ -178,8 +193,16 @@ impl Stylist {
|
|||
append!(style_rule, normal);
|
||||
append!(style_rule, important);
|
||||
rules_source_order += 1;
|
||||
|
||||
for selector in &style_rule.selectors {
|
||||
self.state_deps.note_selector(selector.compound_selectors.clone());
|
||||
self.state_deps.note_selector(&selector.compound_selectors);
|
||||
if selector.affects_siblings() {
|
||||
self.sibling_affecting_selectors.push(selector.clone());
|
||||
}
|
||||
|
||||
if selector.matches_non_common_style_affecting_attribute() {
|
||||
self.non_common_style_affecting_attributes_selectors.push(selector.clone());
|
||||
}
|
||||
}
|
||||
|
||||
self.rules_source_order = rules_source_order;
|
||||
|
@ -202,6 +225,14 @@ impl Stylist {
|
|||
}
|
||||
}
|
||||
|
||||
debug!("Stylist stats:");
|
||||
debug!(" - Got {} sibling-affecting selectors",
|
||||
self.sibling_affecting_selectors.len());
|
||||
debug!(" - Got {} non-common-style-attribute-affecting selectors",
|
||||
self.non_common_style_affecting_attributes_selectors.len());
|
||||
debug!(" - Got {} deps for style-hint calculation",
|
||||
self.state_deps.len());
|
||||
|
||||
TheSelectorImpl::each_precomputed_pseudo_element(|pseudo| {
|
||||
// TODO: Consider not doing this and just getting the rules on the
|
||||
// fly. It should be a bit slower, but we'd take rid of the
|
||||
|
@ -310,11 +341,11 @@ impl Stylist {
|
|||
parent_bf: Option<&BloomFilter>,
|
||||
style_attribute: Option<&PropertyDeclarationBlock>,
|
||||
pseudo_element: Option<&PseudoElement>,
|
||||
applicable_declarations: &mut V)
|
||||
-> bool
|
||||
where E: Element<Impl=TheSelectorImpl> +
|
||||
PresentationalHintsSynthetizer,
|
||||
V: Push<DeclarationBlock> + VecLike<DeclarationBlock> {
|
||||
applicable_declarations: &mut V) -> StyleRelations
|
||||
where E: Element<Impl=TheSelectorImpl> +
|
||||
PresentationalHintsSynthetizer,
|
||||
V: Push<DeclarationBlock> + VecLike<DeclarationBlock>
|
||||
{
|
||||
assert!(!self.is_device_dirty);
|
||||
assert!(style_attribute.is_none() || pseudo_element.is_none(),
|
||||
"Style attributes do not apply to pseudo-elements");
|
||||
|
@ -327,65 +358,82 @@ impl Stylist {
|
|||
None => &self.element_map,
|
||||
};
|
||||
|
||||
let mut shareable = true;
|
||||
let mut relations = StyleRelations::empty();
|
||||
|
||||
debug!("Determining if style is shareable: pseudo: {}", pseudo_element.is_some());
|
||||
// Step 1: Normal user-agent rules.
|
||||
map.user_agent.normal.get_all_matching_rules(element,
|
||||
parent_bf,
|
||||
applicable_declarations,
|
||||
&mut shareable);
|
||||
&mut relations);
|
||||
debug!("UA normal: {:?}", relations);
|
||||
|
||||
// Step 2: Presentational hints.
|
||||
let length = applicable_declarations.len();
|
||||
element.synthesize_presentational_hints_for_legacy_attributes(applicable_declarations);
|
||||
if applicable_declarations.len() != length {
|
||||
// Never share style for elements with preshints
|
||||
shareable = false;
|
||||
relations |= AFFECTED_BY_PRESENTATIONAL_HINTS;
|
||||
}
|
||||
debug!("preshints: {:?}", relations);
|
||||
|
||||
// Step 3: User and author normal rules.
|
||||
map.user.normal.get_all_matching_rules(element,
|
||||
parent_bf,
|
||||
applicable_declarations,
|
||||
&mut shareable);
|
||||
&mut relations);
|
||||
debug!("user normal: {:?}", relations);
|
||||
map.author.normal.get_all_matching_rules(element,
|
||||
parent_bf,
|
||||
applicable_declarations,
|
||||
&mut shareable);
|
||||
&mut relations);
|
||||
debug!("author normal: {:?}", relations);
|
||||
|
||||
// Step 4: Normal style attributes.
|
||||
style_attribute.map(|sa| {
|
||||
shareable = false;
|
||||
if let Some(ref sa) = style_attribute {
|
||||
relations |= AFFECTED_BY_STYLE_ATTRIBUTE;
|
||||
Push::push(
|
||||
applicable_declarations,
|
||||
GenericDeclarationBlock::from_declarations(sa.normal.clone()))
|
||||
});
|
||||
GenericDeclarationBlock::from_declarations(sa.normal.clone()));
|
||||
}
|
||||
|
||||
debug!("style attr: {:?}", relations);
|
||||
|
||||
// Step 5: Author-supplied `!important` rules.
|
||||
map.author.important.get_all_matching_rules(element,
|
||||
parent_bf,
|
||||
applicable_declarations,
|
||||
&mut shareable);
|
||||
&mut relations);
|
||||
|
||||
debug!("author important: {:?}", relations);
|
||||
|
||||
// Step 6: `!important` style attributes.
|
||||
style_attribute.map(|sa| {
|
||||
shareable = false;
|
||||
if let Some(ref sa) = style_attribute {
|
||||
Push::push(
|
||||
applicable_declarations,
|
||||
GenericDeclarationBlock::from_declarations(sa.important.clone()))
|
||||
});
|
||||
GenericDeclarationBlock::from_declarations(sa.important.clone()));
|
||||
}
|
||||
|
||||
debug!("style attr important: {:?}", relations);
|
||||
|
||||
// Step 7: User and UA `!important` rules.
|
||||
map.user.important.get_all_matching_rules(element,
|
||||
parent_bf,
|
||||
applicable_declarations,
|
||||
&mut shareable);
|
||||
&mut relations);
|
||||
|
||||
debug!("user important: {:?}", relations);
|
||||
|
||||
map.user_agent.important.get_all_matching_rules(element,
|
||||
parent_bf,
|
||||
applicable_declarations,
|
||||
&mut shareable);
|
||||
&mut relations);
|
||||
|
||||
shareable
|
||||
debug!("UA important: {:?}", relations);
|
||||
|
||||
debug!("push_applicable_declarations: shareable: {:?}", relations);
|
||||
|
||||
relations
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -398,6 +446,67 @@ impl Stylist {
|
|||
&self.animations
|
||||
}
|
||||
|
||||
pub fn match_same_not_common_style_affecting_attributes_rules<E>(&self,
|
||||
element: &E,
|
||||
candidate: &E) -> bool
|
||||
where E: ElementExt
|
||||
{
|
||||
use selectors::matching::StyleRelations;
|
||||
use selectors::matching::matches_compound_selector;
|
||||
// XXX we can probably do better, the candidate should already know what
|
||||
// rules it matches.
|
||||
//
|
||||
// XXX Could the bloom filter help here? Should be available.
|
||||
for ref selector in self.non_common_style_affecting_attributes_selectors.iter() {
|
||||
let element_matches = matches_compound_selector(&selector.compound_selectors,
|
||||
element,
|
||||
None,
|
||||
&mut StyleRelations::empty());
|
||||
let candidate_matches = matches_compound_selector(&selector.compound_selectors,
|
||||
candidate,
|
||||
None,
|
||||
&mut StyleRelations::empty());
|
||||
|
||||
if element_matches != candidate_matches {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn match_same_sibling_affecting_rules<E>(&self,
|
||||
element: &E,
|
||||
candidate: &E) -> bool
|
||||
where E: ElementExt
|
||||
{
|
||||
use selectors::matching::StyleRelations;
|
||||
use selectors::matching::matches_compound_selector;
|
||||
// XXX we can probably do better, the candidate should already know what
|
||||
// rules it matches.
|
||||
//
|
||||
// XXX The bloom filter would help here, and should be available.
|
||||
for ref selector in self.sibling_affecting_selectors.iter() {
|
||||
let element_matches = matches_compound_selector(&selector.compound_selectors,
|
||||
element,
|
||||
None,
|
||||
&mut StyleRelations::empty());
|
||||
|
||||
let candidate_matches = matches_compound_selector(&selector.compound_selectors,
|
||||
candidate,
|
||||
None,
|
||||
&mut StyleRelations::empty());
|
||||
|
||||
if element_matches != candidate_matches {
|
||||
debug!("match_same_sibling_affecting_rules: Failure due to {:?}",
|
||||
selector.compound_selectors);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn compute_restyle_hint<E>(&self, element: &E,
|
||||
snapshot: &E::Snapshot,
|
||||
// NB: We need to pass current_state as an argument because
|
||||
|
|
|
@ -35,5 +35,6 @@ pub fn traverse_dom<N, C>(root: N,
|
|||
if context.should_process(root) {
|
||||
doit::<N, C>(&context, root);
|
||||
}
|
||||
// Clear the local LRU cache since we store stateful elements inside.
|
||||
context.local_context().style_sharing_candidate_cache.borrow_mut().clear();
|
||||
}
|
||||
|
||||
|
|
|
@ -5,11 +5,13 @@
|
|||
//! Traversing the DOM tree; the bloom filter.
|
||||
|
||||
use animation;
|
||||
use context::{SharedStyleContext, StyleContext};
|
||||
use context::{LocalStyleContext, SharedStyleContext, StyleContext};
|
||||
use dom::{OpaqueNode, TElement, TNode, TRestyleDamage, UnsafeNode};
|
||||
use matching::{ApplicableDeclarations, ElementMatchMethods, MatchMethods, StyleSharingResult};
|
||||
use selectors::bloom::BloomFilter;
|
||||
use selectors::matching::StyleRelations;
|
||||
use std::cell::RefCell;
|
||||
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
|
||||
use tid::tid;
|
||||
use util::opts;
|
||||
use values::HasViewportPercentage;
|
||||
|
@ -27,6 +29,10 @@ pub enum RestyleResult {
|
|||
Stop,
|
||||
}
|
||||
|
||||
/// Style sharing candidate cache stats. These are only used when
|
||||
/// `-Z style-sharing-stats` is given.
|
||||
pub static STYLE_SHARING_CACHE_HITS: AtomicUsize = ATOMIC_USIZE_INIT;
|
||||
pub static STYLE_SHARING_CACHE_MISSES: AtomicUsize = ATOMIC_USIZE_INIT;
|
||||
|
||||
/// A pair of the bloom filter used for css selector matching, and the node to
|
||||
/// which it applies. This is used to efficiently do `Descendant` selector
|
||||
|
@ -149,7 +155,7 @@ pub fn remove_from_bloom_filter<'a, N, C>(context: &C, root: OpaqueNode, node: N
|
|||
};
|
||||
}
|
||||
|
||||
pub trait DomTraversalContext<N: TNode> {
|
||||
pub trait DomTraversalContext<N: TNode> {
|
||||
type SharedContext: Sync + 'static;
|
||||
|
||||
fn new<'a>(&'a Self::SharedContext, OpaqueNode) -> Self;
|
||||
|
@ -191,6 +197,19 @@ pub trait DomTraversalContext<N: TNode> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn local_context(&self) -> &LocalStyleContext;
|
||||
}
|
||||
|
||||
/// Determines the amount of relations where we're going to share style.
|
||||
#[inline]
|
||||
pub fn relations_are_shareable(relations: &StyleRelations) -> bool {
|
||||
use selectors::matching::*;
|
||||
!relations.intersects(AFFECTED_BY_ID_SELECTOR |
|
||||
AFFECTED_BY_PSEUDO_ELEMENTS | AFFECTED_BY_STATE |
|
||||
AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR |
|
||||
AFFECTED_BY_STYLE_ATTRIBUTE |
|
||||
AFFECTED_BY_PRESENTATIONAL_HINTS)
|
||||
}
|
||||
|
||||
pub fn ensure_node_styled<'a, N, C>(node: N,
|
||||
|
@ -301,6 +320,7 @@ pub fn recalc_style_at<'a, N, C>(context: &'a C,
|
|||
Some(element) => {
|
||||
unsafe {
|
||||
element.share_style_if_possible(style_sharing_candidate_cache,
|
||||
context.shared_context(),
|
||||
parent_opt.clone())
|
||||
}
|
||||
},
|
||||
|
@ -312,20 +332,30 @@ pub fn recalc_style_at<'a, N, C>(context: &'a C,
|
|||
StyleSharingResult::CannotShare => {
|
||||
let mut applicable_declarations = ApplicableDeclarations::new();
|
||||
|
||||
let relations;
|
||||
let shareable_element = match node.as_element() {
|
||||
Some(element) => {
|
||||
if opts::get().style_sharing_stats {
|
||||
STYLE_SHARING_CACHE_MISSES.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
// Perform the CSS selector matching.
|
||||
let stylist = &context.shared_context().stylist;
|
||||
|
||||
if element.match_element(&**stylist,
|
||||
Some(&*bf),
|
||||
&mut applicable_declarations) {
|
||||
relations = element.match_element(&**stylist,
|
||||
Some(&*bf),
|
||||
&mut applicable_declarations);
|
||||
|
||||
debug!("Result of selector matching: {:?}", relations);
|
||||
|
||||
if relations_are_shareable(&relations) {
|
||||
Some(element)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
None => {
|
||||
relations = StyleRelations::empty();
|
||||
if node.has_changed() {
|
||||
node.set_restyle_damage(N::ConcreteRestyleDamage::rebuild_and_reflow())
|
||||
}
|
||||
|
@ -336,17 +366,20 @@ pub fn recalc_style_at<'a, N, C>(context: &'a C,
|
|||
// Perform the CSS cascade.
|
||||
unsafe {
|
||||
restyle_result = node.cascade_node(context,
|
||||
parent_opt,
|
||||
&applicable_declarations);
|
||||
parent_opt,
|
||||
&applicable_declarations);
|
||||
}
|
||||
|
||||
// Add ourselves to the LRU cache.
|
||||
if let Some(element) = shareable_element {
|
||||
style_sharing_candidate_cache.insert_if_possible::<'ln, N>(&element);
|
||||
style_sharing_candidate_cache.insert_if_possible(&element, relations);
|
||||
}
|
||||
}
|
||||
StyleSharingResult::StyleWasShared(index, damage, restyle_result_cascade) => {
|
||||
restyle_result = restyle_result_cascade;
|
||||
StyleSharingResult::StyleWasShared(index, damage, cached_restyle_result) => {
|
||||
restyle_result = cached_restyle_result;
|
||||
if opts::get().style_sharing_stats {
|
||||
STYLE_SHARING_CACHE_HITS.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
style_sharing_candidate_cache.touch(index);
|
||||
node.set_restyle_damage(damage);
|
||||
}
|
||||
|
|
|
@ -172,6 +172,9 @@ pub struct Opts {
|
|||
/// Whether Style Sharing Cache is used
|
||||
pub disable_share_style_cache: bool,
|
||||
|
||||
/// Whether to show in stdout style sharing cache stats after a restyle.
|
||||
pub style_sharing_stats: bool,
|
||||
|
||||
/// Translate mouse input into touch events.
|
||||
pub convert_mouse_to_touch: bool,
|
||||
|
||||
|
@ -275,6 +278,9 @@ pub struct DebugOptions {
|
|||
/// Disable the style sharing cache.
|
||||
pub disable_share_style_cache: bool,
|
||||
|
||||
/// Whether to show in stdout style sharing cache stats after a restyle.
|
||||
pub style_sharing_stats: bool,
|
||||
|
||||
/// Translate mouse input into touch events.
|
||||
pub convert_mouse_to_touch: bool,
|
||||
|
||||
|
@ -331,6 +337,7 @@ impl DebugOptions {
|
|||
"paint-flashing" => debug_options.paint_flashing = true,
|
||||
"trace-layout" => debug_options.trace_layout = true,
|
||||
"disable-share-style-cache" => debug_options.disable_share_style_cache = true,
|
||||
"style-sharing-stats" => debug_options.style_sharing_stats = true,
|
||||
"convert-mouse-to-touch" => debug_options.convert_mouse_to_touch = true,
|
||||
"replace-surrogates" => debug_options.replace_surrogates = true,
|
||||
"gc-profile" => debug_options.gc_profile = true,
|
||||
|
@ -512,6 +519,7 @@ pub fn default_opts() -> Opts {
|
|||
profile_script_events: false,
|
||||
profile_heartbeats: false,
|
||||
disable_share_style_cache: false,
|
||||
style_sharing_stats: false,
|
||||
convert_mouse_to_touch: false,
|
||||
exit_after_load: false,
|
||||
no_native_titlebar: false,
|
||||
|
@ -817,6 +825,7 @@ pub fn from_cmdline_args(args: &[String]) -> ArgumentParsingResult {
|
|||
dump_layer_tree: debug_options.dump_layer_tree,
|
||||
relayout_event: debug_options.relayout_event,
|
||||
disable_share_style_cache: debug_options.disable_share_style_cache,
|
||||
style_sharing_stats: debug_options.style_sharing_stats,
|
||||
convert_mouse_to_touch: debug_options.convert_mouse_to_touch,
|
||||
exit_after_load: opt_match.opt_present("x"),
|
||||
no_native_titlebar: do_not_use_native_titlebar,
|
||||
|
|
10
ports/cef/Cargo.lock
generated
10
ports/cef/Cargo.lock
generated
|
@ -1070,7 +1070,7 @@ dependencies = [
|
|||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"script_layout_interface 0.0.1",
|
||||
"script_traits 0.0.1",
|
||||
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_macros 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1783,7 +1783,7 @@ dependencies = [
|
|||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"script_layout_interface 0.0.1",
|
||||
"script_traits 0.0.1",
|
||||
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1820,7 +1820,7 @@ dependencies = [
|
|||
"profile_traits 0.0.1",
|
||||
"range 0.0.1",
|
||||
"script_traits 0.0.1",
|
||||
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"style 0.0.1",
|
||||
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1859,7 +1859,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "selectors"
|
||||
version = "0.8.2"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2122,7 +2122,7 @@ dependencies = [
|
|||
"plugins 0.0.1",
|
||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_macros 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
8
ports/geckolib/Cargo.lock
generated
8
ports/geckolib/Cargo.lock
generated
|
@ -11,7 +11,7 @@ dependencies = [
|
|||
"libc 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"style 0.0.1",
|
||||
"style_traits 0.0.1",
|
||||
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -170,7 +170,7 @@ dependencies = [
|
|||
"gecko_bindings 0.0.1",
|
||||
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -311,7 +311,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
|
||||
[[package]]
|
||||
name = "selectors"
|
||||
version = "0.8.2"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -367,7 +367,7 @@ dependencies = [
|
|||
"ordered-float 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"style_traits 0.0.1",
|
||||
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -19,7 +19,7 @@ lazy_static = "0.2"
|
|||
libc = "0.2"
|
||||
log = {version = "0.3.5", features = ["release_max_level_info"]}
|
||||
num_cpus = "0.2.2"
|
||||
selectors = "0.8"
|
||||
selectors = "0.9"
|
||||
style = {path = "../../components/style", features = ["gecko"]}
|
||||
style_traits = {path = "../../components/style_traits"}
|
||||
url = "1.2"
|
||||
|
|
|
@ -14,5 +14,5 @@ cfg-if = "0.1.0"
|
|||
gecko_bindings = {version = "0.0.1", path = "../gecko_bindings"}
|
||||
heapsize = "0.3.5"
|
||||
libc = "0.2"
|
||||
selectors = "0.8"
|
||||
selectors = "0.9"
|
||||
serde = "0.8"
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use context::StandaloneStyleContext;
|
||||
use std::mem;
|
||||
use style::context::SharedStyleContext;
|
||||
use style::context::{LocalStyleContext, SharedStyleContext, StyleContext};
|
||||
use style::dom::OpaqueNode;
|
||||
use style::traversal::RestyleResult;
|
||||
use style::traversal::{DomTraversalContext, recalc_style_at};
|
||||
|
@ -42,4 +42,8 @@ impl<'lc, 'ln> DomTraversalContext<GeckoNode<'ln>> for RecalcStyleOnly<'lc> {
|
|||
|
||||
/// We don't use the post-order traversal for anything.
|
||||
fn needs_postorder_traversal(&self) -> bool { false }
|
||||
|
||||
fn local_context(&self) -> &LocalStyleContext {
|
||||
self.context.local_context()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -459,6 +459,12 @@ impl<'le> TElement for GeckoElement<'le> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'le> PartialEq for GeckoElement<'le> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.element == other.element
|
||||
}
|
||||
}
|
||||
|
||||
impl<'le> PresentationalHintsSynthetizer for GeckoElement<'le> {
|
||||
fn synthesize_presentational_hints_for_legacy_attributes<V>(&self, _hints: &mut V)
|
||||
where V: Push<DeclarationBlock<Vec<PropertyDeclaration>>>
|
||||
|
|
|
@ -14,7 +14,7 @@ app_units = "0.3"
|
|||
cssparser = {version = "0.5.7", features = ["heap_size"]}
|
||||
euclid = "0.9"
|
||||
rustc-serialize = "0.3"
|
||||
selectors = {version = "0.8", features = ["heap_size"]}
|
||||
selectors = {version = "0.9", features = ["heap_size"]}
|
||||
string_cache = {version = "0.2.23", features = ["heap_size"]}
|
||||
style = {path = "../../../components/style"}
|
||||
style_traits = {path = "../../../components/style_traits"}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[flexbox_columns.htm]
|
||||
type: reftest
|
||||
expected: FAIL
|
|
@ -29,17 +29,22 @@
|
|||
* we're not getting as much test coverage there as we'd like. When we
|
||||
* implement attribute-based restyle hints, we can stop dirtying the subtree
|
||||
* on attribute modifications, and these tests will start to be more useful.
|
||||
*
|
||||
* Also, note that we use window.onload in order to prevent the restyle
|
||||
* hints to interact with any content appended notifications.
|
||||
*/
|
||||
window.dummy = 0;
|
||||
var $ = document.getElementById.bind(document);
|
||||
function syncRestyle() { window.dummy += $("fs2").offsetTop; }
|
||||
syncRestyle();
|
||||
$('fs1').disabled = true;
|
||||
syncRestyle();
|
||||
$('fs1').disabled = false;
|
||||
syncRestyle();
|
||||
$('cb').checked = true;
|
||||
syncRestyle();
|
||||
$('fs2').disabled = true;
|
||||
window.onload = function() {
|
||||
window.dummy = 0;
|
||||
var $ = document.getElementById.bind(document);
|
||||
function syncRestyle() { window.dummy += $("fs2").offsetTop; }
|
||||
syncRestyle();
|
||||
$('fs1').disabled = true;
|
||||
syncRestyle();
|
||||
$('fs1').disabled = false;
|
||||
syncRestyle();
|
||||
$('cb').checked = true;
|
||||
syncRestyle();
|
||||
$('fs2').disabled = true;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue