mirror of
https://github.com/servo/servo.git
synced 2025-07-24 15:50:21 +01:00
Cargoify servo
This commit is contained in:
parent
db2f642c32
commit
c6ab60dbfc
1761 changed files with 8423 additions and 2294 deletions
38
components/layout/Cargo.toml
Normal file
38
components/layout/Cargo.toml
Normal file
|
@ -0,0 +1,38 @@
|
|||
[package]
|
||||
name = "layout"
|
||||
version = "0.0.1"
|
||||
authors = ["The Servo Project Developers"]
|
||||
|
||||
[lib]
|
||||
name = "layout"
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies.gfx]
|
||||
path = "../gfx"
|
||||
|
||||
[dependencies.script]
|
||||
path = "../script"
|
||||
|
||||
[dependencies.layout_traits]
|
||||
path = "../layout_traits"
|
||||
|
||||
[dependencies.script_traits]
|
||||
path = "../script_traits"
|
||||
|
||||
[dependencies.style]
|
||||
path = "../style"
|
||||
|
||||
[dependencies.macros]
|
||||
path = "../macros"
|
||||
|
||||
[dependencies.net]
|
||||
path = "../net"
|
||||
|
||||
[dependencies.util]
|
||||
path = "../util"
|
||||
|
||||
[dependencies.geom]
|
||||
git = "https://github.com/servo/rust-geom"
|
||||
|
||||
[dependencies.url]
|
||||
git = "https://github.com/servo/rust-url"
|
2428
components/layout/block.rs
Normal file
2428
components/layout/block.rs
Normal file
File diff suppressed because it is too large
Load diff
1049
components/layout/construct.rs
Normal file
1049
components/layout/construct.rs
Normal file
File diff suppressed because it is too large
Load diff
123
components/layout/context.rs
Normal file
123
components/layout/context.rs
Normal file
|
@ -0,0 +1,123 @@
|
|||
/* 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/. */
|
||||
|
||||
//! Data needed by the layout task.
|
||||
|
||||
use css::matching::{ApplicableDeclarationsCache, StyleSharingCandidateCache};
|
||||
|
||||
use geom::{Rect, Size2D};
|
||||
use gfx::display_list::OpaqueNode;
|
||||
use gfx::font_context::FontContext;
|
||||
use gfx::font_cache_task::FontCacheTask;
|
||||
use script::layout_interface::LayoutChan;
|
||||
use servo_msg::constellation_msg::ConstellationChan;
|
||||
use servo_net::local_image_cache::LocalImageCache;
|
||||
use servo_util::geometry::Au;
|
||||
use servo_util::opts::Opts;
|
||||
use sync::{Arc, Mutex};
|
||||
use std::mem;
|
||||
use style::Stylist;
|
||||
use url::Url;
|
||||
|
||||
struct LocalLayoutContext {
|
||||
font_context: FontContext,
|
||||
applicable_declarations_cache: ApplicableDeclarationsCache,
|
||||
style_sharing_candidate_cache: StyleSharingCandidateCache,
|
||||
}
|
||||
|
||||
local_data_key!(local_context_key: *mut LocalLayoutContext)
|
||||
|
||||
fn create_or_get_local_context(shared_layout_context: &SharedLayoutContext) -> *mut LocalLayoutContext {
|
||||
let maybe_context = local_context_key.get();
|
||||
|
||||
let context = match maybe_context {
|
||||
None => {
|
||||
let context = box LocalLayoutContext {
|
||||
font_context: FontContext::new(shared_layout_context.font_cache_task.clone()),
|
||||
applicable_declarations_cache: ApplicableDeclarationsCache::new(),
|
||||
style_sharing_candidate_cache: StyleSharingCandidateCache::new(),
|
||||
};
|
||||
local_context_key.replace(Some(unsafe { mem::transmute(context) }));
|
||||
local_context_key.get().unwrap()
|
||||
},
|
||||
Some(context) => context
|
||||
};
|
||||
|
||||
*context
|
||||
}
|
||||
|
||||
pub struct SharedLayoutContext {
|
||||
/// The local image cache.
|
||||
pub image_cache: Arc<Mutex<LocalImageCache>>,
|
||||
|
||||
/// The current screen size.
|
||||
pub screen_size: Size2D<Au>,
|
||||
|
||||
/// A channel up to the constellation.
|
||||
pub constellation_chan: ConstellationChan,
|
||||
|
||||
/// A channel up to the layout task.
|
||||
pub layout_chan: LayoutChan,
|
||||
|
||||
/// Interface to the font cache task.
|
||||
pub font_cache_task: FontCacheTask,
|
||||
|
||||
/// The CSS selector stylist.
|
||||
///
|
||||
/// FIXME(#2604): Make this no longer an unsafe pointer once we have fast `RWArc`s.
|
||||
pub stylist: *const Stylist,
|
||||
|
||||
/// The root node at which we're starting the layout.
|
||||
pub reflow_root: OpaqueNode,
|
||||
|
||||
/// The URL.
|
||||
pub url: Url,
|
||||
|
||||
/// The command line options.
|
||||
pub opts: Opts,
|
||||
|
||||
/// The dirty rectangle, used during display list building.
|
||||
pub dirty: Rect<Au>,
|
||||
}
|
||||
|
||||
pub struct LayoutContext<'a> {
|
||||
pub shared: &'a SharedLayoutContext,
|
||||
cached_local_layout_context: *mut LocalLayoutContext,
|
||||
}
|
||||
|
||||
impl<'a> LayoutContext<'a> {
|
||||
pub fn new(shared_layout_context: &'a SharedLayoutContext) -> LayoutContext<'a> {
|
||||
|
||||
let local_context = create_or_get_local_context(shared_layout_context);
|
||||
|
||||
LayoutContext {
|
||||
shared: shared_layout_context,
|
||||
cached_local_layout_context: local_context,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn font_context<'a>(&'a self) -> &'a mut FontContext {
|
||||
unsafe {
|
||||
let cached_context = &*self.cached_local_layout_context;
|
||||
mem::transmute(&cached_context.font_context)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn applicable_declarations_cache<'a>(&'a self) -> &'a mut ApplicableDeclarationsCache {
|
||||
unsafe {
|
||||
let cached_context = &*self.cached_local_layout_context;
|
||||
mem::transmute(&cached_context.applicable_declarations_cache)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn style_sharing_candidate_cache<'a>(&'a self) -> &'a mut StyleSharingCandidateCache {
|
||||
unsafe {
|
||||
let cached_context = &*self.cached_local_layout_context;
|
||||
mem::transmute(&cached_context.style_sharing_candidate_cache)
|
||||
}
|
||||
}
|
||||
}
|
558
components/layout/css/matching.rs
Normal file
558
components/layout/css/matching.rs
Normal file
|
@ -0,0 +1,558 @@
|
|||
/* 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/. */
|
||||
|
||||
// High-level interface to CSS selector matching.
|
||||
|
||||
use css::node_style::StyledNode;
|
||||
use construct::FlowConstructor;
|
||||
use context::LayoutContext;
|
||||
use extra::LayoutAuxMethods;
|
||||
use util::{LayoutDataAccess, LayoutDataWrapper};
|
||||
use wrapper::{LayoutElement, LayoutNode, PostorderNodeMutTraversal, ThreadSafeLayoutNode};
|
||||
|
||||
use servo_util::atom::Atom;
|
||||
use servo_util::cache::{Cache, LRUCache, SimpleHashCache};
|
||||
use servo_util::namespace::Null;
|
||||
use servo_util::smallvec::{SmallVec, SmallVec16};
|
||||
use servo_util::str::DOMString;
|
||||
use std::mem;
|
||||
use std::hash::{Hash, sip};
|
||||
use std::slice::Items;
|
||||
use style::{After, Before, ComputedValues, DeclarationBlock, Stylist, TElement, TNode, cascade};
|
||||
use sync::Arc;
|
||||
|
||||
pub struct ApplicableDeclarations {
|
||||
pub normal: SmallVec16<DeclarationBlock>,
|
||||
pub before: Vec<DeclarationBlock>,
|
||||
pub after: Vec<DeclarationBlock>,
|
||||
|
||||
/// Whether the `normal` declarations are shareable with other nodes.
|
||||
pub normal_shareable: bool,
|
||||
}
|
||||
|
||||
impl ApplicableDeclarations {
|
||||
pub fn new() -> ApplicableDeclarations {
|
||||
ApplicableDeclarations {
|
||||
normal: SmallVec16::new(),
|
||||
before: Vec::new(),
|
||||
after: Vec::new(),
|
||||
normal_shareable: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.normal = SmallVec16::new();
|
||||
self.before = Vec::new();
|
||||
self.after = Vec::new();
|
||||
self.normal_shareable = false;
|
||||
}
|
||||
}
|
||||
|
||||
#[deriving(Clone)]
|
||||
pub struct ApplicableDeclarationsCacheEntry {
|
||||
pub declarations: Vec<DeclarationBlock>,
|
||||
}
|
||||
|
||||
impl ApplicableDeclarationsCacheEntry {
|
||||
fn new(slice: &[DeclarationBlock]) -> ApplicableDeclarationsCacheEntry {
|
||||
let mut entry_declarations = Vec::new();
|
||||
for declarations in slice.iter() {
|
||||
entry_declarations.push(declarations.clone());
|
||||
}
|
||||
ApplicableDeclarationsCacheEntry {
|
||||
declarations: entry_declarations,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ApplicableDeclarationsCacheEntry {
|
||||
fn eq(&self, other: &ApplicableDeclarationsCacheEntry) -> bool {
|
||||
let this_as_query = ApplicableDeclarationsCacheQuery::new(self.declarations.as_slice());
|
||||
this_as_query.equiv(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for ApplicableDeclarationsCacheEntry {
|
||||
fn hash(&self, state: &mut sip::SipState) {
|
||||
let tmp = ApplicableDeclarationsCacheQuery::new(self.declarations.as_slice());
|
||||
tmp.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
struct ApplicableDeclarationsCacheQuery<'a> {
|
||||
declarations: &'a [DeclarationBlock],
|
||||
}
|
||||
|
||||
impl<'a> ApplicableDeclarationsCacheQuery<'a> {
|
||||
fn new(declarations: &'a [DeclarationBlock]) -> ApplicableDeclarationsCacheQuery<'a> {
|
||||
ApplicableDeclarationsCacheQuery {
|
||||
declarations: declarations,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for lack of `ptr_eq` on Arcs...
|
||||
#[inline]
|
||||
fn arc_ptr_eq<T>(a: &Arc<T>, b: &Arc<T>) -> bool {
|
||||
unsafe {
|
||||
let a: uint = mem::transmute_copy(a);
|
||||
let b: uint = mem::transmute_copy(b);
|
||||
a == b
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Equiv<ApplicableDeclarationsCacheEntry> for ApplicableDeclarationsCacheQuery<'a> {
|
||||
fn equiv(&self, other: &ApplicableDeclarationsCacheEntry) -> bool {
|
||||
if self.declarations.len() != other.declarations.len() {
|
||||
return false
|
||||
}
|
||||
for (this, other) in self.declarations.iter().zip(other.declarations.iter()) {
|
||||
if !arc_ptr_eq(&this.declarations, &other.declarations) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<'a> Hash for ApplicableDeclarationsCacheQuery<'a> {
|
||||
fn hash(&self, state: &mut sip::SipState) {
|
||||
for declaration in self.declarations.iter() {
|
||||
let ptr: uint = unsafe {
|
||||
mem::transmute_copy(declaration)
|
||||
};
|
||||
ptr.hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static APPLICABLE_DECLARATIONS_CACHE_SIZE: uint = 32;
|
||||
|
||||
pub struct ApplicableDeclarationsCache {
|
||||
cache: SimpleHashCache<ApplicableDeclarationsCacheEntry,Arc<ComputedValues>>,
|
||||
}
|
||||
|
||||
impl ApplicableDeclarationsCache {
|
||||
pub fn new() -> ApplicableDeclarationsCache {
|
||||
ApplicableDeclarationsCache {
|
||||
cache: SimpleHashCache::new(APPLICABLE_DECLARATIONS_CACHE_SIZE),
|
||||
}
|
||||
}
|
||||
|
||||
fn find(&self, declarations: &[DeclarationBlock]) -> Option<Arc<ComputedValues>> {
|
||||
match self.cache.find_equiv(&ApplicableDeclarationsCacheQuery::new(declarations)) {
|
||||
None => None,
|
||||
Some(ref values) => Some((*values).clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(&mut self, declarations: &[DeclarationBlock], style: Arc<ComputedValues>) {
|
||||
self.cache.insert(ApplicableDeclarationsCacheEntry::new(declarations), style)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,()>,
|
||||
}
|
||||
|
||||
#[deriving(Clone)]
|
||||
pub struct StyleSharingCandidate {
|
||||
pub style: Arc<ComputedValues>,
|
||||
pub parent_style: Arc<ComputedValues>,
|
||||
pub local_name: Atom,
|
||||
pub class: Option<DOMString>,
|
||||
}
|
||||
|
||||
impl PartialEq for StyleSharingCandidate {
|
||||
fn eq(&self, other: &StyleSharingCandidate) -> bool {
|
||||
arc_ptr_eq(&self.style, &other.style) &&
|
||||
arc_ptr_eq(&self.parent_style, &other.parent_style) &&
|
||||
self.local_name == other.local_name &&
|
||||
self.class == other.class
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
fn new(node: &LayoutNode) -> Option<StyleSharingCandidate> {
|
||||
let parent_node = match node.parent_node() {
|
||||
None => return None,
|
||||
Some(parent_node) => parent_node,
|
||||
};
|
||||
if !parent_node.is_element() {
|
||||
return None
|
||||
}
|
||||
|
||||
let style = unsafe {
|
||||
match *node.borrow_layout_data_unchecked() {
|
||||
None => return None,
|
||||
Some(ref layout_data_ref) => {
|
||||
match layout_data_ref.shared_data.style {
|
||||
None => return None,
|
||||
Some(ref data) => (*data).clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let parent_style = unsafe {
|
||||
match *parent_node.borrow_layout_data_unchecked() {
|
||||
None => return None,
|
||||
Some(ref parent_layout_data_ref) => {
|
||||
match parent_layout_data_ref.shared_data.style {
|
||||
None => return None,
|
||||
Some(ref data) => (*data).clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut style = Some(style);
|
||||
let mut parent_style = Some(parent_style);
|
||||
let element = node.as_element();
|
||||
if element.style_attribute().is_some() {
|
||||
return None
|
||||
}
|
||||
|
||||
Some(StyleSharingCandidate {
|
||||
style: style.take_unwrap(),
|
||||
parent_style: parent_style.take_unwrap(),
|
||||
local_name: element.get_local_name().clone(),
|
||||
class: element.get_attr(&Null, "class")
|
||||
.map(|string| string.to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
fn can_share_style_with(&self, element: &LayoutElement) -> bool {
|
||||
if *element.get_local_name() != self.local_name {
|
||||
return false
|
||||
}
|
||||
match (&self.class, element.get_attr(&Null, "class")) {
|
||||
(&None, Some(_)) | (&Some(_), None) => return false,
|
||||
(&Some(ref this_class), Some(element_class)) if element_class != this_class.as_slice() => {
|
||||
return false
|
||||
}
|
||||
(&Some(_), Some(_)) | (&None, None) => {}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
static STYLE_SHARING_CANDIDATE_CACHE_SIZE: uint = 40;
|
||||
|
||||
impl StyleSharingCandidateCache {
|
||||
pub fn new() -> StyleSharingCandidateCache {
|
||||
StyleSharingCandidateCache {
|
||||
cache: LRUCache::new(STYLE_SHARING_CANDIDATE_CACHE_SIZE),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter<'a>(&'a self) -> Items<'a,(StyleSharingCandidate,())> {
|
||||
self.cache.iter()
|
||||
}
|
||||
|
||||
pub fn insert_if_possible(&mut self, node: &LayoutNode) {
|
||||
match StyleSharingCandidate::new(node) {
|
||||
None => {}
|
||||
Some(candidate) => self.cache.insert(candidate, ())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn touch(&mut self, index: uint) {
|
||||
self.cache.touch(index)
|
||||
}
|
||||
}
|
||||
|
||||
/// The results of attempting to share a style.
|
||||
pub enum StyleSharingResult<'ln> {
|
||||
/// We didn't find anybody to share the style with. The boolean indicates whether the style
|
||||
/// is shareable at all.
|
||||
CannotShare(bool),
|
||||
/// The node's style can be shared. The integer specifies the index in the LRU cache that was
|
||||
/// hit.
|
||||
StyleWasShared(uint),
|
||||
}
|
||||
|
||||
pub trait MatchMethods {
|
||||
/// Performs aux initialization, selector matching, cascading, and flow construction
|
||||
/// sequentially.
|
||||
fn recalc_style_for_subtree(&self,
|
||||
stylist: &Stylist,
|
||||
layout_context: &LayoutContext,
|
||||
applicable_declarations: &mut ApplicableDeclarations,
|
||||
parent: Option<LayoutNode>);
|
||||
|
||||
fn match_node(&self,
|
||||
stylist: &Stylist,
|
||||
applicable_declarations: &mut ApplicableDeclarations,
|
||||
shareable: &mut bool);
|
||||
|
||||
/// Attempts to share a style with another node. This method is unsafe because it depends on
|
||||
/// the `style_sharing_candidate_cache` having only live nodes in it, and we have no way to
|
||||
/// guarantee that at the type system level yet.
|
||||
unsafe fn share_style_if_possible(&self,
|
||||
style_sharing_candidate_cache:
|
||||
&mut StyleSharingCandidateCache,
|
||||
parent: Option<LayoutNode>)
|
||||
-> StyleSharingResult;
|
||||
|
||||
unsafe fn cascade_node(&self,
|
||||
parent: Option<LayoutNode>,
|
||||
applicable_declarations: &ApplicableDeclarations,
|
||||
applicable_declarations_cache: &mut ApplicableDeclarationsCache);
|
||||
}
|
||||
|
||||
trait PrivateMatchMethods {
|
||||
fn cascade_node_pseudo_element(&self,
|
||||
parent_style: Option<&Arc<ComputedValues>>,
|
||||
applicable_declarations: &[DeclarationBlock],
|
||||
style: &mut Option<Arc<ComputedValues>>,
|
||||
applicable_declarations_cache: &mut
|
||||
ApplicableDeclarationsCache,
|
||||
shareable: bool);
|
||||
|
||||
fn share_style_with_candidate_if_possible(&self,
|
||||
parent_node: Option<LayoutNode>,
|
||||
candidate: &StyleSharingCandidate)
|
||||
-> Option<Arc<ComputedValues>>;
|
||||
}
|
||||
|
||||
impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
|
||||
fn cascade_node_pseudo_element(&self,
|
||||
parent_style: Option<&Arc<ComputedValues>>,
|
||||
applicable_declarations: &[DeclarationBlock],
|
||||
style: &mut Option<Arc<ComputedValues>>,
|
||||
applicable_declarations_cache: &mut
|
||||
ApplicableDeclarationsCache,
|
||||
shareable: bool) {
|
||||
let this_style;
|
||||
let cacheable;
|
||||
match parent_style {
|
||||
Some(ref parent_style) => {
|
||||
let cache_entry = applicable_declarations_cache.find(applicable_declarations);
|
||||
let cached_computed_values = match cache_entry {
|
||||
None => None,
|
||||
Some(ref style) => Some(&**style),
|
||||
};
|
||||
let (the_style, is_cacheable) = cascade(applicable_declarations,
|
||||
shareable,
|
||||
Some(&***parent_style),
|
||||
cached_computed_values);
|
||||
cacheable = is_cacheable;
|
||||
this_style = Arc::new(the_style);
|
||||
}
|
||||
None => {
|
||||
let (the_style, is_cacheable) = cascade(applicable_declarations,
|
||||
shareable,
|
||||
None,
|
||||
None);
|
||||
cacheable = is_cacheable;
|
||||
this_style = Arc::new(the_style);
|
||||
}
|
||||
};
|
||||
|
||||
// Cache the resolved style if it was cacheable.
|
||||
if cacheable {
|
||||
applicable_declarations_cache.insert(applicable_declarations, this_style.clone());
|
||||
}
|
||||
|
||||
*style = Some(this_style);
|
||||
}
|
||||
|
||||
|
||||
fn share_style_with_candidate_if_possible(&self,
|
||||
parent_node: Option<LayoutNode>,
|
||||
candidate: &StyleSharingCandidate)
|
||||
-> Option<Arc<ComputedValues>> {
|
||||
assert!(self.is_element());
|
||||
|
||||
let parent_node = match parent_node {
|
||||
Some(ref parent_node) if parent_node.is_element() => parent_node,
|
||||
Some(_) | None => return None,
|
||||
};
|
||||
|
||||
let parent_layout_data: &Option<LayoutDataWrapper> = unsafe {
|
||||
mem::transmute(parent_node.borrow_layout_data_unchecked())
|
||||
};
|
||||
match parent_layout_data {
|
||||
&Some(ref parent_layout_data_ref) => {
|
||||
// Check parent style.
|
||||
let parent_style = parent_layout_data_ref.shared_data.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.as_element()) {
|
||||
return None
|
||||
}
|
||||
|
||||
return Some(candidate.style.clone())
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ln> MatchMethods for LayoutNode<'ln> {
|
||||
fn match_node(&self,
|
||||
stylist: &Stylist,
|
||||
applicable_declarations: &mut ApplicableDeclarations,
|
||||
shareable: &mut bool) {
|
||||
let style_attribute = self.as_element().style_attribute().as_ref();
|
||||
|
||||
applicable_declarations.normal_shareable =
|
||||
stylist.push_applicable_declarations(self,
|
||||
style_attribute,
|
||||
None,
|
||||
&mut applicable_declarations.normal);
|
||||
stylist.push_applicable_declarations(self,
|
||||
None,
|
||||
Some(Before),
|
||||
&mut applicable_declarations.before);
|
||||
stylist.push_applicable_declarations(self,
|
||||
None,
|
||||
Some(After),
|
||||
&mut applicable_declarations.after);
|
||||
|
||||
*shareable = applicable_declarations.normal_shareable
|
||||
}
|
||||
|
||||
unsafe fn share_style_if_possible(&self,
|
||||
style_sharing_candidate_cache:
|
||||
&mut StyleSharingCandidateCache,
|
||||
parent: Option<LayoutNode>)
|
||||
-> StyleSharingResult {
|
||||
if !self.is_element() {
|
||||
return CannotShare(false)
|
||||
}
|
||||
let ok = {
|
||||
let element = self.as_element();
|
||||
element.style_attribute().is_none() && element.get_attr(&Null, "id").is_none()
|
||||
};
|
||||
if !ok {
|
||||
return CannotShare(false)
|
||||
}
|
||||
|
||||
for (i, &(ref candidate, ())) in style_sharing_candidate_cache.iter().enumerate() {
|
||||
match self.share_style_with_candidate_if_possible(parent.clone(), candidate) {
|
||||
Some(shared_style) => {
|
||||
// Yay, cache hit. Share the style.
|
||||
let mut layout_data_ref = self.mutate_layout_data();
|
||||
layout_data_ref.get_mut_ref().shared_data.style = Some(shared_style);
|
||||
return StyleWasShared(i)
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
CannotShare(true)
|
||||
}
|
||||
|
||||
fn recalc_style_for_subtree(&self,
|
||||
stylist: &Stylist,
|
||||
layout_context: &LayoutContext,
|
||||
applicable_declarations: &mut ApplicableDeclarations,
|
||||
parent: Option<LayoutNode>) {
|
||||
self.initialize_layout_data(layout_context.shared.layout_chan.clone());
|
||||
|
||||
// First, check to see whether we can share a style with someone.
|
||||
let sharing_result = unsafe {
|
||||
self.share_style_if_possible(layout_context.style_sharing_candidate_cache(), parent.clone())
|
||||
};
|
||||
|
||||
// Otherwise, match and cascade selectors.
|
||||
match sharing_result {
|
||||
CannotShare(mut shareable) => {
|
||||
if self.is_element() {
|
||||
self.match_node(stylist, applicable_declarations, &mut shareable)
|
||||
}
|
||||
|
||||
unsafe {
|
||||
self.cascade_node(parent,
|
||||
applicable_declarations,
|
||||
layout_context.applicable_declarations_cache())
|
||||
}
|
||||
|
||||
applicable_declarations.clear();
|
||||
|
||||
// Add ourselves to the LRU cache.
|
||||
if shareable {
|
||||
layout_context.style_sharing_candidate_cache().insert_if_possible(self)
|
||||
}
|
||||
}
|
||||
StyleWasShared(index) => layout_context.style_sharing_candidate_cache().touch(index),
|
||||
}
|
||||
|
||||
for kid in self.children() {
|
||||
kid.recalc_style_for_subtree(stylist,
|
||||
layout_context,
|
||||
applicable_declarations,
|
||||
Some(self.clone()))
|
||||
}
|
||||
|
||||
// Construct flows.
|
||||
let layout_node = ThreadSafeLayoutNode::new(self);
|
||||
let mut flow_constructor = FlowConstructor::new(layout_context);
|
||||
flow_constructor.process(&layout_node);
|
||||
}
|
||||
|
||||
unsafe fn cascade_node(&self,
|
||||
parent: Option<LayoutNode>,
|
||||
applicable_declarations: &ApplicableDeclarations,
|
||||
applicable_declarations_cache: &mut ApplicableDeclarationsCache) {
|
||||
// Get our parent's style. This must be unsafe so that we don't touch the parent's
|
||||
// borrow flags.
|
||||
//
|
||||
// FIXME(pcwalton): Isolate this unsafety into the `wrapper` module to allow
|
||||
// enforced safe, race-free access to the parent style.
|
||||
let parent_style = match parent {
|
||||
None => None,
|
||||
Some(parent_node) => {
|
||||
let parent_layout_data = parent_node.borrow_layout_data_unchecked();
|
||||
match *parent_layout_data {
|
||||
None => fail!("no parent data?!"),
|
||||
Some(ref parent_layout_data) => {
|
||||
match parent_layout_data.shared_data.style {
|
||||
None => fail!("parent hasn't been styled yet?!"),
|
||||
Some(ref style) => Some(style),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut layout_data_ref = self.mutate_layout_data();
|
||||
match &mut *layout_data_ref {
|
||||
&None => fail!("no layout data"),
|
||||
&Some(ref mut layout_data) => {
|
||||
self.cascade_node_pseudo_element(parent_style,
|
||||
applicable_declarations.normal.as_slice(),
|
||||
&mut layout_data.shared_data.style,
|
||||
applicable_declarations_cache,
|
||||
applicable_declarations.normal_shareable);
|
||||
if applicable_declarations.before.len() > 0 {
|
||||
self.cascade_node_pseudo_element(Some(layout_data.shared_data.style.get_ref()),
|
||||
applicable_declarations.before.as_slice(),
|
||||
&mut layout_data.data.before_style,
|
||||
applicable_declarations_cache,
|
||||
false);
|
||||
}
|
||||
if applicable_declarations.after.len() > 0 {
|
||||
self.cascade_node_pseudo_element(Some(layout_data.shared_data.style.get_ref()),
|
||||
applicable_declarations.after.as_slice(),
|
||||
&mut layout_data.data.after_style,
|
||||
applicable_declarations_cache,
|
||||
false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
30
components/layout/css/node_style.rs
Normal file
30
components/layout/css/node_style.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
/* 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/. */
|
||||
|
||||
// Style retrieval from DOM elements.
|
||||
|
||||
use css::node_util::NodeUtil;
|
||||
use incremental::RestyleDamage;
|
||||
use wrapper::ThreadSafeLayoutNode;
|
||||
|
||||
use style::ComputedValues;
|
||||
use sync::Arc;
|
||||
|
||||
/// Node mixin providing `style` method that returns a `NodeStyle`
|
||||
pub trait StyledNode {
|
||||
fn style<'a>(&'a self) -> &'a Arc<ComputedValues>;
|
||||
fn restyle_damage(&self) -> RestyleDamage;
|
||||
}
|
||||
|
||||
impl<'ln> StyledNode for ThreadSafeLayoutNode<'ln> {
|
||||
#[inline]
|
||||
fn style<'a>(&'a self) -> &'a Arc<ComputedValues> {
|
||||
self.get_css_select_results()
|
||||
}
|
||||
|
||||
fn restyle_damage(&self) -> RestyleDamage {
|
||||
self.get_restyle_damage()
|
||||
}
|
||||
}
|
||||
|
90
components/layout/css/node_util.rs
Normal file
90
components/layout/css/node_util.rs
Normal file
|
@ -0,0 +1,90 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use incremental::RestyleDamage;
|
||||
use util::LayoutDataAccess;
|
||||
use wrapper::{TLayoutNode, ThreadSafeLayoutNode};
|
||||
use wrapper::{After, AfterBlock, Before, BeforeBlock, Normal};
|
||||
use std::mem;
|
||||
use style::ComputedValues;
|
||||
use sync::Arc;
|
||||
|
||||
pub trait NodeUtil {
|
||||
fn get_css_select_results<'a>(&'a self) -> &'a Arc<ComputedValues>;
|
||||
fn have_css_select_results(&self) -> bool;
|
||||
|
||||
fn get_restyle_damage(&self) -> RestyleDamage;
|
||||
fn set_restyle_damage(&self, damage: RestyleDamage);
|
||||
}
|
||||
|
||||
impl<'ln> NodeUtil for ThreadSafeLayoutNode<'ln> {
|
||||
/// Returns the style results for the given node. If CSS selector
|
||||
/// matching has not yet been performed, fails.
|
||||
#[inline]
|
||||
fn get_css_select_results<'a>(&'a self) -> &'a Arc<ComputedValues> {
|
||||
unsafe {
|
||||
let layout_data_ref = self.borrow_layout_data();
|
||||
match self.get_pseudo_element_type() {
|
||||
Before | BeforeBlock => {
|
||||
mem::transmute(layout_data_ref.as_ref()
|
||||
.unwrap()
|
||||
.data
|
||||
.before_style
|
||||
.as_ref()
|
||||
.unwrap())
|
||||
}
|
||||
After | AfterBlock => {
|
||||
mem::transmute(layout_data_ref.as_ref()
|
||||
.unwrap()
|
||||
.data
|
||||
.after_style
|
||||
.as_ref()
|
||||
.unwrap())
|
||||
}
|
||||
Normal => {
|
||||
mem::transmute(layout_data_ref.as_ref()
|
||||
.unwrap()
|
||||
.shared_data
|
||||
.style
|
||||
.as_ref()
|
||||
.unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Does this node have a computed style yet?
|
||||
fn have_css_select_results(&self) -> bool {
|
||||
let layout_data_ref = self.borrow_layout_data();
|
||||
layout_data_ref.get_ref().shared_data.style.is_some()
|
||||
}
|
||||
|
||||
/// Get the description of how to account for recent style changes.
|
||||
/// This is a simple bitfield and fine to copy by value.
|
||||
fn get_restyle_damage(&self) -> RestyleDamage {
|
||||
// For DOM elements, if we haven't computed damage yet, assume the worst.
|
||||
// Other nodes don't have styles.
|
||||
let default = if self.node_is_element() {
|
||||
RestyleDamage::all()
|
||||
} else {
|
||||
RestyleDamage::empty()
|
||||
};
|
||||
|
||||
let layout_data_ref = self.borrow_layout_data();
|
||||
layout_data_ref
|
||||
.get_ref()
|
||||
.data
|
||||
.restyle_damage
|
||||
.unwrap_or(default)
|
||||
}
|
||||
|
||||
/// Set the restyle damage field.
|
||||
fn set_restyle_damage(&self, damage: RestyleDamage) {
|
||||
let mut layout_data_ref = self.mutate_layout_data();
|
||||
match &mut *layout_data_ref {
|
||||
&Some(ref mut layout_data) => layout_data.data.restyle_damage = Some(damage),
|
||||
_ => fail!("no layout data for this node"),
|
||||
}
|
||||
}
|
||||
}
|
44
components/layout/extra.rs
Normal file
44
components/layout/extra.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
/* 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 for managing the layout data in the DOM.
|
||||
|
||||
use util::{PrivateLayoutData, LayoutDataAccess, LayoutDataWrapper};
|
||||
use wrapper::LayoutNode;
|
||||
use script::dom::node::SharedLayoutData;
|
||||
use script::layout_interface::LayoutChan;
|
||||
|
||||
/// Functionality useful for querying the layout-specific data on DOM nodes.
|
||||
pub trait LayoutAuxMethods {
|
||||
fn initialize_layout_data(&self, chan: LayoutChan);
|
||||
fn initialize_style_for_subtree(&self, chan: LayoutChan);
|
||||
}
|
||||
|
||||
impl<'ln> LayoutAuxMethods for LayoutNode<'ln> {
|
||||
/// Resets layout data and styles for the node.
|
||||
///
|
||||
/// FIXME(pcwalton): Do this as part of fragment building instead of in a traversal.
|
||||
fn initialize_layout_data(&self, chan: LayoutChan) {
|
||||
let mut layout_data_ref = self.mutate_layout_data();
|
||||
match *layout_data_ref {
|
||||
None => {
|
||||
*layout_data_ref = Some(LayoutDataWrapper {
|
||||
chan: Some(chan),
|
||||
shared_data: SharedLayoutData { style: None },
|
||||
data: box PrivateLayoutData::new(),
|
||||
});
|
||||
}
|
||||
Some(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets layout data and styles for a Node tree.
|
||||
///
|
||||
/// FIXME(pcwalton): Do this as part of fragment building instead of in a traversal.
|
||||
fn initialize_style_for_subtree(&self, chan: LayoutChan) {
|
||||
for n in self.traverse_preorder() {
|
||||
n.initialize_layout_data(chan.clone());
|
||||
}
|
||||
}
|
||||
}
|
439
components/layout/floats.rs
Normal file
439
components/layout/floats.rs
Normal file
|
@ -0,0 +1,439 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use servo_util::geometry::{Au, max, min};
|
||||
use servo_util::logical_geometry::WritingMode;
|
||||
use servo_util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize};
|
||||
use std::i32;
|
||||
use std::fmt;
|
||||
use style::computed_values::float;
|
||||
use sync::Arc;
|
||||
|
||||
/// The kind of float: left or right.
|
||||
#[deriving(Clone, Encodable)]
|
||||
pub enum FloatKind {
|
||||
FloatLeft,
|
||||
FloatRight
|
||||
}
|
||||
|
||||
impl FloatKind {
|
||||
pub fn from_property(property: float::T) -> FloatKind {
|
||||
match property {
|
||||
float::none => fail!("can't create a float type from an unfloated property"),
|
||||
float::left => FloatLeft,
|
||||
float::right => FloatRight,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The kind of clearance: left, right, or both.
|
||||
pub enum ClearType {
|
||||
ClearLeft,
|
||||
ClearRight,
|
||||
ClearBoth,
|
||||
}
|
||||
|
||||
/// Information about a single float.
|
||||
#[deriving(Clone)]
|
||||
struct Float {
|
||||
/// The boundaries of this float.
|
||||
bounds: LogicalRect<Au>,
|
||||
/// The kind of float: left or right.
|
||||
kind: FloatKind,
|
||||
}
|
||||
|
||||
impl fmt::Show for Float {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "bounds={} kind={:?}", self.bounds, self.kind)
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about the floats next to a flow.
|
||||
///
|
||||
/// FIXME(pcwalton): When we have fast `MutexArc`s, try removing `#[deriving(Clone)]` and wrap in a
|
||||
/// mutex.
|
||||
#[deriving(Clone)]
|
||||
struct FloatList {
|
||||
/// Information about each of the floats here.
|
||||
floats: Vec<Float>,
|
||||
/// Cached copy of the maximum block-start offset of the float.
|
||||
max_block_start: Au,
|
||||
}
|
||||
|
||||
impl FloatList {
|
||||
fn new() -> FloatList {
|
||||
FloatList {
|
||||
floats: vec!(),
|
||||
max_block_start: Au(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Show for FloatList {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "max_block_start={} floats={:?}", self.max_block_start, self.floats)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps a `FloatList` to avoid allocation in the common case of no floats.
|
||||
///
|
||||
/// FIXME(pcwalton): When we have fast `MutexArc`s, try removing `CowArc` and use a mutex instead.
|
||||
#[deriving(Clone)]
|
||||
struct FloatListRef {
|
||||
list: Option<Arc<FloatList>>,
|
||||
}
|
||||
|
||||
impl FloatListRef {
|
||||
fn new() -> FloatListRef {
|
||||
FloatListRef {
|
||||
list: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the list is allocated and false otherwise. If false, there are guaranteed
|
||||
/// not to be any floats.
|
||||
fn is_present(&self) -> bool {
|
||||
self.list.is_some()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get<'a>(&'a self) -> Option<&'a FloatList> {
|
||||
match self.list {
|
||||
None => None,
|
||||
Some(ref list) => Some(&**list),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(experimental)]
|
||||
#[inline]
|
||||
fn get_mut<'a>(&'a mut self) -> &'a mut FloatList {
|
||||
if self.list.is_none() {
|
||||
self.list = Some(Arc::new(FloatList::new()))
|
||||
}
|
||||
self.list.as_mut().unwrap().make_unique()
|
||||
}
|
||||
}
|
||||
|
||||
/// All the information necessary to place a float.
|
||||
pub struct PlacementInfo {
|
||||
/// The dimensions of the float.
|
||||
pub size: LogicalSize<Au>,
|
||||
/// The minimum block-start of the float, as determined by earlier elements.
|
||||
pub ceiling: Au,
|
||||
/// The maximum inline-end position of the float, generally determined by the containing block.
|
||||
pub max_inline_size: Au,
|
||||
/// The kind of float.
|
||||
pub kind: FloatKind
|
||||
}
|
||||
|
||||
impl fmt::Show for PlacementInfo {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "size={} ceiling={} max_inline_size={} kind={:?}", self.size, self.ceiling, self.max_inline_size, self.kind)
|
||||
}
|
||||
}
|
||||
|
||||
fn range_intersect(block_start_1: Au, block_end_1: Au, block_start_2: Au, block_end_2: Au) -> (Au, Au) {
|
||||
(max(block_start_1, block_start_2), min(block_end_1, block_end_2))
|
||||
}
|
||||
|
||||
/// Encapsulates information about floats. This is optimized to avoid allocation if there are
|
||||
/// no floats, and to avoid copying when translating the list of floats downward.
|
||||
#[deriving(Clone)]
|
||||
pub struct Floats {
|
||||
/// The list of floats.
|
||||
list: FloatListRef,
|
||||
/// The offset of the flow relative to the first float.
|
||||
offset: LogicalSize<Au>,
|
||||
pub writing_mode: WritingMode,
|
||||
}
|
||||
|
||||
impl fmt::Show for Floats {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.list.get() {
|
||||
None => {
|
||||
write!(f, "[empty]")
|
||||
}
|
||||
Some(list) => {
|
||||
write!(f, "offset={} floats={}", self.offset, list)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Floats {
|
||||
/// Creates a new `Floats` object.
|
||||
pub fn new(writing_mode: WritingMode) -> Floats {
|
||||
Floats {
|
||||
list: FloatListRef::new(),
|
||||
offset: LogicalSize::zero(writing_mode),
|
||||
writing_mode: writing_mode,
|
||||
}
|
||||
}
|
||||
|
||||
/// Adjusts the recorded offset of the flow relative to the first float.
|
||||
pub fn translate(&mut self, delta: LogicalSize<Au>) {
|
||||
self.offset = self.offset + delta
|
||||
}
|
||||
|
||||
/// Returns the position of the last float in flow coordinates.
|
||||
pub fn last_float_pos(&self) -> Option<LogicalPoint<Au>> {
|
||||
match self.list.get() {
|
||||
None => None,
|
||||
Some(list) => {
|
||||
match list.floats.last() {
|
||||
None => None,
|
||||
Some(float) => Some(float.bounds.start + self.offset),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a rectangle that encloses the region from block-start to block-start + block-size, with inline-size small
|
||||
/// enough that it doesn't collide with any floats. max_x is the x-coordinate beyond which
|
||||
/// floats have no effect. (Generally this is the containing block inline-size.)
|
||||
pub fn available_rect(&self, block_start: Au, block_size: Au, max_x: Au) -> Option<LogicalRect<Au>> {
|
||||
let list = match self.list.get() {
|
||||
None => return None,
|
||||
Some(list) => list,
|
||||
};
|
||||
|
||||
let block_start = block_start - self.offset.block;
|
||||
|
||||
debug!("available_rect: trying to find space at {}", block_start);
|
||||
|
||||
// Relevant dimensions for the inline-end-most inline-start float
|
||||
let mut max_inline_start = Au(0) - self.offset.inline;
|
||||
let mut l_block_start = None;
|
||||
let mut l_block_end = None;
|
||||
// Relevant dimensions for the inline-start-most inline-end float
|
||||
let mut min_inline_end = max_x - self.offset.inline;
|
||||
let mut r_block_start = None;
|
||||
let mut r_block_end = None;
|
||||
|
||||
// Find the float collisions for the given vertical range.
|
||||
for float in list.floats.iter() {
|
||||
debug!("available_rect: Checking for collision against float");
|
||||
let float_pos = float.bounds.start;
|
||||
let float_size = float.bounds.size;
|
||||
|
||||
debug!("float_pos: {}, float_size: {}", float_pos, float_size);
|
||||
match float.kind {
|
||||
FloatLeft if float_pos.i + float_size.inline > max_inline_start &&
|
||||
float_pos.b + float_size.block > block_start && float_pos.b < block_start + block_size => {
|
||||
max_inline_start = float_pos.i + float_size.inline;
|
||||
|
||||
l_block_start = Some(float_pos.b);
|
||||
l_block_end = Some(float_pos.b + float_size.block);
|
||||
|
||||
debug!("available_rect: collision with inline_start float: new max_inline_start is {}",
|
||||
max_inline_start);
|
||||
}
|
||||
FloatRight if float_pos.i < min_inline_end &&
|
||||
float_pos.b + float_size.block > block_start && float_pos.b < block_start + block_size => {
|
||||
min_inline_end = float_pos.i;
|
||||
|
||||
r_block_start = Some(float_pos.b);
|
||||
r_block_end = Some(float_pos.b + float_size.block);
|
||||
debug!("available_rect: collision with inline_end float: new min_inline_end is {}",
|
||||
min_inline_end);
|
||||
}
|
||||
FloatLeft | FloatRight => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Extend the vertical range of the rectangle to the closest floats.
|
||||
// If there are floats on both sides, take the intersection of the
|
||||
// two areas. Also make sure we never return a block-start smaller than the
|
||||
// given upper bound.
|
||||
let (block_start, block_end) = match (r_block_start, r_block_end, l_block_start, l_block_end) {
|
||||
(Some(r_block_start), Some(r_block_end), Some(l_block_start), Some(l_block_end)) =>
|
||||
range_intersect(max(block_start, r_block_start), r_block_end, max(block_start, l_block_start), l_block_end),
|
||||
|
||||
(None, None, Some(l_block_start), Some(l_block_end)) => (max(block_start, l_block_start), l_block_end),
|
||||
(Some(r_block_start), Some(r_block_end), None, None) => (max(block_start, r_block_start), r_block_end),
|
||||
(None, None, None, None) => return None,
|
||||
_ => fail!("Reached unreachable state when computing float area")
|
||||
};
|
||||
|
||||
// FIXME(eatkinson): This assertion is too strong and fails in some cases. It is OK to
|
||||
// return negative inline-sizes since we check against that inline-end away, but we should still
|
||||
// undersrtand why they occur and add a stronger assertion here.
|
||||
// assert!(max_inline-start < min_inline-end);
|
||||
|
||||
assert!(block_start <= block_end, "Float position error");
|
||||
|
||||
Some(LogicalRect::new(
|
||||
self.writing_mode, max_inline_start + self.offset.inline, block_start + self.offset.block,
|
||||
min_inline_end - max_inline_start, block_end - block_start
|
||||
))
|
||||
}
|
||||
|
||||
/// Adds a new float to the list.
|
||||
pub fn add_float(&mut self, info: &PlacementInfo) {
|
||||
let new_info;
|
||||
{
|
||||
let list = self.list.get_mut();
|
||||
new_info = PlacementInfo {
|
||||
size: info.size,
|
||||
ceiling: max(info.ceiling, list.max_block_start + self.offset.block),
|
||||
max_inline_size: info.max_inline_size,
|
||||
kind: info.kind
|
||||
}
|
||||
}
|
||||
|
||||
debug!("add_float: added float with info {:?}", new_info);
|
||||
|
||||
let new_float = Float {
|
||||
bounds: LogicalRect::from_point_size(
|
||||
self.writing_mode,
|
||||
self.place_between_floats(&new_info).start - self.offset,
|
||||
info.size,
|
||||
),
|
||||
kind: info.kind
|
||||
};
|
||||
|
||||
let list = self.list.get_mut();
|
||||
list.floats.push(new_float);
|
||||
list.max_block_start = max(list.max_block_start, new_float.bounds.start.b);
|
||||
}
|
||||
|
||||
/// Given the block-start 3 sides of the rectangle, finds the largest block-size that will result in the
|
||||
/// rectangle not colliding with any floats. Returns None if that block-size is infinite.
|
||||
fn max_block_size_for_bounds(&self, inline_start: Au, block_start: Au, inline_size: Au) -> Option<Au> {
|
||||
let list = match self.list.get() {
|
||||
None => return None,
|
||||
Some(list) => list,
|
||||
};
|
||||
|
||||
let block_start = block_start - self.offset.block;
|
||||
let inline_start = inline_start - self.offset.inline;
|
||||
let mut max_block_size = None;
|
||||
|
||||
for float in list.floats.iter() {
|
||||
if float.bounds.start.b + float.bounds.size.block > block_start &&
|
||||
float.bounds.start.i + float.bounds.size.inline > inline_start &&
|
||||
float.bounds.start.i < inline_start + inline_size {
|
||||
let new_y = float.bounds.start.b;
|
||||
max_block_size = Some(min(max_block_size.unwrap_or(new_y), new_y));
|
||||
}
|
||||
}
|
||||
|
||||
max_block_size.map(|h| h + self.offset.block)
|
||||
}
|
||||
|
||||
/// Given placement information, finds the closest place a fragment can be positioned without
|
||||
/// colliding with any floats.
|
||||
pub fn place_between_floats(&self, info: &PlacementInfo) -> LogicalRect<Au> {
|
||||
debug!("place_between_floats: Placing object with {}", info.size);
|
||||
|
||||
// If no floats, use this fast path.
|
||||
if !self.list.is_present() {
|
||||
match info.kind {
|
||||
FloatLeft => {
|
||||
return LogicalRect::new(
|
||||
self.writing_mode,
|
||||
Au(0),
|
||||
info.ceiling,
|
||||
info.max_inline_size,
|
||||
Au(i32::MAX))
|
||||
}
|
||||
FloatRight => {
|
||||
return LogicalRect::new(
|
||||
self.writing_mode,
|
||||
info.max_inline_size - info.size.inline,
|
||||
info.ceiling,
|
||||
info.max_inline_size,
|
||||
Au(i32::MAX))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Can't go any higher than previous floats or previous elements in the document.
|
||||
let mut float_b = info.ceiling;
|
||||
loop {
|
||||
let maybe_location = self.available_rect(float_b, info.size.block, info.max_inline_size);
|
||||
debug!("place_float: Got available rect: {:?} for y-pos: {}", maybe_location, float_b);
|
||||
match maybe_location {
|
||||
// If there are no floats blocking us, return the current location
|
||||
// TODO(eatkinson): integrate with overflow
|
||||
None => {
|
||||
return match info.kind {
|
||||
FloatLeft => {
|
||||
LogicalRect::new(
|
||||
self.writing_mode,
|
||||
Au(0),
|
||||
float_b,
|
||||
info.max_inline_size,
|
||||
Au(i32::MAX))
|
||||
}
|
||||
FloatRight => {
|
||||
LogicalRect::new(
|
||||
self.writing_mode,
|
||||
info.max_inline_size - info.size.inline,
|
||||
float_b,
|
||||
info.max_inline_size,
|
||||
Au(i32::MAX))
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(rect) => {
|
||||
assert!(rect.start.b + rect.size.block != float_b,
|
||||
"Non-terminating float placement");
|
||||
|
||||
// Place here if there is enough room
|
||||
if rect.size.inline >= info.size.inline {
|
||||
let block_size = self.max_block_size_for_bounds(rect.start.i,
|
||||
rect.start.b,
|
||||
rect.size.inline);
|
||||
let block_size = block_size.unwrap_or(Au(i32::MAX));
|
||||
return match info.kind {
|
||||
FloatLeft => {
|
||||
LogicalRect::new(
|
||||
self.writing_mode,
|
||||
rect.start.i,
|
||||
float_b,
|
||||
rect.size.inline,
|
||||
block_size)
|
||||
}
|
||||
FloatRight => {
|
||||
LogicalRect::new(
|
||||
self.writing_mode,
|
||||
rect.start.i + rect.size.inline - info.size.inline,
|
||||
float_b,
|
||||
rect.size.inline,
|
||||
block_size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to place at the next-lowest location.
|
||||
// Need to be careful of fencepost errors.
|
||||
float_b = rect.start.b + rect.size.block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clearance(&self, clear: ClearType) -> Au {
|
||||
let list = match self.list.get() {
|
||||
None => return Au(0),
|
||||
Some(list) => list,
|
||||
};
|
||||
|
||||
let mut clearance = Au(0);
|
||||
for float in list.floats.iter() {
|
||||
match (clear, float.kind) {
|
||||
(ClearLeft, FloatLeft) |
|
||||
(ClearRight, FloatRight) |
|
||||
(ClearBoth, _) => {
|
||||
let b = self.offset.block + float.bounds.start.b + float.bounds.size.block;
|
||||
clearance = max(clearance, b);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
clearance
|
||||
}
|
||||
}
|
||||
|
1138
components/layout/flow.rs
Normal file
1138
components/layout/flow.rs
Normal file
File diff suppressed because it is too large
Load diff
296
components/layout/flow_list.rs
Normal file
296
components/layout/flow_list.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 variant of `DList` specialized to store `Flow`s without an extra
|
||||
//! indirection.
|
||||
|
||||
use flow::{Flow, base, mut_base};
|
||||
use flow_ref::FlowRef;
|
||||
|
||||
use std::kinds::marker::ContravariantLifetime;
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use std::raw;
|
||||
|
||||
pub type Link = Option<FlowRef>;
|
||||
|
||||
|
||||
#[allow(raw_pointer_deriving)]
|
||||
pub struct Rawlink<'a> {
|
||||
object: raw::TraitObject,
|
||||
marker: ContravariantLifetime<'a>,
|
||||
}
|
||||
|
||||
/// Doubly-linked list of Flows.
|
||||
///
|
||||
/// The forward links are strong references.
|
||||
/// The backward links are weak references.
|
||||
pub struct FlowList {
|
||||
length: uint,
|
||||
list_head: Link,
|
||||
list_tail: Link,
|
||||
}
|
||||
|
||||
/// Double-ended FlowList iterator
|
||||
pub struct FlowListIterator<'a> {
|
||||
head: &'a Link,
|
||||
nelem: uint,
|
||||
}
|
||||
|
||||
/// Double-ended mutable FlowList iterator
|
||||
pub struct MutFlowListIterator<'a> {
|
||||
head: Rawlink<'a>,
|
||||
nelem: uint,
|
||||
}
|
||||
|
||||
impl<'a> Rawlink<'a> {
|
||||
/// Like Option::None for Rawlink
|
||||
pub fn none() -> Rawlink<'static> {
|
||||
Rawlink {
|
||||
object: raw::TraitObject {
|
||||
vtable: ptr::mut_null(),
|
||||
data: ptr::mut_null(),
|
||||
},
|
||||
marker: ContravariantLifetime,
|
||||
}
|
||||
}
|
||||
|
||||
/// Like Option::Some for Rawlink
|
||||
pub fn some(n: &Flow) -> Rawlink {
|
||||
unsafe {
|
||||
Rawlink {
|
||||
object: mem::transmute::<&Flow, raw::TraitObject>(n),
|
||||
marker: ContravariantLifetime,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn resolve_mut(&self) -> Option<&'a mut Flow> {
|
||||
if self.object.data.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(mem::transmute_copy::<raw::TraitObject, &mut Flow>(&self.object))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the .prev field on `next`, then return `Some(next)`
|
||||
unsafe fn link_with_prev(mut next: FlowRef, prev: Option<FlowRef>) -> Link {
|
||||
mut_base(next.get_mut()).prev_sibling = prev;
|
||||
Some(next)
|
||||
}
|
||||
|
||||
impl Collection for FlowList {
|
||||
/// O(1)
|
||||
#[inline]
|
||||
fn is_empty(&self) -> bool {
|
||||
self.list_head.is_none()
|
||||
}
|
||||
/// O(1)
|
||||
#[inline]
|
||||
fn len(&self) -> uint {
|
||||
self.length
|
||||
}
|
||||
}
|
||||
|
||||
// This doesn't quite fit the Deque trait because of the need to switch between
|
||||
// &Flow and ~Flow.
|
||||
impl FlowList {
|
||||
/// Provide a reference to the front element, or None if the list is empty
|
||||
#[inline]
|
||||
pub fn front<'a>(&'a self) -> Option<&'a Flow> {
|
||||
self.list_head.as_ref().map(|head| head.get())
|
||||
}
|
||||
|
||||
/// Provide a mutable reference to the front element, or None if the list is empty
|
||||
#[inline]
|
||||
pub unsafe fn front_mut<'a>(&'a mut self) -> Option<&'a mut Flow> {
|
||||
self.list_head.as_mut().map(|head| head.get_mut())
|
||||
}
|
||||
|
||||
/// Provide a reference to the back element, or None if the list is empty
|
||||
#[inline]
|
||||
pub fn back<'a>(&'a self) -> Option<&'a Flow> {
|
||||
match self.list_tail {
|
||||
None => None,
|
||||
Some(ref list_tail) => Some(list_tail.get())
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide a mutable reference to the back element, or None if the list is empty
|
||||
#[inline]
|
||||
pub unsafe fn back_mut<'a>(&'a mut self) -> Option<&'a mut Flow> {
|
||||
// Can't use map() due to error:
|
||||
// lifetime of `tail` is too short to guarantee its contents can be safely reborrowed
|
||||
match self.list_tail {
|
||||
None => None,
|
||||
Some(ref mut tail) => {
|
||||
let x: &mut Flow = tail.get_mut();
|
||||
Some(mem::transmute_copy(&x))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an element first in the list
|
||||
///
|
||||
/// O(1)
|
||||
pub fn push_front(&mut self, mut new_head: FlowRef) {
|
||||
unsafe {
|
||||
match self.list_head {
|
||||
None => {
|
||||
self.list_tail = Some(new_head.clone());
|
||||
self.list_head = link_with_prev(new_head, None);
|
||||
}
|
||||
Some(ref mut head) => {
|
||||
mut_base(new_head.get_mut()).prev_sibling = None;
|
||||
mut_base(head.get_mut()).prev_sibling = Some(new_head.clone());
|
||||
mem::swap(head, &mut new_head);
|
||||
mut_base(head.get_mut()).next_sibling = Some(new_head);
|
||||
}
|
||||
}
|
||||
self.length += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove the first element and return it, or None if the list is empty
|
||||
///
|
||||
/// O(1)
|
||||
pub fn pop_front(&mut self) -> Option<FlowRef> {
|
||||
self.list_head.take().map(|mut front_node| {
|
||||
self.length -= 1;
|
||||
unsafe {
|
||||
match mut_base(front_node.get_mut()).next_sibling.take() {
|
||||
Some(node) => self.list_head = link_with_prev(node, None),
|
||||
None => self.list_tail = None,
|
||||
}
|
||||
}
|
||||
front_node
|
||||
})
|
||||
}
|
||||
|
||||
/// Add an element last in the list
|
||||
///
|
||||
/// O(1)
|
||||
pub fn push_back(&mut self, new_tail: FlowRef) {
|
||||
if self.list_tail.is_none() {
|
||||
return self.push_front(new_tail);
|
||||
}
|
||||
|
||||
let old_tail = self.list_tail.clone();
|
||||
self.list_tail = Some(new_tail.clone());
|
||||
let mut tail = (*old_tail.as_ref().unwrap()).clone();
|
||||
let tail_clone = Some(tail.clone());
|
||||
unsafe {
|
||||
mut_base(tail.get_mut()).next_sibling = link_with_prev(new_tail, tail_clone);
|
||||
}
|
||||
self.length += 1;
|
||||
}
|
||||
|
||||
/// Create an empty list
|
||||
#[inline]
|
||||
pub fn new() -> FlowList {
|
||||
FlowList {
|
||||
list_head: None,
|
||||
list_tail: None,
|
||||
length: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide a forward iterator
|
||||
#[inline]
|
||||
pub fn iter<'a>(&'a self) -> FlowListIterator<'a> {
|
||||
FlowListIterator {
|
||||
nelem: self.len(),
|
||||
head: &self.list_head,
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide a forward iterator with mutable references
|
||||
#[inline]
|
||||
pub fn mut_iter<'a>(&'a mut self) -> MutFlowListIterator<'a> {
|
||||
let len = self.len();
|
||||
let head_raw = match self.list_head {
|
||||
Some(ref mut h) => Rawlink::some(h.get()),
|
||||
None => Rawlink::none(),
|
||||
};
|
||||
MutFlowListIterator {
|
||||
nelem: len,
|
||||
head: head_raw,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe_destructor]
|
||||
impl Drop for FlowList {
|
||||
fn drop(&mut self) {
|
||||
// Dissolve the list in backwards direction
|
||||
// Just dropping the list_head can lead to stack exhaustion
|
||||
// when length is >> 1_000_000
|
||||
let mut tail = mem::replace(&mut self.list_tail, None);
|
||||
loop {
|
||||
let new_tail = match tail {
|
||||
None => break,
|
||||
Some(ref mut prev) => {
|
||||
let prev_base = mut_base(prev.get_mut());
|
||||
prev_base.next_sibling.take();
|
||||
prev_base.prev_sibling.clone()
|
||||
}
|
||||
};
|
||||
tail = new_tail
|
||||
}
|
||||
self.length = 0;
|
||||
self.list_head = None;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator<&'a Flow> for FlowListIterator<'a> {
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<&'a Flow> {
|
||||
if self.nelem == 0 {
|
||||
return None;
|
||||
}
|
||||
self.head.as_ref().map(|head| {
|
||||
let head_base = base(head.get());
|
||||
self.nelem -= 1;
|
||||
self.head = &head_base.next_sibling;
|
||||
let ret: &Flow = head.get();
|
||||
ret
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (uint, Option<uint>) {
|
||||
(self.nelem, Some(self.nelem))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator<&'a mut Flow> for MutFlowListIterator<'a> {
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<&'a mut Flow> {
|
||||
if self.nelem == 0 {
|
||||
return None;
|
||||
}
|
||||
unsafe {
|
||||
self.head.resolve_mut().map(|next| {
|
||||
self.nelem -= 1;
|
||||
self.head = match mut_base(next).next_sibling {
|
||||
Some(ref mut node) => {
|
||||
let x: &mut Flow = node.get_mut();
|
||||
// NOTE: transmute needed here to break the link
|
||||
// between x and next so that it is no longer
|
||||
// borrowed.
|
||||
mem::transmute(Rawlink::some(x))
|
||||
}
|
||||
None => Rawlink::none(),
|
||||
};
|
||||
next
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (uint, Option<uint>) {
|
||||
(self.nelem, Some(self.nelem))
|
||||
}
|
||||
}
|
84
components/layout/flow_ref.rs
Normal file
84
components/layout/flow_ref.rs
Normal file
|
@ -0,0 +1,84 @@
|
|||
/* 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/. */
|
||||
|
||||
/// Reference-counted pointers to flows.
|
||||
///
|
||||
/// Eventually, with dynamically sized types in Rust, much of this code will be superfluous.
|
||||
|
||||
use flow::Flow;
|
||||
use flow;
|
||||
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use std::raw;
|
||||
use std::sync::atomics::SeqCst;
|
||||
|
||||
#[unsafe_no_drop_flag]
|
||||
pub struct FlowRef {
|
||||
object: raw::TraitObject,
|
||||
}
|
||||
|
||||
impl FlowRef {
|
||||
pub fn new(mut flow: Box<Flow>) -> FlowRef {
|
||||
unsafe {
|
||||
let result = {
|
||||
let flow_ref: &mut Flow = flow;
|
||||
let object = mem::transmute::<&mut Flow, raw::TraitObject>(flow_ref);
|
||||
FlowRef { object: object }
|
||||
};
|
||||
mem::forget(flow);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get<'a>(&'a self) -> &'a Flow {
|
||||
unsafe {
|
||||
mem::transmute_copy::<raw::TraitObject, &'a Flow>(&self.object)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut<'a>(&'a mut self) -> &'a mut Flow {
|
||||
unsafe {
|
||||
mem::transmute_copy::<raw::TraitObject, &'a mut Flow>(&self.object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FlowRef {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if self.object.vtable.is_null() {
|
||||
return
|
||||
}
|
||||
if flow::base(self.get()).ref_count().fetch_sub(1, SeqCst) > 1 {
|
||||
return
|
||||
}
|
||||
let flow_ref: FlowRef = mem::replace(self, FlowRef {
|
||||
object: raw::TraitObject {
|
||||
vtable: ptr::mut_null(),
|
||||
data: ptr::mut_null(),
|
||||
}
|
||||
});
|
||||
drop(mem::transmute::<raw::TraitObject, Box<Flow>>(flow_ref.object));
|
||||
mem::forget(flow_ref);
|
||||
self.object.vtable = ptr::mut_null();
|
||||
self.object.data = ptr::mut_null();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for FlowRef {
|
||||
fn clone(&self) -> FlowRef {
|
||||
unsafe {
|
||||
drop(flow::base(self.get()).ref_count().fetch_add(1, SeqCst));
|
||||
FlowRef {
|
||||
object: raw::TraitObject {
|
||||
vtable: self.object.vtable,
|
||||
data: self.object.data,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1597
components/layout/fragment.rs
Normal file
1597
components/layout/fragment.rs
Normal file
File diff suppressed because it is too large
Load diff
78
components/layout/incremental.rs
Normal file
78
components/layout/incremental.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use style::ComputedValues;
|
||||
|
||||
bitflags! {
|
||||
#[doc = "Individual layout actions that may be necessary after restyling."]
|
||||
flags RestyleDamage: int {
|
||||
#[doc = "Repaint the node itself."]
|
||||
#[doc = "Currently unused; need to decide how this propagates."]
|
||||
static Repaint = 0x01,
|
||||
|
||||
#[doc = "Recompute intrinsic inline_sizes (minimum and preferred)."]
|
||||
#[doc = "Propagates down the flow tree because the computation is"]
|
||||
#[doc = "bottom-up."]
|
||||
static BubbleISizes = 0x02,
|
||||
|
||||
#[doc = "Recompute actual inline_sizes and block_sizes."]
|
||||
#[doc = "Propagates up the flow tree because the computation is"]
|
||||
#[doc = "top-down."]
|
||||
static Reflow = 0x04
|
||||
}
|
||||
}
|
||||
|
||||
impl RestyleDamage {
|
||||
/// Elements of self which should also get set on any ancestor flow.
|
||||
pub fn propagate_up(self) -> RestyleDamage {
|
||||
self & Reflow
|
||||
}
|
||||
|
||||
/// Elements of self which should also get set on any child flows.
|
||||
pub fn propagate_down(self) -> RestyleDamage {
|
||||
self & BubbleISizes
|
||||
}
|
||||
}
|
||||
|
||||
// NB: We need the braces inside the RHS due to Rust #8012. This particular
|
||||
// version of this macro might be safe anyway, but we want to avoid silent
|
||||
// breakage on modifications.
|
||||
macro_rules! add_if_not_equal(
|
||||
($old:ident, $new:ident, $damage:ident,
|
||||
[ $($effect:ident),* ], [ $($style_struct_getter:ident.$name:ident),* ]) => ({
|
||||
if $( ($old.$style_struct_getter().$name != $new.$style_struct_getter().$name) )||* {
|
||||
$damage.insert($($effect)|*);
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
pub fn compute_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage {
|
||||
let mut damage = RestyleDamage::empty();
|
||||
|
||||
// This checks every CSS property, as enumerated in
|
||||
// impl<'self> CssComputedStyle<'self>
|
||||
// in src/support/netsurfcss/rust-netsurfcss/netsurfcss.rc.
|
||||
|
||||
// FIXME: We can short-circuit more of this.
|
||||
|
||||
add_if_not_equal!(old, new, damage, [ Repaint ],
|
||||
[ get_color.color, get_background.background_color,
|
||||
get_border.border_top_color, get_border.border_right_color,
|
||||
get_border.border_bottom_color, get_border.border_left_color ]);
|
||||
|
||||
add_if_not_equal!(old, new, damage, [ Repaint, BubbleISizes, Reflow ],
|
||||
[ get_border.border_top_width, get_border.border_right_width,
|
||||
get_border.border_bottom_width, get_border.border_left_width,
|
||||
get_margin.margin_top, get_margin.margin_right,
|
||||
get_margin.margin_bottom, get_margin.margin_left,
|
||||
get_padding.padding_top, get_padding.padding_right,
|
||||
get_padding.padding_bottom, get_padding.padding_left,
|
||||
get_box.position, get_box.width, get_box.height, get_box.float, get_box.display,
|
||||
get_font.font_family, get_font.font_size, get_font.font_style, get_font.font_weight,
|
||||
get_inheritedtext.text_align, get_text.text_decoration, get_inheritedbox.line_height ]);
|
||||
|
||||
// FIXME: test somehow that we checked every CSS property
|
||||
|
||||
damage
|
||||
}
|
1170
components/layout/inline.rs
Normal file
1170
components/layout/inline.rs
Normal file
File diff suppressed because it is too large
Load diff
126
components/layout/layout_debug.rs
Normal file
126
components/layout/layout_debug.rs
Normal file
|
@ -0,0 +1,126 @@
|
|||
/* 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/. */
|
||||
|
||||
//! Supports writing a trace file created during each layout scope
|
||||
//! that can be viewed by an external tool to make layout debugging easier.
|
||||
|
||||
#![macro_escape]
|
||||
|
||||
use flow_ref::FlowRef;
|
||||
use serialize::json;
|
||||
use std::cell::RefCell;
|
||||
use std::io::File;
|
||||
use std::sync::atomics::{AtomicUint, SeqCst, INIT_ATOMIC_UINT};
|
||||
|
||||
local_data_key!(state_key: RefCell<State>)
|
||||
|
||||
static mut DEBUG_ID_COUNTER: AtomicUint = INIT_ATOMIC_UINT;
|
||||
|
||||
pub struct Scope;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! layout_debug_scope(
|
||||
($($arg:tt)*) => (
|
||||
if cfg!(not(ndebug)) {
|
||||
layout_debug::Scope::new(format!($($arg)*))
|
||||
} else {
|
||||
layout_debug::Scope
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
#[deriving(Encodable)]
|
||||
struct ScopeData {
|
||||
name: String,
|
||||
pre: String,
|
||||
post: String,
|
||||
children: Vec<Box<ScopeData>>,
|
||||
}
|
||||
|
||||
impl ScopeData {
|
||||
fn new(name: String, pre: String) -> ScopeData {
|
||||
ScopeData {
|
||||
name: name,
|
||||
pre: pre,
|
||||
post: String::new(),
|
||||
children: vec!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct State {
|
||||
flow_root: FlowRef,
|
||||
scope_stack: Vec<Box<ScopeData>>,
|
||||
}
|
||||
|
||||
/// A layout debugging scope. The entire state of the flow tree
|
||||
/// will be output at the beginning and end of this scope.
|
||||
impl Scope {
|
||||
pub fn new(name: String) -> Scope {
|
||||
let maybe_refcell = state_key.get();
|
||||
match maybe_refcell {
|
||||
Some(refcell) => {
|
||||
let mut state = refcell.borrow_mut();
|
||||
let flow_trace = json::encode(&state.flow_root.get());
|
||||
let data = box ScopeData::new(name, flow_trace);
|
||||
state.scope_stack.push(data);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
Scope
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(ndebug))]
|
||||
impl Drop for Scope {
|
||||
fn drop(&mut self) {
|
||||
let maybe_refcell = state_key.get();
|
||||
match maybe_refcell {
|
||||
Some(refcell) => {
|
||||
let mut state = refcell.borrow_mut();
|
||||
let mut current_scope = state.scope_stack.pop().unwrap();
|
||||
current_scope.post = json::encode(&state.flow_root.get());
|
||||
let previous_scope = state.scope_stack.mut_last().unwrap();
|
||||
previous_scope.children.push(current_scope);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a unique ID. This is used for items such as Fragment
|
||||
/// which are often reallocated but represent essentially the
|
||||
/// same data.
|
||||
pub fn generate_unique_debug_id() -> uint {
|
||||
unsafe { DEBUG_ID_COUNTER.fetch_add(1, SeqCst) }
|
||||
}
|
||||
|
||||
/// Begin a layout debug trace. If this has not been called,
|
||||
/// creating debug scopes has no effect.
|
||||
pub fn begin_trace(flow_root: FlowRef) {
|
||||
assert!(state_key.get().is_none());
|
||||
|
||||
let flow_trace = json::encode(&flow_root.get());
|
||||
let state = State {
|
||||
scope_stack: vec![box ScopeData::new("root".to_string(), flow_trace)],
|
||||
flow_root: flow_root,
|
||||
};
|
||||
state_key.replace(Some(RefCell::new(state)));
|
||||
}
|
||||
|
||||
/// End the debug layout trace. This will write the layout
|
||||
/// trace to disk in the current directory. The output
|
||||
/// file can then be viewed with an external tool.
|
||||
pub fn end_trace() {
|
||||
let task_state_cell = state_key.replace(None).unwrap();
|
||||
let mut task_state = task_state_cell.borrow_mut();
|
||||
assert!(task_state.scope_stack.len() == 1);
|
||||
let mut root_scope = task_state.scope_stack.pop().unwrap();
|
||||
root_scope.post = json::encode(&task_state.flow_root.get());
|
||||
|
||||
let result = json::encode(&root_scope);
|
||||
let path = Path::new("layout_trace.json");
|
||||
let mut file = File::create(&path).unwrap();
|
||||
file.write_str(result.as_slice()).unwrap();
|
||||
}
|
1020
components/layout/layout_task.rs
Normal file
1020
components/layout/layout_task.rs
Normal file
File diff suppressed because it is too large
Load diff
68
components/layout/lib.rs
Normal file
68
components/layout/lib.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
/* 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/. */
|
||||
|
||||
#![comment = "The Servo Parallel Browser Project"]
|
||||
#![license = "MPL"]
|
||||
|
||||
#![feature(globs, macro_rules, phase, thread_local, unsafe_destructor)]
|
||||
|
||||
#[phase(plugin, link)]
|
||||
extern crate log;
|
||||
|
||||
extern crate debug;
|
||||
|
||||
extern crate geom;
|
||||
extern crate gfx;
|
||||
extern crate layout_traits;
|
||||
extern crate script;
|
||||
extern crate script_traits;
|
||||
extern crate serialize;
|
||||
extern crate style;
|
||||
#[phase(plugin)]
|
||||
extern crate servo_macros = "macros";
|
||||
extern crate servo_net = "net";
|
||||
extern crate servo_msg = "msg";
|
||||
#[phase(plugin, link)]
|
||||
extern crate servo_util = "util";
|
||||
|
||||
extern crate collections;
|
||||
extern crate green;
|
||||
extern crate libc;
|
||||
extern crate sync;
|
||||
extern crate url;
|
||||
|
||||
// Listed first because of macro definitions
|
||||
pub mod layout_debug;
|
||||
|
||||
pub mod block;
|
||||
pub mod construct;
|
||||
pub mod context;
|
||||
pub mod floats;
|
||||
pub mod flow;
|
||||
pub mod flow_list;
|
||||
pub mod flow_ref;
|
||||
pub mod fragment;
|
||||
pub mod layout_task;
|
||||
pub mod inline;
|
||||
pub mod model;
|
||||
pub mod parallel;
|
||||
pub mod table_wrapper;
|
||||
pub mod table;
|
||||
pub mod table_caption;
|
||||
pub mod table_colgroup;
|
||||
pub mod table_rowgroup;
|
||||
pub mod table_row;
|
||||
pub mod table_cell;
|
||||
pub mod text;
|
||||
pub mod util;
|
||||
pub mod incremental;
|
||||
pub mod wrapper;
|
||||
pub mod extra;
|
||||
|
||||
pub mod css {
|
||||
mod node_util;
|
||||
|
||||
pub mod matching;
|
||||
pub mod node_style;
|
||||
}
|
337
components/layout/model.rs
Normal file
337
components/layout/model.rs
Normal file
|
@ -0,0 +1,337 @@
|
|||
/* 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/. */
|
||||
|
||||
//! Borders, padding, and margins.
|
||||
|
||||
#![deny(unsafe_block)]
|
||||
|
||||
use fragment::Fragment;
|
||||
|
||||
use computed = style::computed_values;
|
||||
use geom::SideOffsets2D;
|
||||
use style::computed_values::{LPA_Auto, LPA_Length, LPA_Percentage, LP_Length, LP_Percentage};
|
||||
use style::ComputedValues;
|
||||
use servo_util::geometry::Au;
|
||||
use servo_util::geometry;
|
||||
use servo_util::logical_geometry::LogicalMargin;
|
||||
use std::fmt;
|
||||
|
||||
/// A collapsible margin. See CSS 2.1 § 8.3.1.
|
||||
pub struct AdjoiningMargins {
|
||||
/// The value of the greatest positive margin.
|
||||
pub most_positive: Au,
|
||||
|
||||
/// The actual value (not the absolute value) of the negative margin with the largest absolute
|
||||
/// value. Since this is not the absolute value, this is always zero or negative.
|
||||
pub most_negative: Au,
|
||||
}
|
||||
|
||||
impl AdjoiningMargins {
|
||||
pub fn new() -> AdjoiningMargins {
|
||||
AdjoiningMargins {
|
||||
most_positive: Au(0),
|
||||
most_negative: Au(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_margin(margin_value: Au) -> AdjoiningMargins {
|
||||
if margin_value >= Au(0) {
|
||||
AdjoiningMargins {
|
||||
most_positive: margin_value,
|
||||
most_negative: Au(0),
|
||||
}
|
||||
} else {
|
||||
AdjoiningMargins {
|
||||
most_positive: Au(0),
|
||||
most_negative: margin_value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn union(&mut self, other: AdjoiningMargins) {
|
||||
self.most_positive = geometry::max(self.most_positive, other.most_positive);
|
||||
self.most_negative = geometry::min(self.most_negative, other.most_negative)
|
||||
}
|
||||
|
||||
pub fn collapse(&self) -> Au {
|
||||
self.most_positive + self.most_negative
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the block-start and block-end margins of a flow with collapsible margins. See CSS 2.1 § 8.3.1.
|
||||
pub enum CollapsibleMargins {
|
||||
/// Margins may not collapse with this flow.
|
||||
NoCollapsibleMargins(Au, Au),
|
||||
|
||||
/// Both the block-start and block-end margins (specified here in that order) may collapse, but the
|
||||
/// margins do not collapse through this flow.
|
||||
MarginsCollapse(AdjoiningMargins, AdjoiningMargins),
|
||||
|
||||
/// Margins collapse *through* this flow. This means, essentially, that the flow doesn’t
|
||||
/// have any border, padding, or out-of-flow (floating or positioned) content
|
||||
MarginsCollapseThrough(AdjoiningMargins),
|
||||
}
|
||||
|
||||
impl CollapsibleMargins {
|
||||
pub fn new() -> CollapsibleMargins {
|
||||
NoCollapsibleMargins(Au(0), Au(0))
|
||||
}
|
||||
}
|
||||
|
||||
enum FinalMarginState {
|
||||
MarginsCollapseThroughFinalMarginState,
|
||||
BottomMarginCollapsesFinalMarginState,
|
||||
}
|
||||
|
||||
pub struct MarginCollapseInfo {
|
||||
pub state: MarginCollapseState,
|
||||
pub block_start_margin: AdjoiningMargins,
|
||||
pub margin_in: AdjoiningMargins,
|
||||
}
|
||||
|
||||
impl MarginCollapseInfo {
|
||||
/// TODO(#2012, pcwalton): Remove this method once `fragment` is not an `Option`.
|
||||
pub fn new() -> MarginCollapseInfo {
|
||||
MarginCollapseInfo {
|
||||
state: AccumulatingCollapsibleTopMargin,
|
||||
block_start_margin: AdjoiningMargins::new(),
|
||||
margin_in: AdjoiningMargins::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialize_block_start_margin(&mut self,
|
||||
fragment: &Fragment,
|
||||
can_collapse_block_start_margin_with_kids: bool) {
|
||||
if !can_collapse_block_start_margin_with_kids {
|
||||
self.state = AccumulatingMarginIn
|
||||
}
|
||||
|
||||
self.block_start_margin = AdjoiningMargins::from_margin(fragment.margin.block_start)
|
||||
}
|
||||
|
||||
pub fn finish_and_compute_collapsible_margins(mut self,
|
||||
fragment: &Fragment,
|
||||
can_collapse_block_end_margin_with_kids: bool)
|
||||
-> (CollapsibleMargins, Au) {
|
||||
let state = match self.state {
|
||||
AccumulatingCollapsibleTopMargin => {
|
||||
match fragment.style().content_block_size() {
|
||||
LPA_Auto | LPA_Length(Au(0)) | LPA_Percentage(0.) => {
|
||||
match fragment.style().min_block_size() {
|
||||
LP_Length(Au(0)) | LP_Percentage(0.) => {
|
||||
MarginsCollapseThroughFinalMarginState
|
||||
},
|
||||
_ => {
|
||||
// If the fragment has non-zero min-block-size, margins may not
|
||||
// collapse through it.
|
||||
BottomMarginCollapsesFinalMarginState
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
// If the fragment has an explicitly specified block-size, margins may not
|
||||
// collapse through it.
|
||||
BottomMarginCollapsesFinalMarginState
|
||||
}
|
||||
}
|
||||
}
|
||||
AccumulatingMarginIn => BottomMarginCollapsesFinalMarginState,
|
||||
};
|
||||
|
||||
// Different logic is needed here depending on whether this flow can collapse its block-end
|
||||
// margin with its children.
|
||||
let block_end_margin = fragment.margin.block_end;
|
||||
if !can_collapse_block_end_margin_with_kids {
|
||||
match state {
|
||||
MarginsCollapseThroughFinalMarginState => {
|
||||
let advance = self.block_start_margin.collapse();
|
||||
self.margin_in.union(AdjoiningMargins::from_margin(block_end_margin));
|
||||
(MarginsCollapse(self.block_start_margin, self.margin_in), advance)
|
||||
}
|
||||
BottomMarginCollapsesFinalMarginState => {
|
||||
let advance = self.margin_in.collapse();
|
||||
self.margin_in.union(AdjoiningMargins::from_margin(block_end_margin));
|
||||
(MarginsCollapse(self.block_start_margin, self.margin_in), advance)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match state {
|
||||
MarginsCollapseThroughFinalMarginState => {
|
||||
self.block_start_margin.union(AdjoiningMargins::from_margin(block_end_margin));
|
||||
(MarginsCollapseThrough(self.block_start_margin), Au(0))
|
||||
}
|
||||
BottomMarginCollapsesFinalMarginState => {
|
||||
self.margin_in.union(AdjoiningMargins::from_margin(block_end_margin));
|
||||
(MarginsCollapse(self.block_start_margin, self.margin_in), Au(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_float_ceiling(&mut self) -> Au {
|
||||
match self.state {
|
||||
AccumulatingCollapsibleTopMargin => self.block_start_margin.collapse(),
|
||||
AccumulatingMarginIn => self.margin_in.collapse(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the child's potentially collapsible block-start margin to the current margin state and
|
||||
/// advances the Y offset by the appropriate amount to handle that margin. Returns the amount
|
||||
/// that should be added to the Y offset during block layout.
|
||||
pub fn advance_block_start_margin(&mut self, child_collapsible_margins: &CollapsibleMargins) -> Au {
|
||||
match (self.state, *child_collapsible_margins) {
|
||||
(AccumulatingCollapsibleTopMargin, NoCollapsibleMargins(block_start, _)) => {
|
||||
self.state = AccumulatingMarginIn;
|
||||
block_start
|
||||
}
|
||||
(AccumulatingCollapsibleTopMargin, MarginsCollapse(block_start, _)) => {
|
||||
self.block_start_margin.union(block_start);
|
||||
self.state = AccumulatingMarginIn;
|
||||
Au(0)
|
||||
}
|
||||
(AccumulatingMarginIn, NoCollapsibleMargins(block_start, _)) => {
|
||||
let previous_margin_value = self.margin_in.collapse();
|
||||
self.margin_in = AdjoiningMargins::new();
|
||||
previous_margin_value + block_start
|
||||
}
|
||||
(AccumulatingMarginIn, MarginsCollapse(block_start, _)) => {
|
||||
self.margin_in.union(block_start);
|
||||
let margin_value = self.margin_in.collapse();
|
||||
self.margin_in = AdjoiningMargins::new();
|
||||
margin_value
|
||||
}
|
||||
(_, MarginsCollapseThrough(_)) => {
|
||||
// For now, we ignore this; this will be handled by `advance_block-end_margin` below.
|
||||
Au(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the child's potentially collapsible block-end margin to the current margin state and
|
||||
/// advances the Y offset by the appropriate amount to handle that margin. Returns the amount
|
||||
/// that should be added to the Y offset during block layout.
|
||||
pub fn advance_block_end_margin(&mut self, child_collapsible_margins: &CollapsibleMargins) -> Au {
|
||||
match (self.state, *child_collapsible_margins) {
|
||||
(AccumulatingCollapsibleTopMargin, NoCollapsibleMargins(..)) |
|
||||
(AccumulatingCollapsibleTopMargin, MarginsCollapse(..)) => {
|
||||
// Can't happen because the state will have been replaced with
|
||||
// `AccumulatingMarginIn` above.
|
||||
fail!("should not be accumulating collapsible block_start margins anymore!")
|
||||
}
|
||||
(AccumulatingCollapsibleTopMargin, MarginsCollapseThrough(margin)) => {
|
||||
self.block_start_margin.union(margin);
|
||||
Au(0)
|
||||
}
|
||||
(AccumulatingMarginIn, NoCollapsibleMargins(_, block_end)) => {
|
||||
assert_eq!(self.margin_in.most_positive, Au(0));
|
||||
assert_eq!(self.margin_in.most_negative, Au(0));
|
||||
block_end
|
||||
}
|
||||
(AccumulatingMarginIn, MarginsCollapse(_, block_end)) |
|
||||
(AccumulatingMarginIn, MarginsCollapseThrough(block_end)) => {
|
||||
self.margin_in.union(block_end);
|
||||
Au(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum MarginCollapseState {
|
||||
AccumulatingCollapsibleTopMargin,
|
||||
AccumulatingMarginIn,
|
||||
}
|
||||
|
||||
/// Intrinsic inline-sizes, which consist of minimum and preferred.
|
||||
#[deriving(Encodable)]
|
||||
pub struct IntrinsicISizes {
|
||||
/// The *minimum inline-size* of the content.
|
||||
pub minimum_inline_size: Au,
|
||||
/// The *preferred inline-size* of the content.
|
||||
pub preferred_inline_size: Au,
|
||||
/// The estimated sum of borders, padding, and margins. Some calculations use this information
|
||||
/// when computing intrinsic inline-sizes.
|
||||
pub surround_inline_size: Au,
|
||||
}
|
||||
|
||||
impl fmt::Show for IntrinsicISizes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "min={}, pref={}, surr={}", self.minimum_inline_size, self.preferred_inline_size, self.surround_inline_size)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntrinsicISizes {
|
||||
pub fn new() -> IntrinsicISizes {
|
||||
IntrinsicISizes {
|
||||
minimum_inline_size: Au(0),
|
||||
preferred_inline_size: Au(0),
|
||||
surround_inline_size: Au(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn total_minimum_inline_size(&self) -> Au {
|
||||
self.minimum_inline_size + self.surround_inline_size
|
||||
}
|
||||
|
||||
pub fn total_preferred_inline_size(&self) -> Au {
|
||||
self.preferred_inline_size + self.surround_inline_size
|
||||
}
|
||||
}
|
||||
|
||||
/// Useful helper data type when computing values for blocks and positioned elements.
|
||||
pub enum MaybeAuto {
|
||||
Auto,
|
||||
Specified(Au),
|
||||
}
|
||||
|
||||
impl MaybeAuto {
|
||||
#[inline]
|
||||
pub fn from_style(length: computed::LengthOrPercentageOrAuto, containing_length: Au)
|
||||
-> MaybeAuto {
|
||||
match length {
|
||||
computed::LPA_Auto => Auto,
|
||||
computed::LPA_Percentage(percent) => Specified(containing_length.scale_by(percent)),
|
||||
computed::LPA_Length(length) => Specified(length)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn specified_or_default(&self, default: Au) -> Au {
|
||||
match *self {
|
||||
Auto => default,
|
||||
Specified(value) => value,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn specified_or_zero(&self) -> Au {
|
||||
self.specified_or_default(Au::new(0))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn specified_or_none(length: computed::LengthOrPercentageOrNone, containing_length: Au) -> Option<Au> {
|
||||
match length {
|
||||
computed::LPN_None => None,
|
||||
computed::LPN_Percentage(percent) => Some(containing_length.scale_by(percent)),
|
||||
computed::LPN_Length(length) => Some(length),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn specified(length: computed::LengthOrPercentage, containing_length: Au) -> Au {
|
||||
match length {
|
||||
computed::LP_Length(length) => length,
|
||||
computed::LP_Percentage(p) => containing_length.scale_by(p)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn padding_from_style(style: &ComputedValues, containing_block_inline_size: Au)
|
||||
-> LogicalMargin<Au> {
|
||||
let padding_style = style.get_padding();
|
||||
LogicalMargin::from_physical(style.writing_mode, SideOffsets2D::new(
|
||||
specified(padding_style.padding_top, containing_block_inline_size),
|
||||
specified(padding_style.padding_right, containing_block_inline_size),
|
||||
specified(padding_style.padding_bottom, containing_block_inline_size),
|
||||
specified(padding_style.padding_left, containing_block_inline_size)))
|
||||
}
|
||||
|
561
components/layout/parallel.rs
Normal file
561
components/layout/parallel.rs
Normal file
|
@ -0,0 +1,561 @@
|
|||
/* 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/. */
|
||||
|
||||
//! Implements parallel traversals over the DOM and flow trees.
|
||||
//!
|
||||
//! This code is highly unsafe. Keep this file small and easy to audit.
|
||||
|
||||
use css::matching::{ApplicableDeclarations, CannotShare, MatchMethods, StyleWasShared};
|
||||
use construct::FlowConstructor;
|
||||
use context::{LayoutContext, SharedLayoutContext};
|
||||
use extra::LayoutAuxMethods;
|
||||
use flow::{Flow, MutableFlowUtils, PreorderFlowTraversal, PostorderFlowTraversal};
|
||||
use flow;
|
||||
use flow_ref::FlowRef;
|
||||
use layout_task::{AssignBSizesAndStoreOverflowTraversal, AssignISizesTraversal};
|
||||
use layout_task::{BubbleISizesTraversal};
|
||||
use util::{LayoutDataAccess, LayoutDataWrapper, OpaqueNodeMethods};
|
||||
use wrapper::{layout_node_to_unsafe_layout_node, layout_node_from_unsafe_layout_node, LayoutNode, PostorderNodeMutTraversal};
|
||||
use wrapper::{ThreadSafeLayoutNode, UnsafeLayoutNode};
|
||||
|
||||
use gfx::display_list::OpaqueNode;
|
||||
use servo_util::time::{TimeProfilerChan, profile};
|
||||
use servo_util::time;
|
||||
use servo_util::workqueue::{WorkQueue, WorkUnit, WorkerProxy};
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use std::sync::atomics::{AtomicInt, Relaxed, SeqCst};
|
||||
use style::TNode;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn static_assertion(node: UnsafeLayoutNode) {
|
||||
unsafe {
|
||||
let _: UnsafeFlow = ::std::intrinsics::transmute(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// Vtable + pointer representation of a Flow trait object.
|
||||
pub type UnsafeFlow = (uint, uint);
|
||||
|
||||
fn null_unsafe_flow() -> UnsafeFlow {
|
||||
(0, 0)
|
||||
}
|
||||
|
||||
pub fn owned_flow_to_unsafe_flow(flow: *const FlowRef) -> UnsafeFlow {
|
||||
unsafe {
|
||||
mem::transmute_copy(&*flow)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mut_owned_flow_to_unsafe_flow(flow: *mut FlowRef) -> UnsafeFlow {
|
||||
unsafe {
|
||||
mem::transmute_copy(&*flow)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn borrowed_flow_to_unsafe_flow(flow: &Flow) -> UnsafeFlow {
|
||||
unsafe {
|
||||
mem::transmute_copy(&flow)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mut_borrowed_flow_to_unsafe_flow(flow: &mut Flow) -> UnsafeFlow {
|
||||
unsafe {
|
||||
mem::transmute_copy(&flow)
|
||||
}
|
||||
}
|
||||
|
||||
/// Information that we need stored in each DOM node.
|
||||
pub struct DomParallelInfo {
|
||||
/// The number of children that still need work done.
|
||||
pub children_count: AtomicInt,
|
||||
}
|
||||
|
||||
impl DomParallelInfo {
|
||||
pub fn new() -> DomParallelInfo {
|
||||
DomParallelInfo {
|
||||
children_count: AtomicInt::new(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Information that we need stored in each flow.
|
||||
pub struct FlowParallelInfo {
|
||||
/// The number of children that still need work done.
|
||||
pub children_count: AtomicInt,
|
||||
/// The number of children and absolute descendants that still need work done.
|
||||
pub children_and_absolute_descendant_count: AtomicInt,
|
||||
/// The address of the parent flow.
|
||||
pub parent: UnsafeFlow,
|
||||
}
|
||||
|
||||
impl FlowParallelInfo {
|
||||
pub fn new() -> FlowParallelInfo {
|
||||
FlowParallelInfo {
|
||||
children_count: AtomicInt::new(0),
|
||||
children_and_absolute_descendant_count: AtomicInt::new(0),
|
||||
parent: null_unsafe_flow(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A parallel bottom-up flow traversal.
|
||||
trait ParallelPostorderFlowTraversal : PostorderFlowTraversal {
|
||||
/// Process current flow and potentially traverse its ancestors.
|
||||
///
|
||||
/// If we are the last child that finished processing, recursively process
|
||||
/// our parent. Else, stop.
|
||||
/// Also, stop at the root (obviously :P).
|
||||
///
|
||||
/// Thus, if we start with all the leaves of a tree, we end up traversing
|
||||
/// the whole tree bottom-up because each parent will be processed exactly
|
||||
/// once (by the last child that finishes processing).
|
||||
///
|
||||
/// The only communication between siblings is that they both
|
||||
/// fetch-and-subtract the parent's children count.
|
||||
fn run_parallel(&mut self,
|
||||
mut unsafe_flow: UnsafeFlow,
|
||||
_: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
|
||||
loop {
|
||||
unsafe {
|
||||
// Get a real flow.
|
||||
let flow: &mut FlowRef = mem::transmute(&unsafe_flow);
|
||||
|
||||
// Perform the appropriate traversal.
|
||||
if self.should_process(flow.get_mut()) {
|
||||
self.process(flow.get_mut());
|
||||
}
|
||||
|
||||
let base = flow::mut_base(flow.get_mut());
|
||||
|
||||
// Reset the count of children for the next layout traversal.
|
||||
base.parallel.children_count.store(base.children.len() as int, Relaxed);
|
||||
|
||||
// Possibly enqueue the parent.
|
||||
let unsafe_parent = base.parallel.parent;
|
||||
if unsafe_parent == null_unsafe_flow() {
|
||||
// We're done!
|
||||
break
|
||||
}
|
||||
|
||||
// No, we're not at the root yet. Then are we the last child
|
||||
// of our parent to finish processing? If so, we can continue
|
||||
// on with our parent; otherwise, we've gotta wait.
|
||||
let parent: &mut FlowRef = mem::transmute(&unsafe_parent);
|
||||
let parent_base = flow::mut_base(parent.get_mut());
|
||||
if parent_base.parallel.children_count.fetch_sub(1, SeqCst) == 1 {
|
||||
// We were the last child of our parent. Reflow our parent.
|
||||
unsafe_flow = unsafe_parent
|
||||
} else {
|
||||
// Stop.
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A parallel top-down flow traversal.
|
||||
trait ParallelPreorderFlowTraversal : PreorderFlowTraversal {
|
||||
fn run_parallel(&mut self,
|
||||
unsafe_flow: UnsafeFlow,
|
||||
proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>);
|
||||
|
||||
#[inline(always)]
|
||||
fn run_parallel_helper(&mut self,
|
||||
unsafe_flow: UnsafeFlow,
|
||||
proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>,
|
||||
top_down_func: extern "Rust" fn(UnsafeFlow,
|
||||
&mut WorkerProxy<*const SharedLayoutContext,
|
||||
UnsafeFlow>),
|
||||
bottom_up_func: extern "Rust" fn(UnsafeFlow,
|
||||
&mut WorkerProxy<*const SharedLayoutContext,
|
||||
UnsafeFlow>)) {
|
||||
let mut had_children = false;
|
||||
unsafe {
|
||||
// Get a real flow.
|
||||
let flow: &mut FlowRef = mem::transmute(&unsafe_flow);
|
||||
|
||||
// Perform the appropriate traversal.
|
||||
self.process(flow.get_mut());
|
||||
|
||||
// Possibly enqueue the children.
|
||||
for kid in flow::child_iter(flow.get_mut()) {
|
||||
had_children = true;
|
||||
proxy.push(WorkUnit {
|
||||
fun: top_down_func,
|
||||
data: borrowed_flow_to_unsafe_flow(kid),
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If there were no more children, start assigning block-sizes.
|
||||
if !had_children {
|
||||
bottom_up_func(unsafe_flow, proxy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ParallelPostorderFlowTraversal for BubbleISizesTraversal<'a> {}
|
||||
|
||||
impl<'a> ParallelPreorderFlowTraversal for AssignISizesTraversal<'a> {
|
||||
fn run_parallel(&mut self,
|
||||
unsafe_flow: UnsafeFlow,
|
||||
proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
|
||||
self.run_parallel_helper(unsafe_flow,
|
||||
proxy,
|
||||
assign_inline_sizes,
|
||||
assign_block_sizes_and_store_overflow)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ParallelPostorderFlowTraversal for AssignBSizesAndStoreOverflowTraversal<'a> {}
|
||||
|
||||
fn recalc_style_for_node(unsafe_layout_node: UnsafeLayoutNode,
|
||||
proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeLayoutNode>) {
|
||||
let shared_layout_context = unsafe { &**proxy.user_data() };
|
||||
let layout_context = LayoutContext::new(shared_layout_context);
|
||||
|
||||
// Get a real layout node.
|
||||
let node: LayoutNode = unsafe {
|
||||
layout_node_from_unsafe_layout_node(&unsafe_layout_node)
|
||||
};
|
||||
|
||||
// Initialize layout data.
|
||||
//
|
||||
// FIXME(pcwalton): Stop allocating here. Ideally this should just be done by the HTML
|
||||
// parser.
|
||||
node.initialize_layout_data(layout_context.shared.layout_chan.clone());
|
||||
|
||||
// Get the parent node.
|
||||
let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&node);
|
||||
let parent_opt = if opaque_node == layout_context.shared.reflow_root {
|
||||
None
|
||||
} else {
|
||||
node.parent_node()
|
||||
};
|
||||
|
||||
// First, check to see whether we can share a style with someone.
|
||||
let style_sharing_candidate_cache = layout_context.style_sharing_candidate_cache();
|
||||
let sharing_result = unsafe {
|
||||
node.share_style_if_possible(style_sharing_candidate_cache,
|
||||
parent_opt.clone())
|
||||
};
|
||||
|
||||
// Otherwise, match and cascade selectors.
|
||||
match sharing_result {
|
||||
CannotShare(mut shareable) => {
|
||||
let mut applicable_declarations = ApplicableDeclarations::new();
|
||||
|
||||
if node.is_element() {
|
||||
// Perform the CSS selector matching.
|
||||
let stylist = unsafe { &*layout_context.shared.stylist };
|
||||
node.match_node(stylist, &mut applicable_declarations, &mut shareable);
|
||||
}
|
||||
|
||||
// Perform the CSS cascade.
|
||||
unsafe {
|
||||
node.cascade_node(parent_opt,
|
||||
&applicable_declarations,
|
||||
layout_context.applicable_declarations_cache());
|
||||
}
|
||||
|
||||
// Add ourselves to the LRU cache.
|
||||
if shareable {
|
||||
style_sharing_candidate_cache.insert_if_possible(&node);
|
||||
}
|
||||
}
|
||||
StyleWasShared(index) => style_sharing_candidate_cache.touch(index),
|
||||
}
|
||||
|
||||
// Prepare for flow construction by counting the node's children and storing that count.
|
||||
let mut child_count = 0u;
|
||||
for _ in node.children() {
|
||||
child_count += 1;
|
||||
}
|
||||
if child_count != 0 {
|
||||
let mut layout_data_ref = node.mutate_layout_data();
|
||||
match &mut *layout_data_ref {
|
||||
&Some(ref mut layout_data) => {
|
||||
layout_data.data.parallel.children_count.store(child_count as int, Relaxed)
|
||||
}
|
||||
&None => fail!("no layout data"),
|
||||
}
|
||||
}
|
||||
|
||||
// It's *very* important that this block is in a separate scope to the block above,
|
||||
// to avoid a data race that can occur (github issue #2308). The block above issues
|
||||
// a borrow on the node layout data. That borrow must be dropped before the child
|
||||
// nodes are actually pushed into the work queue. Otherwise, it's possible for a child
|
||||
// node to get into construct_flows() and move up it's parent hierarchy, which can call
|
||||
// borrow on the layout data before it is dropped from the block above.
|
||||
if child_count != 0 {
|
||||
// Enqueue kids.
|
||||
for kid in node.children() {
|
||||
proxy.push(WorkUnit {
|
||||
fun: recalc_style_for_node,
|
||||
data: layout_node_to_unsafe_layout_node(&kid),
|
||||
});
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// If we got here, we're a leaf. Start construction of flows for this node.
|
||||
construct_flows(unsafe_layout_node, &layout_context)
|
||||
}
|
||||
|
||||
fn construct_flows(mut unsafe_layout_node: UnsafeLayoutNode,
|
||||
layout_context: &LayoutContext) {
|
||||
loop {
|
||||
// Get a real layout node.
|
||||
let node: LayoutNode = unsafe {
|
||||
layout_node_from_unsafe_layout_node(&unsafe_layout_node)
|
||||
};
|
||||
|
||||
// Construct flows for this node.
|
||||
{
|
||||
let mut flow_constructor = FlowConstructor::new(layout_context);
|
||||
flow_constructor.process(&ThreadSafeLayoutNode::new(&node));
|
||||
}
|
||||
|
||||
// Reset the count of children for the next traversal.
|
||||
//
|
||||
// FIXME(pcwalton): Use children().len() when the implementation of that is efficient.
|
||||
let mut child_count = 0u;
|
||||
for _ in node.children() {
|
||||
child_count += 1
|
||||
}
|
||||
{
|
||||
let mut layout_data_ref = node.mutate_layout_data();
|
||||
match &mut *layout_data_ref {
|
||||
&Some(ref mut layout_data) => {
|
||||
layout_data.data.parallel.children_count.store(child_count as int, Relaxed)
|
||||
}
|
||||
&None => fail!("no layout data"),
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the reflow root, we're done.
|
||||
let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&node);
|
||||
if layout_context.shared.reflow_root == opaque_node {
|
||||
break
|
||||
}
|
||||
|
||||
// Otherwise, enqueue the parent.
|
||||
match node.parent_node() {
|
||||
Some(parent) => {
|
||||
|
||||
// No, we're not at the root yet. Then are we the last sibling of our parent?
|
||||
// If so, we can continue on with our parent; otherwise, we've gotta wait.
|
||||
unsafe {
|
||||
match *parent.borrow_layout_data_unchecked() {
|
||||
Some(ref parent_layout_data) => {
|
||||
let parent_layout_data: &mut LayoutDataWrapper = mem::transmute(parent_layout_data);
|
||||
if parent_layout_data.data
|
||||
.parallel
|
||||
.children_count
|
||||
.fetch_sub(1, SeqCst) == 1 {
|
||||
// We were the last child of our parent. Construct flows for our
|
||||
// parent.
|
||||
unsafe_layout_node = layout_node_to_unsafe_layout_node(&parent)
|
||||
} else {
|
||||
// Get out of here and find another node to work on.
|
||||
break
|
||||
}
|
||||
}
|
||||
None => fail!("no layout data for parent?!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
None => fail!("no parent and weren't at reflow root?!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assign_inline_sizes(unsafe_flow: UnsafeFlow,
|
||||
proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
|
||||
let shared_layout_context = unsafe { &**proxy.user_data() };
|
||||
let layout_context = LayoutContext::new(shared_layout_context);
|
||||
let mut assign_inline_sizes_traversal = AssignISizesTraversal {
|
||||
layout_context: &layout_context,
|
||||
};
|
||||
assign_inline_sizes_traversal.run_parallel(unsafe_flow, proxy)
|
||||
}
|
||||
|
||||
fn assign_block_sizes_and_store_overflow(unsafe_flow: UnsafeFlow,
|
||||
proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
|
||||
let shared_layout_context = unsafe { &**proxy.user_data() };
|
||||
let layout_context = LayoutContext::new(shared_layout_context);
|
||||
let mut assign_block_sizes_traversal = AssignBSizesAndStoreOverflowTraversal {
|
||||
layout_context: &layout_context,
|
||||
};
|
||||
assign_block_sizes_traversal.run_parallel(unsafe_flow, proxy)
|
||||
}
|
||||
|
||||
fn compute_absolute_position(unsafe_flow: UnsafeFlow,
|
||||
proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
|
||||
let mut had_descendants = false;
|
||||
unsafe {
|
||||
// Get a real flow.
|
||||
let flow: &mut FlowRef = mem::transmute(&unsafe_flow);
|
||||
|
||||
// Compute the absolute position for the flow.
|
||||
flow.get_mut().compute_absolute_position();
|
||||
|
||||
// Count the number of absolutely-positioned children, so that we can subtract it from
|
||||
// from `children_and_absolute_descendant_count` to get the number of real children.
|
||||
let mut absolutely_positioned_child_count = 0u;
|
||||
for kid in flow::child_iter(flow.get_mut()) {
|
||||
if kid.is_absolutely_positioned() {
|
||||
absolutely_positioned_child_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't enqueue absolutely positioned children.
|
||||
drop(flow::mut_base(flow.get_mut()).parallel
|
||||
.children_and_absolute_descendant_count
|
||||
.fetch_sub(absolutely_positioned_child_count as int,
|
||||
SeqCst));
|
||||
|
||||
// Possibly enqueue the children.
|
||||
for kid in flow::child_iter(flow.get_mut()) {
|
||||
if !kid.is_absolutely_positioned() {
|
||||
had_descendants = true;
|
||||
proxy.push(WorkUnit {
|
||||
fun: compute_absolute_position,
|
||||
data: borrowed_flow_to_unsafe_flow(kid),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Possibly enqueue absolute descendants.
|
||||
for absolute_descendant_link in flow::mut_base(flow.get_mut()).abs_descendants.iter() {
|
||||
had_descendants = true;
|
||||
let descendant = absolute_descendant_link;
|
||||
proxy.push(WorkUnit {
|
||||
fun: compute_absolute_position,
|
||||
data: borrowed_flow_to_unsafe_flow(descendant),
|
||||
});
|
||||
}
|
||||
|
||||
// If there were no more descendants, start building the display list.
|
||||
if !had_descendants {
|
||||
build_display_list(mut_owned_flow_to_unsafe_flow(flow),
|
||||
proxy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_display_list(mut unsafe_flow: UnsafeFlow,
|
||||
proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
|
||||
let shared_layout_context = unsafe { &**proxy.user_data() };
|
||||
let layout_context = LayoutContext::new(shared_layout_context);
|
||||
|
||||
loop {
|
||||
unsafe {
|
||||
// Get a real flow.
|
||||
let flow: &mut FlowRef = mem::transmute(&unsafe_flow);
|
||||
|
||||
// Build display lists.
|
||||
flow.get_mut().build_display_list(&layout_context);
|
||||
|
||||
{
|
||||
let base = flow::mut_base(flow.get_mut());
|
||||
|
||||
// Reset the count of children and absolute descendants for the next layout
|
||||
// traversal.
|
||||
let children_and_absolute_descendant_count = base.children.len() +
|
||||
base.abs_descendants.len();
|
||||
base.parallel
|
||||
.children_and_absolute_descendant_count
|
||||
.store(children_and_absolute_descendant_count as int, Relaxed);
|
||||
}
|
||||
|
||||
// Possibly enqueue the parent.
|
||||
let unsafe_parent = if flow.get().is_absolutely_positioned() {
|
||||
match *flow::mut_base(flow.get_mut()).absolute_cb.get() {
|
||||
None => fail!("no absolute containing block for absolutely positioned?!"),
|
||||
Some(ref mut absolute_cb) => {
|
||||
mut_borrowed_flow_to_unsafe_flow(absolute_cb.get_mut())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
flow::mut_base(flow.get_mut()).parallel.parent
|
||||
};
|
||||
if unsafe_parent == null_unsafe_flow() {
|
||||
// We're done!
|
||||
break
|
||||
}
|
||||
|
||||
// No, we're not at the root yet. Then are we the last child
|
||||
// of our parent to finish processing? If so, we can continue
|
||||
// on with our parent; otherwise, we've gotta wait.
|
||||
let parent: &mut FlowRef = mem::transmute(&unsafe_parent);
|
||||
let parent_base = flow::mut_base(parent.get_mut());
|
||||
if parent_base.parallel
|
||||
.children_and_absolute_descendant_count
|
||||
.fetch_sub(1, SeqCst) == 1 {
|
||||
// We were the last child of our parent. Build display lists for our parent.
|
||||
unsafe_flow = unsafe_parent
|
||||
} else {
|
||||
// Stop.
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recalc_style_for_subtree(root_node: &LayoutNode,
|
||||
shared_layout_context: &SharedLayoutContext,
|
||||
queue: &mut WorkQueue<*const SharedLayoutContext,UnsafeLayoutNode>) {
|
||||
queue.data = shared_layout_context as *const _;
|
||||
|
||||
// Enqueue the root node.
|
||||
queue.push(WorkUnit {
|
||||
fun: recalc_style_for_node,
|
||||
data: layout_node_to_unsafe_layout_node(root_node),
|
||||
});
|
||||
|
||||
queue.run();
|
||||
|
||||
queue.data = ptr::null()
|
||||
}
|
||||
|
||||
pub fn traverse_flow_tree_preorder(root: &mut FlowRef,
|
||||
time_profiler_chan: TimeProfilerChan,
|
||||
shared_layout_context: &SharedLayoutContext,
|
||||
queue: &mut WorkQueue<*const SharedLayoutContext,UnsafeFlow>) {
|
||||
queue.data = shared_layout_context as *const _;
|
||||
|
||||
profile(time::LayoutParallelWarmupCategory, time_profiler_chan, || {
|
||||
queue.push(WorkUnit {
|
||||
fun: assign_inline_sizes,
|
||||
data: mut_owned_flow_to_unsafe_flow(root),
|
||||
})
|
||||
});
|
||||
|
||||
queue.run();
|
||||
|
||||
queue.data = ptr::null()
|
||||
}
|
||||
|
||||
pub fn build_display_list_for_subtree(root: &mut FlowRef,
|
||||
time_profiler_chan: TimeProfilerChan,
|
||||
shared_layout_context: &SharedLayoutContext,
|
||||
queue: &mut WorkQueue<*const SharedLayoutContext,UnsafeFlow>) {
|
||||
queue.data = shared_layout_context as *const _;
|
||||
|
||||
profile(time::LayoutParallelWarmupCategory, time_profiler_chan, || {
|
||||
queue.push(WorkUnit {
|
||||
fun: compute_absolute_position,
|
||||
data: mut_owned_flow_to_unsafe_flow(root),
|
||||
})
|
||||
});
|
||||
|
||||
queue.run();
|
||||
|
||||
queue.data = ptr::null()
|
||||
}
|
||||
|
324
components/layout/table.rs
Normal file
324
components/layout/table.rs
Normal file
|
@ -0,0 +1,324 @@
|
|||
/* 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/. */
|
||||
|
||||
//! CSS table formatting contexts.
|
||||
|
||||
#![deny(unsafe_block)]
|
||||
|
||||
use block::{BlockFlow, MarginsMayNotCollapse, ISizeAndMarginsComputer};
|
||||
use block::{ISizeConstraintInput, ISizeConstraintSolution};
|
||||
use construct::FlowConstructor;
|
||||
use context::LayoutContext;
|
||||
use floats::FloatKind;
|
||||
use flow::{TableFlowClass, FlowClass, Flow, ImmutableFlowUtils};
|
||||
use fragment::Fragment;
|
||||
use table_wrapper::{TableLayout, FixedLayout, AutoLayout};
|
||||
use wrapper::ThreadSafeLayoutNode;
|
||||
|
||||
use servo_util::geometry::Au;
|
||||
use servo_util::geometry;
|
||||
use std::fmt;
|
||||
use style::computed_values::table_layout;
|
||||
|
||||
/// A table flow corresponded to the table's internal table fragment under a table wrapper flow.
|
||||
/// The properties `position`, `float`, and `margin-*` are used on the table wrapper fragment,
|
||||
/// not table fragment per CSS 2.1 § 10.5.
|
||||
pub struct TableFlow {
|
||||
pub block_flow: BlockFlow,
|
||||
|
||||
/// Column inline-sizes
|
||||
pub col_inline_sizes: Vec<Au>,
|
||||
|
||||
/// Column min inline-sizes.
|
||||
pub col_min_inline_sizes: Vec<Au>,
|
||||
|
||||
/// Column pref inline-sizes.
|
||||
pub col_pref_inline_sizes: Vec<Au>,
|
||||
|
||||
/// Table-layout property
|
||||
pub table_layout: TableLayout,
|
||||
}
|
||||
|
||||
impl TableFlow {
|
||||
pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode,
|
||||
fragment: Fragment)
|
||||
-> TableFlow {
|
||||
let mut block_flow = BlockFlow::from_node_and_fragment(node, fragment);
|
||||
let table_layout = if block_flow.fragment().style().get_table().table_layout ==
|
||||
table_layout::fixed {
|
||||
FixedLayout
|
||||
} else {
|
||||
AutoLayout
|
||||
};
|
||||
TableFlow {
|
||||
block_flow: block_flow,
|
||||
col_inline_sizes: vec!(),
|
||||
col_min_inline_sizes: vec!(),
|
||||
col_pref_inline_sizes: vec!(),
|
||||
table_layout: table_layout
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_node(constructor: &mut FlowConstructor,
|
||||
node: &ThreadSafeLayoutNode)
|
||||
-> TableFlow {
|
||||
let mut block_flow = BlockFlow::from_node(constructor, node);
|
||||
let table_layout = if block_flow.fragment().style().get_table().table_layout ==
|
||||
table_layout::fixed {
|
||||
FixedLayout
|
||||
} else {
|
||||
AutoLayout
|
||||
};
|
||||
TableFlow {
|
||||
block_flow: block_flow,
|
||||
col_inline_sizes: vec!(),
|
||||
col_min_inline_sizes: vec!(),
|
||||
col_pref_inline_sizes: vec!(),
|
||||
table_layout: table_layout
|
||||
}
|
||||
}
|
||||
|
||||
pub fn float_from_node(constructor: &mut FlowConstructor,
|
||||
node: &ThreadSafeLayoutNode,
|
||||
float_kind: FloatKind)
|
||||
-> TableFlow {
|
||||
let mut block_flow = BlockFlow::float_from_node(constructor, node, float_kind);
|
||||
let table_layout = if block_flow.fragment().style().get_table().table_layout ==
|
||||
table_layout::fixed {
|
||||
FixedLayout
|
||||
} else {
|
||||
AutoLayout
|
||||
};
|
||||
TableFlow {
|
||||
block_flow: block_flow,
|
||||
col_inline_sizes: vec!(),
|
||||
col_min_inline_sizes: vec!(),
|
||||
col_pref_inline_sizes: vec!(),
|
||||
table_layout: table_layout
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the corresponding value of self_inline-sizes if a value of kid_inline-sizes has larger value
|
||||
/// than one of self_inline-sizes.
|
||||
pub fn update_col_inline_sizes(self_inline_sizes: &mut Vec<Au>, kid_inline_sizes: &Vec<Au>) -> Au {
|
||||
let mut sum_inline_sizes = Au(0);
|
||||
let mut kid_inline_sizes_it = kid_inline_sizes.iter();
|
||||
for self_inline_size in self_inline_sizes.mut_iter() {
|
||||
match kid_inline_sizes_it.next() {
|
||||
Some(kid_inline_size) => {
|
||||
if *self_inline_size < *kid_inline_size {
|
||||
*self_inline_size = *kid_inline_size;
|
||||
}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
sum_inline_sizes = sum_inline_sizes + *self_inline_size;
|
||||
}
|
||||
sum_inline_sizes
|
||||
}
|
||||
|
||||
/// Assign block-size for table flow.
|
||||
///
|
||||
/// TODO(#2014, pcwalton): This probably doesn't handle margin collapse right.
|
||||
///
|
||||
/// inline(always) because this is only ever called by in-order or non-in-order top-level
|
||||
/// methods
|
||||
#[inline(always)]
|
||||
fn assign_block_size_table_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
|
||||
self.block_flow.assign_block_size_block_base(layout_context, MarginsMayNotCollapse);
|
||||
}
|
||||
|
||||
pub fn build_display_list_table(&mut self, layout_context: &LayoutContext) {
|
||||
debug!("build_display_list_table: same process as block flow");
|
||||
self.block_flow.build_display_list_block(layout_context);
|
||||
}
|
||||
}
|
||||
|
||||
impl Flow for TableFlow {
|
||||
fn class(&self) -> FlowClass {
|
||||
TableFlowClass
|
||||
}
|
||||
|
||||
fn as_table<'a>(&'a mut self) -> &'a mut TableFlow {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
|
||||
&mut self.block_flow
|
||||
}
|
||||
|
||||
fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> {
|
||||
&mut self.col_inline_sizes
|
||||
}
|
||||
|
||||
fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
|
||||
&self.col_min_inline_sizes
|
||||
}
|
||||
|
||||
fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
|
||||
&self.col_pref_inline_sizes
|
||||
}
|
||||
|
||||
/// The specified column inline-sizes are set from column group and the first row for the fixed
|
||||
/// table layout calculation.
|
||||
/// The maximum min/pref inline-sizes of each column are set from the rows for the automatic
|
||||
/// table layout calculation.
|
||||
fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
|
||||
let mut min_inline_size = Au(0);
|
||||
let mut pref_inline_size = Au(0);
|
||||
let mut did_first_row = false;
|
||||
|
||||
for kid in self.block_flow.base.child_iter() {
|
||||
assert!(kid.is_proper_table_child());
|
||||
|
||||
if kid.is_table_colgroup() {
|
||||
self.col_inline_sizes.push_all(kid.as_table_colgroup().inline_sizes.as_slice());
|
||||
self.col_min_inline_sizes = self.col_inline_sizes.clone();
|
||||
self.col_pref_inline_sizes = self.col_inline_sizes.clone();
|
||||
} else if kid.is_table_rowgroup() || kid.is_table_row() {
|
||||
// read column inline-sizes from table-row-group/table-row, and assign
|
||||
// inline-size=0 for the columns not defined in column-group
|
||||
// FIXME: need to read inline-sizes from either table-header-group OR
|
||||
// first table-row
|
||||
match self.table_layout {
|
||||
FixedLayout => {
|
||||
let kid_col_inline_sizes = kid.col_inline_sizes();
|
||||
if !did_first_row {
|
||||
did_first_row = true;
|
||||
let mut child_inline_sizes = kid_col_inline_sizes.iter();
|
||||
for col_inline_size in self.col_inline_sizes.mut_iter() {
|
||||
match child_inline_sizes.next() {
|
||||
Some(child_inline_size) => {
|
||||
if *col_inline_size == Au::new(0) {
|
||||
*col_inline_size = *child_inline_size;
|
||||
}
|
||||
},
|
||||
None => break
|
||||
}
|
||||
}
|
||||
}
|
||||
let num_child_cols = kid_col_inline_sizes.len();
|
||||
let num_cols = self.col_inline_sizes.len();
|
||||
debug!("table until the previous row has {} column(s) and this row has {} column(s)",
|
||||
num_cols, num_child_cols);
|
||||
for i in range(num_cols, num_child_cols) {
|
||||
self.col_inline_sizes.push((*kid_col_inline_sizes)[i]);
|
||||
}
|
||||
},
|
||||
AutoLayout => {
|
||||
min_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_min_inline_sizes, kid.col_min_inline_sizes());
|
||||
pref_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_pref_inline_sizes, kid.col_pref_inline_sizes());
|
||||
|
||||
// update the number of column inline-sizes from table-rows.
|
||||
let num_cols = self.col_min_inline_sizes.len();
|
||||
let num_child_cols = kid.col_min_inline_sizes().len();
|
||||
debug!("table until the previous row has {} column(s) and this row has {} column(s)",
|
||||
num_cols, num_child_cols);
|
||||
for i in range(num_cols, num_child_cols) {
|
||||
self.col_inline_sizes.push(Au::new(0));
|
||||
let new_kid_min = kid.col_min_inline_sizes()[i];
|
||||
self.col_min_inline_sizes.push( new_kid_min );
|
||||
let new_kid_pref = kid.col_pref_inline_sizes()[i];
|
||||
self.col_pref_inline_sizes.push( new_kid_pref );
|
||||
min_inline_size = min_inline_size + new_kid_min;
|
||||
pref_inline_size = pref_inline_size + new_kid_pref;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size;
|
||||
self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size =
|
||||
geometry::max(min_inline_size, pref_inline_size);
|
||||
}
|
||||
|
||||
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When
|
||||
/// called on this context, the context has had its inline-size set by the parent context.
|
||||
fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
|
||||
debug!("assign_inline_sizes({}): assigning inline_size for flow", "table");
|
||||
|
||||
// The position was set to the containing block by the flow's parent.
|
||||
let containing_block_inline_size = self.block_flow.base.position.size.inline;
|
||||
|
||||
let mut num_unspecified_inline_sizes = 0;
|
||||
let mut total_column_inline_size = Au::new(0);
|
||||
for col_inline_size in self.col_inline_sizes.iter() {
|
||||
if *col_inline_size == Au::new(0) {
|
||||
num_unspecified_inline_sizes += 1;
|
||||
} else {
|
||||
total_column_inline_size = total_column_inline_size.add(col_inline_size);
|
||||
}
|
||||
}
|
||||
|
||||
let inline_size_computer = InternalTable;
|
||||
inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size);
|
||||
|
||||
let inline_start_content_edge = self.block_flow.fragment.border_padding.inline_start;
|
||||
let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end();
|
||||
let content_inline_size = self.block_flow.fragment.border_box.size.inline - padding_and_borders;
|
||||
|
||||
match self.table_layout {
|
||||
FixedLayout => {
|
||||
// In fixed table layout, we distribute extra space among the unspecified columns if there are
|
||||
// any, or among all the columns if all are specified.
|
||||
if (total_column_inline_size < content_inline_size) && (num_unspecified_inline_sizes == 0) {
|
||||
let ratio = content_inline_size.to_f64().unwrap() / total_column_inline_size.to_f64().unwrap();
|
||||
for col_inline_size in self.col_inline_sizes.mut_iter() {
|
||||
*col_inline_size = (*col_inline_size).scale_by(ratio);
|
||||
}
|
||||
} else if num_unspecified_inline_sizes != 0 {
|
||||
let extra_column_inline_size = (content_inline_size - total_column_inline_size) / Au::new(num_unspecified_inline_sizes);
|
||||
for col_inline_size in self.col_inline_sizes.mut_iter() {
|
||||
if *col_inline_size == Au(0) {
|
||||
*col_inline_size = extra_column_inline_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, Some(self.col_inline_sizes.clone()));
|
||||
}
|
||||
|
||||
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
|
||||
debug!("assign_block_size: assigning block_size for table");
|
||||
self.assign_block_size_table_base(ctx);
|
||||
}
|
||||
|
||||
fn compute_absolute_position(&mut self) {
|
||||
self.block_flow.compute_absolute_position()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Show for TableFlow {
|
||||
/// Outputs a debugging string describing this table flow.
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "TableFlow: {}", self.block_flow)
|
||||
}
|
||||
}
|
||||
|
||||
/// Table, TableRowGroup, TableRow, TableCell types.
|
||||
/// Their inline-sizes are calculated in the same way and do not have margins.
|
||||
pub struct InternalTable;
|
||||
|
||||
impl ISizeAndMarginsComputer for InternalTable {
|
||||
/// Compute the used value of inline-size, taking care of min-inline-size and max-inline-size.
|
||||
///
|
||||
/// CSS Section 10.4: Minimum and Maximum inline-sizes
|
||||
fn compute_used_inline_size(&self,
|
||||
block: &mut BlockFlow,
|
||||
ctx: &LayoutContext,
|
||||
parent_flow_inline_size: Au) {
|
||||
let input = self.compute_inline_size_constraint_inputs(block, parent_flow_inline_size, ctx);
|
||||
let solution = self.solve_inline_size_constraints(block, &input);
|
||||
self.set_inline_size_constraint_solutions(block, solution);
|
||||
}
|
||||
|
||||
/// Solve the inline-size and margins constraints for this block flow.
|
||||
fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput)
|
||||
-> ISizeConstraintSolution {
|
||||
ISizeConstraintSolution::new(input.available_inline_size, Au::new(0), Au::new(0))
|
||||
}
|
||||
}
|
73
components/layout/table_caption.rs
Normal file
73
components/layout/table_caption.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
/* 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/. */
|
||||
|
||||
//! CSS table formatting contexts.
|
||||
|
||||
#![deny(unsafe_block)]
|
||||
|
||||
use block::BlockFlow;
|
||||
use construct::FlowConstructor;
|
||||
use context::LayoutContext;
|
||||
use flow::{TableCaptionFlowClass, FlowClass, Flow};
|
||||
use wrapper::ThreadSafeLayoutNode;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// A table formatting context.
|
||||
pub struct TableCaptionFlow {
|
||||
pub block_flow: BlockFlow,
|
||||
}
|
||||
|
||||
impl TableCaptionFlow {
|
||||
pub fn from_node(constructor: &mut FlowConstructor,
|
||||
node: &ThreadSafeLayoutNode)
|
||||
-> TableCaptionFlow {
|
||||
TableCaptionFlow {
|
||||
block_flow: BlockFlow::from_node(constructor, node)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_display_list_table_caption(&mut self, layout_context: &LayoutContext) {
|
||||
debug!("build_display_list_table_caption: same process as block flow");
|
||||
self.block_flow.build_display_list_block(layout_context)
|
||||
}
|
||||
}
|
||||
|
||||
impl Flow for TableCaptionFlow {
|
||||
fn class(&self) -> FlowClass {
|
||||
TableCaptionFlowClass
|
||||
}
|
||||
|
||||
fn as_table_caption<'a>(&'a mut self) -> &'a mut TableCaptionFlow {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
|
||||
&mut self.block_flow
|
||||
}
|
||||
|
||||
fn bubble_inline_sizes(&mut self, ctx: &LayoutContext) {
|
||||
self.block_flow.bubble_inline_sizes(ctx);
|
||||
}
|
||||
|
||||
fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
|
||||
debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_caption");
|
||||
self.block_flow.assign_inline_sizes(ctx);
|
||||
}
|
||||
|
||||
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
|
||||
debug!("assign_block_size: assigning block_size for table_caption");
|
||||
self.block_flow.assign_block_size(ctx);
|
||||
}
|
||||
|
||||
fn compute_absolute_position(&mut self) {
|
||||
self.block_flow.compute_absolute_position()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Show for TableCaptionFlow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "TableCaptionFlow: {}", self.block_flow)
|
||||
}
|
||||
}
|
121
components/layout/table_cell.rs
Normal file
121
components/layout/table_cell.rs
Normal file
|
@ -0,0 +1,121 @@
|
|||
/* 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/. */
|
||||
|
||||
//! CSS table formatting contexts.
|
||||
|
||||
#![deny(unsafe_block)]
|
||||
|
||||
use block::{BlockFlow, MarginsMayNotCollapse, ISizeAndMarginsComputer};
|
||||
use context::LayoutContext;
|
||||
use flow::{TableCellFlowClass, FlowClass, Flow};
|
||||
use fragment::Fragment;
|
||||
use model::{MaybeAuto};
|
||||
use table::InternalTable;
|
||||
use wrapper::ThreadSafeLayoutNode;
|
||||
|
||||
use servo_util::geometry::Au;
|
||||
use std::fmt;
|
||||
|
||||
/// A table formatting context.
|
||||
pub struct TableCellFlow {
|
||||
/// Data common to all flows.
|
||||
pub block_flow: BlockFlow,
|
||||
}
|
||||
|
||||
impl TableCellFlow {
|
||||
pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, fragment: Fragment) -> TableCellFlow {
|
||||
TableCellFlow {
|
||||
block_flow: BlockFlow::from_node_and_fragment(node, fragment)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fragment<'a>(&'a mut self) -> &'a Fragment {
|
||||
&self.block_flow.fragment
|
||||
}
|
||||
|
||||
pub fn mut_fragment<'a>(&'a mut self) -> &'a mut Fragment {
|
||||
&mut self.block_flow.fragment
|
||||
}
|
||||
|
||||
/// Assign block-size for table-cell flow.
|
||||
///
|
||||
/// TODO(#2015, pcwalton): This doesn't handle floats right.
|
||||
///
|
||||
/// inline(always) because this is only ever called by in-order or non-in-order top-level
|
||||
/// methods
|
||||
#[inline(always)]
|
||||
fn assign_block_size_table_cell_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
|
||||
self.block_flow.assign_block_size_block_base(layout_context, MarginsMayNotCollapse)
|
||||
}
|
||||
|
||||
pub fn build_display_list_table_cell(&mut self, layout_context: &LayoutContext) {
|
||||
debug!("build_display_list_table: same process as block flow");
|
||||
self.block_flow.build_display_list_block(layout_context)
|
||||
}
|
||||
}
|
||||
|
||||
impl Flow for TableCellFlow {
|
||||
fn class(&self) -> FlowClass {
|
||||
TableCellFlowClass
|
||||
}
|
||||
|
||||
fn as_table_cell<'a>(&'a mut self) -> &'a mut TableCellFlow {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
|
||||
&mut self.block_flow
|
||||
}
|
||||
|
||||
/// Minimum/preferred inline-sizes set by this function are used in automatic table layout calculation.
|
||||
fn bubble_inline_sizes(&mut self, ctx: &LayoutContext) {
|
||||
self.block_flow.bubble_inline_sizes(ctx);
|
||||
let specified_inline_size = MaybeAuto::from_style(self.block_flow.fragment.style().content_inline_size(),
|
||||
Au::new(0)).specified_or_zero();
|
||||
if self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size < specified_inline_size {
|
||||
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = specified_inline_size;
|
||||
}
|
||||
if self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size <
|
||||
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size {
|
||||
self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size =
|
||||
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size;
|
||||
}
|
||||
}
|
||||
|
||||
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When
|
||||
/// called on this context, the context has had its inline-size set by the parent table row.
|
||||
fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
|
||||
debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_cell");
|
||||
|
||||
// The position was set to the column inline-size by the parent flow, table row flow.
|
||||
let containing_block_inline_size = self.block_flow.base.position.size.inline;
|
||||
|
||||
let inline_size_computer = InternalTable;
|
||||
inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size);
|
||||
|
||||
let inline_start_content_edge = self.block_flow.fragment.border_box.start.i +
|
||||
self.block_flow.fragment.border_padding.inline_start;
|
||||
let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end();
|
||||
let content_inline_size = self.block_flow.fragment.border_box.size.inline - padding_and_borders;
|
||||
|
||||
self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge,
|
||||
content_inline_size,
|
||||
None);
|
||||
}
|
||||
|
||||
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
|
||||
debug!("assign_block_size: assigning block_size for table_cell");
|
||||
self.assign_block_size_table_cell_base(ctx);
|
||||
}
|
||||
|
||||
fn compute_absolute_position(&mut self) {
|
||||
self.block_flow.compute_absolute_position()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Show for TableCellFlow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "TableCellFlow: {}", self.block_flow)
|
||||
}
|
||||
}
|
88
components/layout/table_colgroup.rs
Normal file
88
components/layout/table_colgroup.rs
Normal file
|
@ -0,0 +1,88 @@
|
|||
/* 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/. */
|
||||
|
||||
//! CSS table formatting contexts.
|
||||
|
||||
#![deny(unsafe_block)]
|
||||
|
||||
use context::LayoutContext;
|
||||
use flow::{BaseFlow, TableColGroupFlowClass, FlowClass, Flow};
|
||||
use fragment::{Fragment, TableColumnFragment};
|
||||
use model::{MaybeAuto};
|
||||
use wrapper::ThreadSafeLayoutNode;
|
||||
|
||||
use servo_util::geometry::Au;
|
||||
use std::fmt;
|
||||
|
||||
/// A table formatting context.
|
||||
pub struct TableColGroupFlow {
|
||||
/// Data common to all flows.
|
||||
pub base: BaseFlow,
|
||||
|
||||
/// The associated fragment.
|
||||
pub fragment: Option<Fragment>,
|
||||
|
||||
/// The table column fragments
|
||||
pub cols: Vec<Fragment>,
|
||||
|
||||
/// The specified inline-sizes of table columns
|
||||
pub inline_sizes: Vec<Au>,
|
||||
}
|
||||
|
||||
impl TableColGroupFlow {
|
||||
pub fn from_node_and_fragments(node: &ThreadSafeLayoutNode,
|
||||
fragment: Fragment,
|
||||
fragments: Vec<Fragment>) -> TableColGroupFlow {
|
||||
TableColGroupFlow {
|
||||
base: BaseFlow::new((*node).clone()),
|
||||
fragment: Some(fragment),
|
||||
cols: fragments,
|
||||
inline_sizes: vec!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Flow for TableColGroupFlow {
|
||||
fn class(&self) -> FlowClass {
|
||||
TableColGroupFlowClass
|
||||
}
|
||||
|
||||
fn as_table_colgroup<'a>(&'a mut self) -> &'a mut TableColGroupFlow {
|
||||
self
|
||||
}
|
||||
|
||||
fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
|
||||
for fragment in self.cols.iter() {
|
||||
// get the specified value from inline-size property
|
||||
let inline_size = MaybeAuto::from_style(fragment.style().content_inline_size(),
|
||||
Au::new(0)).specified_or_zero();
|
||||
|
||||
let span: int = match fragment.specific {
|
||||
TableColumnFragment(col_fragment) => col_fragment.span.unwrap_or(1),
|
||||
_ => fail!("Other fragment come out in TableColGroupFlow. {:?}", fragment.specific)
|
||||
};
|
||||
for _ in range(0, span) {
|
||||
self.inline_sizes.push(inline_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Table column inline-sizes are assigned in table flow and propagated to table row or rowgroup flow.
|
||||
/// Therefore, table colgroup flow does not need to assign its inline-size.
|
||||
fn assign_inline_sizes(&mut self, _ctx: &LayoutContext) {
|
||||
}
|
||||
|
||||
/// Table column do not have block-size.
|
||||
fn assign_block_size(&mut self, _ctx: &LayoutContext) {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Show for TableColGroupFlow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.fragment {
|
||||
Some(ref rb) => write!(f, "TableColGroupFlow: {}", rb),
|
||||
None => write!(f, "TableColGroupFlow"),
|
||||
}
|
||||
}
|
||||
}
|
225
components/layout/table_row.rs
Normal file
225
components/layout/table_row.rs
Normal file
|
@ -0,0 +1,225 @@
|
|||
/* 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/. */
|
||||
|
||||
//! CSS table formatting contexts.
|
||||
|
||||
#![deny(unsafe_block)]
|
||||
|
||||
use block::BlockFlow;
|
||||
use block::ISizeAndMarginsComputer;
|
||||
use construct::FlowConstructor;
|
||||
use context::LayoutContext;
|
||||
use flow::{TableRowFlowClass, FlowClass, Flow, ImmutableFlowUtils};
|
||||
use flow;
|
||||
use fragment::Fragment;
|
||||
use table::InternalTable;
|
||||
use model::{MaybeAuto, Specified, Auto};
|
||||
use wrapper::ThreadSafeLayoutNode;
|
||||
|
||||
use servo_util::geometry::Au;
|
||||
use servo_util::geometry;
|
||||
use std::fmt;
|
||||
|
||||
/// A table formatting context.
|
||||
pub struct TableRowFlow {
|
||||
pub block_flow: BlockFlow,
|
||||
|
||||
/// Column inline-sizes.
|
||||
pub col_inline_sizes: Vec<Au>,
|
||||
|
||||
/// Column min inline-sizes.
|
||||
pub col_min_inline_sizes: Vec<Au>,
|
||||
|
||||
/// Column pref inline-sizes.
|
||||
pub col_pref_inline_sizes: Vec<Au>,
|
||||
}
|
||||
|
||||
impl TableRowFlow {
|
||||
pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode,
|
||||
fragment: Fragment)
|
||||
-> TableRowFlow {
|
||||
TableRowFlow {
|
||||
block_flow: BlockFlow::from_node_and_fragment(node, fragment),
|
||||
col_inline_sizes: vec!(),
|
||||
col_min_inline_sizes: vec!(),
|
||||
col_pref_inline_sizes: vec!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_node(constructor: &mut FlowConstructor,
|
||||
node: &ThreadSafeLayoutNode)
|
||||
-> TableRowFlow {
|
||||
TableRowFlow {
|
||||
block_flow: BlockFlow::from_node(constructor, node),
|
||||
col_inline_sizes: vec!(),
|
||||
col_min_inline_sizes: vec!(),
|
||||
col_pref_inline_sizes: vec!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fragment<'a>(&'a mut self) -> &'a Fragment {
|
||||
&self.block_flow.fragment
|
||||
}
|
||||
|
||||
fn initialize_offsets(&mut self) -> (Au, Au, Au) {
|
||||
// TODO: If border-collapse: collapse, block-start_offset, block-end_offset, and inline-start_offset
|
||||
// should be updated. Currently, they are set as Au(0).
|
||||
(Au(0), Au(0), Au(0))
|
||||
}
|
||||
|
||||
/// Assign block-size for table-row flow.
|
||||
///
|
||||
/// TODO(pcwalton): This doesn't handle floats and positioned elements right.
|
||||
///
|
||||
/// inline(always) because this is only ever called by in-order or non-in-order top-level
|
||||
/// methods
|
||||
#[inline(always)]
|
||||
fn assign_block_size_table_row_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
|
||||
let (block_start_offset, _, _) = self.initialize_offsets();
|
||||
|
||||
let /* mut */ cur_y = block_start_offset;
|
||||
|
||||
// Per CSS 2.1 § 17.5.3, find max_y = max( computed `block-size`, minimum block-size of all cells )
|
||||
let mut max_y = Au::new(0);
|
||||
for kid in self.block_flow.base.child_iter() {
|
||||
kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
|
||||
|
||||
{
|
||||
let child_fragment = kid.as_table_cell().fragment();
|
||||
// TODO: Percentage block-size
|
||||
let child_specified_block_size = MaybeAuto::from_style(child_fragment.style().content_block_size(),
|
||||
Au::new(0)).specified_or_zero();
|
||||
max_y =
|
||||
geometry::max(max_y,
|
||||
child_specified_block_size + child_fragment.border_padding.block_start_end());
|
||||
}
|
||||
let child_node = flow::mut_base(kid);
|
||||
child_node.position.start.b = cur_y;
|
||||
max_y = geometry::max(max_y, child_node.position.size.block);
|
||||
}
|
||||
|
||||
let mut block_size = max_y;
|
||||
// TODO: Percentage block-size
|
||||
block_size = match MaybeAuto::from_style(self.block_flow.fragment.style().content_block_size(), Au(0)) {
|
||||
Auto => block_size,
|
||||
Specified(value) => geometry::max(value, block_size)
|
||||
};
|
||||
// cur_y = cur_y + block-size;
|
||||
|
||||
// Assign the block-size of own fragment
|
||||
//
|
||||
// FIXME(pcwalton): Take `cur_y` into account.
|
||||
let mut position = self.block_flow.fragment.border_box;
|
||||
position.size.block = block_size;
|
||||
self.block_flow.fragment.border_box = position;
|
||||
self.block_flow.base.position.size.block = block_size;
|
||||
|
||||
// Assign the block-size of kid fragments, which is the same value as own block-size.
|
||||
for kid in self.block_flow.base.child_iter() {
|
||||
{
|
||||
let kid_fragment = kid.as_table_cell().mut_fragment();
|
||||
let mut position = kid_fragment.border_box;
|
||||
position.size.block = block_size;
|
||||
kid_fragment.border_box = position;
|
||||
}
|
||||
let child_node = flow::mut_base(kid);
|
||||
child_node.position.size.block = block_size;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_display_list_table_row(&mut self, layout_context: &LayoutContext) {
|
||||
debug!("build_display_list_table_row: same process as block flow");
|
||||
self.block_flow.build_display_list_block(layout_context)
|
||||
}
|
||||
}
|
||||
|
||||
impl Flow for TableRowFlow {
|
||||
fn class(&self) -> FlowClass {
|
||||
TableRowFlowClass
|
||||
}
|
||||
|
||||
fn as_table_row<'a>(&'a mut self) -> &'a mut TableRowFlow {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
|
||||
&mut self.block_flow
|
||||
}
|
||||
|
||||
fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> {
|
||||
&mut self.col_inline_sizes
|
||||
}
|
||||
|
||||
fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
|
||||
&self.col_min_inline_sizes
|
||||
}
|
||||
|
||||
fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
|
||||
&self.col_pref_inline_sizes
|
||||
}
|
||||
|
||||
/// Recursively (bottom-up) determines the context's preferred and minimum inline-sizes. When called
|
||||
/// on this context, all child contexts have had their min/pref inline-sizes set. This function must
|
||||
/// decide min/pref inline-sizes based on child context inline-sizes and dimensions of any fragments it is
|
||||
/// responsible for flowing.
|
||||
/// Min/pref inline-sizes set by this function are used in automatic table layout calculation.
|
||||
/// The specified column inline-sizes of children cells are used in fixed table layout calculation.
|
||||
fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
|
||||
let mut min_inline_size = Au(0);
|
||||
let mut pref_inline_size = Au(0);
|
||||
/* find the specified inline_sizes from child table-cell contexts */
|
||||
for kid in self.block_flow.base.child_iter() {
|
||||
assert!(kid.is_table_cell());
|
||||
|
||||
// collect the specified column inline-sizes of cells. These are used in fixed table layout calculation.
|
||||
{
|
||||
let child_fragment = kid.as_table_cell().fragment();
|
||||
let child_specified_inline_size = MaybeAuto::from_style(child_fragment.style().content_inline_size(),
|
||||
Au::new(0)).specified_or_zero();
|
||||
self.col_inline_sizes.push(child_specified_inline_size);
|
||||
}
|
||||
|
||||
// collect min_inline-size & pref_inline-size of children cells for automatic table layout calculation.
|
||||
let child_base = flow::mut_base(kid);
|
||||
self.col_min_inline_sizes.push(child_base.intrinsic_inline_sizes.minimum_inline_size);
|
||||
self.col_pref_inline_sizes.push(child_base.intrinsic_inline_sizes.preferred_inline_size);
|
||||
min_inline_size = min_inline_size + child_base.intrinsic_inline_sizes.minimum_inline_size;
|
||||
pref_inline_size = pref_inline_size + child_base.intrinsic_inline_sizes.preferred_inline_size;
|
||||
}
|
||||
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size;
|
||||
self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = geometry::max(min_inline_size,
|
||||
pref_inline_size);
|
||||
}
|
||||
|
||||
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When called
|
||||
/// on this context, the context has had its inline-size set by the parent context.
|
||||
fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
|
||||
debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_row");
|
||||
|
||||
// The position was set to the containing block by the flow's parent.
|
||||
let containing_block_inline_size = self.block_flow.base.position.size.inline;
|
||||
// FIXME: In case of border-collapse: collapse, inline-start_content_edge should be border-inline-start
|
||||
let inline_start_content_edge = Au::new(0);
|
||||
|
||||
let inline_size_computer = InternalTable;
|
||||
inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size);
|
||||
|
||||
self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, Au(0), Some(self.col_inline_sizes.clone()));
|
||||
}
|
||||
|
||||
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
|
||||
debug!("assign_block_size: assigning block_size for table_row");
|
||||
self.assign_block_size_table_row_base(ctx);
|
||||
}
|
||||
|
||||
fn compute_absolute_position(&mut self) {
|
||||
self.block_flow.compute_absolute_position()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Show for TableRowFlow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "TableRowFlow: {}", self.block_flow.fragment)
|
||||
}
|
||||
}
|
208
components/layout/table_rowgroup.rs
Normal file
208
components/layout/table_rowgroup.rs
Normal file
|
@ -0,0 +1,208 @@
|
|||
/* 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/. */
|
||||
|
||||
//! CSS table formatting contexts.
|
||||
|
||||
#![deny(unsafe_block)]
|
||||
|
||||
use block::BlockFlow;
|
||||
use block::ISizeAndMarginsComputer;
|
||||
use construct::FlowConstructor;
|
||||
use context::LayoutContext;
|
||||
use flow::{TableRowGroupFlowClass, FlowClass, Flow, ImmutableFlowUtils};
|
||||
use flow;
|
||||
use fragment::Fragment;
|
||||
use table::{InternalTable, TableFlow};
|
||||
use wrapper::ThreadSafeLayoutNode;
|
||||
|
||||
use servo_util::geometry::Au;
|
||||
use servo_util::geometry;
|
||||
use std::fmt;
|
||||
|
||||
/// A table formatting context.
|
||||
pub struct TableRowGroupFlow {
|
||||
pub block_flow: BlockFlow,
|
||||
|
||||
/// Column inline-sizes
|
||||
pub col_inline_sizes: Vec<Au>,
|
||||
|
||||
/// Column min inline-sizes.
|
||||
pub col_min_inline_sizes: Vec<Au>,
|
||||
|
||||
/// Column pref inline-sizes.
|
||||
pub col_pref_inline_sizes: Vec<Au>,
|
||||
}
|
||||
|
||||
impl TableRowGroupFlow {
|
||||
pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode,
|
||||
fragment: Fragment)
|
||||
-> TableRowGroupFlow {
|
||||
TableRowGroupFlow {
|
||||
block_flow: BlockFlow::from_node_and_fragment(node, fragment),
|
||||
col_inline_sizes: vec!(),
|
||||
col_min_inline_sizes: vec!(),
|
||||
col_pref_inline_sizes: vec!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_node(constructor: &mut FlowConstructor,
|
||||
node: &ThreadSafeLayoutNode)
|
||||
-> TableRowGroupFlow {
|
||||
TableRowGroupFlow {
|
||||
block_flow: BlockFlow::from_node(constructor, node),
|
||||
col_inline_sizes: vec!(),
|
||||
col_min_inline_sizes: vec!(),
|
||||
col_pref_inline_sizes: vec!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fragment<'a>(&'a mut self) -> &'a Fragment {
|
||||
&self.block_flow.fragment
|
||||
}
|
||||
|
||||
fn initialize_offsets(&mut self) -> (Au, Au, Au) {
|
||||
// TODO: If border-collapse: collapse, block-start_offset, block-end_offset, and inline-start_offset
|
||||
// should be updated. Currently, they are set as Au(0).
|
||||
(Au(0), Au(0), Au(0))
|
||||
}
|
||||
|
||||
/// Assign block-size for table-rowgroup flow.
|
||||
///
|
||||
/// FIXME(pcwalton): This doesn't handle floats right.
|
||||
///
|
||||
/// inline(always) because this is only ever called by in-order or non-in-order top-level
|
||||
/// methods
|
||||
#[inline(always)]
|
||||
fn assign_block_size_table_rowgroup_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
|
||||
let (block_start_offset, _, _) = self.initialize_offsets();
|
||||
|
||||
let mut cur_y = block_start_offset;
|
||||
|
||||
for kid in self.block_flow.base.child_iter() {
|
||||
kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
|
||||
|
||||
let child_node = flow::mut_base(kid);
|
||||
child_node.position.start.b = cur_y;
|
||||
cur_y = cur_y + child_node.position.size.block;
|
||||
}
|
||||
|
||||
let block_size = cur_y - block_start_offset;
|
||||
|
||||
let mut position = self.block_flow.fragment.border_box;
|
||||
position.size.block = block_size;
|
||||
self.block_flow.fragment.border_box = position;
|
||||
self.block_flow.base.position.size.block = block_size;
|
||||
}
|
||||
|
||||
pub fn build_display_list_table_rowgroup(&mut self, layout_context: &LayoutContext) {
|
||||
debug!("build_display_list_table_rowgroup: same process as block flow");
|
||||
self.block_flow.build_display_list_block(layout_context)
|
||||
}
|
||||
}
|
||||
|
||||
impl Flow for TableRowGroupFlow {
|
||||
fn class(&self) -> FlowClass {
|
||||
TableRowGroupFlowClass
|
||||
}
|
||||
|
||||
fn as_table_rowgroup<'a>(&'a mut self) -> &'a mut TableRowGroupFlow {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
|
||||
&mut self.block_flow
|
||||
}
|
||||
|
||||
fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> {
|
||||
&mut self.col_inline_sizes
|
||||
}
|
||||
|
||||
fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
|
||||
&self.col_min_inline_sizes
|
||||
}
|
||||
|
||||
fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
|
||||
&self.col_pref_inline_sizes
|
||||
}
|
||||
|
||||
/// Recursively (bottom-up) determines the context's preferred and minimum inline-sizes. When called
|
||||
/// on this context, all child contexts have had their min/pref inline-sizes set. This function must
|
||||
/// decide min/pref inline-sizes based on child context inline-sizes and dimensions of any fragments it is
|
||||
/// responsible for flowing.
|
||||
/// Min/pref inline-sizes set by this function are used in automatic table layout calculation.
|
||||
/// Also, this function finds the specified column inline-sizes from the first row.
|
||||
/// Those are used in fixed table layout calculation
|
||||
fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
|
||||
let mut min_inline_size = Au(0);
|
||||
let mut pref_inline_size = Au(0);
|
||||
|
||||
for kid in self.block_flow.base.child_iter() {
|
||||
assert!(kid.is_table_row());
|
||||
|
||||
// calculate min_inline-size & pref_inline-size for automatic table layout calculation
|
||||
// 'self.col_min_inline-sizes' collects the maximum value of cells' min-inline-sizes for each column.
|
||||
// 'self.col_pref_inline-sizes' collects the maximum value of cells' pref-inline-sizes for each column.
|
||||
if self.col_inline_sizes.is_empty() { // First Row
|
||||
assert!(self.col_min_inline_sizes.is_empty() && self.col_pref_inline_sizes.is_empty());
|
||||
// 'self.col_inline-sizes' collects the specified column inline-sizes from the first table-row for fixed table layout calculation.
|
||||
self.col_inline_sizes = kid.col_inline_sizes().clone();
|
||||
self.col_min_inline_sizes = kid.col_min_inline_sizes().clone();
|
||||
self.col_pref_inline_sizes = kid.col_pref_inline_sizes().clone();
|
||||
} else {
|
||||
min_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_min_inline_sizes, kid.col_min_inline_sizes());
|
||||
pref_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_pref_inline_sizes, kid.col_pref_inline_sizes());
|
||||
|
||||
// update the number of column inline-sizes from table-rows.
|
||||
let num_cols = self.col_inline_sizes.len();
|
||||
let num_child_cols = kid.col_min_inline_sizes().len();
|
||||
for i in range(num_cols, num_child_cols) {
|
||||
self.col_inline_sizes.push(Au::new(0));
|
||||
let new_kid_min = kid.col_min_inline_sizes()[i];
|
||||
self.col_min_inline_sizes.push(kid.col_min_inline_sizes()[i]);
|
||||
let new_kid_pref = kid.col_pref_inline_sizes()[i];
|
||||
self.col_pref_inline_sizes.push(kid.col_pref_inline_sizes()[i]);
|
||||
min_inline_size = min_inline_size + new_kid_min;
|
||||
pref_inline_size = pref_inline_size + new_kid_pref;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size;
|
||||
self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = geometry::max(min_inline_size,
|
||||
pref_inline_size);
|
||||
}
|
||||
|
||||
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When
|
||||
/// called on this context, the context has had its inline-size set by the parent context.
|
||||
fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
|
||||
debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_rowgroup");
|
||||
|
||||
// The position was set to the containing block by the flow's parent.
|
||||
let containing_block_inline_size = self.block_flow.base.position.size.inline;
|
||||
// FIXME: In case of border-collapse: collapse, inline-start_content_edge should be
|
||||
// the border width on the inline-start side.
|
||||
let inline_start_content_edge = Au::new(0);
|
||||
let content_inline_size = containing_block_inline_size;
|
||||
|
||||
let inline_size_computer = InternalTable;
|
||||
inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size);
|
||||
|
||||
self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, Some(self.col_inline_sizes.clone()));
|
||||
}
|
||||
|
||||
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
|
||||
debug!("assign_block_size: assigning block_size for table_rowgroup");
|
||||
self.assign_block_size_table_rowgroup_base(ctx);
|
||||
}
|
||||
|
||||
fn compute_absolute_position(&mut self) {
|
||||
self.block_flow.compute_absolute_position()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Show for TableRowGroupFlow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "TableRowGroupFlow: {}", self.block_flow.fragment)
|
||||
}
|
||||
}
|
325
components/layout/table_wrapper.rs
Normal file
325
components/layout/table_wrapper.rs
Normal file
|
@ -0,0 +1,325 @@
|
|||
/* 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/. */
|
||||
|
||||
//! CSS table formatting contexts.
|
||||
|
||||
#![deny(unsafe_block)]
|
||||
|
||||
use block::{BlockFlow, MarginsMayNotCollapse, ISizeAndMarginsComputer};
|
||||
use block::{ISizeConstraintInput, ISizeConstraintSolution};
|
||||
use construct::FlowConstructor;
|
||||
use context::LayoutContext;
|
||||
use floats::FloatKind;
|
||||
use flow::{TableWrapperFlowClass, FlowClass, Flow, ImmutableFlowUtils};
|
||||
use fragment::Fragment;
|
||||
use model::{Specified, Auto, specified};
|
||||
use wrapper::ThreadSafeLayoutNode;
|
||||
|
||||
use servo_util::geometry::Au;
|
||||
use servo_util::geometry;
|
||||
use std::fmt;
|
||||
use style::computed_values::table_layout;
|
||||
|
||||
pub enum TableLayout {
|
||||
FixedLayout,
|
||||
AutoLayout
|
||||
}
|
||||
|
||||
/// A table wrapper flow based on a block formatting context.
|
||||
pub struct TableWrapperFlow {
|
||||
pub block_flow: BlockFlow,
|
||||
|
||||
/// Column inline-sizes
|
||||
pub col_inline_sizes: Vec<Au>,
|
||||
|
||||
/// Table-layout property
|
||||
pub table_layout: TableLayout,
|
||||
}
|
||||
|
||||
impl TableWrapperFlow {
|
||||
pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode,
|
||||
fragment: Fragment)
|
||||
-> TableWrapperFlow {
|
||||
let mut block_flow = BlockFlow::from_node_and_fragment(node, fragment);
|
||||
let table_layout = if block_flow.fragment().style().get_table().table_layout ==
|
||||
table_layout::fixed {
|
||||
FixedLayout
|
||||
} else {
|
||||
AutoLayout
|
||||
};
|
||||
TableWrapperFlow {
|
||||
block_flow: block_flow,
|
||||
col_inline_sizes: vec!(),
|
||||
table_layout: table_layout
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_node(constructor: &mut FlowConstructor,
|
||||
node: &ThreadSafeLayoutNode)
|
||||
-> TableWrapperFlow {
|
||||
let mut block_flow = BlockFlow::from_node(constructor, node);
|
||||
let table_layout = if block_flow.fragment().style().get_table().table_layout ==
|
||||
table_layout::fixed {
|
||||
FixedLayout
|
||||
} else {
|
||||
AutoLayout
|
||||
};
|
||||
TableWrapperFlow {
|
||||
block_flow: block_flow,
|
||||
col_inline_sizes: vec!(),
|
||||
table_layout: table_layout
|
||||
}
|
||||
}
|
||||
|
||||
pub fn float_from_node(constructor: &mut FlowConstructor,
|
||||
node: &ThreadSafeLayoutNode,
|
||||
float_kind: FloatKind)
|
||||
-> TableWrapperFlow {
|
||||
let mut block_flow = BlockFlow::float_from_node(constructor, node, float_kind);
|
||||
let table_layout = if block_flow.fragment().style().get_table().table_layout ==
|
||||
table_layout::fixed {
|
||||
FixedLayout
|
||||
} else {
|
||||
AutoLayout
|
||||
};
|
||||
TableWrapperFlow {
|
||||
block_flow: block_flow,
|
||||
col_inline_sizes: vec!(),
|
||||
table_layout: table_layout
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_float(&self) -> bool {
|
||||
self.block_flow.float.is_some()
|
||||
}
|
||||
|
||||
/// Assign block-size for table-wrapper flow.
|
||||
/// `Assign block-size` of table-wrapper flow follows a similar process to that of block flow.
|
||||
///
|
||||
/// inline(always) because this is only ever called by in-order or non-in-order top-level
|
||||
/// methods
|
||||
#[inline(always)]
|
||||
fn assign_block_size_table_wrapper_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
|
||||
self.block_flow.assign_block_size_block_base(layout_context, MarginsMayNotCollapse);
|
||||
}
|
||||
|
||||
pub fn build_display_list_table_wrapper(&mut self, layout_context: &LayoutContext) {
|
||||
debug!("build_display_list_table_wrapper: same process as block flow");
|
||||
self.block_flow.build_display_list_block(layout_context);
|
||||
}
|
||||
}
|
||||
|
||||
impl Flow for TableWrapperFlow {
|
||||
fn class(&self) -> FlowClass {
|
||||
TableWrapperFlowClass
|
||||
}
|
||||
|
||||
fn as_table_wrapper<'a>(&'a mut self) -> &'a mut TableWrapperFlow {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
|
||||
&mut self.block_flow
|
||||
}
|
||||
|
||||
/* Recursively (bottom-up) determine the context's preferred and
|
||||
minimum inline_sizes. When called on this context, all child contexts
|
||||
have had their min/pref inline_sizes set. This function must decide
|
||||
min/pref inline_sizes based on child context inline_sizes and dimensions of
|
||||
any fragments it is responsible for flowing. */
|
||||
|
||||
fn bubble_inline_sizes(&mut self, ctx: &LayoutContext) {
|
||||
// get column inline-sizes info from table flow
|
||||
for kid in self.block_flow.base.child_iter() {
|
||||
assert!(kid.is_table_caption() || kid.is_table());
|
||||
|
||||
if kid.is_table() {
|
||||
self.col_inline_sizes.push_all(kid.as_table().col_inline_sizes.as_slice());
|
||||
}
|
||||
}
|
||||
|
||||
self.block_flow.bubble_inline_sizes(ctx);
|
||||
}
|
||||
|
||||
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When
|
||||
/// called on this context, the context has had its inline-size set by the parent context.
|
||||
///
|
||||
/// Dual fragments consume some inline-size first, and the remainder is assigned to all child (block)
|
||||
/// contexts.
|
||||
fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
|
||||
debug!("assign_inline_sizes({}): assigning inline_size for flow",
|
||||
if self.is_float() {
|
||||
"floated table_wrapper"
|
||||
} else {
|
||||
"table_wrapper"
|
||||
});
|
||||
|
||||
// The position was set to the containing block by the flow's parent.
|
||||
let containing_block_inline_size = self.block_flow.base.position.size.inline;
|
||||
|
||||
let inline_size_computer = TableWrapper;
|
||||
inline_size_computer.compute_used_inline_size_table_wrapper(self, ctx, containing_block_inline_size);
|
||||
|
||||
let inline_start_content_edge = self.block_flow.fragment.border_box.start.i;
|
||||
let content_inline_size = self.block_flow.fragment.border_box.size.inline;
|
||||
|
||||
match self.table_layout {
|
||||
FixedLayout | _ if self.is_float() =>
|
||||
self.block_flow.base.position.size.inline = content_inline_size,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// In case of fixed layout, column inline-sizes are calculated in table flow.
|
||||
let assigned_col_inline_sizes = match self.table_layout {
|
||||
FixedLayout => None,
|
||||
AutoLayout => Some(self.col_inline_sizes.clone())
|
||||
};
|
||||
self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, assigned_col_inline_sizes);
|
||||
}
|
||||
|
||||
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
|
||||
if self.is_float() {
|
||||
debug!("assign_block_size_float: assigning block_size for floated table_wrapper");
|
||||
self.block_flow.assign_block_size_float(ctx);
|
||||
} else {
|
||||
debug!("assign_block_size: assigning block_size for table_wrapper");
|
||||
self.assign_block_size_table_wrapper_base(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_absolute_position(&mut self) {
|
||||
self.block_flow.compute_absolute_position()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Show for TableWrapperFlow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self.is_float() {
|
||||
write!(f, "TableWrapperFlow(Float): {}", self.block_flow.fragment)
|
||||
} else {
|
||||
write!(f, "TableWrapperFlow: {}", self.block_flow.fragment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TableWrapper;
|
||||
|
||||
impl TableWrapper {
|
||||
fn compute_used_inline_size_table_wrapper(&self,
|
||||
table_wrapper: &mut TableWrapperFlow,
|
||||
ctx: &LayoutContext,
|
||||
parent_flow_inline_size: Au) {
|
||||
let input = self.compute_inline_size_constraint_inputs_table_wrapper(table_wrapper,
|
||||
parent_flow_inline_size,
|
||||
ctx);
|
||||
|
||||
let solution = self.solve_inline_size_constraints(&mut table_wrapper.block_flow, &input);
|
||||
|
||||
self.set_inline_size_constraint_solutions(&mut table_wrapper.block_flow, solution);
|
||||
self.set_flow_x_coord_if_necessary(&mut table_wrapper.block_flow, solution);
|
||||
}
|
||||
|
||||
fn compute_inline_size_constraint_inputs_table_wrapper(&self,
|
||||
table_wrapper: &mut TableWrapperFlow,
|
||||
parent_flow_inline_size: Au,
|
||||
ctx: &LayoutContext)
|
||||
-> ISizeConstraintInput {
|
||||
let mut input = self.compute_inline_size_constraint_inputs(&mut table_wrapper.block_flow,
|
||||
parent_flow_inline_size,
|
||||
ctx);
|
||||
let computed_inline_size = match table_wrapper.table_layout {
|
||||
FixedLayout => {
|
||||
let fixed_cells_inline_size = table_wrapper.col_inline_sizes.iter().fold(Au(0),
|
||||
|sum, inline_size| sum.add(inline_size));
|
||||
|
||||
let mut computed_inline_size = input.computed_inline_size.specified_or_zero();
|
||||
let style = table_wrapper.block_flow.fragment.style();
|
||||
|
||||
// Get inline-start and inline-end paddings, borders for table.
|
||||
// We get these values from the fragment's style since table_wrapper doesn't have it's own border or padding.
|
||||
// input.available_inline-size is same as containing_block_inline-size in table_wrapper.
|
||||
let padding = style.logical_padding();
|
||||
let border = style.logical_border_width();
|
||||
let padding_and_borders =
|
||||
specified(padding.inline_start, input.available_inline_size) +
|
||||
specified(padding.inline_end, input.available_inline_size) +
|
||||
border.inline_start +
|
||||
border.inline_end;
|
||||
// Compare border-edge inline-sizes. Because fixed_cells_inline-size indicates content-inline-size,
|
||||
// padding and border values are added to fixed_cells_inline-size.
|
||||
computed_inline_size = geometry::max(
|
||||
fixed_cells_inline_size + padding_and_borders, computed_inline_size);
|
||||
computed_inline_size
|
||||
},
|
||||
AutoLayout => {
|
||||
// Automatic table layout is calculated according to CSS 2.1 § 17.5.2.2.
|
||||
let mut cap_min = Au(0);
|
||||
let mut cols_min = Au(0);
|
||||
let mut cols_max = Au(0);
|
||||
let mut col_min_inline_sizes = &vec!();
|
||||
let mut col_pref_inline_sizes = &vec!();
|
||||
for kid in table_wrapper.block_flow.base.child_iter() {
|
||||
if kid.is_table_caption() {
|
||||
cap_min = kid.as_block().base.intrinsic_inline_sizes.minimum_inline_size;
|
||||
} else {
|
||||
assert!(kid.is_table());
|
||||
cols_min = kid.as_block().base.intrinsic_inline_sizes.minimum_inline_size;
|
||||
cols_max = kid.as_block().base.intrinsic_inline_sizes.preferred_inline_size;
|
||||
col_min_inline_sizes = kid.col_min_inline_sizes();
|
||||
col_pref_inline_sizes = kid.col_pref_inline_sizes();
|
||||
}
|
||||
}
|
||||
// 'extra_inline-size': difference between the calculated table inline-size and minimum inline-size
|
||||
// required by all columns. It will be distributed over the columns.
|
||||
let (inline_size, extra_inline_size) = match input.computed_inline_size {
|
||||
Auto => {
|
||||
if input.available_inline_size > geometry::max(cols_max, cap_min) {
|
||||
if cols_max > cap_min {
|
||||
table_wrapper.col_inline_sizes = col_pref_inline_sizes.clone();
|
||||
(cols_max, Au(0))
|
||||
} else {
|
||||
(cap_min, cap_min - cols_min)
|
||||
}
|
||||
} else {
|
||||
let max = if cols_min >= input.available_inline_size && cols_min >= cap_min {
|
||||
table_wrapper.col_inline_sizes = col_min_inline_sizes.clone();
|
||||
cols_min
|
||||
} else {
|
||||
geometry::max(input.available_inline_size, cap_min)
|
||||
};
|
||||
(max, max - cols_min)
|
||||
}
|
||||
},
|
||||
Specified(inline_size) => {
|
||||
let max = if cols_min >= inline_size && cols_min >= cap_min {
|
||||
table_wrapper.col_inline_sizes = col_min_inline_sizes.clone();
|
||||
cols_min
|
||||
} else {
|
||||
geometry::max(inline_size, cap_min)
|
||||
};
|
||||
(max, max - cols_min)
|
||||
}
|
||||
};
|
||||
// The extra inline-size is distributed over the columns
|
||||
if extra_inline_size > Au(0) {
|
||||
let cell_len = table_wrapper.col_inline_sizes.len() as f64;
|
||||
table_wrapper.col_inline_sizes = col_min_inline_sizes.iter().map(|inline_size| {
|
||||
inline_size + extra_inline_size.scale_by(1.0 / cell_len)
|
||||
}).collect();
|
||||
}
|
||||
inline_size
|
||||
}
|
||||
};
|
||||
input.computed_inline_size = Specified(computed_inline_size);
|
||||
input
|
||||
}
|
||||
}
|
||||
|
||||
impl ISizeAndMarginsComputer for TableWrapper {
|
||||
/// Solve the inline-size and margins constraints for this block flow.
|
||||
fn solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput)
|
||||
-> ISizeConstraintSolution {
|
||||
self.solve_block_inline_size_constraints(block, input)
|
||||
}
|
||||
}
|
327
components/layout/text.rs
Normal file
327
components/layout/text.rs
Normal file
|
@ -0,0 +1,327 @@
|
|||
/* 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/. */
|
||||
|
||||
//! Text layout.
|
||||
|
||||
#![deny(unsafe_block)]
|
||||
|
||||
use flow::Flow;
|
||||
use fragment::{Fragment, ScannedTextFragment, ScannedTextFragmentInfo, UnscannedTextFragment};
|
||||
|
||||
use gfx::font::{FontMetrics, FontStyle, RunMetrics};
|
||||
use gfx::font_context::FontContext;
|
||||
use gfx::text::glyph::CharIndex;
|
||||
use gfx::text::text_run::TextRun;
|
||||
use gfx::text::util::{CompressWhitespaceNewline, transform_text, CompressNone};
|
||||
use servo_util::geometry::Au;
|
||||
use servo_util::logical_geometry::{LogicalSize, WritingMode};
|
||||
use servo_util::range::Range;
|
||||
use style::ComputedValues;
|
||||
use style::computed_values::{font_family, line_height, text_orientation, white_space};
|
||||
use sync::Arc;
|
||||
|
||||
struct NewLinePositions {
|
||||
new_line_pos: Vec<CharIndex>,
|
||||
}
|
||||
|
||||
// A helper function.
|
||||
fn can_coalesce_text_nodes(fragments: &[Fragment], left_i: uint, right_i: uint) -> bool {
|
||||
assert!(left_i != right_i);
|
||||
fragments[left_i].can_merge_with_fragment(&fragments[right_i])
|
||||
}
|
||||
|
||||
/// A stack-allocated object for scanning an inline flow into `TextRun`-containing `TextFragment`s.
|
||||
pub struct TextRunScanner {
|
||||
pub clump: Range<CharIndex>,
|
||||
}
|
||||
|
||||
impl TextRunScanner {
|
||||
pub fn new() -> TextRunScanner {
|
||||
TextRunScanner {
|
||||
clump: Range::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scan_for_runs(&mut self, font_context: &mut FontContext, flow: &mut Flow) {
|
||||
{
|
||||
let inline = flow.as_immutable_inline();
|
||||
debug!("TextRunScanner: scanning {:u} fragments for text runs...", inline.fragments.len());
|
||||
}
|
||||
|
||||
let fragments = &mut flow.as_inline().fragments;
|
||||
|
||||
let mut last_whitespace = true;
|
||||
let mut new_fragments = Vec::new();
|
||||
for fragment_i in range(0, fragments.fragments.len()) {
|
||||
debug!("TextRunScanner: considering fragment: {:u}", fragment_i);
|
||||
if fragment_i > 0 && !can_coalesce_text_nodes(fragments.fragments.as_slice(), fragment_i - 1, fragment_i) {
|
||||
last_whitespace = self.flush_clump_to_list(font_context,
|
||||
fragments.fragments.as_slice(),
|
||||
&mut new_fragments,
|
||||
last_whitespace);
|
||||
}
|
||||
|
||||
self.clump.extend_by(CharIndex(1));
|
||||
}
|
||||
|
||||
// Handle remaining clumps.
|
||||
if self.clump.length() > CharIndex(0) {
|
||||
drop(self.flush_clump_to_list(font_context,
|
||||
fragments.fragments.as_slice(),
|
||||
&mut new_fragments,
|
||||
last_whitespace))
|
||||
}
|
||||
|
||||
debug!("TextRunScanner: swapping out fragments.");
|
||||
|
||||
fragments.fragments = new_fragments;
|
||||
}
|
||||
|
||||
/// A "clump" is a range of inline flow leaves that can be merged together into a single
|
||||
/// fragment. Adjacent text with the same style can be merged, and nothing else can.
|
||||
///
|
||||
/// The flow keeps track of the fragments contained by all non-leaf DOM nodes. This is necessary
|
||||
/// for correct painting order. Since we compress several leaf fragments here, the mapping must
|
||||
/// be adjusted.
|
||||
///
|
||||
/// FIXME(#2267, pcwalton): Stop cloning fragments. Instead we will need to replace each
|
||||
/// `in_fragment` with some smaller stub.
|
||||
fn flush_clump_to_list(&mut self,
|
||||
font_context: &mut FontContext,
|
||||
in_fragments: &[Fragment],
|
||||
out_fragments: &mut Vec<Fragment>,
|
||||
last_whitespace: bool)
|
||||
-> bool {
|
||||
assert!(self.clump.length() > CharIndex(0));
|
||||
|
||||
debug!("TextRunScanner: flushing fragments in range={}", self.clump);
|
||||
let is_singleton = self.clump.length() == CharIndex(1);
|
||||
|
||||
let is_text_clump = match in_fragments[self.clump.begin().to_uint()].specific {
|
||||
UnscannedTextFragment(_) => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let mut new_whitespace = last_whitespace;
|
||||
match (is_singleton, is_text_clump) {
|
||||
(false, false) => {
|
||||
fail!("WAT: can't coalesce non-text nodes in flush_clump_to_list()!")
|
||||
}
|
||||
(true, false) => {
|
||||
// FIXME(pcwalton): Stop cloning fragments, as above.
|
||||
debug!("TextRunScanner: pushing single non-text fragment in range: {}", self.clump);
|
||||
let new_fragment = in_fragments[self.clump.begin().to_uint()].clone();
|
||||
out_fragments.push(new_fragment)
|
||||
},
|
||||
(true, true) => {
|
||||
let old_fragment = &in_fragments[self.clump.begin().to_uint()];
|
||||
let text = match old_fragment.specific {
|
||||
UnscannedTextFragment(ref text_fragment_info) => &text_fragment_info.text,
|
||||
_ => fail!("Expected an unscanned text fragment!"),
|
||||
};
|
||||
|
||||
let font_style = old_fragment.font_style();
|
||||
|
||||
let compression = match old_fragment.white_space() {
|
||||
white_space::normal => CompressWhitespaceNewline,
|
||||
white_space::pre => CompressNone,
|
||||
};
|
||||
|
||||
let mut new_line_pos = vec![];
|
||||
|
||||
let (transformed_text, whitespace) = transform_text(text.as_slice(),
|
||||
compression,
|
||||
last_whitespace,
|
||||
&mut new_line_pos);
|
||||
|
||||
new_whitespace = whitespace;
|
||||
|
||||
if transformed_text.len() > 0 {
|
||||
// TODO(#177): Text run creation must account for the renderability of text by
|
||||
// font group fonts. This is probably achieved by creating the font group above
|
||||
// and then letting `FontGroup` decide which `Font` to stick into the text run.
|
||||
let fontgroup = font_context.get_layout_font_group_for_style(&font_style);
|
||||
let run = box fontgroup.create_textrun(
|
||||
transformed_text.clone());
|
||||
|
||||
debug!("TextRunScanner: pushing single text fragment in range: {} ({})",
|
||||
self.clump,
|
||||
*text);
|
||||
let range = Range::new(CharIndex(0), run.char_len());
|
||||
let new_metrics = run.metrics_for_range(&range);
|
||||
let bounding_box_size = bounding_box_for_run_metrics(
|
||||
&new_metrics, old_fragment.style.writing_mode);
|
||||
let new_text_fragment_info = ScannedTextFragmentInfo::new(Arc::new(run), range);
|
||||
let mut new_fragment = old_fragment.transform(
|
||||
bounding_box_size, ScannedTextFragment(new_text_fragment_info));
|
||||
new_fragment.new_line_pos = new_line_pos;
|
||||
out_fragments.push(new_fragment)
|
||||
}
|
||||
},
|
||||
(false, true) => {
|
||||
// TODO(#177): Text run creation must account for the renderability of text by
|
||||
// font group fonts. This is probably achieved by creating the font group above
|
||||
// and then letting `FontGroup` decide which `Font` to stick into the text run.
|
||||
let in_fragment = &in_fragments[self.clump.begin().to_uint()];
|
||||
let font_style = in_fragment.font_style();
|
||||
let fontgroup = font_context.get_layout_font_group_for_style(&font_style);
|
||||
|
||||
let compression = match in_fragment.white_space() {
|
||||
white_space::normal => CompressWhitespaceNewline,
|
||||
white_space::pre => CompressNone,
|
||||
};
|
||||
|
||||
let mut new_line_positions: Vec<NewLinePositions> = vec![];
|
||||
|
||||
// First, transform/compress text of all the nodes.
|
||||
let mut last_whitespace_in_clump = new_whitespace;
|
||||
let transformed_strs: Vec<String> = Vec::from_fn(self.clump.length().to_uint(), |i| {
|
||||
let idx = CharIndex(i as int) + self.clump.begin();
|
||||
let in_fragment = match in_fragments[idx.to_uint()].specific {
|
||||
UnscannedTextFragment(ref text_fragment_info) => &text_fragment_info.text,
|
||||
_ => fail!("Expected an unscanned text fragment!"),
|
||||
};
|
||||
|
||||
let mut new_line_pos = vec![];
|
||||
|
||||
let (new_str, new_whitespace) = transform_text(in_fragment.as_slice(),
|
||||
compression,
|
||||
last_whitespace_in_clump,
|
||||
&mut new_line_pos);
|
||||
new_line_positions.push(NewLinePositions { new_line_pos: new_line_pos });
|
||||
|
||||
last_whitespace_in_clump = new_whitespace;
|
||||
new_str
|
||||
});
|
||||
new_whitespace = last_whitespace_in_clump;
|
||||
|
||||
// Next, concatenate all of the transformed strings together, saving the new
|
||||
// character indices.
|
||||
let mut run_str = String::new();
|
||||
let mut new_ranges: Vec<Range<CharIndex>> = vec![];
|
||||
let mut char_total = CharIndex(0);
|
||||
for i in range(0, transformed_strs.len() as int) {
|
||||
let added_chars = CharIndex(transformed_strs[i as uint].as_slice().char_len() as int);
|
||||
new_ranges.push(Range::new(char_total, added_chars));
|
||||
run_str.push_str(transformed_strs[i as uint].as_slice());
|
||||
char_total = char_total + added_chars;
|
||||
}
|
||||
|
||||
// Now create the run.
|
||||
// TextRuns contain a cycle which is usually resolved by the teardown
|
||||
// sequence. If no clump takes ownership, however, it will leak.
|
||||
let clump = self.clump;
|
||||
let run = if clump.length() != CharIndex(0) && run_str.len() > 0 {
|
||||
Some(Arc::new(box TextRun::new(
|
||||
&mut *fontgroup.fonts[0].borrow_mut(),
|
||||
run_str.to_string())))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Make new fragments with the run and adjusted text indices.
|
||||
debug!("TextRunScanner: pushing fragment(s) in range: {}", self.clump);
|
||||
for i in clump.each_index() {
|
||||
let logical_offset = i - self.clump.begin();
|
||||
let range = new_ranges[logical_offset.to_uint()];
|
||||
if range.length() == CharIndex(0) {
|
||||
debug!("Elided an `UnscannedTextFragment` because it was zero-length after \
|
||||
compression; {}", in_fragments[i.to_uint()]);
|
||||
continue
|
||||
}
|
||||
|
||||
let new_text_fragment_info = ScannedTextFragmentInfo::new(run.get_ref().clone(), range);
|
||||
let old_fragment = &in_fragments[i.to_uint()];
|
||||
let new_metrics = new_text_fragment_info.run.metrics_for_range(&range);
|
||||
let bounding_box_size = bounding_box_for_run_metrics(
|
||||
&new_metrics, old_fragment.style.writing_mode);
|
||||
let mut new_fragment = old_fragment.transform(
|
||||
bounding_box_size, ScannedTextFragment(new_text_fragment_info));
|
||||
new_fragment.new_line_pos = new_line_positions[logical_offset.to_uint()].new_line_pos.clone();
|
||||
out_fragments.push(new_fragment)
|
||||
}
|
||||
}
|
||||
} // End of match.
|
||||
|
||||
let end = self.clump.end(); // FIXME: borrow checker workaround
|
||||
self.clump.reset(end, CharIndex(0));
|
||||
|
||||
new_whitespace
|
||||
} // End of `flush_clump_to_list`.
|
||||
}
|
||||
|
||||
|
||||
#[inline]
|
||||
fn bounding_box_for_run_metrics(metrics: &RunMetrics, writing_mode: WritingMode)
|
||||
-> LogicalSize<Au> {
|
||||
|
||||
// This does nothing, but it will fail to build
|
||||
// when more values are added to the `text-orientation` CSS property.
|
||||
// This will be a reminder to update the code below.
|
||||
let dummy: Option<text_orientation::T> = None;
|
||||
match dummy {
|
||||
Some(text_orientation::sideways_right) |
|
||||
Some(text_orientation::sideways_left) |
|
||||
Some(text_orientation::sideways) |
|
||||
None => {}
|
||||
}
|
||||
|
||||
// In vertical sideways or horizontal upgright text,
|
||||
// the "width" of text metrics is always inline
|
||||
// This will need to be updated when other text orientations are supported.
|
||||
LogicalSize::new(
|
||||
writing_mode,
|
||||
metrics.bounding_box.size.width,
|
||||
metrics.bounding_box.size.height)
|
||||
|
||||
}
|
||||
|
||||
/// Returns the metrics of the font represented by the given `FontStyle`, respectively.
|
||||
///
|
||||
/// `#[inline]` because often the caller only needs a few fields from the font metrics.
|
||||
#[inline]
|
||||
pub fn font_metrics_for_style(font_context: &mut FontContext, font_style: &FontStyle)
|
||||
-> FontMetrics {
|
||||
let fontgroup = font_context.get_layout_font_group_for_style(font_style);
|
||||
fontgroup.fonts[0].borrow().metrics.clone()
|
||||
}
|
||||
|
||||
/// Converts a computed style to a font style used for rendering.
|
||||
///
|
||||
/// FIXME(pcwalton): This should not be necessary; just make the font part of the style sharable
|
||||
/// with the display list somehow. (Perhaps we should use an ARC.)
|
||||
pub fn computed_style_to_font_style(style: &ComputedValues) -> FontStyle {
|
||||
debug!("(font style) start");
|
||||
|
||||
// FIXME: Too much allocation here.
|
||||
let mut font_families = style.get_font().font_family.iter().map(|family| {
|
||||
match *family {
|
||||
font_family::FamilyName(ref name) => (*name).clone(),
|
||||
}
|
||||
});
|
||||
debug!("(font style) font families: `{:?}`", font_families);
|
||||
|
||||
let font_size = style.get_font().font_size.to_f64().unwrap() / 60.0;
|
||||
debug!("(font style) font size: `{:f}px`", font_size);
|
||||
|
||||
FontStyle {
|
||||
pt_size: font_size,
|
||||
weight: style.get_font().font_weight,
|
||||
style: style.get_font().font_style,
|
||||
families: font_families.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the line block-size needed by the given computed style and font size.
|
||||
pub fn line_height_from_style(style: &ComputedValues, metrics: &FontMetrics) -> Au {
|
||||
let font_size = style.get_font().font_size;
|
||||
let from_inline = match style.get_inheritedbox().line_height {
|
||||
line_height::Normal => metrics.line_gap,
|
||||
line_height::Number(l) => font_size.scale_by(l),
|
||||
line_height::Length(l) => l
|
||||
};
|
||||
let minimum = style.get_inheritedbox()._servo_minimum_line_height;
|
||||
Au::max(from_inline, minimum)
|
||||
}
|
||||
|
164
components/layout/util.rs
Normal file
164
components/layout/util.rs
Normal file
|
@ -0,0 +1,164 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use construct::{ConstructionResult, NoConstructionResult};
|
||||
use incremental::RestyleDamage;
|
||||
use parallel::DomParallelInfo;
|
||||
use wrapper::{LayoutNode, TLayoutNode, ThreadSafeLayoutNode};
|
||||
|
||||
use gfx::display_list::OpaqueNode;
|
||||
use gfx;
|
||||
use libc::uintptr_t;
|
||||
use script::dom::bindings::js::JS;
|
||||
use script::dom::bindings::utils::Reflectable;
|
||||
use script::dom::node::{Node, SharedLayoutData};
|
||||
use script::layout_interface::{LayoutChan, UntrustedNodeAddress, TrustedNodeAddress};
|
||||
use std::mem;
|
||||
use std::cell::{Ref, RefMut};
|
||||
use style::ComputedValues;
|
||||
use style;
|
||||
use sync::Arc;
|
||||
|
||||
/// Data that layout associates with a node.
|
||||
pub struct PrivateLayoutData {
|
||||
/// The results of CSS styling for this node's `before` pseudo-element, if any.
|
||||
pub before_style: Option<Arc<ComputedValues>>,
|
||||
|
||||
/// The results of CSS styling for this node's `after` pseudo-element, if any.
|
||||
pub after_style: Option<Arc<ComputedValues>>,
|
||||
|
||||
/// Description of how to account for recent style changes.
|
||||
pub restyle_damage: Option<RestyleDamage>,
|
||||
|
||||
/// The current results of flow construction for this node. This is either a flow or a
|
||||
/// `ConstructionItem`. See comments in `construct.rs` for more details.
|
||||
pub flow_construction_result: ConstructionResult,
|
||||
|
||||
pub before_flow_construction_result: ConstructionResult,
|
||||
|
||||
pub after_flow_construction_result: ConstructionResult,
|
||||
|
||||
/// Information needed during parallel traversals.
|
||||
pub parallel: DomParallelInfo,
|
||||
}
|
||||
|
||||
impl PrivateLayoutData {
|
||||
/// Creates new layout data.
|
||||
pub fn new() -> PrivateLayoutData {
|
||||
PrivateLayoutData {
|
||||
before_style: None,
|
||||
after_style: None,
|
||||
restyle_damage: None,
|
||||
flow_construction_result: NoConstructionResult,
|
||||
before_flow_construction_result: NoConstructionResult,
|
||||
after_flow_construction_result: NoConstructionResult,
|
||||
parallel: DomParallelInfo::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LayoutDataWrapper {
|
||||
pub chan: Option<LayoutChan>,
|
||||
pub shared_data: SharedLayoutData,
|
||||
pub data: Box<PrivateLayoutData>,
|
||||
}
|
||||
|
||||
/// A trait that allows access to the layout data of a DOM node.
|
||||
pub trait LayoutDataAccess {
|
||||
/// Borrows the layout data without checks.
|
||||
unsafe fn borrow_layout_data_unchecked(&self) -> *const Option<LayoutDataWrapper>;
|
||||
/// Borrows the layout data immutably. Fails on a conflicting borrow.
|
||||
fn borrow_layout_data<'a>(&'a self) -> Ref<'a,Option<LayoutDataWrapper>>;
|
||||
/// Borrows the layout data mutably. Fails on a conflicting borrow.
|
||||
fn mutate_layout_data<'a>(&'a self) -> RefMut<'a,Option<LayoutDataWrapper>>;
|
||||
}
|
||||
|
||||
impl<'ln> LayoutDataAccess for LayoutNode<'ln> {
|
||||
#[inline(always)]
|
||||
unsafe fn borrow_layout_data_unchecked(&self) -> *const Option<LayoutDataWrapper> {
|
||||
mem::transmute(self.get().layout_data.borrow_unchecked())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn borrow_layout_data<'a>(&'a self) -> Ref<'a,Option<LayoutDataWrapper>> {
|
||||
unsafe {
|
||||
mem::transmute(self.get().layout_data.borrow())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mutate_layout_data<'a>(&'a self) -> RefMut<'a,Option<LayoutDataWrapper>> {
|
||||
unsafe {
|
||||
mem::transmute(self.get().layout_data.borrow_mut())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OpaqueNodeMethods {
|
||||
/// Converts a DOM node (layout view) to an `OpaqueNode`.
|
||||
fn from_layout_node(node: &LayoutNode) -> Self;
|
||||
|
||||
/// Converts a thread-safe DOM node (layout view) to an `OpaqueNode`.
|
||||
fn from_thread_safe_layout_node(node: &ThreadSafeLayoutNode) -> Self;
|
||||
|
||||
/// Converts a DOM node (script view) to an `OpaqueNode`.
|
||||
fn from_script_node(node: TrustedNodeAddress) -> Self;
|
||||
|
||||
/// Converts a DOM node to an `OpaqueNode'.
|
||||
fn from_jsmanaged(node: &JS<Node>) -> Self;
|
||||
|
||||
/// Converts this node to an `UntrustedNodeAddress`. An `UntrustedNodeAddress` is just the type
|
||||
/// of node that script expects to receive in a hit test.
|
||||
fn to_untrusted_node_address(&self) -> UntrustedNodeAddress;
|
||||
}
|
||||
|
||||
impl OpaqueNodeMethods for OpaqueNode {
|
||||
fn from_layout_node(node: &LayoutNode) -> OpaqueNode {
|
||||
unsafe {
|
||||
OpaqueNodeMethods::from_jsmanaged(node.get_jsmanaged())
|
||||
}
|
||||
}
|
||||
|
||||
fn from_thread_safe_layout_node(node: &ThreadSafeLayoutNode) -> OpaqueNode {
|
||||
unsafe {
|
||||
let abstract_node = node.get_jsmanaged();
|
||||
let ptr: uintptr_t = abstract_node.reflector().get_jsobject() as uint;
|
||||
OpaqueNode(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
fn from_script_node(node: TrustedNodeAddress) -> OpaqueNode {
|
||||
unsafe {
|
||||
OpaqueNodeMethods::from_jsmanaged(&JS::from_trusted_node_address(node))
|
||||
}
|
||||
}
|
||||
|
||||
fn from_jsmanaged(node: &JS<Node>) -> OpaqueNode {
|
||||
unsafe {
|
||||
let ptr: uintptr_t = mem::transmute(node.reflector().get_jsobject());
|
||||
OpaqueNode(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_untrusted_node_address(&self) -> UntrustedNodeAddress {
|
||||
unsafe {
|
||||
let OpaqueNode(addr) = *self;
|
||||
let addr: UntrustedNodeAddress = mem::transmute(addr);
|
||||
addr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows a CSS color to be converted into a graphics color.
|
||||
pub trait ToGfxColor {
|
||||
/// Converts a CSS color to a graphics color.
|
||||
fn to_gfx_color(&self) -> gfx::color::Color;
|
||||
}
|
||||
|
||||
impl ToGfxColor for style::computed_values::RGBA {
|
||||
fn to_gfx_color(&self) -> gfx::color::Color {
|
||||
gfx::color::rgba(self.red, self.green, self.blue, self.alpha)
|
||||
}
|
||||
}
|
||||
|
783
components/layout/wrapper.rs
Normal file
783
components/layout/wrapper.rs
Normal file
|
@ -0,0 +1,783 @@
|
|||
/* 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 safe wrapper for DOM nodes that prevents layout from mutating the DOM, from letting DOM nodes
|
||||
//! escape, and from generally doing anything that it isn't supposed to. This is accomplished via
|
||||
//! a simple whitelist of allowed operations, along with some lifetime magic to prevent nodes from
|
||||
//! escaping.
|
||||
//!
|
||||
//! As a security wrapper is only as good as its whitelist, be careful when adding operations to
|
||||
//! this list. The cardinal rules are:
|
||||
//!
|
||||
//! 1. Layout is not allowed to mutate the DOM.
|
||||
//!
|
||||
//! 2. Layout is not allowed to see anything with `JS` in the name, because it could hang
|
||||
//! onto these objects and cause use-after-free.
|
||||
//!
|
||||
//! When implementing wrapper functions, be careful that you do not touch the borrow flags, or you
|
||||
//! will race and cause spurious task failure. (Note that I do not believe these races are
|
||||
//! exploitable, but they'll result in brokenness nonetheless.)
|
||||
//!
|
||||
//! Rules of the road for this file:
|
||||
//!
|
||||
//! * In general, you must not use the `Cast` functions; use explicit checks and `transmute_copy`
|
||||
//! instead.
|
||||
//!
|
||||
//! * You must also not use `.get()`; instead, use `.unsafe_get()`.
|
||||
//!
|
||||
//! * Do not call any methods on DOM nodes without checking to see whether they use borrow flags.
|
||||
//!
|
||||
//! o Instead of `get_attr()`, use `.get_attr_val_for_layout()`.
|
||||
//!
|
||||
//! o Instead of `html_element_in_html_document()`, use
|
||||
//! `html_element_in_html_document_for_layout()`.
|
||||
|
||||
use css::node_style::StyledNode;
|
||||
use util::LayoutDataWrapper;
|
||||
|
||||
use script::dom::bindings::codegen::InheritTypes::{HTMLIFrameElementDerived};
|
||||
use script::dom::bindings::codegen::InheritTypes::{HTMLImageElementDerived, TextDerived};
|
||||
use script::dom::bindings::js::JS;
|
||||
use script::dom::element::{Element, HTMLAreaElementTypeId, HTMLAnchorElementTypeId};
|
||||
use script::dom::element::{HTMLLinkElementTypeId, LayoutElementHelpers, RawLayoutElementHelpers};
|
||||
use script::dom::htmliframeelement::HTMLIFrameElement;
|
||||
use script::dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers};
|
||||
use script::dom::node::{DocumentNodeTypeId, ElementNodeTypeId, Node, NodeTypeId};
|
||||
use script::dom::node::{LayoutNodeHelpers, RawLayoutNodeHelpers, TextNodeTypeId};
|
||||
use script::dom::text::Text;
|
||||
use servo_msg::constellation_msg::{PipelineId, SubpageId};
|
||||
use servo_util::atom::Atom;
|
||||
use servo_util::namespace::Namespace;
|
||||
use servo_util::namespace;
|
||||
use servo_util::str::is_whitespace;
|
||||
use std::cell::{RefCell, Ref, RefMut};
|
||||
use std::kinds::marker::ContravariantLifetime;
|
||||
use std::mem;
|
||||
use style::computed_values::{content, display, white_space};
|
||||
use style::{AnyNamespace, AttrSelector, PropertyDeclarationBlock, SpecificNamespace, TElement};
|
||||
use style::{TNode};
|
||||
use url::Url;
|
||||
|
||||
/// Allows some convenience methods on generic layout nodes.
|
||||
pub trait TLayoutNode {
|
||||
/// Creates a new layout node with the same lifetime as this layout node.
|
||||
unsafe fn new_with_this_lifetime(&self, node: &JS<Node>) -> Self;
|
||||
|
||||
/// Returns the type ID of this node. Fails if this node is borrowed mutably. Returns `None`
|
||||
/// if this is a pseudo-element; otherwise, returns `Some`.
|
||||
fn type_id(&self) -> Option<NodeTypeId>;
|
||||
|
||||
/// Returns the interior of this node as a `JS`. This is highly unsafe for layout to
|
||||
/// call and as such is marked `unsafe`.
|
||||
unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node>;
|
||||
|
||||
/// Returns the interior of this node as a `Node`. This is highly unsafe for layout to call
|
||||
/// and as such is marked `unsafe`.
|
||||
unsafe fn get<'a>(&'a self) -> &'a Node {
|
||||
&*self.get_jsmanaged().unsafe_get()
|
||||
}
|
||||
|
||||
fn node_is_element(&self) -> bool {
|
||||
match self.type_id() {
|
||||
Some(ElementNodeTypeId(..)) => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
fn node_is_document(&self) -> bool {
|
||||
match self.type_id() {
|
||||
Some(DocumentNodeTypeId(..)) => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
/// If this is an image element, returns its URL. If this is not an image element, fails.
|
||||
///
|
||||
/// FIXME(pcwalton): Don't copy URLs.
|
||||
fn image_url(&self) -> Option<Url> {
|
||||
unsafe {
|
||||
if !self.get().is_htmlimageelement() {
|
||||
fail!("not an image!")
|
||||
}
|
||||
let image_element: JS<HTMLImageElement> = self.get_jsmanaged().transmute_copy();
|
||||
image_element.image().as_ref().map(|url| (*url).clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// If this node is an iframe element, returns its pipeline and subpage IDs. If this node is
|
||||
/// not an iframe element, fails.
|
||||
fn iframe_pipeline_and_subpage_ids(&self) -> (PipelineId, SubpageId) {
|
||||
unsafe {
|
||||
if !self.get().is_htmliframeelement() {
|
||||
fail!("not an iframe element!")
|
||||
}
|
||||
let iframe_element: JS<HTMLIFrameElement> = self.get_jsmanaged().transmute_copy();
|
||||
let size = (*iframe_element.unsafe_get()).size.deref().get().unwrap();
|
||||
(size.pipeline_id, size.subpage_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// If this is a text node, copies out the text. If this is not a text node, fails.
|
||||
///
|
||||
/// FIXME(pcwalton): Don't copy text. Atomically reference count instead.
|
||||
fn text(&self) -> String;
|
||||
|
||||
/// Returns the first child of this node.
|
||||
fn first_child(&self) -> Option<Self>;
|
||||
|
||||
/// Dumps this node tree, for debugging.
|
||||
fn dump(&self) {
|
||||
// TODO(pcwalton): Reimplement this in a way that's safe for layout to call.
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper so that layout can access only the methods that it should have access to. Layout must
|
||||
/// only ever see these and must never see instances of `JS`.
|
||||
pub struct LayoutNode<'a> {
|
||||
/// The wrapped node.
|
||||
node: JS<Node>,
|
||||
|
||||
/// Being chained to a ContravariantLifetime prevents `LayoutNode`s from escaping.
|
||||
pub chain: ContravariantLifetime<'a>,
|
||||
}
|
||||
|
||||
impl<'ln> Clone for LayoutNode<'ln> {
|
||||
fn clone(&self) -> LayoutNode<'ln> {
|
||||
LayoutNode {
|
||||
node: self.node.clone(),
|
||||
chain: self.chain,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialEq for LayoutNode<'a> {
|
||||
#[inline]
|
||||
fn eq(&self, other: &LayoutNode) -> bool {
|
||||
self.node == other.node
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<'ln> TLayoutNode for LayoutNode<'ln> {
|
||||
unsafe fn new_with_this_lifetime(&self, node: &JS<Node>) -> LayoutNode<'ln> {
|
||||
LayoutNode {
|
||||
node: node.transmute_copy(),
|
||||
chain: self.chain,
|
||||
}
|
||||
}
|
||||
|
||||
fn type_id(&self) -> Option<NodeTypeId> {
|
||||
unsafe {
|
||||
Some(self.node.type_id_for_layout())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node> {
|
||||
&self.node
|
||||
}
|
||||
|
||||
fn first_child(&self) -> Option<LayoutNode<'ln>> {
|
||||
unsafe {
|
||||
self.get_jsmanaged().first_child_ref().map(|node| self.new_with_this_lifetime(&node))
|
||||
}
|
||||
}
|
||||
|
||||
fn text(&self) -> String {
|
||||
unsafe {
|
||||
if !self.get().is_text() {
|
||||
fail!("not text!")
|
||||
}
|
||||
let text: JS<Text> = self.get_jsmanaged().transmute_copy();
|
||||
(*text.unsafe_get()).characterdata.data.deref().borrow().clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ln> LayoutNode<'ln> {
|
||||
/// Creates a new layout node, scoped to the given closure.
|
||||
pub unsafe fn with_layout_node<R>(node: JS<Node>, f: <'a> |LayoutNode<'a>| -> R) -> R {
|
||||
f(LayoutNode {
|
||||
node: node,
|
||||
chain: ContravariantLifetime,
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterates over this node and all its descendants, in preorder.
|
||||
///
|
||||
/// FIXME(pcwalton): Terribly inefficient. We should use parallelism.
|
||||
pub fn traverse_preorder(&self) -> LayoutTreeIterator<'ln> {
|
||||
let mut nodes = vec!();
|
||||
gather_layout_nodes(self, &mut nodes, false);
|
||||
LayoutTreeIterator::new(nodes)
|
||||
}
|
||||
|
||||
/// Returns an iterator over this node's children.
|
||||
pub fn children(&self) -> LayoutNodeChildrenIterator<'ln> {
|
||||
LayoutNodeChildrenIterator {
|
||||
current_node: self.first_child(),
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node> {
|
||||
&self.node
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ln> TNode<LayoutElement<'ln>> for LayoutNode<'ln> {
|
||||
fn parent_node(&self) -> Option<LayoutNode<'ln>> {
|
||||
unsafe {
|
||||
self.node.parent_node_ref().map(|node| self.new_with_this_lifetime(&node))
|
||||
}
|
||||
}
|
||||
|
||||
fn prev_sibling(&self) -> Option<LayoutNode<'ln>> {
|
||||
unsafe {
|
||||
self.node.prev_sibling_ref().map(|node| self.new_with_this_lifetime(&node))
|
||||
}
|
||||
}
|
||||
|
||||
fn next_sibling(&self) -> Option<LayoutNode<'ln>> {
|
||||
unsafe {
|
||||
self.node.next_sibling_ref().map(|node| self.new_with_this_lifetime(&node))
|
||||
}
|
||||
}
|
||||
|
||||
/// If this is an element, accesses the element data. Fails if this is not an element node.
|
||||
#[inline]
|
||||
fn as_element(&self) -> LayoutElement<'ln> {
|
||||
unsafe {
|
||||
assert!(self.node.is_element_for_layout());
|
||||
let elem: JS<Element> = self.node.transmute_copy();
|
||||
let element = &*elem.unsafe_get();
|
||||
LayoutElement {
|
||||
element: mem::transmute(element),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_element(&self) -> bool {
|
||||
self.node_is_element()
|
||||
}
|
||||
|
||||
fn is_document(&self) -> bool {
|
||||
self.node_is_document()
|
||||
}
|
||||
|
||||
fn match_attr(&self, attr: &AttrSelector, test: |&str| -> bool) -> bool {
|
||||
assert!(self.is_element())
|
||||
let name = if self.is_html_element_in_html_document() {
|
||||
attr.lower_name.as_slice()
|
||||
} else {
|
||||
attr.name.as_slice()
|
||||
};
|
||||
match attr.namespace {
|
||||
SpecificNamespace(ref ns) => {
|
||||
let element = self.as_element();
|
||||
element.get_attr(ns, name)
|
||||
.map_or(false, |attr| test(attr))
|
||||
},
|
||||
// FIXME: https://github.com/mozilla/servo/issues/1558
|
||||
AnyNamespace => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_html_element_in_html_document(&self) -> bool {
|
||||
unsafe {
|
||||
self.is_element() && {
|
||||
let element: JS<Element> = self.node.transmute_copy();
|
||||
element.html_element_in_html_document_for_layout()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LayoutNodeChildrenIterator<'a> {
|
||||
current_node: Option<LayoutNode<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator<LayoutNode<'a>> for LayoutNodeChildrenIterator<'a> {
|
||||
fn next(&mut self) -> Option<LayoutNode<'a>> {
|
||||
let node = self.current_node.clone();
|
||||
self.current_node = node.clone().and_then(|node| {
|
||||
node.next_sibling()
|
||||
});
|
||||
node
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Do this without precomputing a vector of refs.
|
||||
// Easy for preorder; harder for postorder.
|
||||
//
|
||||
// FIXME(pcwalton): Parallelism! Eventually this should just be nuked.
|
||||
pub struct LayoutTreeIterator<'a> {
|
||||
nodes: Vec<LayoutNode<'a>>,
|
||||
index: uint,
|
||||
}
|
||||
|
||||
impl<'a> LayoutTreeIterator<'a> {
|
||||
fn new(nodes: Vec<LayoutNode<'a>>) -> LayoutTreeIterator<'a> {
|
||||
LayoutTreeIterator {
|
||||
nodes: nodes,
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator<LayoutNode<'a>> for LayoutTreeIterator<'a> {
|
||||
fn next(&mut self) -> Option<LayoutNode<'a>> {
|
||||
if self.index >= self.nodes.len() {
|
||||
None
|
||||
} else {
|
||||
let v = self.nodes[self.index].clone();
|
||||
self.index += 1;
|
||||
Some(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// FIXME(pcwalton): This is super inefficient.
|
||||
fn gather_layout_nodes<'a>(cur: &LayoutNode<'a>, refs: &mut Vec<LayoutNode<'a>>, postorder: bool) {
|
||||
if !postorder {
|
||||
refs.push(cur.clone());
|
||||
}
|
||||
for kid in cur.children() {
|
||||
gather_layout_nodes(&kid, refs, postorder)
|
||||
}
|
||||
if postorder {
|
||||
refs.push(cur.clone());
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around elements that ensures layout can only ever access safe properties.
|
||||
pub struct LayoutElement<'le> {
|
||||
element: &'le Element,
|
||||
}
|
||||
|
||||
impl<'le> LayoutElement<'le> {
|
||||
pub fn style_attribute(&self) -> &'le Option<PropertyDeclarationBlock> {
|
||||
let style: &Option<PropertyDeclarationBlock> = unsafe {
|
||||
let style: &RefCell<Option<PropertyDeclarationBlock>> = self.element.style_attribute.deref();
|
||||
// cast to the direct reference to T placed on the head of RefCell<T>
|
||||
mem::transmute(style)
|
||||
};
|
||||
style
|
||||
}
|
||||
}
|
||||
|
||||
impl<'le> TElement for LayoutElement<'le> {
|
||||
#[inline]
|
||||
fn get_local_name<'a>(&'a self) -> &'a Atom {
|
||||
&self.element.local_name
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_namespace<'a>(&'a self) -> &'a Namespace {
|
||||
&self.element.namespace
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_attr(&self, namespace: &Namespace, name: &str) -> Option<&'static str> {
|
||||
unsafe { self.element.get_attr_val_for_layout(namespace, name) }
|
||||
}
|
||||
|
||||
fn get_link(&self) -> Option<&'static str> {
|
||||
// FIXME: This is HTML only.
|
||||
match self.element.node.type_id_for_layout() {
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/multipage/selectors.html#
|
||||
// selector-link
|
||||
ElementNodeTypeId(HTMLAnchorElementTypeId) |
|
||||
ElementNodeTypeId(HTMLAreaElementTypeId) |
|
||||
ElementNodeTypeId(HTMLLinkElementTypeId) => {
|
||||
unsafe { self.element.get_attr_val_for_layout(&namespace::Null, "href") }
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_hover_state(&self) -> bool {
|
||||
unsafe {
|
||||
self.element.node.get_hover_state_for_layout()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_id(&self) -> Option<Atom> {
|
||||
unsafe { self.element.get_attr_atom_for_layout(&namespace::Null, "id") }
|
||||
}
|
||||
|
||||
fn get_disabled_state(&self) -> bool {
|
||||
unsafe {
|
||||
self.element.node.get_disabled_state_for_layout()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_enabled_state(&self) -> bool {
|
||||
unsafe {
|
||||
self.element.node.get_enabled_state_for_layout()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_content(content_list: &content::T) -> String {
|
||||
match *content_list {
|
||||
content::Content(ref value) => {
|
||||
let iter = &mut value.clone().move_iter().peekable();
|
||||
match iter.next() {
|
||||
Some(content::StringContent(content)) => content,
|
||||
_ => "".to_string(),
|
||||
}
|
||||
}
|
||||
_ => "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[deriving(PartialEq, Clone)]
|
||||
pub enum PseudoElementType {
|
||||
Normal,
|
||||
Before,
|
||||
After,
|
||||
BeforeBlock,
|
||||
AfterBlock,
|
||||
}
|
||||
|
||||
/// A thread-safe version of `LayoutNode`, used during flow construction. This type of layout
|
||||
/// node does not allow any parents or siblings of nodes to be accessed, to avoid races.
|
||||
#[deriving(Clone)]
|
||||
pub struct ThreadSafeLayoutNode<'ln> {
|
||||
/// The wrapped node.
|
||||
node: LayoutNode<'ln>,
|
||||
|
||||
pseudo: PseudoElementType,
|
||||
}
|
||||
|
||||
impl<'ln> TLayoutNode for ThreadSafeLayoutNode<'ln> {
|
||||
/// Creates a new layout node with the same lifetime as this layout node.
|
||||
unsafe fn new_with_this_lifetime(&self, node: &JS<Node>) -> ThreadSafeLayoutNode<'ln> {
|
||||
ThreadSafeLayoutNode {
|
||||
node: LayoutNode {
|
||||
node: node.transmute_copy(),
|
||||
chain: self.node.chain,
|
||||
},
|
||||
pseudo: Normal,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `None` if this is a pseudo-element.
|
||||
fn type_id(&self) -> Option<NodeTypeId> {
|
||||
if self.pseudo == Before || self.pseudo == After {
|
||||
return None
|
||||
}
|
||||
|
||||
self.node.type_id()
|
||||
}
|
||||
|
||||
unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node> {
|
||||
self.node.get_jsmanaged()
|
||||
}
|
||||
|
||||
unsafe fn get<'a>(&'a self) -> &'a Node { // this change.
|
||||
mem::transmute::<*mut Node,&'a Node>(self.get_jsmanaged().unsafe_get())
|
||||
}
|
||||
|
||||
fn first_child(&self) -> Option<ThreadSafeLayoutNode<'ln>> {
|
||||
if self.pseudo == Before || self.pseudo == After {
|
||||
return None
|
||||
}
|
||||
|
||||
if self.has_before_pseudo() {
|
||||
if self.is_block(Before) && self.pseudo == Normal {
|
||||
let pseudo_before_node = self.with_pseudo(BeforeBlock);
|
||||
return Some(pseudo_before_node)
|
||||
} else if self.pseudo == Normal || self.pseudo == BeforeBlock {
|
||||
let pseudo_before_node = self.with_pseudo(Before);
|
||||
return Some(pseudo_before_node)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
self.get_jsmanaged().first_child_ref().map(|node| self.new_with_this_lifetime(&node))
|
||||
}
|
||||
}
|
||||
|
||||
fn text(&self) -> String {
|
||||
if self.pseudo == Before || self.pseudo == After {
|
||||
let layout_data_ref = self.borrow_layout_data();
|
||||
let node_layout_data_wrapper = layout_data_ref.get_ref();
|
||||
|
||||
if self.pseudo == Before {
|
||||
let before_style = node_layout_data_wrapper.data.before_style.get_ref();
|
||||
return get_content(&before_style.get_box().content)
|
||||
} else {
|
||||
let after_style = node_layout_data_wrapper.data.after_style.get_ref();
|
||||
return get_content(&after_style.get_box().content)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
if !self.get().is_text() {
|
||||
fail!("not text!")
|
||||
}
|
||||
let text: JS<Text> = self.get_jsmanaged().transmute_copy();
|
||||
(*text.unsafe_get()).characterdata.data.deref().borrow().clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<'ln> ThreadSafeLayoutNode<'ln> {
|
||||
/// Creates a new `ThreadSafeLayoutNode` from the given `LayoutNode`.
|
||||
pub fn new<'a>(node: &LayoutNode<'a>) -> ThreadSafeLayoutNode<'a> {
|
||||
ThreadSafeLayoutNode {
|
||||
node: node.clone(),
|
||||
pseudo: Normal,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `ThreadSafeLayoutNode` for the same `LayoutNode`
|
||||
/// with a different pseudo-element type.
|
||||
fn with_pseudo(&self, pseudo: PseudoElementType) -> ThreadSafeLayoutNode<'ln> {
|
||||
ThreadSafeLayoutNode {
|
||||
node: self.node.clone(),
|
||||
pseudo: pseudo,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the next sibling of this node. Unsafe and private because this can lead to races.
|
||||
unsafe fn next_sibling(&self) -> Option<ThreadSafeLayoutNode<'ln>> {
|
||||
if self.pseudo == Before || self.pseudo == BeforeBlock {
|
||||
return self.get_jsmanaged().first_child_ref().map(|node| self.new_with_this_lifetime(&node))
|
||||
}
|
||||
|
||||
self.get_jsmanaged().next_sibling_ref().map(|node| self.new_with_this_lifetime(&node))
|
||||
}
|
||||
|
||||
/// Returns an iterator over this node's children.
|
||||
pub fn children(&self) -> ThreadSafeLayoutNodeChildrenIterator<'ln> {
|
||||
ThreadSafeLayoutNodeChildrenIterator {
|
||||
current_node: self.first_child(),
|
||||
parent_node: Some(self.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
/// If this is an element, accesses the element data. Fails if this is not an element node.
|
||||
#[inline]
|
||||
pub fn as_element(&self) -> ThreadSafeLayoutElement {
|
||||
unsafe {
|
||||
assert!(self.get_jsmanaged().is_element_for_layout());
|
||||
let elem: JS<Element> = self.get_jsmanaged().transmute_copy();
|
||||
let element = elem.unsafe_get();
|
||||
// FIXME(pcwalton): Workaround until Rust gets multiple lifetime parameters on
|
||||
// implementations.
|
||||
ThreadSafeLayoutElement {
|
||||
element: &mut *element,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_pseudo_element_type(&self) -> PseudoElementType {
|
||||
self.pseudo
|
||||
}
|
||||
|
||||
pub fn is_block(&self, kind: PseudoElementType) -> bool {
|
||||
let mut layout_data_ref = self.mutate_layout_data();
|
||||
let node_layout_data_wrapper = layout_data_ref.get_mut_ref();
|
||||
|
||||
let display = match kind {
|
||||
Before | BeforeBlock => {
|
||||
let before_style = node_layout_data_wrapper.data.before_style.get_ref();
|
||||
before_style.get_box().display
|
||||
}
|
||||
After | AfterBlock => {
|
||||
let after_style = node_layout_data_wrapper.data.after_style.get_ref();
|
||||
after_style.get_box().display
|
||||
}
|
||||
Normal => {
|
||||
let after_style = node_layout_data_wrapper.shared_data.style.get_ref();
|
||||
after_style.get_box().display
|
||||
}
|
||||
};
|
||||
|
||||
display == display::block
|
||||
}
|
||||
|
||||
pub fn has_before_pseudo(&self) -> bool {
|
||||
let layout_data_wrapper = self.borrow_layout_data();
|
||||
let layout_data_wrapper_ref = layout_data_wrapper.get_ref();
|
||||
layout_data_wrapper_ref.data.before_style.is_some()
|
||||
}
|
||||
|
||||
pub fn has_after_pseudo(&self) -> bool {
|
||||
let layout_data_wrapper = self.borrow_layout_data();
|
||||
let layout_data_wrapper_ref = layout_data_wrapper.get_ref();
|
||||
layout_data_wrapper_ref.data.after_style.is_some()
|
||||
}
|
||||
|
||||
/// Borrows the layout data immutably. Fails on a conflicting borrow.
|
||||
#[inline(always)]
|
||||
pub fn borrow_layout_data<'a>(&'a self) -> Ref<'a,Option<LayoutDataWrapper>> {
|
||||
unsafe {
|
||||
mem::transmute(self.get().layout_data.borrow())
|
||||
}
|
||||
}
|
||||
|
||||
/// Borrows the layout data mutably. Fails on a conflicting borrow.
|
||||
#[inline(always)]
|
||||
pub fn mutate_layout_data<'a>(&'a self) -> RefMut<'a,Option<LayoutDataWrapper>> {
|
||||
unsafe {
|
||||
mem::transmute(self.get().layout_data.borrow_mut())
|
||||
}
|
||||
}
|
||||
|
||||
/// Traverses the tree in postorder.
|
||||
///
|
||||
/// TODO(pcwalton): Offer a parallel version with a compatible API.
|
||||
pub fn traverse_postorder_mut<T:PostorderNodeMutTraversal>(&mut self, traversal: &mut T)
|
||||
-> bool {
|
||||
if traversal.should_prune(self) {
|
||||
return true
|
||||
}
|
||||
|
||||
let mut opt_kid = self.first_child();
|
||||
loop {
|
||||
match opt_kid {
|
||||
None => break,
|
||||
Some(mut kid) => {
|
||||
if !kid.traverse_postorder_mut(traversal) {
|
||||
return false
|
||||
}
|
||||
unsafe {
|
||||
opt_kid = kid.next_sibling()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
traversal.process(self)
|
||||
}
|
||||
|
||||
pub fn is_ignorable_whitespace(&self) -> bool {
|
||||
match self.type_id() {
|
||||
Some(TextNodeTypeId) => {
|
||||
unsafe {
|
||||
let text: JS<Text> = self.get_jsmanaged().transmute_copy();
|
||||
if !is_whitespace((*text.unsafe_get()).characterdata.data.deref().borrow().as_slice()) {
|
||||
return false
|
||||
}
|
||||
|
||||
// NB: See the rules for `white-space` here:
|
||||
//
|
||||
// http://www.w3.org/TR/CSS21/text.html#propdef-white-space
|
||||
//
|
||||
// If you implement other values for this property, you will almost certainly
|
||||
// want to update this check.
|
||||
match self.style().get_inheritedtext().white_space {
|
||||
white_space::normal => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThreadSafeLayoutNodeChildrenIterator<'a> {
|
||||
current_node: Option<ThreadSafeLayoutNode<'a>>,
|
||||
parent_node: Option<ThreadSafeLayoutNode<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator<ThreadSafeLayoutNode<'a>> for ThreadSafeLayoutNodeChildrenIterator<'a> {
|
||||
fn next(&mut self) -> Option<ThreadSafeLayoutNode<'a>> {
|
||||
let node = self.current_node.clone();
|
||||
|
||||
match node {
|
||||
Some(ref node) => {
|
||||
if node.pseudo == After || node.pseudo == AfterBlock {
|
||||
return None
|
||||
}
|
||||
|
||||
match self.parent_node {
|
||||
Some(ref parent_node) => {
|
||||
if parent_node.pseudo == Normal {
|
||||
self.current_node = self.current_node.clone().and_then(|node| {
|
||||
unsafe {
|
||||
node.next_sibling()
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self.current_node = None;
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
match self.parent_node {
|
||||
Some(ref parent_node) => {
|
||||
if parent_node.has_after_pseudo() {
|
||||
let pseudo_after_node = if parent_node.is_block(After) && parent_node.pseudo == Normal {
|
||||
let pseudo_after_node = parent_node.with_pseudo(AfterBlock);
|
||||
Some(pseudo_after_node)
|
||||
} else if parent_node.pseudo == Normal || parent_node.pseudo == AfterBlock {
|
||||
let pseudo_after_node = parent_node.with_pseudo(After);
|
||||
Some(pseudo_after_node)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.current_node = pseudo_after_node;
|
||||
return self.current_node.clone()
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around elements that ensures layout can only ever access safe properties and cannot
|
||||
/// race on elements.
|
||||
pub struct ThreadSafeLayoutElement<'le> {
|
||||
element: &'le Element,
|
||||
}
|
||||
|
||||
impl<'le> ThreadSafeLayoutElement<'le> {
|
||||
#[inline]
|
||||
pub fn get_attr(&self, namespace: &Namespace, name: &str) -> Option<&'static str> {
|
||||
unsafe { self.element.get_attr_val_for_layout(namespace, name) }
|
||||
}
|
||||
}
|
||||
|
||||
/// A bottom-up, parallelizable traversal.
|
||||
pub trait PostorderNodeMutTraversal {
|
||||
/// The operation to perform. Return true to continue or false to stop.
|
||||
fn process<'a>(&'a mut self, node: &ThreadSafeLayoutNode<'a>) -> bool;
|
||||
|
||||
/// Returns true if this node should be pruned. If this returns true, we skip the operation
|
||||
/// entirely and do not process any descendant nodes. This is called *before* child nodes are
|
||||
/// visited. The default implementation never prunes any nodes.
|
||||
fn should_prune<'a>(&'a self, _node: &ThreadSafeLayoutNode<'a>) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Opaque type stored in type-unsafe work queues for parallel layout.
|
||||
/// Must be transmutable to and from LayoutNode/ThreadSafeLayoutNode.
|
||||
pub type UnsafeLayoutNode = (uint, uint);
|
||||
|
||||
pub fn layout_node_to_unsafe_layout_node(node: &LayoutNode) -> UnsafeLayoutNode {
|
||||
unsafe {
|
||||
let ptr: uint = mem::transmute_copy(node);
|
||||
(ptr, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME(#3044): This should be updated to use a real lifetime instead of
|
||||
// faking one.
|
||||
pub unsafe fn layout_node_from_unsafe_layout_node(node: &UnsafeLayoutNode) -> LayoutNode<'static> {
|
||||
let (node, _) = *node;
|
||||
mem::transmute(node)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue