Remove most of the things in layout 2020

We keep mostly the query system. There is probably more to delete but
that's a good start I think.
This commit is contained in:
Anthony Ramine 2019-07-24 11:19:23 +02:00
parent 4846d76e82
commit 317d700f5d
47 changed files with 75 additions and 29854 deletions

23
Cargo.lock generated
View file

@ -2332,47 +2332,24 @@ name = "layout_2020"
version = "0.0.1"
dependencies = [
"app_units 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"canvas_traits 0.0.1",
"crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"embedder_traits 0.0.1",
"euclid 0.19.8 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"gfx 0.0.1",
"gfx_traits 0.0.1",
"html5ever 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ipc-channel 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"malloc_size_of 0.0.1",
"msg 0.0.1",
"net_traits 0.0.1",
"num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
"ordered-float 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"profile_traits 0.0.1",
"range 0.0.1",
"rayon 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"script_layout_interface 0.0.1",
"script_traits 0.0.1",
"selectors 0.21.0",
"serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
"servo_arc 0.1.1",
"servo_atoms 0.0.1",
"servo_config 0.0.1",
"servo_geometry 0.0.1",
"servo_url 0.0.1",
"size_of_test 0.0.1",
"smallvec 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)",
"style 0.0.1",
"style_traits 0.0.1",
"unicode-bidi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-script 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"webrender_api 0.60.0 (git+https://github.com/servo/webrender)",
"xi-unicode 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]

View file

@ -14,46 +14,21 @@ doctest = false
[dependencies]
app_units = "0.7"
atomic_refcell = "0.1"
bitflags = "1.0"
canvas_traits = {path = "../canvas_traits"}
crossbeam-channel = "0.3"
embedder_traits = {path = "../embedder_traits"}
euclid = "0.19"
fnv = "1.0"
fxhash = "0.2"
gfx = {path = "../gfx"}
gfx_traits = {path = "../gfx_traits"}
html5ever = "0.23"
ipc-channel = "0.11"
libc = "0.2"
log = "0.4"
malloc_size_of = { path = "../malloc_size_of" }
msg = {path = "../msg"}
net_traits = {path = "../net_traits"}
num-traits = "0.2"
ordered-float = "1.0"
parking_lot = "0.8"
profile_traits = {path = "../profile_traits"}
range = {path = "../range"}
rayon = "1"
script_layout_interface = {path = "../script_layout_interface"}
script_traits = {path = "../script_traits"}
selectors = { path = "../selectors" }
serde = "1.0"
servo_arc = {path = "../servo_arc"}
servo_atoms = {path = "../atoms"}
servo_geometry = {path = "../geometry"}
serde_json = "1.0"
servo_config = {path = "../config"}
servo_url = {path = "../url"}
smallvec = { version = "0.6", features = ["std", "union"] }
style = {path = "../style", features = ["servo", "servo-layout-2020"]}
style_traits = {path = "../style_traits"}
unicode-bidi = {version = "0.3", features = ["with_serde"]}
unicode-script = {version = "0.3", features = ["harfbuzz"]}
webrender_api = {git = "https://github.com/servo/webrender", features = ["ipc"]}
xi-unicode = "0.1.0"
[dev-dependencies]
size_of_test = {path = "../size_of_test"}

View file

@ -1,211 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! CSS transitions and animations.
use crate::context::LayoutContext;
use crate::display_list::items::OpaqueNode;
use crate::flow::{Flow, GetBaseFlow};
use crate::opaque_node::OpaqueNodeMethods;
use crossbeam_channel::Receiver;
use fxhash::{FxHashMap, FxHashSet};
use ipc_channel::ipc::IpcSender;
use msg::constellation_msg::PipelineId;
use script_traits::UntrustedNodeAddress;
use script_traits::{AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg};
use style::animation::{update_style_for_animation, Animation};
use style::dom::TElement;
use style::font_metrics::ServoMetricsProvider;
use style::selector_parser::RestyleDamage;
use style::timer::Timer;
/// Processes any new animations that were discovered after style recalculation.
/// Also expire any old animations that have completed, inserting them into
/// `expired_animations`.
pub fn update_animation_state<E>(
constellation_chan: &IpcSender<ConstellationMsg>,
script_chan: &IpcSender<ConstellationControlMsg>,
running_animations: &mut FxHashMap<OpaqueNode, Vec<Animation>>,
expired_animations: &mut FxHashMap<OpaqueNode, Vec<Animation>>,
mut keys_to_remove: FxHashSet<OpaqueNode>,
mut newly_transitioning_nodes: Option<&mut Vec<UntrustedNodeAddress>>,
new_animations_receiver: &Receiver<Animation>,
pipeline_id: PipelineId,
timer: &Timer,
) where
E: TElement,
{
let mut new_running_animations = vec![];
while let Ok(animation) = new_animations_receiver.try_recv() {
let mut should_push = true;
if let Animation::Keyframes(ref node, _, ref name, ref state) = animation {
// If the animation was already present in the list for the
// node, just update its state, else push the new animation to
// run.
if let Some(ref mut animations) = running_animations.get_mut(node) {
// TODO: This being linear is probably not optimal.
for anim in animations.iter_mut() {
if let Animation::Keyframes(_, _, ref anim_name, ref mut anim_state) = *anim {
if *name == *anim_name {
debug!("update_animation_state: Found other animation {}", name);
anim_state.update_from_other(&state, timer);
should_push = false;
break;
}
}
}
}
}
if should_push {
new_running_animations.push(animation);
}
}
if running_animations.is_empty() && new_running_animations.is_empty() {
// Nothing to do. Return early so we don't flood the compositor with
// `ChangeRunningAnimationsState` messages.
return;
}
let now = timer.seconds();
// Expire old running animations.
//
// TODO: Do not expunge Keyframes animations, since we need that state if
// the animation gets re-triggered. Probably worth splitting in two
// different maps, or at least using a linked list?
for (key, running_animations) in running_animations.iter_mut() {
let mut animations_still_running = vec![];
for mut running_animation in running_animations.drain(..) {
let still_running = !running_animation.is_expired() &&
match running_animation {
Animation::Transition(_, started_at, ref frame) => {
now < started_at + frame.duration
},
Animation::Keyframes(_, _, _, ref mut state) => {
// This animation is still running, or we need to keep
// iterating.
now < state.started_at + state.duration || state.tick()
},
};
debug!(
"update_animation_state({:?}): {:?}",
still_running, running_animation
);
if still_running {
animations_still_running.push(running_animation);
continue;
}
if let Animation::Transition(node, _, ref frame) = running_animation {
script_chan
.send(ConstellationControlMsg::TransitionEnd(
node.to_untrusted_node_address(),
frame.property_animation.property_name().into(),
frame.duration,
))
.unwrap();
}
expired_animations
.entry(*key)
.or_insert_with(Vec::new)
.push(running_animation);
}
if animations_still_running.is_empty() {
keys_to_remove.insert(*key);
} else {
*running_animations = animations_still_running
}
}
for key in keys_to_remove {
running_animations.remove(&key).unwrap();
}
// Add new running animations.
for new_running_animation in new_running_animations {
if new_running_animation.is_transition() {
match newly_transitioning_nodes {
Some(ref mut nodes) => {
nodes.push(new_running_animation.node().to_untrusted_node_address());
},
None => {
warn!("New transition encountered from compositor-initiated layout.");
},
}
}
running_animations
.entry(*new_running_animation.node())
.or_insert_with(Vec::new)
.push(new_running_animation)
}
let animation_state = if running_animations.is_empty() {
AnimationState::NoAnimationsPresent
} else {
AnimationState::AnimationsPresent
};
constellation_chan
.send(ConstellationMsg::ChangeRunningAnimationsState(
pipeline_id,
animation_state,
))
.unwrap();
}
/// Recalculates style for a set of animations. This does *not* run with the DOM
/// lock held. Returns a set of nodes associated with animations that are no longer
/// valid.
pub fn recalc_style_for_animations<E>(
context: &LayoutContext,
flow: &mut dyn Flow,
animations: &FxHashMap<OpaqueNode, Vec<Animation>>,
) -> FxHashSet<OpaqueNode>
where
E: TElement,
{
let mut invalid_nodes = animations.keys().cloned().collect();
do_recalc_style_for_animations::<E>(context, flow, animations, &mut invalid_nodes);
invalid_nodes
}
fn do_recalc_style_for_animations<E>(
context: &LayoutContext,
flow: &mut dyn Flow,
animations: &FxHashMap<OpaqueNode, Vec<Animation>>,
invalid_nodes: &mut FxHashSet<OpaqueNode>,
) where
E: TElement,
{
let mut damage = RestyleDamage::empty();
flow.mutate_fragments(&mut |fragment| {
if let Some(ref animations) = animations.get(&fragment.node) {
invalid_nodes.remove(&fragment.node);
for animation in animations.iter() {
let old_style = fragment.style.clone();
update_style_for_animation::<E>(
&context.style_context,
animation,
&mut fragment.style,
&ServoMetricsProvider,
);
let difference =
RestyleDamage::compute_style_difference(&old_style, &fragment.style);
damage |= difference.damage;
}
}
});
let base = flow.mut_base();
base.restyle_damage.insert(damage);
for kid in base.children.iter_mut() {
do_recalc_style_for_animations::<E>(context, kid, animations, invalid_nodes)
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2,100 +2,12 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Data needed by the layout thread.
use crate::display_list::items::{OpaqueNode, WebRenderImageInfo};
use crate::opaque_node::OpaqueNodeMethods;
use fnv::FnvHasher;
use gfx::font_cache_thread::FontCacheThread;
use gfx::font_context::FontContext;
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use msg::constellation_msg::PipelineId;
use net_traits::image_cache::{CanRequestImages, ImageCache, ImageState};
use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder};
use parking_lot::RwLock;
use script_layout_interface::{PendingImage, PendingImageState};
use script_traits::Painter;
use script_traits::UntrustedNodeAddress;
use servo_atoms::Atom;
use servo_url::ServoUrl;
use std::cell::{RefCell, RefMut};
use std::collections::HashMap;
use std::hash::BuildHasherDefault;
use std::sync::{Arc, Mutex};
use std::thread;
use style::context::RegisteredSpeculativePainter;
use style::context::SharedStyleContext;
pub type LayoutFontContext = FontContext<FontCacheThread>;
thread_local!(static FONT_CONTEXT_KEY: RefCell<Option<LayoutFontContext>> = RefCell::new(None));
pub fn with_thread_local_font_context<F, R>(layout_context: &LayoutContext, f: F) -> R
where
F: FnOnce(&mut LayoutFontContext) -> R,
{
FONT_CONTEXT_KEY.with(|k| {
let mut font_context = k.borrow_mut();
if font_context.is_none() {
let font_cache_thread = layout_context.font_cache_thread.lock().unwrap().clone();
*font_context = Some(FontContext::new(font_cache_thread));
}
f(&mut RefMut::map(font_context, |x| x.as_mut().unwrap()))
})
}
pub fn malloc_size_of_persistent_local_context(ops: &mut MallocSizeOfOps) -> usize {
FONT_CONTEXT_KEY.with(|r| {
if let Some(ref context) = *r.borrow() {
context.size_of(ops)
} else {
0
}
})
}
/// Layout information shared among all workers. This must be thread-safe.
pub struct LayoutContext<'a> {
/// The pipeline id of this LayoutContext.
pub id: PipelineId,
/// Bits shared by the layout and style system.
pub style_context: SharedStyleContext<'a>,
/// Reference to the script thread image cache.
pub image_cache: Arc<dyn ImageCache>,
/// Interface to the font cache thread.
pub font_cache_thread: Mutex<FontCacheThread>,
/// A cache of WebRender image info.
pub webrender_image_cache: Arc<
RwLock<
HashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo, BuildHasherDefault<FnvHasher>>,
>,
>,
/// Paint worklets
pub registered_painters: &'a dyn RegisteredPainters,
/// A list of in-progress image loads to be shared with the script thread.
/// A None value means that this layout was not initiated by the script thread.
pub pending_images: Option<Mutex<Vec<PendingImage>>>,
/// A list of nodes that have just initiated a CSS transition.
/// A None value means that this layout was not initiated by the script thread.
pub newly_transitioning_nodes: Option<Mutex<Vec<UntrustedNodeAddress>>>,
}
impl<'a> Drop for LayoutContext<'a> {
fn drop(&mut self) {
if !thread::panicking() {
if let Some(ref pending_images) = self.pending_images {
assert!(pending_images.lock().unwrap().is_empty());
}
}
}
}
impl<'a> LayoutContext<'a> {
@ -103,99 +15,4 @@ impl<'a> LayoutContext<'a> {
pub fn shared_context(&self) -> &SharedStyleContext {
&self.style_context
}
pub fn get_or_request_image_or_meta(
&self,
node: OpaqueNode,
url: ServoUrl,
use_placeholder: UsePlaceholder,
) -> Option<ImageOrMetadataAvailable> {
//XXXjdm For cases where we do not request an image, we still need to
// ensure the node gets another script-initiated reflow or it
// won't be requested at all.
let can_request = if self.pending_images.is_some() {
CanRequestImages::Yes
} else {
CanRequestImages::No
};
// See if the image is already available
let result =
self.image_cache
.find_image_or_metadata(url.clone(), use_placeholder, can_request);
match result {
Ok(image_or_metadata) => Some(image_or_metadata),
// Image failed to load, so just return nothing
Err(ImageState::LoadError) => None,
// Not yet requested - request image or metadata from the cache
Err(ImageState::NotRequested(id)) => {
let image = PendingImage {
state: PendingImageState::Unrequested(url),
node: node.to_untrusted_node_address(),
id: id,
};
self.pending_images
.as_ref()
.unwrap()
.lock()
.unwrap()
.push(image);
None
},
// Image has been requested, is still pending. Return no image for this paint loop.
// When the image loads it will trigger a reflow and/or repaint.
Err(ImageState::Pending(id)) => {
//XXXjdm if self.pending_images is not available, we should make sure that
// this node gets marked dirty again so it gets a script-initiated
// reflow that deals with this properly.
if let Some(ref pending_images) = self.pending_images {
let image = PendingImage {
state: PendingImageState::PendingResponse,
node: node.to_untrusted_node_address(),
id: id,
};
pending_images.lock().unwrap().push(image);
}
None
},
}
}
pub fn get_webrender_image_for_url(
&self,
node: OpaqueNode,
url: ServoUrl,
use_placeholder: UsePlaceholder,
) -> Option<WebRenderImageInfo> {
if let Some(existing_webrender_image) = self
.webrender_image_cache
.read()
.get(&(url.clone(), use_placeholder))
{
return Some((*existing_webrender_image).clone());
}
match self.get_or_request_image_or_meta(node, url.clone(), use_placeholder) {
Some(ImageOrMetadataAvailable::ImageAvailable(image, _)) => {
let image_info = WebRenderImageInfo::from_image(&*image);
if image_info.key.is_none() {
Some(image_info)
} else {
let mut webrender_image_cache = self.webrender_image_cache.write();
webrender_image_cache.insert((url, use_placeholder), image_info);
Some(image_info)
}
},
None | Some(ImageOrMetadataAvailable::MetadataAvailable(_)) => None,
}
}
}
/// A registered painter
pub trait RegisteredPainter: RegisteredSpeculativePainter + Painter {}
/// A set of registered painters
pub trait RegisteredPainters: Sync {
/// Look up a painter
fn get(&self, name: &Atom) -> Option<&dyn RegisteredPainter>;
}

View file

@ -2,67 +2,17 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::construct::ConstructionResult;
use atomic_refcell::AtomicRefCell;
use script_layout_interface::StyleData;
#[repr(C)]
pub struct StyleAndLayoutData {
/// Data accessed by script_layout_interface. This must be first to allow
/// casting between StyleAndLayoutData and StyleData.
pub style_data: StyleData,
/// The layout data associated with a node.
pub layout_data: AtomicRefCell<LayoutData>,
}
impl StyleAndLayoutData {
pub fn new() -> Self {
Self {
style_data: StyleData::new(),
layout_data: AtomicRefCell::new(LayoutData::new()),
}
}
}
/// Data that layout associates with a node.
#[repr(C)]
pub struct LayoutData {
/// 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,
pub details_summary_flow_construction_result: ConstructionResult,
pub details_content_flow_construction_result: ConstructionResult,
/// Various flags.
pub flags: LayoutDataFlags,
}
impl LayoutData {
/// Creates new layout data.
pub fn new() -> LayoutData {
Self {
flow_construction_result: ConstructionResult::None,
before_flow_construction_result: ConstructionResult::None,
after_flow_construction_result: ConstructionResult::None,
details_summary_flow_construction_result: ConstructionResult::None,
details_content_flow_construction_result: ConstructionResult::None,
flags: LayoutDataFlags::empty(),
}
}
}
bitflags! {
pub struct LayoutDataFlags: u8 {
#[doc = "Whether a flow has been newly constructed."]
const HAS_NEWLY_CONSTRUCTED_FLOW = 0x01;
#[doc = "Whether this node has been traversed by layout."]
const HAS_BEEN_TRAVERSED = 0x02;
}
}

View file

@ -1,336 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use crate::display_list::border;
use app_units::Au;
use euclid::{Point2D, Rect, SideOffsets2D, Size2D};
use style::computed_values::background_attachment::single_value::T as BackgroundAttachment;
use style::computed_values::background_clip::single_value::T as BackgroundClip;
use style::computed_values::background_origin::single_value::T as BackgroundOrigin;
use style::properties::style_structs::Background;
use style::values::computed::{BackgroundSize, NonNegativeLengthPercentageOrAuto};
use style::values::specified::background::BackgroundRepeatKeyword;
use webrender_api::BorderRadius;
/// Placment information for both image and gradient backgrounds.
#[derive(Clone, Copy, Debug)]
pub struct BackgroundPlacement {
/// Rendering bounds. The background will start in the uppper-left corner
/// and fill the whole area.
pub bounds: Rect<Au>,
/// Background tile size. Some backgrounds are repeated. These are the
/// dimensions of a single image of the background.
pub tile_size: Size2D<Au>,
/// Spacing between tiles. Some backgrounds are not repeated seamless
/// but have seams between them like tiles in real life.
pub tile_spacing: Size2D<Au>,
/// A clip area. While the background is rendered according to all the
/// measures above it is only shown within these bounds.
pub clip_rect: Rect<Au>,
/// Rounded corners for the clip_rect.
pub clip_radii: BorderRadius,
/// Whether or not the background is fixed to the viewport.
pub fixed: bool,
}
/// Access element at index modulo the array length.
///
/// Obviously it does not work with empty arrays.
///
/// This is used for multiple layered background images.
/// See: https://drafts.csswg.org/css-backgrounds-3/#layering
pub fn get_cyclic<T>(arr: &[T], index: usize) -> &T {
&arr[index % arr.len()]
}
/// For a given area and an image compute how big the
/// image should be displayed on the background.
fn compute_background_image_size(
bg_size: BackgroundSize,
bounds_size: Size2D<Au>,
intrinsic_size: Option<Size2D<Au>>,
) -> Size2D<Au> {
match intrinsic_size {
None => match bg_size {
BackgroundSize::Cover | BackgroundSize::Contain => bounds_size,
BackgroundSize::ExplicitSize { width, height } => Size2D::new(
width
.to_used_value(bounds_size.width)
.unwrap_or(bounds_size.width),
height
.to_used_value(bounds_size.height)
.unwrap_or(bounds_size.height),
),
},
Some(own_size) => {
// If `image_aspect_ratio` < `bounds_aspect_ratio`, the image is tall; otherwise, it is
// wide.
let image_aspect_ratio = own_size.width.to_f32_px() / own_size.height.to_f32_px();
let bounds_aspect_ratio =
bounds_size.width.to_f32_px() / bounds_size.height.to_f32_px();
match (bg_size, image_aspect_ratio < bounds_aspect_ratio) {
(BackgroundSize::Contain, false) | (BackgroundSize::Cover, true) => Size2D::new(
bounds_size.width,
bounds_size.width.scale_by(image_aspect_ratio.recip()),
),
(BackgroundSize::Contain, true) | (BackgroundSize::Cover, false) => Size2D::new(
bounds_size.height.scale_by(image_aspect_ratio),
bounds_size.height,
),
(
BackgroundSize::ExplicitSize {
width,
height: NonNegativeLengthPercentageOrAuto::Auto,
},
_,
) => {
let width = width
.to_used_value(bounds_size.width)
.unwrap_or(own_size.width);
Size2D::new(width, width.scale_by(image_aspect_ratio.recip()))
},
(
BackgroundSize::ExplicitSize {
width: NonNegativeLengthPercentageOrAuto::Auto,
height,
},
_,
) => {
let height = height
.to_used_value(bounds_size.height)
.unwrap_or(own_size.height);
Size2D::new(height.scale_by(image_aspect_ratio), height)
},
(BackgroundSize::ExplicitSize { width, height }, _) => Size2D::new(
width
.to_used_value(bounds_size.width)
.unwrap_or(own_size.width),
height
.to_used_value(bounds_size.height)
.unwrap_or(own_size.height),
),
}
},
}
}
/// Compute a rounded clip rect for the background.
pub fn clip(
bg_clip: BackgroundClip,
absolute_bounds: Rect<Au>,
border: SideOffsets2D<Au>,
border_padding: SideOffsets2D<Au>,
border_radii: BorderRadius,
) -> (Rect<Au>, BorderRadius) {
match bg_clip {
BackgroundClip::BorderBox => (absolute_bounds, border_radii),
BackgroundClip::PaddingBox => (
absolute_bounds.inner_rect(border),
border::inner_radii(border_radii, border),
),
BackgroundClip::ContentBox => (
absolute_bounds.inner_rect(border_padding),
border::inner_radii(border_radii, border_padding),
),
}
}
/// Determines where to place an element background image or gradient.
///
/// Photos have their resolution as intrinsic size while gradients have
/// no intrinsic size.
pub fn placement(
bg: &Background,
viewport_size: Size2D<Au>,
absolute_bounds: Rect<Au>,
intrinsic_size: Option<Size2D<Au>>,
border: SideOffsets2D<Au>,
border_padding: SideOffsets2D<Au>,
border_radii: BorderRadius,
index: usize,
) -> BackgroundPlacement {
let bg_attachment = *get_cyclic(&bg.background_attachment.0, index);
let bg_clip = *get_cyclic(&bg.background_clip.0, index);
let bg_origin = *get_cyclic(&bg.background_origin.0, index);
let bg_position_x = get_cyclic(&bg.background_position_x.0, index);
let bg_position_y = get_cyclic(&bg.background_position_y.0, index);
let bg_repeat = get_cyclic(&bg.background_repeat.0, index);
let bg_size = *get_cyclic(&bg.background_size.0, index);
let (clip_rect, clip_radii) = clip(
bg_clip,
absolute_bounds,
border,
border_padding,
border_radii,
);
let mut fixed = false;
let mut bounds = match bg_attachment {
BackgroundAttachment::Scroll => match bg_origin {
BackgroundOrigin::BorderBox => absolute_bounds,
BackgroundOrigin::PaddingBox => absolute_bounds.inner_rect(border),
BackgroundOrigin::ContentBox => absolute_bounds.inner_rect(border_padding),
},
BackgroundAttachment::Fixed => {
fixed = true;
Rect::new(Point2D::origin(), viewport_size)
},
};
let mut tile_size = compute_background_image_size(bg_size, bounds.size, intrinsic_size);
let mut tile_spacing = Size2D::zero();
let own_position = bounds.size - tile_size;
let pos_x = bg_position_x.to_used_value(own_position.width);
let pos_y = bg_position_y.to_used_value(own_position.height);
tile_image_axis(
bg_repeat.0,
&mut bounds.origin.x,
&mut bounds.size.width,
&mut tile_size.width,
&mut tile_spacing.width,
pos_x,
clip_rect.origin.x,
clip_rect.size.width,
);
tile_image_axis(
bg_repeat.1,
&mut bounds.origin.y,
&mut bounds.size.height,
&mut tile_size.height,
&mut tile_spacing.height,
pos_y,
clip_rect.origin.y,
clip_rect.size.height,
);
BackgroundPlacement {
bounds,
tile_size,
tile_spacing,
clip_rect,
clip_radii,
fixed,
}
}
fn tile_image_round(
position: &mut Au,
size: &mut Au,
absolute_anchor_origin: Au,
image_size: &mut Au,
) {
if *size == Au(0) || *image_size == Au(0) {
*position = Au(0);
*size = Au(0);
return;
}
let number_of_tiles = (size.to_f32_px() / image_size.to_f32_px()).round().max(1.0);
*image_size = *size / (number_of_tiles as i32);
tile_image(position, size, absolute_anchor_origin, *image_size);
}
fn tile_image_spaced(
position: &mut Au,
size: &mut Au,
tile_spacing: &mut Au,
absolute_anchor_origin: Au,
image_size: Au,
) {
if *size == Au(0) || image_size == Au(0) {
*position = Au(0);
*size = Au(0);
*tile_spacing = Au(0);
return;
}
// Per the spec, if the space available is not enough for two images, just tile as
// normal but only display a single tile.
if image_size * 2 >= *size {
tile_image(position, size, absolute_anchor_origin, image_size);
*tile_spacing = Au(0);
*size = image_size;
return;
}
// Take the box size, remove room for two tiles on the edges, and then calculate how many
// other tiles fit in between them.
let size_remaining = *size - (image_size * 2);
let num_middle_tiles = (size_remaining.to_f32_px() / image_size.to_f32_px()).floor() as i32;
// Allocate the remaining space as padding between tiles. background-position is ignored
// as per the spec, so the position is just the box origin. We are also ignoring
// background-attachment here, which seems unspecced when combined with
// background-repeat: space.
let space_for_middle_tiles = image_size * num_middle_tiles;
*tile_spacing = (size_remaining - space_for_middle_tiles) / (num_middle_tiles + 1);
}
/// Tile an image
fn tile_image(position: &mut Au, size: &mut Au, absolute_anchor_origin: Au, image_size: Au) {
// Avoid division by zero below!
// Images with a zero width or height are not displayed.
// Therefore the positions do not matter and can be left unchanged.
// NOTE: A possible optimization is not to build
// display items in this case at all.
if image_size == Au(0) {
return;
}
let delta_pixels = absolute_anchor_origin - *position;
let image_size_px = image_size.to_f32_px();
let tile_count = ((delta_pixels.to_f32_px() + image_size_px - 1.0) / image_size_px).floor();
let offset = image_size * (tile_count as i32);
let new_position = absolute_anchor_origin - offset;
*size = *position - new_position + *size;
*position = new_position;
}
/// For either the x or the y axis ajust various values to account for tiling.
///
/// This is done separately for both axes because the repeat keywords may differ.
fn tile_image_axis(
repeat: BackgroundRepeatKeyword,
position: &mut Au,
size: &mut Au,
tile_size: &mut Au,
tile_spacing: &mut Au,
offset: Au,
clip_origin: Au,
clip_size: Au,
) {
let absolute_anchor_origin = *position + offset;
match repeat {
BackgroundRepeatKeyword::NoRepeat => {
*position += offset;
*size = *tile_size;
},
BackgroundRepeatKeyword::Repeat => {
*position = clip_origin;
*size = clip_size;
tile_image(position, size, absolute_anchor_origin, *tile_size);
},
BackgroundRepeatKeyword::Space => {
tile_image_spaced(
position,
size,
tile_spacing,
absolute_anchor_origin,
*tile_size,
);
let combined_tile_size = *tile_size + *tile_spacing;
*position = clip_origin;
*size = clip_size;
tile_image(position, size, absolute_anchor_origin, combined_tile_size);
},
BackgroundRepeatKeyword::Round => {
tile_image_round(position, size, absolute_anchor_origin, tile_size);
*position = clip_origin;
*size = clip_size;
tile_image(position, size, absolute_anchor_origin, *tile_size);
},
}
}

View file

@ -1,199 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use crate::display_list::ToLayout;
use app_units::Au;
use euclid::{Rect, SideOffsets2D, Size2D};
use style::computed_values::border_image_outset::T as BorderImageOutset;
use style::properties::style_structs::Border;
use style::values::computed::NumberOrPercentage;
use style::values::computed::{BorderCornerRadius, BorderImageWidth};
use style::values::computed::{BorderImageSideWidth, NonNegativeLengthOrNumber};
use style::values::generics::rect::Rect as StyleRect;
use style::values::generics::NonNegative;
use webrender_api::units::{LayoutSideOffsets, LayoutSize};
use webrender_api::{BorderRadius, BorderSide, BorderStyle, ColorF, NormalBorder};
/// Computes a border radius size against the containing size.
///
/// Note that percentages in `border-radius` are resolved against the relevant
/// box dimension instead of only against the width per [1]:
///
/// > Percentages: Refer to corresponding dimension of the border box.
///
/// [1]: https://drafts.csswg.org/css-backgrounds-3/#border-radius
fn corner_radius(radius: BorderCornerRadius, containing_size: Size2D<Au>) -> Size2D<Au> {
let w = radius.0.width().to_used_value(containing_size.width);
let h = radius.0.height().to_used_value(containing_size.height);
Size2D::new(w, h)
}
fn scaled_radii(radii: BorderRadius, factor: f32) -> BorderRadius {
BorderRadius {
top_left: radii.top_left * factor,
top_right: radii.top_right * factor,
bottom_left: radii.bottom_left * factor,
bottom_right: radii.bottom_right * factor,
}
}
fn overlapping_radii(size: LayoutSize, radii: BorderRadius) -> BorderRadius {
// No two corners' border radii may add up to more than the length of the edge
// between them. To prevent that, all radii are scaled down uniformly.
fn scale_factor(radius_a: f32, radius_b: f32, edge_length: f32) -> f32 {
let required = radius_a + radius_b;
if required <= edge_length {
1.0
} else {
edge_length / required
}
}
let top_factor = scale_factor(radii.top_left.width, radii.top_right.width, size.width);
let bottom_factor = scale_factor(
radii.bottom_left.width,
radii.bottom_right.width,
size.width,
);
let left_factor = scale_factor(radii.top_left.height, radii.bottom_left.height, size.height);
let right_factor = scale_factor(
radii.top_right.height,
radii.bottom_right.height,
size.height,
);
let min_factor = top_factor
.min(bottom_factor)
.min(left_factor)
.min(right_factor);
if min_factor < 1.0 {
scaled_radii(radii, min_factor)
} else {
radii
}
}
/// Determine the four corner radii of a border.
///
/// Radii may either be absolute or relative to the absolute bounds.
/// Each corner radius has a width and a height which may differ.
/// Lastly overlapping radii are shrank so they don't collide anymore.
pub fn radii(abs_bounds: Rect<Au>, border_style: &Border) -> BorderRadius {
// TODO(cgaebel): Support border radii even in the case of multiple border widths.
// This is an extension of supporting elliptical radii. For now, all percentage
// radii will be relative to the width.
overlapping_radii(
abs_bounds.size.to_layout(),
BorderRadius {
top_left: corner_radius(border_style.border_top_left_radius, abs_bounds.size)
.to_layout(),
top_right: corner_radius(border_style.border_top_right_radius, abs_bounds.size)
.to_layout(),
bottom_right: corner_radius(border_style.border_bottom_right_radius, abs_bounds.size)
.to_layout(),
bottom_left: corner_radius(border_style.border_bottom_left_radius, abs_bounds.size)
.to_layout(),
},
)
}
/// Calculates radii for the inner side.
///
/// Radii usually describe the outer side of a border but for the lines to look nice
/// the inner radii need to be smaller depending on the line width.
///
/// This is used to determine clipping areas.
pub fn inner_radii(mut radii: BorderRadius, offsets: SideOffsets2D<Au>) -> BorderRadius {
fn inner_length(x: f32, offset: Au) -> f32 {
0.0_f32.max(x - offset.to_f32_px())
}
radii.top_left.width = inner_length(radii.top_left.width, offsets.left);
radii.bottom_left.width = inner_length(radii.bottom_left.width, offsets.left);
radii.top_right.width = inner_length(radii.top_right.width, offsets.right);
radii.bottom_right.width = inner_length(radii.bottom_right.width, offsets.right);
radii.top_left.height = inner_length(radii.top_left.height, offsets.top);
radii.top_right.height = inner_length(radii.top_right.height, offsets.top);
radii.bottom_left.height = inner_length(radii.bottom_left.height, offsets.bottom);
radii.bottom_right.height = inner_length(radii.bottom_right.height, offsets.bottom);
radii
}
/// Creates a four-sided border with square corners and uniform color and width.
pub fn simple(color: ColorF, style: BorderStyle) -> NormalBorder {
let side = BorderSide { color, style };
NormalBorder {
left: side,
right: side,
top: side,
bottom: side,
radius: BorderRadius::zero(),
do_aa: true,
}
}
fn side_image_outset(outset: NonNegativeLengthOrNumber, border_width: Au) -> Au {
match outset {
NonNegativeLengthOrNumber::Length(length) => length.into(),
NonNegativeLengthOrNumber::Number(factor) => border_width.scale_by(factor.0),
}
}
/// Compute the additional border-image area.
pub fn image_outset(outset: BorderImageOutset, border: SideOffsets2D<Au>) -> SideOffsets2D<Au> {
SideOffsets2D::new(
side_image_outset(outset.0, border.top),
side_image_outset(outset.1, border.right),
side_image_outset(outset.2, border.bottom),
side_image_outset(outset.3, border.left),
)
}
fn side_image_width(
border_image_width: BorderImageSideWidth,
border_width: f32,
total_length: Au,
) -> f32 {
match border_image_width {
BorderImageSideWidth::LengthPercentage(v) => v.to_used_value(total_length).to_f32_px(),
BorderImageSideWidth::Number(x) => border_width * x.0,
BorderImageSideWidth::Auto => border_width,
}
}
pub fn image_width(
width: &BorderImageWidth,
border: LayoutSideOffsets,
border_area: Size2D<Au>,
) -> LayoutSideOffsets {
LayoutSideOffsets::new(
side_image_width(width.0, border.top, border_area.height),
side_image_width(width.1, border.right, border_area.width),
side_image_width(width.2, border.bottom, border_area.height),
side_image_width(width.3, border.left, border_area.width),
)
}
fn resolve_percentage(value: NonNegative<NumberOrPercentage>, length: i32) -> i32 {
match value.0 {
NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as i32,
NumberOrPercentage::Number(n) => n.round() as i32,
}
}
pub fn image_slice(
border_image_slice: &StyleRect<NonNegative<NumberOrPercentage>>,
width: i32,
height: i32,
) -> SideOffsets2D<i32> {
SideOffsets2D::new(
resolve_percentage(border_image_slice.0, height),
resolve_percentage(border_image_slice.1, width),
resolve_percentage(border_image_slice.2, height),
resolve_percentage(border_image_slice.3, width),
)
}

File diff suppressed because it is too large Load diff

View file

@ -1,167 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use app_units::Au;
use euclid::{Point2D, Rect, SideOffsets2D, Size2D, Vector2D};
use style::computed_values::image_rendering::T as ImageRendering;
use style::computed_values::mix_blend_mode::T as MixBlendMode;
use style::computed_values::transform_style::T as TransformStyle;
use style::values::computed::{BorderStyle, Filter};
use style::values::specified::border::BorderImageRepeatKeyword;
use style::values::RGBA;
use webrender_api as wr;
pub trait ToLayout {
type Type;
fn to_layout(&self) -> Self::Type;
}
impl ToLayout for BorderStyle {
type Type = wr::BorderStyle;
fn to_layout(&self) -> Self::Type {
match *self {
BorderStyle::None => wr::BorderStyle::None,
BorderStyle::Solid => wr::BorderStyle::Solid,
BorderStyle::Double => wr::BorderStyle::Double,
BorderStyle::Dotted => wr::BorderStyle::Dotted,
BorderStyle::Dashed => wr::BorderStyle::Dashed,
BorderStyle::Hidden => wr::BorderStyle::Hidden,
BorderStyle::Groove => wr::BorderStyle::Groove,
BorderStyle::Ridge => wr::BorderStyle::Ridge,
BorderStyle::Inset => wr::BorderStyle::Inset,
BorderStyle::Outset => wr::BorderStyle::Outset,
}
}
}
impl ToLayout for Filter {
type Type = wr::FilterOp;
fn to_layout(&self) -> Self::Type {
match *self {
Filter::Blur(radius) => wr::FilterOp::Blur(radius.px()),
Filter::Brightness(amount) => wr::FilterOp::Brightness(amount.0),
Filter::Contrast(amount) => wr::FilterOp::Contrast(amount.0),
Filter::Grayscale(amount) => wr::FilterOp::Grayscale(amount.0),
Filter::HueRotate(angle) => wr::FilterOp::HueRotate(angle.radians()),
Filter::Invert(amount) => wr::FilterOp::Invert(amount.0),
Filter::Opacity(amount) => wr::FilterOp::Opacity(amount.0.into(), amount.0),
Filter::Saturate(amount) => wr::FilterOp::Saturate(amount.0),
Filter::Sepia(amount) => wr::FilterOp::Sepia(amount.0),
// Statically check that DropShadow is impossible.
Filter::DropShadow(ref shadow) => match *shadow {},
// Statically check that Url is impossible.
Filter::Url(ref url) => match *url {},
}
}
}
impl ToLayout for ImageRendering {
type Type = wr::ImageRendering;
fn to_layout(&self) -> Self::Type {
match *self {
ImageRendering::Auto => wr::ImageRendering::Auto,
ImageRendering::CrispEdges => wr::ImageRendering::CrispEdges,
ImageRendering::Pixelated => wr::ImageRendering::Pixelated,
}
}
}
impl ToLayout for MixBlendMode {
type Type = wr::MixBlendMode;
fn to_layout(&self) -> Self::Type {
match *self {
MixBlendMode::Normal => wr::MixBlendMode::Normal,
MixBlendMode::Multiply => wr::MixBlendMode::Multiply,
MixBlendMode::Screen => wr::MixBlendMode::Screen,
MixBlendMode::Overlay => wr::MixBlendMode::Overlay,
MixBlendMode::Darken => wr::MixBlendMode::Darken,
MixBlendMode::Lighten => wr::MixBlendMode::Lighten,
MixBlendMode::ColorDodge => wr::MixBlendMode::ColorDodge,
MixBlendMode::ColorBurn => wr::MixBlendMode::ColorBurn,
MixBlendMode::HardLight => wr::MixBlendMode::HardLight,
MixBlendMode::SoftLight => wr::MixBlendMode::SoftLight,
MixBlendMode::Difference => wr::MixBlendMode::Difference,
MixBlendMode::Exclusion => wr::MixBlendMode::Exclusion,
MixBlendMode::Hue => wr::MixBlendMode::Hue,
MixBlendMode::Saturation => wr::MixBlendMode::Saturation,
MixBlendMode::Color => wr::MixBlendMode::Color,
MixBlendMode::Luminosity => wr::MixBlendMode::Luminosity,
}
}
}
impl ToLayout for TransformStyle {
type Type = wr::TransformStyle;
fn to_layout(&self) -> Self::Type {
match *self {
TransformStyle::Auto | TransformStyle::Flat => wr::TransformStyle::Flat,
TransformStyle::Preserve3d => wr::TransformStyle::Preserve3D,
}
}
}
impl ToLayout for RGBA {
type Type = wr::ColorF;
fn to_layout(&self) -> Self::Type {
wr::ColorF::new(
self.red_f32(),
self.green_f32(),
self.blue_f32(),
self.alpha_f32(),
)
}
}
impl ToLayout for Point2D<Au> {
type Type = wr::units::LayoutPoint;
fn to_layout(&self) -> Self::Type {
wr::units::LayoutPoint::new(self.x.to_f32_px(), self.y.to_f32_px())
}
}
impl ToLayout for Rect<Au> {
type Type = wr::units::LayoutRect;
fn to_layout(&self) -> Self::Type {
wr::units::LayoutRect::new(self.origin.to_layout(), self.size.to_layout())
}
}
impl ToLayout for SideOffsets2D<Au> {
type Type = wr::units::LayoutSideOffsets;
fn to_layout(&self) -> Self::Type {
wr::units::LayoutSideOffsets::new(
self.top.to_f32_px(),
self.right.to_f32_px(),
self.bottom.to_f32_px(),
self.left.to_f32_px(),
)
}
}
impl ToLayout for Size2D<Au> {
type Type = wr::units::LayoutSize;
fn to_layout(&self) -> Self::Type {
wr::units::LayoutSize::new(self.width.to_f32_px(), self.height.to_f32_px())
}
}
impl ToLayout for Vector2D<Au> {
type Type = wr::units::LayoutVector2D;
fn to_layout(&self) -> Self::Type {
wr::units::LayoutVector2D::new(self.x.to_f32_px(), self.y.to_f32_px())
}
}
impl ToLayout for BorderImageRepeatKeyword {
type Type = wr::RepeatMode;
fn to_layout(&self) -> Self::Type {
match *self {
BorderImageRepeatKeyword::Stretch => wr::RepeatMode::Stretch,
BorderImageRepeatKeyword::Repeat => wr::RepeatMode::Repeat,
BorderImageRepeatKeyword::Round => wr::RepeatMode::Round,
BorderImageRepeatKeyword::Space => wr::RepeatMode::Space,
}
}
}

View file

@ -1,323 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use crate::display_list::ToLayout;
use app_units::Au;
use euclid::{Point2D, Size2D, Vector2D};
use style::properties::ComputedValues;
use style::values::computed::image::{EndingShape, LineDirection};
use style::values::computed::{Angle, GradientItem, LengthPercentage, Percentage, Position};
use style::values::generics::image::{Circle, ColorStop, Ellipse, ShapeExtent};
use webrender_api::{ExtendMode, Gradient, GradientBuilder, GradientStop, RadialGradient};
/// A helper data structure for gradients.
#[derive(Clone, Copy)]
struct StopRun {
start_offset: f32,
end_offset: f32,
start_index: usize,
stop_count: usize,
}
/// Determines the radius of a circle if it was not explictly provided.
/// <https://drafts.csswg.org/css-images-3/#typedef-size>
fn circle_size_keyword(
keyword: ShapeExtent,
size: &Size2D<Au>,
center: &Point2D<Au>,
) -> Size2D<Au> {
let radius = match keyword {
ShapeExtent::ClosestSide | ShapeExtent::Contain => {
let dist = distance_to_sides(size, center, ::std::cmp::min);
::std::cmp::min(dist.width, dist.height)
},
ShapeExtent::FarthestSide => {
let dist = distance_to_sides(size, center, ::std::cmp::max);
::std::cmp::max(dist.width, dist.height)
},
ShapeExtent::ClosestCorner => distance_to_corner(size, center, ::std::cmp::min),
ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
distance_to_corner(size, center, ::std::cmp::max)
},
};
Size2D::new(radius, radius)
}
/// Returns the radius for an ellipse with the same ratio as if it was matched to the sides.
fn ellipse_radius<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
where
F: Fn(Au, Au) -> Au,
{
let dist = distance_to_sides(size, center, cmp);
Size2D::new(
dist.width.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
dist.height
.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
)
}
/// Determines the radius of an ellipse if it was not explictly provided.
/// <https://drafts.csswg.org/css-images-3/#typedef-size>
fn ellipse_size_keyword(
keyword: ShapeExtent,
size: &Size2D<Au>,
center: &Point2D<Au>,
) -> Size2D<Au> {
match keyword {
ShapeExtent::ClosestSide | ShapeExtent::Contain => {
distance_to_sides(size, center, ::std::cmp::min)
},
ShapeExtent::FarthestSide => distance_to_sides(size, center, ::std::cmp::max),
ShapeExtent::ClosestCorner => ellipse_radius(size, center, ::std::cmp::min),
ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
ellipse_radius(size, center, ::std::cmp::max)
},
}
}
fn convert_gradient_stops(
style: &ComputedValues,
gradient_items: &[GradientItem],
total_length: Au,
) -> GradientBuilder {
// Determine the position of each stop per CSS-IMAGES § 3.4.
// Only keep the color stops, discard the color interpolation hints.
let mut stop_items = gradient_items
.iter()
.filter_map(|item| match *item {
GradientItem::SimpleColorStop(color) => Some(ColorStop {
color,
position: None,
}),
GradientItem::ComplexColorStop { color, position } => Some(ColorStop {
color,
position: Some(position),
}),
_ => None,
})
.collect::<Vec<_>>();
assert!(stop_items.len() >= 2);
// Run the algorithm from
// https://drafts.csswg.org/css-images-3/#color-stop-syntax
// Step 1:
// If the first color stop does not have a position, set its position to 0%.
{
let first = stop_items.first_mut().unwrap();
if first.position.is_none() {
first.position = Some(LengthPercentage::new_percent(Percentage(0.)));
}
}
// If the last color stop does not have a position, set its position to 100%.
{
let last = stop_items.last_mut().unwrap();
if last.position.is_none() {
last.position = Some(LengthPercentage::new_percent(Percentage(1.0)));
}
}
// Step 2: Move any stops placed before earlier stops to the
// same position as the preceding stop.
let mut last_stop_position = stop_items.first().unwrap().position.unwrap();
for stop in stop_items.iter_mut().skip(1) {
if let Some(pos) = stop.position {
if position_to_offset(last_stop_position, total_length) >
position_to_offset(pos, total_length)
{
stop.position = Some(last_stop_position);
}
last_stop_position = stop.position.unwrap();
}
}
// Step 3: Evenly space stops without position.
let mut stops = GradientBuilder::new();
let mut stop_run = None;
for (i, stop) in stop_items.iter().enumerate() {
let offset = match stop.position {
None => {
if stop_run.is_none() {
// Initialize a new stop run.
// `unwrap()` here should never fail because this is the beginning of
// a stop run, which is always bounded by a length or percentage.
let start_offset =
position_to_offset(stop_items[i - 1].position.unwrap(), total_length);
// `unwrap()` here should never fail because this is the end of
// a stop run, which is always bounded by a length or percentage.
let (end_index, end_stop) = stop_items[(i + 1)..]
.iter()
.enumerate()
.find(|&(_, ref stop)| stop.position.is_some())
.unwrap();
let end_offset = position_to_offset(end_stop.position.unwrap(), total_length);
stop_run = Some(StopRun {
start_offset,
end_offset,
start_index: i - 1,
stop_count: end_index,
})
}
let stop_run = stop_run.unwrap();
let stop_run_length = stop_run.end_offset - stop_run.start_offset;
stop_run.start_offset +
stop_run_length * (i - stop_run.start_index) as f32 /
((2 + stop_run.stop_count) as f32)
},
Some(position) => {
stop_run = None;
position_to_offset(position, total_length)
},
};
assert!(offset.is_finite());
stops.push(GradientStop {
offset: offset,
color: style.resolve_color(stop.color).to_layout(),
})
}
stops
}
fn extend_mode(repeating: bool) -> ExtendMode {
if repeating {
ExtendMode::Repeat
} else {
ExtendMode::Clamp
}
}
/// Returns the the distance to the nearest or farthest corner depending on the comperator.
fn distance_to_corner<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Au
where
F: Fn(Au, Au) -> Au,
{
let dist = distance_to_sides(size, center, cmp);
Au::from_f32_px(dist.width.to_f32_px().hypot(dist.height.to_f32_px()))
}
/// Returns the distance to the nearest or farthest sides depending on the comparator.
///
/// The first return value is horizontal distance the second vertical distance.
fn distance_to_sides<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
where
F: Fn(Au, Au) -> Au,
{
let top_side = center.y;
let right_side = size.width - center.x;
let bottom_side = size.height - center.y;
let left_side = center.x;
Size2D::new(cmp(left_side, right_side), cmp(top_side, bottom_side))
}
fn position_to_offset(position: LengthPercentage, total_length: Au) -> f32 {
if total_length == Au(0) {
return 0.0;
}
position.to_used_value(total_length).0 as f32 / total_length.0 as f32
}
pub fn linear(
style: &ComputedValues,
size: Size2D<Au>,
stops: &[GradientItem],
direction: LineDirection,
repeating: bool,
) -> (Gradient, Vec<GradientStop>) {
use style::values::specified::position::HorizontalPositionKeyword::*;
use style::values::specified::position::VerticalPositionKeyword::*;
let angle = match direction {
LineDirection::Angle(angle) => angle.radians(),
LineDirection::Horizontal(x) => match x {
Left => Angle::from_degrees(270.).radians(),
Right => Angle::from_degrees(90.).radians(),
},
LineDirection::Vertical(y) => match y {
Top => Angle::from_degrees(0.).radians(),
Bottom => Angle::from_degrees(180.).radians(),
},
LineDirection::Corner(horizontal, vertical) => {
// This the angle for one of the diagonals of the box. Our angle
// will either be this one, this one + PI, or one of the other
// two perpendicular angles.
let atan = (size.height.to_f32_px() / size.width.to_f32_px()).atan();
match (horizontal, vertical) {
(Right, Bottom) => ::std::f32::consts::PI - atan,
(Left, Bottom) => ::std::f32::consts::PI + atan,
(Right, Top) => atan,
(Left, Top) => -atan,
}
},
};
// Get correct gradient line length, based on:
// https://drafts.csswg.org/css-images-3/#linear-gradients
let dir = Point2D::new(angle.sin(), -angle.cos());
let line_length =
(dir.x * size.width.to_f32_px()).abs() + (dir.y * size.height.to_f32_px()).abs();
let inv_dir_length = 1.0 / (dir.x * dir.x + dir.y * dir.y).sqrt();
// This is the vector between the center and the ending point; i.e. half
// of the distance between the starting point and the ending point.
let delta = Vector2D::new(
Au::from_f32_px(dir.x * inv_dir_length * line_length / 2.0),
Au::from_f32_px(dir.y * inv_dir_length * line_length / 2.0),
);
// This is the length of the gradient line.
let length = Au::from_f32_px((delta.x.to_f32_px() * 2.0).hypot(delta.y.to_f32_px() * 2.0));
let mut builder = convert_gradient_stops(style, stops, length);
let center = Point2D::new(size.width / 2, size.height / 2);
(
builder.gradient(
(center - delta).to_layout(),
(center + delta).to_layout(),
extend_mode(repeating),
),
builder.into_stops(),
)
}
pub fn radial(
style: &ComputedValues,
size: Size2D<Au>,
stops: &[GradientItem],
shape: EndingShape,
center: Position,
repeating: bool,
) -> (RadialGradient, Vec<GradientStop>) {
let center = Point2D::new(
center.horizontal.to_used_value(size.width),
center.vertical.to_used_value(size.height),
);
let radius = match shape {
EndingShape::Circle(Circle::Radius(length)) => {
let length = Au::from(length);
Size2D::new(length, length)
},
EndingShape::Circle(Circle::Extent(extent)) => circle_size_keyword(extent, &size, &center),
EndingShape::Ellipse(Ellipse::Radii(x, y)) => {
Size2D::new(x.to_used_value(size.width), y.to_used_value(size.height))
},
EndingShape::Ellipse(Ellipse::Extent(extent)) => {
ellipse_size_keyword(extent, &size, &center)
},
};
let mut builder = convert_gradient_stops(style, stops, radius.width);
(
builder.radial_gradient(
center.to_layout(),
radius.to_layout(),
extend_mode(repeating),
),
builder.into_stops(),
)
}

View file

@ -2,794 +2,22 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Servo heavily uses display lists, which are retained-mode lists of painting commands to
//! perform. Using a list instead of painting elements in immediate mode allows transforms, hit
//! testing, and invalidation to be performed using the same primitives as painting. It also allows
//! Servo to aggressively cull invisible and out-of-bounds painting elements, to reduce overdraw.
//!
//! Display items describe relatively high-level drawing operations (for example, entire borders
//! and shadows instead of lines and blur operations), to reduce the amount of allocation required.
//! They are therefore not exactly analogous to constructs like Skia pictures, which consist of
//! low-level drawing primitives.
use euclid::{SideOffsets2D, Vector2D};
use gfx_traits::print_tree::PrintTree;
use gfx_traits::{self, StackingContextId};
use msg::constellation_msg::PipelineId;
use net_traits::image::base::Image;
use servo_geometry::MaxRect;
use std::cmp::Ordering;
use euclid::Vector2D;
use gfx_traits;
use std::collections::HashMap;
use std::f32;
use std::fmt;
use style::computed_values::_servo_top_layer::T as InTopLayer;
use webrender_api as wr;
use webrender_api::units::{LayoutPoint, LayoutRect, LayoutSize, LayoutTransform};
use webrender_api::{BorderRadius, ClipId, ClipMode, CommonItemProperties, ComplexClipRegion};
use webrender_api::{ExternalScrollId, FilterOp, GlyphInstance, GradientStop, ImageKey};
use webrender_api::{MixBlendMode, ScrollSensitivity, Shadow, SpatialId};
use webrender_api::{StickyOffsetBounds, TransformStyle};
use webrender_api::ExternalScrollId;
pub use style::dom::OpaqueNode;
/// The factor that we multiply the blur radius by in order to inflate the boundaries of display
/// items that involve a blur. This ensures that the display item boundaries include all the ink.
pub static BLUR_INFLATION_FACTOR: i32 = 3;
/// An index into the vector of ClipScrollNodes. During WebRender conversion these nodes
/// are given ClipIds.
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
pub struct ClipScrollNodeIndex(usize);
impl ClipScrollNodeIndex {
pub fn root_scroll_node() -> ClipScrollNodeIndex {
ClipScrollNodeIndex(1)
}
pub fn root_reference_frame() -> ClipScrollNodeIndex {
ClipScrollNodeIndex(0)
}
pub fn new(index: usize) -> ClipScrollNodeIndex {
assert_ne!(index, 0, "Use the root_reference_frame constructor");
assert_ne!(index, 1, "Use the root_scroll_node constructor");
ClipScrollNodeIndex(index)
}
pub fn is_root_scroll_node(&self) -> bool {
*self == Self::root_scroll_node()
}
pub fn to_define_item(&self) -> DisplayItem {
DisplayItem::DefineClipScrollNode(Box::new(DefineClipScrollNodeItem {
base: BaseDisplayItem::empty(),
node_index: *self,
}))
}
pub fn to_index(self) -> usize {
self.0
}
}
/// A set of indices into the clip scroll node vector for a given item.
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
pub struct ClippingAndScrolling {
pub scrolling: ClipScrollNodeIndex,
pub clipping: Option<ClipScrollNodeIndex>,
}
impl ClippingAndScrolling {
pub fn simple(scrolling: ClipScrollNodeIndex) -> ClippingAndScrolling {
ClippingAndScrolling {
scrolling,
clipping: None,
}
}
pub fn new(scrolling: ClipScrollNodeIndex, clipping: ClipScrollNodeIndex) -> Self {
ClippingAndScrolling {
scrolling,
clipping: Some(clipping),
}
}
}
#[derive(Serialize)]
pub struct DisplayList {
pub list: Vec<DisplayItem>,
pub clip_scroll_nodes: Vec<ClipScrollNode>,
}
impl DisplayList {
/// Return the bounds of this display list based on the dimensions of the root
/// stacking context.
pub fn bounds(&self) -> LayoutRect {
match self.list.get(0) {
Some(&DisplayItem::PushStackingContext(ref item)) => item.stacking_context.bounds,
Some(_) => unreachable!("Root element of display list not stacking context."),
None => LayoutRect::zero(),
}
}
pub fn print(&self) {
let mut print_tree = PrintTree::new("Display List".to_owned());
self.print_with_tree(&mut print_tree);
}
pub fn print_with_tree(&self, print_tree: &mut PrintTree) {
print_tree.new_level("ClipScrollNodes".to_owned());
for node in &self.clip_scroll_nodes {
print_tree.add_item(format!("{:?}", node));
}
print_tree.end_level();
print_tree.new_level("Items".to_owned());
for item in &self.list {
print_tree.add_item(format!(
"{:?} StackingContext: {:?} {:?}",
item,
item.base().stacking_context_id,
item.clipping_and_scrolling()
));
}
print_tree.end_level();
}
}
pub struct DisplayList {}
impl gfx_traits::DisplayList for DisplayList {
/// Analyze the display list to figure out if this may be the first
/// contentful paint (i.e. the display list contains items of type text,
/// image, non-white canvas or SVG). Used by metrics.
fn is_contentful(&self) -> bool {
for item in &self.list {
match item {
&DisplayItem::Text(_) | &DisplayItem::Image(_) => return true,
_ => (),
}
}
false
}
}
/// Display list sections that make up a stacking context. Each section here refers
/// to the steps in CSS 2.1 Appendix E.
///
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub enum DisplayListSection {
BackgroundAndBorders,
BlockBackgroundsAndBorders,
Content,
Outlines,
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub enum StackingContextType {
Real,
PseudoPositioned,
PseudoFloat,
}
#[derive(Clone, Serialize)]
/// Represents one CSS stacking context, which may or may not have a hardware layer.
pub struct StackingContext {
/// The ID of this StackingContext for uniquely identifying it.
pub id: StackingContextId,
/// The type of this StackingContext. Used for collecting and sorting.
pub context_type: StackingContextType,
/// The position and size of this stacking context.
pub bounds: LayoutRect,
/// The overflow rect for this stacking context in its coordinate system.
pub overflow: LayoutRect,
/// The `z-index` for this stacking context.
pub z_index: i32,
/// Whether this is the top layer.
pub in_top_layer: InTopLayer,
/// CSS filters to be applied to this stacking context (including opacity).
pub filters: Vec<FilterOp>,
/// The blend mode with which this stacking context blends with its backdrop.
pub mix_blend_mode: MixBlendMode,
/// A transform to be applied to this stacking context.
pub transform: Option<LayoutTransform>,
/// The transform style of this stacking context.
pub transform_style: TransformStyle,
/// The perspective matrix to be applied to children.
pub perspective: Option<LayoutTransform>,
/// The clip and scroll info for this StackingContext.
pub parent_clipping_and_scrolling: ClippingAndScrolling,
/// The index of the reference frame that this stacking context estalishes.
pub established_reference_frame: Option<ClipScrollNodeIndex>,
}
impl StackingContext {
/// Creates a new stacking context.
#[inline]
pub fn new(
id: StackingContextId,
context_type: StackingContextType,
bounds: LayoutRect,
overflow: LayoutRect,
z_index: i32,
in_top_layer: InTopLayer,
filters: Vec<FilterOp>,
mix_blend_mode: MixBlendMode,
transform: Option<LayoutTransform>,
transform_style: TransformStyle,
perspective: Option<LayoutTransform>,
parent_clipping_and_scrolling: ClippingAndScrolling,
established_reference_frame: Option<ClipScrollNodeIndex>,
) -> StackingContext {
StackingContext {
id,
context_type,
bounds,
overflow,
z_index,
in_top_layer,
filters,
mix_blend_mode,
transform,
transform_style,
perspective,
parent_clipping_and_scrolling,
established_reference_frame,
}
}
#[inline]
pub fn root() -> StackingContext {
StackingContext::new(
StackingContextId::root(),
StackingContextType::Real,
LayoutRect::zero(),
LayoutRect::zero(),
0,
InTopLayer::None,
vec![],
MixBlendMode::Normal,
None,
TransformStyle::Flat,
None,
ClippingAndScrolling::simple(ClipScrollNodeIndex::root_scroll_node()),
None,
)
}
pub fn to_display_list_items(self) -> (DisplayItem, DisplayItem) {
let mut base_item = BaseDisplayItem::empty();
base_item.stacking_context_id = self.id;
base_item.clipping_and_scrolling = self.parent_clipping_and_scrolling;
let pop_item = DisplayItem::PopStackingContext(Box::new(PopStackingContextItem {
base: base_item.clone(),
stacking_context_id: self.id,
}));
let push_item = DisplayItem::PushStackingContext(Box::new(PushStackingContextItem {
base: base_item,
stacking_context: self,
}));
(push_item, pop_item)
}
}
impl Ord for StackingContext {
fn cmp(&self, other: &Self) -> Ordering {
if self.in_top_layer == InTopLayer::Top {
if other.in_top_layer == InTopLayer::Top {
return Ordering::Equal;
} else {
return Ordering::Greater;
}
} else if other.in_top_layer == InTopLayer::Top {
return Ordering::Less;
}
if self.z_index != 0 || other.z_index != 0 {
return self.z_index.cmp(&other.z_index);
}
match (self.context_type, other.context_type) {
(StackingContextType::PseudoFloat, StackingContextType::PseudoFloat) => Ordering::Equal,
(StackingContextType::PseudoFloat, _) => Ordering::Less,
(_, StackingContextType::PseudoFloat) => Ordering::Greater,
(_, _) => Ordering::Equal,
}
}
}
impl PartialOrd for StackingContext {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Eq for StackingContext {}
impl PartialEq for StackingContext {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl fmt::Debug for StackingContext {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let type_string = if self.context_type == StackingContextType::Real {
"StackingContext"
} else {
"Pseudo-StackingContext"
};
write!(
f,
"{} at {:?} with overflow {:?}: {:?}",
type_string, self.bounds, self.overflow, self.id
)
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct StickyFrameData {
pub margins: SideOffsets2D<Option<f32>>,
pub vertical_offset_bounds: StickyOffsetBounds,
pub horizontal_offset_bounds: StickyOffsetBounds,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum ClipScrollNodeType {
Placeholder,
ScrollFrame(ScrollSensitivity, ExternalScrollId),
StickyFrame(StickyFrameData),
Clip,
}
/// Defines a clip scroll node.
#[derive(Clone, Debug, Serialize)]
pub struct ClipScrollNode {
/// The index of the parent of this ClipScrollNode.
pub parent_index: ClipScrollNodeIndex,
/// The position of this scroll root's frame in the parent stacking context.
pub clip: ClippingRegion,
/// The rect of the contents that can be scrolled inside of the scroll root.
pub content_rect: LayoutRect,
/// The type of this ClipScrollNode.
pub node_type: ClipScrollNodeType,
}
impl ClipScrollNode {
pub fn placeholder() -> ClipScrollNode {
ClipScrollNode {
parent_index: ClipScrollNodeIndex(0),
clip: ClippingRegion::from_rect(LayoutRect::zero()),
content_rect: LayoutRect::zero(),
node_type: ClipScrollNodeType::Placeholder,
}
}
pub fn is_placeholder(&self) -> bool {
self.node_type == ClipScrollNodeType::Placeholder
}
}
/// One drawing command in the list.
#[derive(Clone, Serialize)]
pub enum DisplayItem {
Rectangle(Box<CommonDisplayItem<wr::RectangleDisplayItem>>),
Text(Box<CommonDisplayItem<wr::TextDisplayItem, Vec<GlyphInstance>>>),
Image(Box<CommonDisplayItem<wr::ImageDisplayItem>>),
Border(Box<CommonDisplayItem<wr::BorderDisplayItem, Vec<GradientStop>>>),
Gradient(Box<CommonDisplayItem<wr::GradientDisplayItem, Vec<GradientStop>>>),
RadialGradient(Box<CommonDisplayItem<wr::RadialGradientDisplayItem, Vec<GradientStop>>>),
Line(Box<CommonDisplayItem<wr::LineDisplayItem>>),
BoxShadow(Box<CommonDisplayItem<wr::BoxShadowDisplayItem>>),
PushTextShadow(Box<PushTextShadowDisplayItem>),
PopAllTextShadows(Box<PopAllTextShadowsDisplayItem>),
Iframe(Box<IframeDisplayItem>),
PushStackingContext(Box<PushStackingContextItem>),
PopStackingContext(Box<PopStackingContextItem>),
DefineClipScrollNode(Box<DefineClipScrollNodeItem>),
}
/// Information common to all display items.
#[derive(Clone, Serialize)]
pub struct BaseDisplayItem {
/// Metadata attached to this display item.
pub metadata: DisplayItemMetadata,
/// The clip rectangle to use for this item.
pub clip_rect: LayoutRect,
/// The section of the display list that this item belongs to.
pub section: DisplayListSection,
/// The id of the stacking context this item belongs to.
pub stacking_context_id: StackingContextId,
/// The clip and scroll info for this item.
pub clipping_and_scrolling: ClippingAndScrolling,
}
impl BaseDisplayItem {
#[inline(always)]
pub fn new(
metadata: DisplayItemMetadata,
clip_rect: LayoutRect,
section: DisplayListSection,
stacking_context_id: StackingContextId,
clipping_and_scrolling: ClippingAndScrolling,
) -> BaseDisplayItem {
BaseDisplayItem {
metadata,
clip_rect,
section,
stacking_context_id,
clipping_and_scrolling,
}
}
#[inline(always)]
pub fn empty() -> BaseDisplayItem {
BaseDisplayItem {
metadata: DisplayItemMetadata {
node: OpaqueNode(0),
pointing: None,
},
// Create a rectangle of maximal size.
clip_rect: LayoutRect::max_rect(),
section: DisplayListSection::Content,
stacking_context_id: StackingContextId::root(),
clipping_and_scrolling: ClippingAndScrolling::simple(
ClipScrollNodeIndex::root_scroll_node(),
),
}
}
}
pub fn empty_common_item_properties() -> CommonItemProperties {
CommonItemProperties {
clip_rect: LayoutRect::max_rect(),
clip_id: ClipId::root(wr::PipelineId::dummy()),
spatial_id: SpatialId::root_scroll_node(wr::PipelineId::dummy()),
hit_info: None,
is_backface_visible: false,
}
}
/// A clipping region for a display item. Currently, this can describe rectangles, rounded
/// rectangles (for `border-radius`), or arbitrary intersections of the two. Arbitrary transforms
/// are not supported because those are handled by the higher-level `StackingContext` abstraction.
#[derive(Clone, PartialEq, Serialize)]
pub struct ClippingRegion {
/// The main rectangular region. This does not include any corners.
pub main: LayoutRect,
/// Any complex regions.
///
/// TODO(pcwalton): Atomically reference count these? Not sure if it's worth the trouble.
/// Measure and follow up.
pub complex: Vec<ComplexClipRegion>,
}
impl ClippingRegion {
/// Returns an empty clipping region that, if set, will result in no pixels being visible.
#[inline]
pub fn empty() -> ClippingRegion {
ClippingRegion {
main: LayoutRect::zero(),
complex: Vec::new(),
}
}
/// Returns an all-encompassing clipping region that clips no pixels out.
#[inline]
pub fn max() -> ClippingRegion {
ClippingRegion {
main: LayoutRect::max_rect(),
complex: Vec::new(),
}
}
/// Returns a clipping region that represents the given rectangle.
#[inline]
pub fn from_rect(rect: LayoutRect) -> ClippingRegion {
ClippingRegion {
main: rect,
complex: Vec::new(),
}
}
/// Intersects this clipping region with the given rounded rectangle.
#[inline]
pub fn intersect_with_rounded_rect(&mut self, rect: LayoutRect, radii: BorderRadius) {
let new_complex_region = ComplexClipRegion {
rect,
radii,
mode: ClipMode::Clip,
};
// FIXME(pcwalton): This is O(n²) worst case for disjoint clipping regions. Is that OK?
// They're slow anyway…
//
// Possibly relevant if we want to do better:
//
// http://www.inrg.csie.ntu.edu.tw/algorithm2014/presentation/D&C%20Lee-84.pdf
for existing_complex_region in &mut self.complex {
if completely_encloses(&existing_complex_region, &new_complex_region) {
*existing_complex_region = new_complex_region;
return;
}
if completely_encloses(&new_complex_region, &existing_complex_region) {
return;
}
}
self.complex.push(new_complex_region);
}
}
impl fmt::Debug for ClippingRegion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if *self == ClippingRegion::max() {
write!(f, "ClippingRegion::Max")
} else if *self == ClippingRegion::empty() {
write!(f, "ClippingRegion::Empty")
} else if self.main == LayoutRect::max_rect() {
write!(f, "ClippingRegion(Complex={:?})", self.complex)
} else {
write!(
f,
"ClippingRegion(Rect={:?}, Complex={:?})",
self.main, self.complex
)
}
}
}
// TODO(pcwalton): This could be more aggressive by considering points that touch the inside of
// the border radius ellipse.
fn completely_encloses(this: &ComplexClipRegion, other: &ComplexClipRegion) -> bool {
let left = this.radii.top_left.width.max(this.radii.bottom_left.width);
let top = this.radii.top_left.height.max(this.radii.top_right.height);
let right = this
.radii
.top_right
.width
.max(this.radii.bottom_right.width);
let bottom = this
.radii
.bottom_left
.height
.max(this.radii.bottom_right.height);
let interior = LayoutRect::new(
LayoutPoint::new(this.rect.origin.x + left, this.rect.origin.y + top),
LayoutSize::new(
this.rect.size.width - left - right,
this.rect.size.height - top - bottom,
),
);
interior.origin.x <= other.rect.origin.x &&
interior.origin.y <= other.rect.origin.y &&
interior.max_x() >= other.rect.max_x() &&
interior.max_y() >= other.rect.max_y()
}
/// Metadata attached to each display item. This is useful for performing auxiliary threads with
/// the display list involving hit testing: finding the originating DOM node and determining the
/// cursor to use when the element is hovered over.
#[derive(Clone, Copy, Serialize)]
pub struct DisplayItemMetadata {
/// The DOM node from which this display item originated.
pub node: OpaqueNode,
/// The value of the `cursor` property when the mouse hovers over this display item. If `None`,
/// this display item is ineligible for pointer events (`pointer-events: none`).
pub pointing: Option<u16>,
}
#[derive(Clone, Eq, PartialEq, Serialize)]
pub enum TextOrientation {
Upright,
SidewaysLeft,
SidewaysRight,
}
/// Paints an iframe.
#[derive(Clone, Serialize)]
pub struct IframeDisplayItem {
pub base: BaseDisplayItem,
pub iframe: PipelineId,
pub bounds: LayoutRect,
}
#[derive(Clone, Serialize)]
pub struct CommonDisplayItem<T, U = ()> {
pub base: BaseDisplayItem,
pub item: T,
pub data: U,
}
impl<T> CommonDisplayItem<T> {
pub fn new(base: BaseDisplayItem, item: T) -> Box<CommonDisplayItem<T>> {
Box::new(CommonDisplayItem {
base,
item,
data: (),
})
}
}
impl<T, U> CommonDisplayItem<T, U> {
pub fn with_data(base: BaseDisplayItem, item: T, data: U) -> Box<CommonDisplayItem<T, U>> {
Box::new(CommonDisplayItem { base, item, data })
}
}
/// Defines a text shadow that affects all items until the paired PopTextShadow.
#[derive(Clone, Serialize)]
pub struct PushTextShadowDisplayItem {
/// Fields common to all display items.
pub base: BaseDisplayItem,
pub shadow: Shadow,
}
/// Defines a text shadow that affects all items until the next PopTextShadow.
#[derive(Clone, Serialize)]
pub struct PopAllTextShadowsDisplayItem {
/// Fields common to all display items.
pub base: BaseDisplayItem,
}
/// Defines a stacking context.
#[derive(Clone, Serialize)]
pub struct PushStackingContextItem {
/// Fields common to all display items.
pub base: BaseDisplayItem,
pub stacking_context: StackingContext,
}
/// Defines a stacking context.
#[derive(Clone, Serialize)]
pub struct PopStackingContextItem {
/// Fields common to all display items.
pub base: BaseDisplayItem,
pub stacking_context_id: StackingContextId,
}
/// Starts a group of items inside a particular scroll root.
#[derive(Clone, Serialize)]
pub struct DefineClipScrollNodeItem {
/// Fields common to all display items.
pub base: BaseDisplayItem,
/// The scroll root that this item starts.
pub node_index: ClipScrollNodeIndex,
}
impl DisplayItem {
pub fn base(&self) -> &BaseDisplayItem {
match *self {
DisplayItem::Rectangle(ref rect) => &rect.base,
DisplayItem::Text(ref text) => &text.base,
DisplayItem::Image(ref image_item) => &image_item.base,
DisplayItem::Border(ref border) => &border.base,
DisplayItem::Gradient(ref gradient) => &gradient.base,
DisplayItem::RadialGradient(ref gradient) => &gradient.base,
DisplayItem::Line(ref line) => &line.base,
DisplayItem::BoxShadow(ref box_shadow) => &box_shadow.base,
DisplayItem::PushTextShadow(ref push_text_shadow) => &push_text_shadow.base,
DisplayItem::PopAllTextShadows(ref pop_text_shadow) => &pop_text_shadow.base,
DisplayItem::Iframe(ref iframe) => &iframe.base,
DisplayItem::PushStackingContext(ref stacking_context) => &stacking_context.base,
DisplayItem::PopStackingContext(ref item) => &item.base,
DisplayItem::DefineClipScrollNode(ref item) => &item.base,
}
}
pub fn clipping_and_scrolling(&self) -> ClippingAndScrolling {
self.base().clipping_and_scrolling
}
pub fn stacking_context_id(&self) -> StackingContextId {
self.base().stacking_context_id
}
pub fn section(&self) -> DisplayListSection {
self.base().section
}
pub fn bounds(&self) -> LayoutRect {
match *self {
DisplayItem::Rectangle(ref item) => item.item.common.clip_rect,
DisplayItem::Text(ref item) => item.item.bounds,
DisplayItem::Image(ref item) => item.item.bounds,
DisplayItem::Border(ref item) => item.item.bounds,
DisplayItem::Gradient(ref item) => item.item.bounds,
DisplayItem::RadialGradient(ref item) => item.item.bounds,
DisplayItem::Line(ref item) => item.item.area,
DisplayItem::BoxShadow(ref item) => item.item.box_bounds,
DisplayItem::PushTextShadow(_) => LayoutRect::zero(),
DisplayItem::PopAllTextShadows(_) => LayoutRect::zero(),
DisplayItem::Iframe(ref item) => item.bounds,
DisplayItem::PushStackingContext(ref item) => item.stacking_context.bounds,
DisplayItem::PopStackingContext(_) => LayoutRect::zero(),
DisplayItem::DefineClipScrollNode(_) => LayoutRect::zero(),
}
}
}
impl fmt::Debug for DisplayItem {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let DisplayItem::PushStackingContext(ref item) = *self {
return write!(f, "PushStackingContext({:?})", item.stacking_context);
}
if let DisplayItem::PopStackingContext(ref item) = *self {
return write!(f, "PopStackingContext({:?}", item.stacking_context_id);
}
if let DisplayItem::DefineClipScrollNode(ref item) = *self {
return write!(f, "DefineClipScrollNode({:?}", item.node_index);
}
write!(
f,
"{} @ {:?} {:?}",
match *self {
DisplayItem::Rectangle(_) => "Rectangle".to_owned(),
DisplayItem::Text(_) => "Text".to_owned(),
DisplayItem::Image(_) => "Image".to_owned(),
DisplayItem::Border(_) => "Border".to_owned(),
DisplayItem::Gradient(_) => "Gradient".to_owned(),
DisplayItem::RadialGradient(_) => "RadialGradient".to_owned(),
DisplayItem::Line(_) => "Line".to_owned(),
DisplayItem::BoxShadow(_) => "BoxShadow".to_owned(),
DisplayItem::PushTextShadow(_) => "PushTextShadow".to_owned(),
DisplayItem::PopAllTextShadows(_) => "PopTextShadow".to_owned(),
DisplayItem::Iframe(_) => "Iframe".to_owned(),
DisplayItem::PushStackingContext(_) |
DisplayItem::PopStackingContext(_) |
DisplayItem::DefineClipScrollNode(_) => "".to_owned(),
},
self.bounds(),
self.base().clip_rect
)
}
}
#[derive(Clone, Copy, Serialize)]
pub struct WebRenderImageInfo {
pub width: u32,
pub height: u32,
pub key: Option<ImageKey>,
}
impl WebRenderImageInfo {
#[inline]
pub fn from_image(image: &Image) -> WebRenderImageInfo {
WebRenderImageInfo {
width: image.width,
height: image.height,
key: image.id,
}
}
}
/// The type of the scroll offset list. This is only populated if WebRender is in use.
pub type ScrollOffsetMap = HashMap<ExternalScrollId, Vector2D<f32>>;

View file

@ -2,18 +2,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
pub use self::builder::BorderPaintingMode;
pub use self::builder::DisplayListBuildState;
pub use self::builder::IndexableText;
pub use self::builder::StackingContextCollectionFlags;
pub use self::builder::StackingContextCollectionState;
pub use self::conversions::ToLayout;
pub use self::webrender_helpers::WebRenderDisplayListConverter;
mod background;
mod border;
mod builder;
mod conversions;
mod gradient;
pub mod items;
mod webrender_helpers;

View file

@ -2,320 +2,25 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
// TODO(gw): This contains helper traits and implementations for converting Servo display lists
// into WebRender display lists. In the future, this step should be completely removed.
// This might be achieved by sharing types between WR and Servo display lists, or
// completely converting layout to directly generate WebRender display lists, for example.
use crate::display_list::items::{BaseDisplayItem, ClipScrollNode, ClipScrollNodeType};
use crate::display_list::items::{DisplayItem, DisplayList, StackingContextType};
use crate::display_list::items::DisplayList;
use msg::constellation_msg::PipelineId;
use webrender_api::units::LayoutPoint;
use webrender_api::{self, ClipId, CommonItemProperties, DisplayItem as WrDisplayItem};
use webrender_api::{DisplayListBuilder, PropertyBinding, PushStackingContextDisplayItem};
use webrender_api::{
RasterSpace, ReferenceFrameKind, SpaceAndClipInfo, SpatialId, StackingContext,
};
use webrender_api::{self, DisplayListBuilder};
use webrender_api::units::{LayoutSize};
pub trait WebRenderDisplayListConverter {
fn convert_to_webrender(&mut self, pipeline_id: PipelineId) -> DisplayListBuilder;
}
struct ClipScrollState {
clip_ids: Vec<Option<ClipId>>,
spatial_ids: Vec<Option<SpatialId>>,
active_clip_id: ClipId,
active_spatial_id: SpatialId,
}
trait WebRenderDisplayItemConverter {
fn convert_to_webrender(
&mut self,
clip_scroll_nodes: &[ClipScrollNode],
state: &mut ClipScrollState,
builder: &mut DisplayListBuilder,
);
}
impl WebRenderDisplayListConverter for DisplayList {
fn convert_to_webrender(&mut self, pipeline_id: PipelineId) -> DisplayListBuilder {
let mut clip_ids = vec![None; self.clip_scroll_nodes.len()];
let mut spatial_ids = vec![None; self.clip_scroll_nodes.len()];
// We need to add the WebRender root reference frame and root scroll node ids
// here manually, because WebRender creates these automatically.
// We also follow the "old" WebRender API for clip/scroll for now,
// hence both arrays are initialized based on FIRST_SPATIAL_NODE_INDEX,
// while FIRST_CLIP_NODE_INDEX is not taken into account.
let webrender_pipeline = pipeline_id.to_webrender();
clip_ids[0] = Some(ClipId::root(webrender_pipeline));
clip_ids[1] = Some(ClipId::root(webrender_pipeline));
spatial_ids[0] = Some(SpatialId::root_reference_frame(webrender_pipeline));
spatial_ids[1] = Some(SpatialId::root_scroll_node(webrender_pipeline));
let mut state = ClipScrollState {
clip_ids,
spatial_ids,
active_clip_id: ClipId::root(webrender_pipeline),
active_spatial_id: SpatialId::root_scroll_node(webrender_pipeline),
};
let mut builder = DisplayListBuilder::with_capacity(
let builder = DisplayListBuilder::with_capacity(
webrender_pipeline,
self.bounds().size,
LayoutSize::zero(),
1024 * 1024, // 1 MB of space
);
for item in &mut self.list {
item.convert_to_webrender(&self.clip_scroll_nodes, &mut state, &mut builder);
}
builder
}
}
impl WebRenderDisplayItemConverter for DisplayItem {
fn convert_to_webrender(
&mut self,
clip_scroll_nodes: &[ClipScrollNode],
state: &mut ClipScrollState,
builder: &mut DisplayListBuilder,
) {
// Note: for each time of a display item, if we register one of `clip_ids` or `spatial_ids`,
// we also register the other one as inherited from the current state or the stack.
// This is not an ideal behavior, but it is compatible with the old WebRender model
// of the clip-scroll tree.
let clip_and_scroll_indices = self.base().clipping_and_scrolling;
trace!("converting {:?}", clip_and_scroll_indices);
let cur_spatial_id = state.spatial_ids[clip_and_scroll_indices.scrolling.to_index()]
.expect("Tried to use WebRender SpatialId before it was defined.");
if cur_spatial_id != state.active_spatial_id {
state.active_spatial_id = cur_spatial_id;
}
let internal_clip_id = clip_and_scroll_indices
.clipping
.unwrap_or(clip_and_scroll_indices.scrolling);
let cur_clip_id = state.clip_ids[internal_clip_id.to_index()]
.expect("Tried to use WebRender ClipId before it was defined.");
if cur_clip_id != state.active_clip_id {
state.active_clip_id = cur_clip_id;
}
match *self {
DisplayItem::Rectangle(ref mut item) => {
item.item.common = build_common_item_properties(&item.base, state);
builder.push_item(&WrDisplayItem::Rectangle(item.item));
},
DisplayItem::Text(ref mut item) => {
item.item.common = build_common_item_properties(&item.base, state);
builder.push_item(&WrDisplayItem::Text(item.item));
builder.push_iter(item.data.iter());
},
DisplayItem::Image(ref mut item) => {
item.item.common = build_common_item_properties(&item.base, state);
builder.push_item(&WrDisplayItem::Image(item.item));
},
DisplayItem::Border(ref mut item) => {
item.item.common = build_common_item_properties(&item.base, state);
if !item.data.is_empty() {
builder.push_stops(item.data.as_ref());
}
builder.push_item(&WrDisplayItem::Border(item.item));
},
DisplayItem::Gradient(ref mut item) => {
item.item.common = build_common_item_properties(&item.base, state);
builder.push_stops(item.data.as_ref());
builder.push_item(&WrDisplayItem::Gradient(item.item));
},
DisplayItem::RadialGradient(ref mut item) => {
item.item.common = build_common_item_properties(&item.base, state);
builder.push_stops(item.data.as_ref());
builder.push_item(&WrDisplayItem::RadialGradient(item.item));
},
DisplayItem::Line(ref mut item) => {
item.item.common = build_common_item_properties(&item.base, state);
builder.push_item(&WrDisplayItem::Line(item.item));
},
DisplayItem::BoxShadow(ref mut item) => {
item.item.common = build_common_item_properties(&item.base, state);
builder.push_item(&WrDisplayItem::BoxShadow(item.item));
},
DisplayItem::PushTextShadow(ref mut item) => {
let common = build_common_item_properties(&item.base, state);
builder.push_shadow(
&SpaceAndClipInfo {
spatial_id: common.spatial_id,
clip_id: common.clip_id,
},
item.shadow,
true,
);
},
DisplayItem::PopAllTextShadows(_) => {
builder.push_item(&WrDisplayItem::PopAllShadows);
},
DisplayItem::Iframe(ref mut item) => {
let common = build_common_item_properties(&item.base, state);
builder.push_iframe(
item.bounds,
common.clip_rect,
&SpaceAndClipInfo {
spatial_id: common.spatial_id,
clip_id: common.clip_id,
},
item.iframe.to_webrender(),
true,
);
},
DisplayItem::PushStackingContext(ref mut item) => {
let stacking_context = &item.stacking_context;
debug_assert_eq!(stacking_context.context_type, StackingContextType::Real);
//let mut info = webrender_api::LayoutPrimitiveInfo::new(stacking_context.bounds);
let mut bounds = stacking_context.bounds;
let spatial_id =
if let Some(frame_index) = stacking_context.established_reference_frame {
let (transform, ref_frame) =
match (stacking_context.transform, stacking_context.perspective) {
(None, Some(p)) => (
p,
ReferenceFrameKind::Perspective {
scrolling_relative_to: None,
},
),
(Some(t), None) => (t, ReferenceFrameKind::Transform),
(Some(t), Some(p)) => (
t.pre_mul(&p),
ReferenceFrameKind::Perspective {
scrolling_relative_to: None,
},
),
(None, None) => unreachable!(),
};
let spatial_id = builder.push_reference_frame(
stacking_context.bounds.origin,
state.active_spatial_id,
stacking_context.transform_style,
PropertyBinding::Value(transform),
ref_frame,
);
state.spatial_ids[frame_index.to_index()] = Some(spatial_id);
state.clip_ids[frame_index.to_index()] = Some(cur_clip_id);
bounds.origin = LayoutPoint::zero();
spatial_id
} else {
state.active_spatial_id
};
if !stacking_context.filters.is_empty() {
builder.push_item(&WrDisplayItem::SetFilterOps);
builder.push_iter(&stacking_context.filters);
}
let wr_item = PushStackingContextDisplayItem {
origin: bounds.origin,
spatial_id,
is_backface_visible: true,
stacking_context: StackingContext {
transform_style: stacking_context.transform_style,
mix_blend_mode: stacking_context.mix_blend_mode,
clip_id: None,
raster_space: RasterSpace::Screen,
// TODO(pcwalton): Enable picture caching?
cache_tiles: false,
},
};
builder.push_item(&WrDisplayItem::PushStackingContext(wr_item));
},
DisplayItem::PopStackingContext(_) => builder.pop_stacking_context(),
DisplayItem::DefineClipScrollNode(ref mut item) => {
let node = &clip_scroll_nodes[item.node_index.to_index()];
let item_rect = node.clip.main;
let parent_spatial_id = state.spatial_ids[node.parent_index.to_index()]
.expect("Tried to use WebRender parent SpatialId before it was defined.");
let parent_clip_id = state.clip_ids[node.parent_index.to_index()]
.expect("Tried to use WebRender parent ClipId before it was defined.");
match node.node_type {
ClipScrollNodeType::Clip => {
let id = builder.define_clip(
&SpaceAndClipInfo {
clip_id: parent_clip_id,
spatial_id: parent_spatial_id,
},
item_rect,
node.clip.complex.clone(),
None,
);
state.spatial_ids[item.node_index.to_index()] = Some(parent_spatial_id);
state.clip_ids[item.node_index.to_index()] = Some(id);
},
ClipScrollNodeType::ScrollFrame(scroll_sensitivity, external_id) => {
let space_clip_info = builder.define_scroll_frame(
&SpaceAndClipInfo {
clip_id: parent_clip_id,
spatial_id: parent_spatial_id,
},
Some(external_id),
node.content_rect,
node.clip.main,
node.clip.complex.clone(),
None,
scroll_sensitivity,
webrender_api::units::LayoutVector2D::zero(),
);
state.clip_ids[item.node_index.to_index()] = Some(space_clip_info.clip_id);
state.spatial_ids[item.node_index.to_index()] =
Some(space_clip_info.spatial_id);
},
ClipScrollNodeType::StickyFrame(ref sticky_data) => {
// TODO: Add define_sticky_frame_with_parent to WebRender.
let id = builder.define_sticky_frame(
parent_spatial_id,
item_rect,
sticky_data.margins,
sticky_data.vertical_offset_bounds,
sticky_data.horizontal_offset_bounds,
webrender_api::units::LayoutVector2D::zero(),
);
state.spatial_ids[item.node_index.to_index()] = Some(id);
state.clip_ids[item.node_index.to_index()] = Some(parent_clip_id);
},
ClipScrollNodeType::Placeholder => {
unreachable!("Found DefineClipScrollNode for Placeholder type node.");
},
};
},
}
}
}
fn build_common_item_properties(
base: &BaseDisplayItem,
state: &ClipScrollState,
) -> CommonItemProperties {
let tag = match base.metadata.pointing {
Some(cursor) => Some((base.metadata.node.0 as u64, cursor)),
None => None,
};
CommonItemProperties {
clip_rect: base.clip_rect,
spatial_id: state.active_spatial_id,
clip_id: state.active_clip_id,
// TODO(gw): Make use of the WR backface visibility functionality.
is_backface_visible: true,
hit_info: tag,
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,605 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use crate::block::FormattingContextType;
use crate::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils};
use crate::persistent_list::PersistentList;
use app_units::{Au, MAX_AU};
use std::cmp::{max, min};
use std::fmt;
use style::computed_values::float::T as StyleFloat;
use style::logical_geometry::{LogicalRect, LogicalSize, WritingMode};
use style::values::computed::Size;
/// The kind of float: left or right.
#[derive(Clone, Copy, Debug, Serialize)]
pub enum FloatKind {
Left,
Right,
}
impl FloatKind {
pub fn from_property(property: StyleFloat) -> Option<FloatKind> {
match property {
StyleFloat::None => None,
StyleFloat::Left => Some(FloatKind::Left),
StyleFloat::Right => Some(FloatKind::Right),
}
}
}
/// The kind of clearance: left, right, or both.
#[derive(Clone, Copy)]
pub enum ClearType {
Left,
Right,
Both,
}
/// Information about a single float.
#[derive(Clone, Copy)]
struct Float {
/// The boundaries of this float.
bounds: LogicalRect<Au>,
/// The kind of float: left or right.
kind: FloatKind,
}
impl fmt::Debug 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.
#[derive(Clone)]
struct FloatList {
/// Information about each of the floats here.
floats: PersistentList<Float>,
/// Cached copy of the maximum block-start offset of the float.
max_block_start: Option<Au>,
}
impl FloatList {
fn new() -> FloatList {
FloatList {
floats: PersistentList::new(),
max_block_start: 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.floats.len() > 0
}
}
impl fmt::Debug for FloatList {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"max_block_start={:?} floats={}",
self.max_block_start,
self.floats.len()
)?;
for float in self.floats.iter() {
write!(f, " {:?}", float)?;
}
Ok(())
}
}
/// 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::Debug 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.
#[derive(Clone)]
pub struct Floats {
/// The list of floats.
list: FloatList,
/// The offset of the flow relative to the first float.
offset: LogicalSize<Au>,
/// The writing mode of these floats.
pub writing_mode: WritingMode,
}
impl fmt::Debug for Floats {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if !self.list.is_present() {
write!(f, "[empty]")
} else {
write!(f, "offset={:?} floats={:?}", self.offset, self.list)
}
}
}
impl Floats {
/// Creates a new `Floats` object.
pub fn new(writing_mode: WritingMode) -> Floats {
Floats {
list: FloatList::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<LogicalRect<Au>> {
match self.list.floats.front() {
None => None,
Some(float) => Some(float.bounds.translate_by_size(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
/// inline-size 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 = &self.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 range in the block direction.
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 {
FloatKind::Left
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
);
}
FloatKind::Right
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
);
}
FloatKind::Left | FloatKind::Right => {},
}
}
// 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,
_ => panic!("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 understand 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 = PlacementInfo {
size: info.size,
ceiling: match self.list.max_block_start {
None => info.ceiling,
Some(max_block_start) => max(info.ceiling, 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,
};
self.list.floats = self.list.floats.prepend_elem(new_float);
self.list.max_block_start = match self.list.max_block_start {
None => Some(new_float.bounds.start.b),
Some(max_block_start) => Some(max(max_block_start, new_float.bounds.start.b)),
}
}
/// Given the three sides of the bounding rectangle in the block-start direction, 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 = &self.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 {
FloatKind::Left => {
return LogicalRect::new(
self.writing_mode,
Au(0),
info.ceiling,
info.max_inline_size,
MAX_AU,
);
},
FloatKind::Right => {
return LogicalRect::new(
self.writing_mode,
info.max_inline_size - info.size.inline,
info.ceiling,
info.max_inline_size,
MAX_AU,
);
},
}
}
// 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 block-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 {
FloatKind::Left => LogicalRect::new(
self.writing_mode,
Au(0),
float_b,
info.max_inline_size,
MAX_AU,
),
FloatKind::Right => LogicalRect::new(
self.writing_mode,
info.max_inline_size - info.size.inline,
float_b,
info.max_inline_size,
MAX_AU,
),
};
},
Some(rect) => {
assert_ne!(
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(MAX_AU);
return match info.kind {
FloatKind::Left => LogicalRect::new(
self.writing_mode,
rect.start.i,
float_b,
rect.size.inline,
block_size,
),
FloatKind::Right => 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 = &self.list;
let mut clearance = Au(0);
for float in list.floats.iter() {
match (clear, float.kind) {
(ClearType::Left, FloatKind::Left) |
(ClearType::Right, FloatKind::Right) |
(ClearType::Both, _) => {
let b = self.offset.block + float.bounds.start.b + float.bounds.size.block;
clearance = max(clearance, b);
},
_ => {},
}
}
clearance
}
pub fn is_present(&self) -> bool {
self.list.is_present()
}
}
/// The speculated inline sizes of floats flowing through or around a flow (depending on whether
/// the flow is a block formatting context). These speculations are always *upper bounds*; the
/// actual inline sizes might be less. Note that this implies that a speculated value of zero is a
/// guarantee that there will be no floats on that side.
///
/// This is used for two purposes: (a) determining whether we can lay out blocks in parallel; (b)
/// guessing the inline-sizes of block formatting contexts in an effort to lay them out in
/// parallel.
#[derive(Clone, Copy)]
pub struct SpeculatedFloatPlacement {
/// The estimated inline size (an upper bound) of the left floats flowing through this flow.
pub left: Au,
/// The estimated inline size (an upper bound) of the right floats flowing through this flow.
pub right: Au,
}
impl fmt::Debug for SpeculatedFloatPlacement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "L {:?} R {:?}", self.left, self.right)
}
}
impl SpeculatedFloatPlacement {
/// Returns a `SpeculatedFloatPlacement` objects with both left and right speculated inline
/// sizes initialized to zero.
pub fn zero() -> SpeculatedFloatPlacement {
SpeculatedFloatPlacement {
left: Au(0),
right: Au(0),
}
}
/// Given the speculated inline size of the floats out for the inorder predecessor of this
/// flow, computes the speculated inline size of the floats flowing in.
pub fn compute_floats_in(&mut self, flow: &mut dyn Flow) {
let base_flow = flow.base();
if base_flow.flags.contains(FlowFlags::CLEARS_LEFT) {
self.left = Au(0)
}
if base_flow.flags.contains(FlowFlags::CLEARS_RIGHT) {
self.right = Au(0)
}
}
/// Given the speculated inline size of the floats out for this flow's last child, computes the
/// speculated inline size of the floats out for this flow.
pub fn compute_floats_out(&mut self, flow: &mut dyn Flow) {
if flow.is_block_like() {
let block_flow = flow.as_block();
if block_flow.formatting_context_type() != FormattingContextType::None {
*self = block_flow.base.speculated_float_placement_in;
} else {
if self.left > Au(0) || self.right > Au(0) {
let speculated_inline_content_edge_offsets =
block_flow.fragment.guess_inline_content_edge_offsets();
if self.left > Au(0) && speculated_inline_content_edge_offsets.start > Au(0) {
self.left = self.left + speculated_inline_content_edge_offsets.start
}
if self.right > Au(0) && speculated_inline_content_edge_offsets.end > Au(0) {
self.right = self.right + speculated_inline_content_edge_offsets.end
}
}
self.left = max(
self.left,
block_flow.base.speculated_float_placement_in.left,
);
self.right = max(
self.right,
block_flow.base.speculated_float_placement_in.right,
);
}
}
let base_flow = flow.base();
if !base_flow.flags.is_float() {
return;
}
let mut float_inline_size = base_flow.intrinsic_inline_sizes.preferred_inline_size;
if float_inline_size == Au(0) {
if flow.is_block_like() {
// Hack: If the size of the float is not fixed, then there's no
// way we can guess at its size now. So just pick an arbitrary
// nonzero value (in this case, 1px) so that the layout
// traversal logic will know that objects later in the document
// might flow around this float.
let inline_size = flow.as_block().fragment.style.content_inline_size();
let fixed = match inline_size {
Size::Auto => false,
Size::LengthPercentage(ref lp) => {
lp.0.is_definitely_zero() || lp.0.maybe_to_used_value(None).is_some()
},
};
if !fixed {
float_inline_size = Au::from_px(1)
}
}
}
match base_flow.flags.float_kind() {
StyleFloat::None => {},
StyleFloat::Left => self.left = self.left + float_inline_size,
StyleFloat::Right => self.right = self.right + float_inline_size,
}
}
/// Given a flow, computes the speculated inline size of the floats in of its first child.
pub fn compute_floats_in_for_first_child(
parent_flow: &mut dyn Flow,
) -> SpeculatedFloatPlacement {
if !parent_flow.is_block_like() {
return parent_flow.base().speculated_float_placement_in;
}
let parent_block_flow = parent_flow.as_block();
if parent_block_flow.formatting_context_type() != FormattingContextType::None {
return SpeculatedFloatPlacement::zero();
}
let mut placement = parent_block_flow.base.speculated_float_placement_in;
let speculated_inline_content_edge_offsets = parent_block_flow
.fragment
.guess_inline_content_edge_offsets();
if speculated_inline_content_edge_offsets.start > Au(0) {
placement.left = if placement.left > speculated_inline_content_edge_offsets.start {
placement.left - speculated_inline_content_edge_offsets.start
} else {
Au(0)
}
}
if speculated_inline_content_edge_offsets.end > Au(0) {
placement.right = if placement.right > speculated_inline_content_edge_offsets.end {
placement.right - speculated_inline_content_edge_offsets.end
} else {
Au(0)
}
}
placement
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,197 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use crate::flow::{Flow, FlowClass};
use crate::flow_ref::FlowRef;
use serde::ser::{Serialize, SerializeSeq, Serializer};
use serde_json::{to_value, Map, Value};
use std::collections::{linked_list, LinkedList};
use std::ops::Deref;
use std::sync::Arc;
/// This needs to be reworked now that we have dynamically-sized types in Rust.
/// Until then, it's just a wrapper around LinkedList.
///
/// SECURITY-NOTE(pcwalton): It is very important that `FlowRef` values not leak directly to
/// layout. Layout code must only interact with `&Flow` or `&mut Flow` values. Otherwise, layout
/// could stash `FlowRef` values in random places unknown to the system and thereby cause data
/// races. Those data races can lead to memory safety problems, potentially including arbitrary
/// remote code execution! In general, do not add new methods to this file (e.g. new ways of
/// iterating over flows) unless you are *very* sure of what you are doing.
pub struct FlowList {
flows: LinkedList<FlowRef>,
}
impl Serialize for FlowList {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut serializer = serializer.serialize_seq(Some(self.len()))?;
for f in self.iter() {
let mut flow_val = Map::new();
flow_val.insert("class".to_owned(), to_value(f.class()).unwrap());
let data = match f.class() {
FlowClass::Block => to_value(f.as_block()).unwrap(),
FlowClass::Inline => to_value(f.as_inline()).unwrap(),
FlowClass::Table => to_value(f.as_table()).unwrap(),
FlowClass::TableWrapper => to_value(f.as_table_wrapper()).unwrap(),
FlowClass::TableRowGroup => to_value(f.as_table_rowgroup()).unwrap(),
FlowClass::TableRow => to_value(f.as_table_row()).unwrap(),
FlowClass::TableCell => to_value(f.as_table_cell()).unwrap(),
FlowClass::Flex => to_value(f.as_flex()).unwrap(),
FlowClass::ListItem |
FlowClass::TableColGroup |
FlowClass::TableCaption |
FlowClass::Multicol |
FlowClass::MulticolColumn => {
Value::Null // Not implemented yet
},
};
flow_val.insert("data".to_owned(), data);
serializer.serialize_element(&flow_val)?;
}
serializer.end()
}
}
pub struct MutFlowListIterator<'a> {
it: linked_list::IterMut<'a, FlowRef>,
}
pub struct FlowListIterator<'a> {
it: linked_list::Iter<'a, FlowRef>,
}
impl FlowList {
/// Add an element last in the list
///
/// O(1)
pub fn push_back(&mut self, new_tail: FlowRef) {
self.flows.push_back(new_tail);
}
pub fn push_back_arc(&mut self, new_head: Arc<dyn Flow>) {
self.flows.push_back(FlowRef::new(new_head));
}
pub fn push_front_arc(&mut self, new_head: Arc<dyn Flow>) {
self.flows.push_front(FlowRef::new(new_head));
}
pub fn pop_front_arc(&mut self) -> Option<Arc<dyn Flow>> {
self.flows.pop_front().map(FlowRef::into_arc)
}
/// Create an empty list
#[inline]
pub fn new() -> FlowList {
FlowList {
flows: LinkedList::new(),
}
}
/// Provide a forward iterator.
///
/// SECURITY-NOTE(pcwalton): This does not hand out `FlowRef`s by design. Do not add a method
/// to do so! See the comment above in `FlowList`.
#[inline]
pub fn iter<'a>(&'a self) -> FlowListIterator {
FlowListIterator {
it: self.flows.iter(),
}
}
/// Provide a forward iterator with mutable references
///
/// SECURITY-NOTE(pcwalton): This does not hand out `FlowRef`s by design. Do not add a method
/// to do so! See the comment above in `FlowList`.
#[inline]
pub fn iter_mut(&mut self) -> MutFlowListIterator {
MutFlowListIterator {
it: self.flows.iter_mut(),
}
}
/// Provides a caching random-access iterator that yields mutable references. This is
/// guaranteed to perform no more than O(n) pointer chases.
///
/// SECURITY-NOTE(pcwalton): This does not hand out `FlowRef`s by design. Do not add a method
/// to do so! See the comment above in `FlowList`.
#[inline]
pub fn random_access_mut(&mut self) -> FlowListRandomAccessMut {
let length = self.flows.len();
FlowListRandomAccessMut {
iterator: self.flows.iter_mut(),
cache: Vec::with_capacity(length),
}
}
/// O(1)
#[inline]
pub fn len(&self) -> usize {
self.flows.len()
}
#[inline]
pub fn split_off(&mut self, i: usize) -> Self {
FlowList {
flows: self.flows.split_off(i),
}
}
}
impl<'a> DoubleEndedIterator for FlowListIterator<'a> {
fn next_back(&mut self) -> Option<&'a dyn Flow> {
self.it.next_back().map(Deref::deref)
}
}
impl<'a> DoubleEndedIterator for MutFlowListIterator<'a> {
fn next_back(&mut self) -> Option<&'a mut dyn Flow> {
self.it.next_back().map(FlowRef::deref_mut)
}
}
impl<'a> Iterator for FlowListIterator<'a> {
type Item = &'a dyn Flow;
#[inline]
fn next(&mut self) -> Option<&'a dyn Flow> {
self.it.next().map(Deref::deref)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.it.size_hint()
}
}
impl<'a> Iterator for MutFlowListIterator<'a> {
type Item = &'a mut dyn Flow;
#[inline]
fn next(&mut self) -> Option<&'a mut dyn Flow> {
self.it.next().map(FlowRef::deref_mut)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.it.size_hint()
}
}
/// A caching random-access iterator that yields mutable references. This is guaranteed to perform
/// no more than O(n) pointer chases.
pub struct FlowListRandomAccessMut<'a> {
iterator: linked_list::IterMut<'a, FlowRef>,
cache: Vec<FlowRef>,
}
impl<'a> FlowListRandomAccessMut<'a> {
pub fn get<'b>(&'b mut self, index: usize) -> &'b mut dyn Flow {
while index >= self.cache.len() {
match self.iterator.next() {
None => panic!("Flow index out of range!"),
Some(next_flow) => self.cache.push((*next_flow).clone()),
}
}
FlowRef::deref_mut(&mut self.cache[index])
}
}

View file

@ -2,15 +2,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Reference-counted pointers to flows.
//!
//! Eventually, with dynamically sized types in Rust, much of this code will
//! be superfluous. This design is largely duplicating logic of Arc<T> and
//! Weak<T>; please see comments there for details.
use crate::flow::Flow;
use std::ops::Deref;
use std::sync::{Arc, Weak};
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct FlowRef(Arc<dyn Flow>);
@ -23,42 +17,14 @@ impl Deref for FlowRef {
}
impl FlowRef {
/// `FlowRef`s can only be made available to the traversal code.
/// See https://github.com/servo/servo/issues/14014 for more details.
pub fn new(mut r: Arc<dyn Flow>) -> Self {
// This assertion checks that this `FlowRef` does not alias normal `Arc`s.
// If that happens, we're in trouble.
assert!(Arc::get_mut(&mut r).is_some());
FlowRef(r)
}
pub fn get_mut(this: &mut FlowRef) -> Option<&mut dyn Flow> {
Arc::get_mut(&mut this.0)
}
pub fn downgrade(this: &FlowRef) -> WeakFlowRef {
WeakFlowRef(Arc::downgrade(&this.0))
}
pub fn into_arc(mut this: FlowRef) -> Arc<dyn Flow> {
// This assertion checks that this `FlowRef` does not alias normal `Arc`s.
// If that happens, we're in trouble.
assert!(FlowRef::get_mut(&mut this).is_some());
this.0
}
/// WARNING: This should only be used when there is no aliasing:
/// when the traversal ensures that no other threads accesses the same flow at the same time.
/// See https://github.com/servo/servo/issues/6503
/// Use Arc::get_mut instead when possible (e.g. on an Arc that was just created).
#[allow(unsafe_code)]
pub fn deref_mut(this: &mut FlowRef) -> &mut dyn Flow {
let ptr: *const dyn Flow = &*this.0;
unsafe { &mut *(ptr as *mut dyn Flow) }
}
}
#[derive(Clone, Debug)]
pub struct WeakFlowRef(Weak<dyn Flow>);
impl WeakFlowRef {
pub fn upgrade(&self) -> Option<FlowRef> {
self.0.upgrade().map(FlowRef)
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,651 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! The generated content assignment phase.
//!
//! This phase handles CSS counters, quotes, and ordered lists per CSS § 12.3-12.5. It cannot be
//! done in parallel and is therefore a sequential pass that runs on as little of the flow tree
//! as possible.
use crate::context::{with_thread_local_font_context, LayoutContext};
use crate::display_list::items::OpaqueNode;
use crate::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils};
use crate::fragment::{
Fragment, GeneratedContentInfo, SpecificFragmentInfo, UnscannedTextFragmentInfo,
};
use crate::text::TextRunScanner;
use crate::traversal::InorderFlowTraversal;
use script_layout_interface::wrapper_traits::PseudoElementType;
use smallvec::SmallVec;
use std::collections::{HashMap, LinkedList};
use style::computed_values::display::T as Display;
use style::computed_values::list_style_type::T as ListStyleType;
use style::properties::ComputedValues;
use style::selector_parser::RestyleDamage;
use style::servo::restyle_damage::ServoRestyleDamage;
use style::values::generics::counters::ContentItem;
// Decimal styles per CSS-COUNTER-STYLES § 6.1:
static DECIMAL: [char; 10] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
// TODO(pcwalton): `decimal-leading-zero`
static ARABIC_INDIC: [char; 10] = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'];
// TODO(pcwalton): `armenian`, `upper-armenian`, `lower-armenian`
static BENGALI: [char; 10] = [
'', '১', '২', '৩', '', '৫', '৬', '', '৮', '৯',
];
static CAMBODIAN: [char; 10] = [
'០', '១', '២', '៣', '៤', '៥', '៦', '៧', '៨', '៩',
];
// TODO(pcwalton): Suffix for CJK decimal.
static CJK_DECIMAL: [char; 10] = [
'', '一', '二', '三', '四', '五', '六', '七', '八', '九',
];
static DEVANAGARI: [char; 10] = [
'', '१', '२', '३', '४', '५', '६', '७', '८', '९',
];
// TODO(pcwalton): `georgian`
static GUJARATI: [char; 10] = [
'', '૧', '૨', '૩', '૪', '૫', '૬', '૭', '૮', '૯',
];
static GURMUKHI: [char; 10] = [
'', '', '੨', '੩', '', '੫', '੬', '੭', '੮', '੯',
];
// TODO(pcwalton): `hebrew`
static KANNADA: [char; 10] = [
'', '೧', '೨', '೩', '೪', '೫', '೬', '೭', '೮', '೯',
];
static LAO: [char; 10] = [
'', '໑', '໒', '໓', '໔', '໕', '໖', '໗', '໘', '໙',
];
static MALAYALAM: [char; 10] = [
'', '൧', '൨', '൩', '൪', '൫', '൬', '', '൮', '൯',
];
static MONGOLIAN: [char; 10] = [
'᠐', '᠑', '᠒', '᠓', '᠔', '᠕', '᠖', '᠗', '᠘', '᠙',
];
static MYANMAR: [char; 10] = [
'', '၁', '၂', '၃', '၄', '၅', '၆', '၇', '၈', '၉',
];
static ORIYA: [char; 10] = [
'', '୧', '', '୩', '୪', '୫', '୬', '୭', '୮', '୯',
];
static PERSIAN: [char; 10] = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
// TODO(pcwalton): `lower-roman`, `upper-roman`
static TELUGU: [char; 10] = [
'', '౧', '౨', '౩', '౪', '౫', '౬', '౭', '౮', '౯',
];
static THAI: [char; 10] = [
'', '๑', '๒', '๓', '๔', '๕', '๖', '๗', '๘', '๙',
];
static TIBETAN: [char; 10] = [
'༠', '༡', '༢', '༣', '༤', '༥', '༦', '༧', '༨', '༩',
];
// Alphabetic styles per CSS-COUNTER-STYLES § 6.2:
static LOWER_ALPHA: [char; 26] = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
't', 'u', 'v', 'w', 'x', 'y', 'z',
];
static UPPER_ALPHA: [char; 26] = [
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
];
static CJK_EARTHLY_BRANCH: [char; 12] = [
'子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥',
];
static CJK_HEAVENLY_STEM: [char; 10] = [
'甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸',
];
static LOWER_GREEK: [char; 24] = [
'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π',
'ρ', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω',
];
static HIRAGANA: [char; 48] = [
'あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ', 'し', 'す',
'せ', 'そ', 'た', 'ち', 'つ', 'て', 'と', 'な', 'に', 'ぬ', 'ね', 'の', 'は',
'ひ', 'ふ', 'へ', 'ほ', 'ま', 'み', 'む', 'め', 'も', 'や', 'ゆ', 'よ', 'ら',
'り', 'る', 'れ', 'ろ', 'わ', 'ゐ', 'ゑ', 'を', 'ん',
];
static HIRAGANA_IROHA: [char; 47] = [
'い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る', 'を', 'わ',
'か', 'よ', 'た', 'れ', 'そ', 'つ', 'ね', 'な', 'ら', 'む', 'う', 'ゐ', 'の',
'お', 'く', 'や', 'ま', 'け', 'ふ', 'こ', 'え', 'て', 'あ', 'さ', 'き', 'ゆ',
'め', 'み', 'し', 'ゑ', 'ひ', 'も', 'せ', 'す',
];
static KATAKANA: [char; 48] = [
'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ', 'シ', 'ス',
'セ', 'ソ', 'タ', 'チ', 'ツ', 'テ', 'ト', 'ナ', 'ニ', 'ヌ', 'ネ', '', 'ハ',
'ヒ', 'フ', 'ヘ', 'ホ', 'マ', 'ミ', 'ム', 'メ', 'モ', 'ヤ', 'ユ', 'ヨ', 'ラ',
'リ', 'ル', 'レ', 'ロ', 'ワ', 'ヰ', 'ヱ', 'ヲ', 'ン',
];
static KATAKANA_IROHA: [char; 47] = [
'イ', 'ロ', 'ハ', 'ニ', 'ホ', 'ヘ', 'ト', 'チ', 'リ', 'ヌ', 'ル', 'ヲ', 'ワ',
'カ', 'ヨ', 'タ', 'レ', 'ソ', 'ツ', 'ネ', 'ナ', 'ラ', 'ム', 'ウ', 'ヰ', '',
'オ', 'ク', 'ヤ', 'マ', 'ケ', 'フ', 'コ', 'エ', 'テ', 'ア', 'サ', 'キ', 'ユ',
'メ', 'ミ', 'シ', 'ヱ', 'ヒ', 'モ', 'セ', 'ス',
];
/// The generated content resolution traversal.
pub struct ResolveGeneratedContent<'a> {
/// The layout context.
layout_context: &'a LayoutContext<'a>,
/// The counter representing an ordered list item.
list_item: Counter,
/// Named CSS counters.
counters: HashMap<String, Counter>,
/// The level of quote nesting.
quote: u32,
}
impl<'a> ResolveGeneratedContent<'a> {
/// Creates a new generated content resolution traversal.
pub fn new(layout_context: &'a LayoutContext) -> ResolveGeneratedContent<'a> {
ResolveGeneratedContent {
layout_context: layout_context,
list_item: Counter::new(),
counters: HashMap::new(),
quote: 0,
}
}
}
impl<'a> InorderFlowTraversal for ResolveGeneratedContent<'a> {
#[inline]
fn process(&mut self, flow: &mut dyn Flow, level: u32) {
let mut mutator = ResolveGeneratedContentFragmentMutator {
traversal: self,
level: level,
is_block: flow.is_block_like(),
incremented: false,
};
flow.mutate_fragments(&mut |fragment| mutator.mutate_fragment(fragment))
}
#[inline]
fn should_process_subtree(&mut self, flow: &mut dyn Flow) -> bool {
flow.base()
.restyle_damage
.intersects(ServoRestyleDamage::RESOLVE_GENERATED_CONTENT) ||
flow.base().flags.intersects(
FlowFlags::AFFECTS_COUNTERS | FlowFlags::HAS_COUNTER_AFFECTING_CHILDREN,
)
}
}
/// The object that mutates the generated content fragments.
struct ResolveGeneratedContentFragmentMutator<'a, 'b: 'a> {
/// The traversal.
traversal: &'a mut ResolveGeneratedContent<'b>,
/// The level we're at in the flow tree.
level: u32,
/// Whether this flow is a block flow.
is_block: bool,
/// Whether we've incremented the counter yet.
incremented: bool,
}
impl<'a, 'b> ResolveGeneratedContentFragmentMutator<'a, 'b> {
fn mutate_fragment(&mut self, fragment: &mut Fragment) {
// We only reset and/or increment counters once per flow. This avoids double-incrementing
// counters on list items (once for the main fragment and once for the marker).
if !self.incremented {
self.reset_and_increment_counters_as_necessary(fragment);
}
let mut list_style_type = fragment.style().get_list().list_style_type;
if fragment.style().get_box().display != Display::ListItem {
list_style_type = ListStyleType::None
}
let mut new_info = None;
{
let info =
if let SpecificFragmentInfo::GeneratedContent(ref mut info) = fragment.specific {
info
} else {
return;
};
match **info {
GeneratedContentInfo::ListItem => {
new_info = self.traversal.list_item.render(
self.traversal.layout_context,
fragment.node,
fragment.pseudo.clone(),
fragment.style.clone(),
list_style_type,
RenderingMode::Suffix(".\u{00a0}"),
)
},
GeneratedContentInfo::Empty |
GeneratedContentInfo::ContentItem(ContentItem::String(_)) => {
// Nothing to do here.
},
GeneratedContentInfo::ContentItem(ContentItem::Counter(
ref counter_name,
counter_style,
)) => {
let temporary_counter = Counter::new();
let counter = self
.traversal
.counters
.get(&*counter_name.0)
.unwrap_or(&temporary_counter);
new_info = counter.render(
self.traversal.layout_context,
fragment.node,
fragment.pseudo.clone(),
fragment.style.clone(),
counter_style,
RenderingMode::Plain,
)
},
GeneratedContentInfo::ContentItem(ContentItem::Counters(
ref counter_name,
ref separator,
counter_style,
)) => {
let temporary_counter = Counter::new();
let counter = self
.traversal
.counters
.get(&*counter_name.0)
.unwrap_or(&temporary_counter);
new_info = counter.render(
self.traversal.layout_context,
fragment.node,
fragment.pseudo,
fragment.style.clone(),
counter_style,
RenderingMode::All(&separator),
);
},
GeneratedContentInfo::ContentItem(ContentItem::OpenQuote) => {
new_info = render_text(
self.traversal.layout_context,
fragment.node,
fragment.pseudo,
fragment.style.clone(),
self.quote(&*fragment.style, false),
);
self.traversal.quote += 1
},
GeneratedContentInfo::ContentItem(ContentItem::CloseQuote) => {
if self.traversal.quote >= 1 {
self.traversal.quote -= 1
}
new_info = render_text(
self.traversal.layout_context,
fragment.node,
fragment.pseudo,
fragment.style.clone(),
self.quote(&*fragment.style, true),
);
},
GeneratedContentInfo::ContentItem(ContentItem::NoOpenQuote) => {
self.traversal.quote += 1
},
GeneratedContentInfo::ContentItem(ContentItem::NoCloseQuote) => {
if self.traversal.quote >= 1 {
self.traversal.quote -= 1
}
},
GeneratedContentInfo::ContentItem(ContentItem::Url(..)) => {
unreachable!("Servo doesn't parse content: url(..) yet")
},
}
};
fragment.specific = match new_info {
Some(new_info) => new_info,
// If the fragment did not generate any content, replace it with a no-op placeholder
// so that it isn't processed again on the next layout. FIXME (mbrubeck): When
// processing an inline flow, this traversal should be allowed to insert or remove
// fragments. Then we can just remove these fragments rather than adding placeholders.
None => SpecificFragmentInfo::GeneratedContent(Box::new(GeneratedContentInfo::Empty)),
};
}
fn reset_and_increment_counters_as_necessary(&mut self, fragment: &mut Fragment) {
let mut list_style_type = fragment.style().get_list().list_style_type;
if !self.is_block || fragment.style().get_box().display != Display::ListItem {
list_style_type = ListStyleType::None
}
match list_style_type {
ListStyleType::Disc |
ListStyleType::None |
ListStyleType::Circle |
ListStyleType::Square |
ListStyleType::DisclosureOpen |
ListStyleType::DisclosureClosed => {},
_ => self.traversal.list_item.increment(self.level, 1),
}
// Truncate down counters.
for (_, counter) in &mut self.traversal.counters {
counter.truncate_to_level(self.level);
}
self.traversal.list_item.truncate_to_level(self.level);
for pair in &*fragment.style().get_counters().counter_reset {
let counter_name = &*pair.name.0;
if let Some(ref mut counter) = self.traversal.counters.get_mut(counter_name) {
counter.reset(self.level, pair.value);
continue;
}
let mut counter = Counter::new();
counter.reset(self.level, pair.value);
self.traversal
.counters
.insert(counter_name.to_owned(), counter);
}
for pair in &*fragment.style().get_counters().counter_increment {
let counter_name = &*pair.name.0;
if let Some(ref mut counter) = self.traversal.counters.get_mut(counter_name) {
counter.increment(self.level, pair.value);
continue;
}
let mut counter = Counter::new();
counter.increment(self.level, pair.value);
self.traversal
.counters
.insert(counter_name.to_owned(), counter);
}
self.incremented = true
}
fn quote(&self, style: &ComputedValues, close: bool) -> String {
let quotes = &style.get_list().quotes;
if quotes.0.is_empty() {
return String::new();
}
let pair = if self.traversal.quote as usize >= quotes.0.len() {
quotes.0.last().unwrap()
} else {
&quotes.0[self.traversal.quote as usize]
};
if close {
pair.closing.to_string()
} else {
pair.opening.to_string()
}
}
}
/// A counter per CSS 2.1 § 12.4.
struct Counter {
/// The values at each level.
values: Vec<CounterValue>,
}
impl Counter {
fn new() -> Counter {
Counter { values: Vec::new() }
}
fn reset(&mut self, level: u32, value: i32) {
// Do we have an instance of the counter at this level? If so, just mutate it.
if let Some(ref mut existing_value) = self.values.last_mut() {
if level == existing_value.level {
existing_value.value = value;
return;
}
}
// Otherwise, push a new instance of the counter.
self.values.push(CounterValue {
level: level,
value: value,
})
}
fn truncate_to_level(&mut self, level: u32) {
if let Some(position) = self.values.iter().position(|value| value.level > level) {
self.values.truncate(position)
}
}
fn increment(&mut self, level: u32, amount: i32) {
if let Some(ref mut value) = self.values.last_mut() {
value.value += amount;
return;
}
self.values.push(CounterValue {
level: level,
value: amount,
})
}
fn render(
&self,
layout_context: &LayoutContext,
node: OpaqueNode,
pseudo: PseudoElementType,
style: crate::ServoArc<ComputedValues>,
list_style_type: ListStyleType,
mode: RenderingMode,
) -> Option<SpecificFragmentInfo> {
let mut string = String::new();
match mode {
RenderingMode::Plain => {
let value = match self.values.last() {
Some(ref value) => value.value,
None => 0,
};
push_representation(value, list_style_type, &mut string)
},
RenderingMode::Suffix(suffix) => {
let value = match self.values.last() {
Some(ref value) => value.value,
None => 0,
};
push_representation(value, list_style_type, &mut string);
string.push_str(suffix)
},
RenderingMode::All(separator) => {
let mut first = true;
for value in &self.values {
if !first {
string.push_str(separator)
}
first = false;
push_representation(value.value, list_style_type, &mut string)
}
},
}
if string.is_empty() {
None
} else {
render_text(layout_context, node, pseudo, style, string)
}
}
}
/// How a counter value is to be rendered.
enum RenderingMode<'a> {
/// The innermost counter value is rendered with no extra decoration.
Plain,
/// The innermost counter value is rendered with the given string suffix.
Suffix(&'a str),
/// All values of the counter are rendered with the given separator string between them.
All(&'a str),
}
/// The value of a counter at a given level.
struct CounterValue {
/// The level of the flow tree that this corresponds to.
level: u32,
/// The value of the counter at this level.
value: i32,
}
/// Creates fragment info for a literal string.
fn render_text(
layout_context: &LayoutContext,
node: OpaqueNode,
pseudo: PseudoElementType,
style: crate::ServoArc<ComputedValues>,
string: String,
) -> Option<SpecificFragmentInfo> {
let mut fragments = LinkedList::new();
let info = SpecificFragmentInfo::UnscannedText(Box::new(UnscannedTextFragmentInfo::new(
string.into_boxed_str(),
None,
)));
fragments.push_back(Fragment::from_opaque_node_and_style(
node,
pseudo,
style.clone(),
style,
RestyleDamage::rebuild_and_reflow(),
info,
));
// FIXME(pcwalton): This should properly handle multiple marker fragments. This could happen
// due to text run splitting.
let fragments = with_thread_local_font_context(layout_context, |font_context| {
TextRunScanner::new().scan_for_runs(font_context, fragments)
});
if fragments.is_empty() {
None
} else {
Some(fragments.fragments.into_iter().next().unwrap().specific)
}
}
/// Appends string that represents the value rendered using the system appropriate for the given
/// `list-style-type` onto the given string.
fn push_representation(value: i32, list_style_type: ListStyleType, accumulator: &mut String) {
match list_style_type {
ListStyleType::None => {},
ListStyleType::Disc |
ListStyleType::Circle |
ListStyleType::Square |
ListStyleType::DisclosureOpen |
ListStyleType::DisclosureClosed => accumulator.push(static_representation(list_style_type)),
ListStyleType::Decimal => push_numeric_representation(value, &DECIMAL, accumulator),
ListStyleType::ArabicIndic => {
push_numeric_representation(value, &ARABIC_INDIC, accumulator)
},
ListStyleType::Bengali => push_numeric_representation(value, &BENGALI, accumulator),
ListStyleType::Cambodian | ListStyleType::Khmer => {
push_numeric_representation(value, &CAMBODIAN, accumulator)
},
ListStyleType::CjkDecimal => push_numeric_representation(value, &CJK_DECIMAL, accumulator),
ListStyleType::Devanagari => push_numeric_representation(value, &DEVANAGARI, accumulator),
ListStyleType::Gujarati => push_numeric_representation(value, &GUJARATI, accumulator),
ListStyleType::Gurmukhi => push_numeric_representation(value, &GURMUKHI, accumulator),
ListStyleType::Kannada => push_numeric_representation(value, &KANNADA, accumulator),
ListStyleType::Lao => push_numeric_representation(value, &LAO, accumulator),
ListStyleType::Malayalam => push_numeric_representation(value, &MALAYALAM, accumulator),
ListStyleType::Mongolian => push_numeric_representation(value, &MONGOLIAN, accumulator),
ListStyleType::Myanmar => push_numeric_representation(value, &MYANMAR, accumulator),
ListStyleType::Oriya => push_numeric_representation(value, &ORIYA, accumulator),
ListStyleType::Persian => push_numeric_representation(value, &PERSIAN, accumulator),
ListStyleType::Telugu => push_numeric_representation(value, &TELUGU, accumulator),
ListStyleType::Thai => push_numeric_representation(value, &THAI, accumulator),
ListStyleType::Tibetan => push_numeric_representation(value, &TIBETAN, accumulator),
ListStyleType::LowerAlpha => {
push_alphabetic_representation(value, &LOWER_ALPHA, accumulator)
},
ListStyleType::UpperAlpha => {
push_alphabetic_representation(value, &UPPER_ALPHA, accumulator)
},
ListStyleType::CjkEarthlyBranch => {
push_alphabetic_representation(value, &CJK_EARTHLY_BRANCH, accumulator)
},
ListStyleType::CjkHeavenlyStem => {
push_alphabetic_representation(value, &CJK_HEAVENLY_STEM, accumulator)
},
ListStyleType::LowerGreek => {
push_alphabetic_representation(value, &LOWER_GREEK, accumulator)
},
ListStyleType::Hiragana => push_alphabetic_representation(value, &HIRAGANA, accumulator),
ListStyleType::HiraganaIroha => {
push_alphabetic_representation(value, &HIRAGANA_IROHA, accumulator)
},
ListStyleType::Katakana => push_alphabetic_representation(value, &KATAKANA, accumulator),
ListStyleType::KatakanaIroha => {
push_alphabetic_representation(value, &KATAKANA_IROHA, accumulator)
},
}
}
/// Returns the static character that represents the value rendered using the given list-style, if
/// possible.
pub fn static_representation(list_style_type: ListStyleType) -> char {
match list_style_type {
ListStyleType::Disc => '•',
ListStyleType::Circle => '◦',
ListStyleType::Square => '▪',
ListStyleType::DisclosureOpen => '▾',
ListStyleType::DisclosureClosed => '‣',
_ => panic!("No static representation for this list-style-type!"),
}
}
/// Pushes the string that represents the value rendered using the given *alphabetic system* onto
/// the accumulator per CSS-COUNTER-STYLES § 3.1.4.
fn push_alphabetic_representation(value: i32, system: &[char], accumulator: &mut String) {
let mut abs_value = handle_negative_value(value, accumulator);
let mut string: SmallVec<[char; 8]> = SmallVec::new();
while abs_value != 0 {
// Step 1.
abs_value = abs_value - 1;
// Step 2.
string.push(system[abs_value % system.len()]);
// Step 3.
abs_value = abs_value / system.len();
}
accumulator.extend(string.iter().cloned().rev())
}
/// Pushes the string that represents the value rendered using the given *numeric system* onto the
/// accumulator per CSS-COUNTER-STYLES § 3.1.5.
fn push_numeric_representation(value: i32, system: &[char], accumulator: &mut String) {
let mut abs_value = handle_negative_value(value, accumulator);
// Step 1.
if abs_value == 0 {
accumulator.push(system[0]);
return;
}
// Step 2.
let mut string: SmallVec<[char; 8]> = SmallVec::new();
while abs_value != 0 {
// Step 2.1.
string.push(system[abs_value % system.len()]);
// Step 2.2.
abs_value = abs_value / system.len();
}
// Step 3.
accumulator.extend(string.iter().cloned().rev())
}
/// If the system uses a negative sign, handle negative values per CSS-COUNTER-STYLES § 2.
///
/// Returns the absolute value of the counter.
fn handle_negative_value(value: i32, accumulator: &mut String) -> usize {
// 3. If the counter value is negative and the counter style uses a negative sign, instead
// generate an initial representation using the absolute value of the counter value.
if value < 0 {
// TODO: Support different negative signs using the 'negative' descriptor.
// https://drafts.csswg.org/date/2015-07-16/css-counter-styles/#counter-style-negative
accumulator.push('-');
value.abs() as usize
} else {
value as usize
}
}

View file

@ -1,101 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use crate::flow::{Flow, FlowFlags, GetBaseFlow};
use style::computed_values::float::T as Float;
use style::selector_parser::RestyleDamage;
use style::servo::restyle_damage::ServoRestyleDamage;
/// Used in a flow traversal to indicate whether this re-layout should be incremental or not.
#[derive(Clone, Copy, PartialEq)]
pub enum RelayoutMode {
Incremental,
Force,
}
bitflags! {
pub struct SpecialRestyleDamage: u8 {
#[doc = "If this flag is set, we need to reflow the entire document. This is more or less a \
temporary hack to deal with cases that we don't handle incrementally yet."]
const REFLOW_ENTIRE_DOCUMENT = 0x01;
}
}
impl dyn Flow {
pub fn compute_layout_damage(&mut self) -> SpecialRestyleDamage {
let mut special_damage = SpecialRestyleDamage::empty();
let is_absolutely_positioned = self
.base()
.flags
.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED);
// In addition to damage, we use this phase to compute whether nodes affect CSS counters.
let mut has_counter_affecting_children = false;
{
let self_base = self.mut_base();
// Take a snapshot of the parent damage before updating it with damage from children.
let parent_damage = self_base.restyle_damage;
for kid in self_base.children.iter_mut() {
let child_is_absolutely_positioned = kid
.base()
.flags
.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED);
kid.mut_base().restyle_damage.insert(
parent_damage
.damage_for_child(is_absolutely_positioned, child_is_absolutely_positioned),
);
{
let kid: &mut dyn Flow = kid;
special_damage.insert(kid.compute_layout_damage());
}
self_base.restyle_damage.insert(
kid.base()
.restyle_damage
.damage_for_parent(child_is_absolutely_positioned),
);
has_counter_affecting_children = has_counter_affecting_children ||
kid.base().flags.intersects(
FlowFlags::AFFECTS_COUNTERS | FlowFlags::HAS_COUNTER_AFFECTING_CHILDREN,
);
}
}
let self_base = self.mut_base();
if self_base.flags.float_kind() != Float::None &&
self_base
.restyle_damage
.intersects(ServoRestyleDamage::REFLOW)
{
special_damage.insert(SpecialRestyleDamage::REFLOW_ENTIRE_DOCUMENT);
}
if has_counter_affecting_children {
self_base
.flags
.insert(FlowFlags::HAS_COUNTER_AFFECTING_CHILDREN)
} else {
self_base
.flags
.remove(FlowFlags::HAS_COUNTER_AFFECTING_CHILDREN)
}
special_damage
}
pub fn reflow_entire_document(&mut self) {
let self_base = self.mut_base();
self_base
.restyle_damage
.insert(RestyleDamage::rebuild_and_reflow());
self_base
.restyle_damage
.remove(ServoRestyleDamage::RECONSTRUCT_FLOW);
for kid in self_base.children.iter_mut() {
kid.reflow_entire_document();
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,124 +0,0 @@
/* 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 https://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.
use crate::flow::GetBaseFlow;
use crate::flow_ref::FlowRef;
use serde_json::{to_string, to_value, Value};
use std::borrow::ToOwned;
use std::cell::RefCell;
use std::fs::File;
use std::io::Write;
#[cfg(debug_assertions)]
use std::sync::atomic::{AtomicUsize, Ordering};
thread_local!(static STATE_KEY: RefCell<Option<State>> = RefCell::new(None));
#[cfg(debug_assertions)]
static DEBUG_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
pub struct Scope;
#[macro_export]
macro_rules! layout_debug_scope(
($($arg:tt)*) => (
if cfg!(debug_assertions) {
layout_debug::Scope::new(format!($($arg)*))
} else {
layout_debug::Scope
}
)
);
#[derive(Serialize)]
struct ScopeData {
name: String,
pre: Value,
post: Value,
children: Vec<Box<ScopeData>>,
}
impl ScopeData {
fn new(name: String, pre: Value) -> ScopeData {
ScopeData {
name: name,
pre: pre,
post: Value::Null,
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 {
STATE_KEY.with(|ref r| {
if let Some(ref mut state) = *r.borrow_mut() {
let flow_trace = to_value(&state.flow_root.base()).unwrap();
let data = Box::new(ScopeData::new(name.clone(), flow_trace));
state.scope_stack.push(data);
}
});
Scope
}
}
#[cfg(debug_assertions)]
impl Drop for Scope {
fn drop(&mut self) {
STATE_KEY.with(|ref r| {
if let Some(ref mut state) = *r.borrow_mut() {
let mut current_scope = state.scope_stack.pop().unwrap();
current_scope.post = to_value(&state.flow_root.base()).unwrap();
let previous_scope = state.scope_stack.last_mut().unwrap();
previous_scope.children.push(current_scope);
}
});
}
}
/// Generate a unique ID. This is used for items such as Fragment
/// which are often reallocated but represent essentially the
/// same data.
#[cfg(debug_assertions)]
pub fn generate_unique_debug_id() -> u16 {
DEBUG_ID_COUNTER.fetch_add(1, Ordering::SeqCst) as u16
}
/// 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.with(|ref r| r.borrow().is_none()));
STATE_KEY.with(|ref r| {
let flow_trace = to_value(&flow_root.base()).unwrap();
let state = State {
scope_stack: vec![Box::new(ScopeData::new("root".to_owned(), flow_trace))],
flow_root: flow_root.clone(),
};
*r.borrow_mut() = Some(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(generation: u32) {
let mut thread_state = STATE_KEY.with(|ref r| r.borrow_mut().take().unwrap());
assert_eq!(thread_state.scope_stack.len(), 1);
let mut root_scope = thread_state.scope_stack.pop().unwrap();
root_scope.post = to_value(&thread_state.flow_root.base()).unwrap();
let result = to_string(&root_scope).unwrap();
let mut file = File::create(format!("layout_trace-{}.json", generation)).unwrap();
file.write_all(result.as_bytes()).unwrap();
}

View file

@ -4,59 +4,22 @@
#![deny(unsafe_code)]
#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate html5ever;
#[macro_use]
extern crate log;
#[macro_use]
extern crate range;
#[macro_use]
extern crate serde;
#[macro_use]
pub mod layout_debug;
pub mod animation;
mod block;
pub mod construct;
pub mod context;
pub mod data;
pub mod display_list;
mod flex;
mod floats;
pub mod flow;
mod flow_list;
pub mod flow_ref;
mod fragment;
mod generated_content;
pub mod incremental;
mod inline;
mod linked_list;
mod list_item;
mod model;
mod multicol;
pub mod opaque_node;
pub mod parallel;
mod persistent_list;
pub mod query;
pub mod sequential;
mod table;
mod table_caption;
mod table_cell;
mod table_colgroup;
mod table_row;
mod table_rowgroup;
mod table_wrapper;
mod text;
pub mod traversal;
pub mod wrapper;
// For unit tests:
pub use self::data::LayoutData;
pub use crate::fragment::Fragment;
pub use crate::fragment::SpecificFragmentInfo;
// We can't use servo_arc for everything in layout, because the Flow stuff uses
// weak references.

View file

@ -1,21 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! Utility functions for doubly-linked lists.
use std::collections::LinkedList;
use std::mem;
/// Splits the head off a list in O(1) time, and returns the head.
pub fn split_off_head<T>(list: &mut LinkedList<T>) -> LinkedList<T> {
let tail = list.split_off(1);
mem::replace(list, tail)
}
/// Prepends the items in the other list to this one, leaving the other list empty.
#[inline]
pub fn prepend_from<T>(this: &mut LinkedList<T>, other: &mut LinkedList<T>) {
other.append(this);
mem::swap(this, other);
}

View file

@ -1,323 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! Layout for elements with a CSS `display` property of `list-item`. These elements consist of a
//! block and an extra inline fragment for the marker.
use crate::block::BlockFlow;
use crate::context::{with_thread_local_font_context, LayoutContext};
use crate::display_list::items::DisplayListSection;
use crate::display_list::{
BorderPaintingMode, DisplayListBuildState, StackingContextCollectionState,
};
use crate::floats::FloatKind;
use crate::flow::{Flow, FlowClass, OpaqueFlow};
use crate::fragment::Overflow;
use crate::fragment::{
CoordinateSystem, Fragment, FragmentBorderBoxIterator, GeneratedContentInfo,
};
use crate::generated_content;
use crate::inline::InlineFlow;
use app_units::Au;
use euclid::Point2D;
use style::computed_values::list_style_type::T as ListStyleType;
use style::computed_values::position::T as Position;
use style::logical_geometry::LogicalSize;
use style::properties::ComputedValues;
use style::servo::restyle_damage::ServoRestyleDamage;
#[allow(unsafe_code)]
unsafe impl crate::flow::HasBaseFlow for ListItemFlow {}
/// A block with the CSS `display` property equal to `list-item`.
#[derive(Debug)]
#[repr(C)]
pub struct ListItemFlow {
/// Data common to all block flows.
pub block_flow: BlockFlow,
/// The marker, if outside. (Markers that are inside are instead just fragments on the interior
/// `InlineFlow`.)
pub marker_fragments: Vec<Fragment>,
}
impl ListItemFlow {
pub fn from_fragments_and_flotation(
main_fragment: Fragment,
marker_fragments: Vec<Fragment>,
flotation: Option<FloatKind>,
) -> ListItemFlow {
let mut this = ListItemFlow {
block_flow: BlockFlow::from_fragment_and_float_kind(main_fragment, flotation),
marker_fragments: marker_fragments,
};
if let Some(ref marker) = this.marker_fragments.first() {
match marker.style().get_list().list_style_type {
ListStyleType::Disc |
ListStyleType::None |
ListStyleType::Circle |
ListStyleType::Square |
ListStyleType::DisclosureOpen |
ListStyleType::DisclosureClosed => {},
_ => this
.block_flow
.base
.restyle_damage
.insert(ServoRestyleDamage::RESOLVE_GENERATED_CONTENT),
}
}
this
}
/// Assign inline size and position for the marker. This is done during the `assign_block_size`
/// traversal because floats will impact the marker position. Therefore we need to have already
/// called `assign_block_size` on the list item's block flow, in order to know which floats
/// impact the position.
///
/// Per CSS 2.1 § 12.5.1, the marker position is not precisely specified, but it must be on the
/// left side of the content (for ltr direction). However, flowing the marker around floats
/// matches the rendering of Gecko and Blink.
fn assign_marker_inline_sizes(&mut self, layout_context: &LayoutContext) {
let base = &self.block_flow.base;
let available_rect = base.floats.available_rect(
-base.position.size.block,
base.position.size.block,
base.block_container_inline_size,
);
let mut marker_inline_start = available_rect
.unwrap_or(self.block_flow.fragment.border_box)
.start
.i;
for marker in self.marker_fragments.iter_mut().rev() {
let container_block_size = self
.block_flow
.explicit_block_containing_size(layout_context.shared_context());
marker.assign_replaced_inline_size_if_necessary(
base.block_container_inline_size,
container_block_size,
);
// Do this now. There's no need to do this in bubble-widths, since markers do not
// contribute to the inline size of this flow.
let intrinsic_inline_sizes = marker.compute_intrinsic_inline_sizes();
marker.border_box.size.inline = intrinsic_inline_sizes
.content_intrinsic_sizes
.preferred_inline_size;
marker_inline_start = marker_inline_start - marker.border_box.size.inline;
marker.border_box.start.i = marker_inline_start;
}
}
fn assign_marker_block_sizes(&mut self, layout_context: &LayoutContext) {
// FIXME(pcwalton): Do this during flow construction, like `InlineFlow` does?
let marker_line_metrics = with_thread_local_font_context(layout_context, |font_context| {
InlineFlow::minimum_line_metrics_for_fragments(
&self.marker_fragments,
font_context,
&*self.block_flow.fragment.style,
)
});
for marker in &mut self.marker_fragments {
marker.assign_replaced_block_size_if_necessary();
let marker_inline_metrics = marker.aligned_inline_metrics(
layout_context,
&marker_line_metrics,
Some(&marker_line_metrics),
);
marker.border_box.start.b =
marker_line_metrics.space_above_baseline - marker_inline_metrics.ascent;
}
}
}
impl Flow for ListItemFlow {
fn class(&self) -> FlowClass {
FlowClass::ListItem
}
fn as_mut_block(&mut self) -> &mut BlockFlow {
&mut self.block_flow
}
fn as_block(&self) -> &BlockFlow {
&self.block_flow
}
fn bubble_inline_sizes(&mut self) {
// The marker contributes no intrinsic inline-size, so…
self.block_flow.bubble_inline_sizes()
}
fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
self.block_flow.assign_inline_sizes(layout_context);
}
fn assign_block_size(&mut self, layout_context: &LayoutContext) {
self.block_flow.assign_block_size(layout_context);
self.assign_marker_inline_sizes(layout_context);
self.assign_marker_block_sizes(layout_context);
}
fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
self.block_flow
.compute_stacking_relative_position(layout_context)
}
fn place_float_if_applicable<'a>(&mut self) {
self.block_flow.place_float_if_applicable()
}
fn contains_roots_of_absolute_flow_tree(&self) -> bool {
self.block_flow.contains_roots_of_absolute_flow_tree()
}
fn is_absolute_containing_block(&self) -> bool {
self.block_flow.is_absolute_containing_block()
}
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
self.block_flow
.update_late_computed_inline_position_if_necessary(inline_position)
}
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
self.block_flow
.update_late_computed_block_position_if_necessary(block_position)
}
fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
// Draw the marker, if applicable.
for marker in &mut self.marker_fragments {
let stacking_relative_border_box = self
.block_flow
.base
.stacking_relative_border_box_for_display_list(marker);
marker.build_display_list(
state,
stacking_relative_border_box,
BorderPaintingMode::Separate,
DisplayListSection::Content,
self.block_flow.base.clip,
None,
);
}
// Draw the rest of the block.
self.block_flow
.build_display_list_for_block(state, BorderPaintingMode::Separate)
}
fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
self.block_flow.collect_stacking_contexts(state);
}
fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
self.block_flow.repair_style(new_style)
}
fn compute_overflow(&self) -> Overflow {
let mut overflow = self.block_flow.compute_overflow();
let flow_size = self
.block_flow
.base
.position
.size
.to_physical(self.block_flow.base.writing_mode);
let relative_containing_block_size = &self
.block_flow
.base
.early_absolute_position_info
.relative_containing_block_size;
for fragment in &self.marker_fragments {
overflow.union(&fragment.compute_overflow(&flow_size, &relative_containing_block_size))
}
overflow
}
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
self.block_flow.generated_containing_block_size(flow)
}
/// The 'position' property of this flow.
fn positioning(&self) -> Position {
self.block_flow.positioning()
}
fn iterate_through_fragment_border_boxes(
&self,
iterator: &mut dyn FragmentBorderBoxIterator,
level: i32,
stacking_context_position: &Point2D<Au>,
) {
self.block_flow.iterate_through_fragment_border_boxes(
iterator,
level,
stacking_context_position,
);
for marker in &self.marker_fragments {
if iterator.should_process(marker) {
iterator.process(
marker,
level,
&marker
.stacking_relative_border_box(
&self.block_flow.base.stacking_relative_position,
&self
.block_flow
.base
.early_absolute_position_info
.relative_containing_block_size,
self.block_flow
.base
.early_absolute_position_info
.relative_containing_block_mode,
CoordinateSystem::Own,
)
.translate(&stacking_context_position.to_vector()),
);
}
}
}
fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
self.block_flow.mutate_fragments(mutator);
for marker in &mut self.marker_fragments {
(*mutator)(marker)
}
}
}
/// The kind of content that `list-style-type` results in.
pub enum ListStyleTypeContent {
None,
StaticText(char),
GeneratedContent(Box<GeneratedContentInfo>),
}
impl ListStyleTypeContent {
/// Returns the content to be used for the given value of the `list-style-type` property.
pub fn from_list_style_type(list_style_type: ListStyleType) -> ListStyleTypeContent {
// Just to keep things simple, use a nonbreaking space (Unicode 0xa0) to provide the marker
// separation.
match list_style_type {
ListStyleType::None => ListStyleTypeContent::None,
ListStyleType::Disc |
ListStyleType::Circle |
ListStyleType::Square |
ListStyleType::DisclosureOpen |
ListStyleType::DisclosureClosed => {
let text = generated_content::static_representation(list_style_type);
ListStyleTypeContent::StaticText(text)
},
_ => ListStyleTypeContent::GeneratedContent(Box::new(GeneratedContentInfo::ListItem)),
}
}
}

View file

@ -1,609 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! Borders, padding, and margins.
use crate::fragment::Fragment;
use app_units::Au;
use euclid::SideOffsets2D;
use std::cmp::{max, min};
use std::fmt;
use style::logical_geometry::{LogicalMargin, WritingMode};
use style::properties::ComputedValues;
use style::values::computed::MaxSize;
use style::values::computed::{LengthPercentageOrAuto, Size};
/// A collapsible margin. See CSS 2.1 § 8.3.1.
#[derive(Clone, Copy, Debug)]
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 = max(self.most_positive, other.most_positive);
self.most_negative = 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.
#[derive(Clone, Copy, Debug)]
pub enum CollapsibleMargins {
/// Margins may not collapse with this flow.
None(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.
Collapse(AdjoiningMargins, AdjoiningMargins),
/// Margins collapse *through* this flow. This means, essentially, that the flow doesnt
/// have any border, padding, or out-of-flow (floating or positioned) content
CollapseThrough(AdjoiningMargins),
}
impl CollapsibleMargins {
pub fn new() -> CollapsibleMargins {
CollapsibleMargins::None(Au(0), Au(0))
}
/// Returns the amount of margin that should be applied in a noncollapsible context. This is
/// currently used to apply block-start margin for hypothetical boxes, since we do not collapse
/// margins of hypothetical boxes.
pub fn block_start_margin_for_noncollapsible_context(&self) -> Au {
match *self {
CollapsibleMargins::None(block_start, _) => block_start,
CollapsibleMargins::Collapse(ref block_start, _) |
CollapsibleMargins::CollapseThrough(ref block_start) => block_start.collapse(),
}
}
pub fn block_end_margin_for_noncollapsible_context(&self) -> Au {
match *self {
CollapsibleMargins::None(_, block_end) => block_end,
CollapsibleMargins::Collapse(_, ref block_end) |
CollapsibleMargins::CollapseThrough(ref block_end) => block_end.collapse(),
}
}
}
enum FinalMarginState {
MarginsCollapseThrough,
BottomMarginCollapses,
}
pub struct MarginCollapseInfo {
pub state: MarginCollapseState,
pub block_start_margin: AdjoiningMargins,
pub margin_in: AdjoiningMargins,
}
impl MarginCollapseInfo {
pub fn initialize_block_start_margin(
fragment: &Fragment,
can_collapse_block_start_margin_with_kids: bool,
) -> MarginCollapseInfo {
MarginCollapseInfo {
state: if can_collapse_block_start_margin_with_kids {
MarginCollapseState::AccumulatingCollapsibleTopMargin
} else {
MarginCollapseState::AccumulatingMarginIn
},
block_start_margin: AdjoiningMargins::from_margin(fragment.margin.block_start),
margin_in: AdjoiningMargins::new(),
}
}
pub fn finish_and_compute_collapsible_margins(
mut self,
fragment: &Fragment,
containing_block_size: Option<Au>,
can_collapse_block_end_margin_with_kids: bool,
mut may_collapse_through: bool,
) -> (CollapsibleMargins, Au) {
let state = match self.state {
MarginCollapseState::AccumulatingCollapsibleTopMargin => {
may_collapse_through = may_collapse_through &&
match fragment.style().content_block_size() {
Size::Auto => true,
Size::LengthPercentage(ref lp) => {
lp.is_definitely_zero() ||
lp.maybe_to_used_value(containing_block_size).is_none()
},
};
if may_collapse_through {
if fragment.style.min_block_size().is_auto() ||
fragment.style().min_block_size().is_definitely_zero()
{
FinalMarginState::MarginsCollapseThrough
} else {
// If the fragment has non-zero min-block-size, margins may not
// collapse through it.
FinalMarginState::BottomMarginCollapses
}
} else {
// If the fragment has an explicitly specified block-size, margins may not
// collapse through it.
FinalMarginState::BottomMarginCollapses
}
},
MarginCollapseState::AccumulatingMarginIn => FinalMarginState::BottomMarginCollapses,
};
// 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 {
FinalMarginState::MarginsCollapseThrough => {
let advance = self.block_start_margin.collapse();
self.margin_in
.union(AdjoiningMargins::from_margin(block_end_margin));
(
CollapsibleMargins::Collapse(self.block_start_margin, self.margin_in),
advance,
)
},
FinalMarginState::BottomMarginCollapses => {
let advance = self.margin_in.collapse();
self.margin_in
.union(AdjoiningMargins::from_margin(block_end_margin));
(
CollapsibleMargins::Collapse(self.block_start_margin, self.margin_in),
advance,
)
},
}
} else {
match state {
FinalMarginState::MarginsCollapseThrough => {
self.block_start_margin
.union(AdjoiningMargins::from_margin(block_end_margin));
(
CollapsibleMargins::CollapseThrough(self.block_start_margin),
Au(0),
)
},
FinalMarginState::BottomMarginCollapses => {
self.margin_in
.union(AdjoiningMargins::from_margin(block_end_margin));
(
CollapsibleMargins::Collapse(self.block_start_margin, self.margin_in),
Au(0),
)
},
}
}
}
pub fn current_float_ceiling(&mut self) -> Au {
match self.state {
MarginCollapseState::AccumulatingCollapsibleTopMargin => {
// We do not include the top margin in the float ceiling, because the float flow
// needs to be positioned relative to our *border box*, not our margin box. See
// `tests/ref/float_under_top_margin_a.html`.
Au(0)
},
MarginCollapseState::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,
can_collapse_block_start_margin: bool,
) -> Au {
if !can_collapse_block_start_margin {
self.state = MarginCollapseState::AccumulatingMarginIn
}
match (self.state, *child_collapsible_margins) {
(
MarginCollapseState::AccumulatingCollapsibleTopMargin,
CollapsibleMargins::None(block_start, _),
) => {
self.state = MarginCollapseState::AccumulatingMarginIn;
block_start
},
(
MarginCollapseState::AccumulatingCollapsibleTopMargin,
CollapsibleMargins::Collapse(block_start, _),
) => {
self.block_start_margin.union(block_start);
self.state = MarginCollapseState::AccumulatingMarginIn;
Au(0)
},
(
MarginCollapseState::AccumulatingMarginIn,
CollapsibleMargins::None(block_start, _),
) => {
let previous_margin_value = self.margin_in.collapse();
self.margin_in = AdjoiningMargins::new();
previous_margin_value + block_start
},
(
MarginCollapseState::AccumulatingMarginIn,
CollapsibleMargins::Collapse(block_start, _),
) => {
self.margin_in.union(block_start);
let margin_value = self.margin_in.collapse();
self.margin_in = AdjoiningMargins::new();
margin_value
},
(_, CollapsibleMargins::CollapseThrough(_)) => {
// 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) {
(
MarginCollapseState::AccumulatingCollapsibleTopMargin,
CollapsibleMargins::None(..),
) |
(
MarginCollapseState::AccumulatingCollapsibleTopMargin,
CollapsibleMargins::Collapse(..),
) => {
// Can't happen because the state will have been replaced with
// `MarginCollapseState::AccumulatingMarginIn` above.
panic!("should not be accumulating collapsible block_start margins anymore!")
},
(
MarginCollapseState::AccumulatingCollapsibleTopMargin,
CollapsibleMargins::CollapseThrough(margin),
) => {
self.block_start_margin.union(margin);
Au(0)
},
(MarginCollapseState::AccumulatingMarginIn, CollapsibleMargins::None(_, block_end)) => {
assert_eq!(self.margin_in.most_positive, Au(0));
assert_eq!(self.margin_in.most_negative, Au(0));
block_end
},
(
MarginCollapseState::AccumulatingMarginIn,
CollapsibleMargins::Collapse(_, block_end),
) |
(
MarginCollapseState::AccumulatingMarginIn,
CollapsibleMargins::CollapseThrough(block_end),
) => {
self.margin_in.union(block_end);
Au(0)
},
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum MarginCollapseState {
/// We are accumulating margin on the logical top of this flow.
AccumulatingCollapsibleTopMargin,
/// We are accumulating margin between two blocks.
AccumulatingMarginIn,
}
/// Intrinsic inline-sizes, which consist of minimum and preferred.
#[derive(Clone, Copy, Serialize)]
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,
}
impl fmt::Debug for IntrinsicISizes {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"min={:?}, pref={:?}",
self.minimum_inline_size, self.preferred_inline_size
)
}
}
impl IntrinsicISizes {
pub fn new() -> IntrinsicISizes {
IntrinsicISizes {
minimum_inline_size: Au(0),
preferred_inline_size: Au(0),
}
}
}
/// The temporary result of the computation of intrinsic inline-sizes.
#[derive(Debug)]
pub struct IntrinsicISizesContribution {
/// Intrinsic sizes for the content only (not counting borders, padding, or margins).
pub content_intrinsic_sizes: IntrinsicISizes,
/// The inline size of borders and padding, as well as margins if appropriate.
pub surrounding_size: Au,
}
impl IntrinsicISizesContribution {
/// Creates and initializes an inline size computation with all sizes set to zero.
pub fn new() -> IntrinsicISizesContribution {
IntrinsicISizesContribution {
content_intrinsic_sizes: IntrinsicISizes::new(),
surrounding_size: Au(0),
}
}
/// Adds the content intrinsic sizes and the surrounding size together to yield the final
/// intrinsic size computation.
pub fn finish(self) -> IntrinsicISizes {
IntrinsicISizes {
minimum_inline_size: self.content_intrinsic_sizes.minimum_inline_size +
self.surrounding_size,
preferred_inline_size: self.content_intrinsic_sizes.preferred_inline_size +
self.surrounding_size,
}
}
/// Updates the computation so that the minimum is the maximum of the current minimum and the
/// given minimum and the preferred is the sum of the current preferred and the given
/// preferred. This is used when laying out fragments in the inline direction.
///
/// FIXME(pcwalton): This is incorrect when the inline fragment contains forced line breaks
/// (e.g. `<br>` or `white-space: pre`).
pub fn union_inline(&mut self, sizes: &IntrinsicISizes) {
self.content_intrinsic_sizes.minimum_inline_size = max(
self.content_intrinsic_sizes.minimum_inline_size,
sizes.minimum_inline_size,
);
self.content_intrinsic_sizes.preferred_inline_size =
self.content_intrinsic_sizes.preferred_inline_size + sizes.preferred_inline_size
}
/// Updates the computation so that the minimum is the sum of the current minimum and the
/// given minimum and the preferred is the sum of the current preferred and the given
/// preferred. This is used when laying out fragments in the inline direction when
/// `white-space` is `pre` or `nowrap`.
pub fn union_nonbreaking_inline(&mut self, sizes: &IntrinsicISizes) {
self.content_intrinsic_sizes.minimum_inline_size =
self.content_intrinsic_sizes.minimum_inline_size + sizes.minimum_inline_size;
self.content_intrinsic_sizes.preferred_inline_size =
self.content_intrinsic_sizes.preferred_inline_size + sizes.preferred_inline_size
}
/// Updates the computation so that the minimum is the maximum of the current minimum and the
/// given minimum and the preferred is the maximum of the current preferred and the given
/// preferred. This can be useful when laying out fragments in the block direction (but note
/// that it does not take floats into account, so `BlockFlow` does not use it).
///
/// This is used when contributing the intrinsic sizes for individual fragments.
pub fn union_block(&mut self, sizes: &IntrinsicISizes) {
self.content_intrinsic_sizes.minimum_inline_size = max(
self.content_intrinsic_sizes.minimum_inline_size,
sizes.minimum_inline_size,
);
self.content_intrinsic_sizes.preferred_inline_size = max(
self.content_intrinsic_sizes.preferred_inline_size,
sizes.preferred_inline_size,
)
}
}
/// Useful helper data type when computing values for blocks and positioned elements.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MaybeAuto {
Auto,
Specified(Au),
}
impl MaybeAuto {
#[inline]
pub fn from_style(length: LengthPercentageOrAuto, containing_length: Au) -> MaybeAuto {
match length {
LengthPercentageOrAuto::Auto => MaybeAuto::Auto,
LengthPercentageOrAuto::LengthPercentage(ref lp) => {
MaybeAuto::Specified(lp.to_used_value(containing_length))
},
}
}
#[inline]
pub fn from_option(au: Option<Au>) -> MaybeAuto {
match au {
Some(l) => MaybeAuto::Specified(l),
_ => MaybeAuto::Auto,
}
}
#[inline]
pub fn to_option(&self) -> Option<Au> {
match *self {
MaybeAuto::Specified(value) => Some(value),
MaybeAuto::Auto => None,
}
}
#[inline]
pub fn specified_or_default(&self, default: Au) -> Au {
match *self {
MaybeAuto::Auto => default,
MaybeAuto::Specified(value) => value,
}
}
#[inline]
pub fn specified_or_zero(&self) -> Au {
self.specified_or_default(Au::new(0))
}
#[inline]
pub fn is_auto(&self) -> bool {
match *self {
MaybeAuto::Auto => true,
MaybeAuto::Specified(..) => false,
}
}
#[inline]
pub fn map<F>(&self, mapper: F) -> MaybeAuto
where
F: FnOnce(Au) -> Au,
{
match *self {
MaybeAuto::Auto => MaybeAuto::Auto,
MaybeAuto::Specified(value) => MaybeAuto::Specified(mapper(value)),
}
}
}
/// Receive an optional container size and return used value for width or height.
///
/// `style_length`: content size as given in the CSS.
pub fn style_length(style_length: Size, container_size: Option<Au>) -> MaybeAuto {
match style_length {
Size::Auto => MaybeAuto::Auto,
Size::LengthPercentage(ref lp) => {
MaybeAuto::from_option(lp.0.maybe_to_used_value(container_size))
},
}
}
#[inline]
pub fn padding_from_style(
style: &ComputedValues,
containing_block_inline_size: Au,
writing_mode: WritingMode,
) -> LogicalMargin<Au> {
let padding_style = style.get_padding();
LogicalMargin::from_physical(
writing_mode,
SideOffsets2D::new(
padding_style
.padding_top
.to_used_value(containing_block_inline_size),
padding_style
.padding_right
.to_used_value(containing_block_inline_size),
padding_style
.padding_bottom
.to_used_value(containing_block_inline_size),
padding_style
.padding_left
.to_used_value(containing_block_inline_size),
),
)
}
/// Returns the explicitly-specified margin lengths from the given style. Percentage and auto
/// margins are returned as zero.
///
/// This is used when calculating intrinsic inline sizes.
#[inline]
pub fn specified_margin_from_style(
style: &ComputedValues,
writing_mode: WritingMode,
) -> LogicalMargin<Au> {
let margin_style = style.get_margin();
LogicalMargin::from_physical(
writing_mode,
SideOffsets2D::new(
MaybeAuto::from_style(margin_style.margin_top, Au(0)).specified_or_zero(),
MaybeAuto::from_style(margin_style.margin_right, Au(0)).specified_or_zero(),
MaybeAuto::from_style(margin_style.margin_bottom, Au(0)).specified_or_zero(),
MaybeAuto::from_style(margin_style.margin_left, Au(0)).specified_or_zero(),
),
)
}
/// A min-size and max-size constraint. The constructor has a optional `border`
/// parameter, and when it is present the constraint will be subtracted. This is
/// used to adjust the constraint for `box-sizing: border-box`, and when you do so
/// make sure the size you want to clamp is intended to be used for content box.
#[derive(Clone, Copy, Debug, Serialize)]
pub struct SizeConstraint {
min_size: Au,
max_size: Option<Au>,
}
impl SizeConstraint {
/// Create a `SizeConstraint` for an axis.
pub fn new(
container_size: Option<Au>,
min_size: Size,
max_size: MaxSize,
border: Option<Au>,
) -> SizeConstraint {
let mut min_size = match min_size {
Size::Auto => Au(0),
Size::LengthPercentage(ref lp) => {
lp.maybe_to_used_value(container_size).unwrap_or(Au(0))
},
};
let mut max_size = match max_size {
MaxSize::None => None,
MaxSize::LengthPercentage(ref lp) => lp.maybe_to_used_value(container_size),
};
// Make sure max size is not smaller than min size.
max_size = max_size.map(|x| max(x, min_size));
if let Some(border) = border {
min_size = max(min_size - border, Au(0));
max_size = max_size.map(|x| max(x - border, Au(0)));
}
SizeConstraint { min_size, max_size }
}
/// Clamp the given size by the given min size and max size constraint.
pub fn clamp(&self, other: Au) -> Au {
if other < self.min_size {
self.min_size
} else {
match self.max_size {
Some(max_size) if max_size < other => max_size,
_ => other,
}
}
}
}

View file

@ -1,384 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! CSS Multi-column layout http://dev.w3.org/csswg/css-multicol/
use crate::block::BlockFlow;
use crate::context::LayoutContext;
use crate::display_list::{DisplayListBuildState, StackingContextCollectionState};
use crate::floats::FloatKind;
use crate::flow::{Flow, FlowClass, FragmentationContext, GetBaseFlow, OpaqueFlow};
use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
use crate::ServoArc;
use app_units::Au;
use euclid::{Point2D, Vector2D};
use gfx_traits::print_tree::PrintTree;
use std::cmp::{max, min};
use std::fmt;
use std::sync::Arc;
use style::logical_geometry::LogicalSize;
use style::properties::ComputedValues;
use style::values::computed::length::{
MaxSize, NonNegativeLengthOrAuto, NonNegativeLengthPercentageOrNormal, Size,
};
use style::values::generics::column::ColumnCount;
#[allow(unsafe_code)]
unsafe impl crate::flow::HasBaseFlow for MulticolFlow {}
#[repr(C)]
pub struct MulticolFlow {
pub block_flow: BlockFlow,
/// Length between the inline-start edge of a column and that of the next.
/// That is, the used column-width + used column-gap.
pub column_pitch: Au,
}
#[allow(unsafe_code)]
unsafe impl crate::flow::HasBaseFlow for MulticolColumnFlow {}
#[repr(C)]
pub struct MulticolColumnFlow {
pub block_flow: BlockFlow,
}
impl MulticolFlow {
pub fn from_fragment(fragment: Fragment, float_kind: Option<FloatKind>) -> MulticolFlow {
MulticolFlow {
block_flow: BlockFlow::from_fragment_and_float_kind(fragment, float_kind),
column_pitch: Au(0),
}
}
}
impl MulticolColumnFlow {
pub fn from_fragment(fragment: Fragment) -> MulticolColumnFlow {
MulticolColumnFlow {
block_flow: BlockFlow::from_fragment(fragment),
}
}
}
impl Flow for MulticolFlow {
fn class(&self) -> FlowClass {
FlowClass::Multicol
}
fn as_mut_block(&mut self) -> &mut BlockFlow {
&mut self.block_flow
}
fn as_block(&self) -> &BlockFlow {
&self.block_flow
}
fn bubble_inline_sizes(&mut self) {
// FIXME(SimonSapin) http://dev.w3.org/csswg/css-sizing/#multicol-intrinsic
self.block_flow.bubble_inline_sizes();
}
fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
debug!(
"assign_inline_sizes({}): assigning inline_size for flow",
"multicol"
);
let shared_context = layout_context.shared_context();
self.block_flow.compute_inline_sizes(shared_context);
// Move in from the inline-start border edge.
let inline_start_content_edge = self.block_flow.fragment.border_box.start.i +
self.block_flow.fragment.border_padding.inline_start;
// Distance from the inline-end margin edge to the inline-end content edge.
let inline_end_content_edge = self.block_flow.fragment.margin.inline_end +
self.block_flow.fragment.border_padding.inline_end;
self.block_flow.assign_inline_sizes(layout_context);
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;
let column_width;
{
let style = &self.block_flow.fragment.style;
let column_gap = match style.get_position().column_gap {
NonNegativeLengthPercentageOrNormal::LengthPercentage(len) => {
len.0.to_pixel_length(content_inline_size).into()
},
NonNegativeLengthPercentageOrNormal::Normal => {
self.block_flow.fragment.style.get_font().font_size.size()
},
};
let column_style = style.get_column();
let mut column_count;
if let NonNegativeLengthOrAuto::LengthPercentage(column_width) =
column_style.column_width
{
let column_width = Au::from(column_width);
column_count = max(
1,
(content_inline_size + column_gap).0 / (column_width + column_gap).0,
);
if let ColumnCount::Integer(specified_column_count) = column_style.column_count {
column_count = min(column_count, specified_column_count.0 as i32);
}
} else {
column_count = match column_style.column_count {
ColumnCount::Integer(n) => n.0,
_ => unreachable!(),
}
}
column_width = max(
Au(0),
(content_inline_size + column_gap) / column_count - column_gap,
);
self.column_pitch = column_width + column_gap;
}
self.block_flow.fragment.border_box.size.inline = content_inline_size + padding_and_borders;
self.block_flow.propagate_assigned_inline_size_to_children(
shared_context,
inline_start_content_edge,
inline_end_content_edge,
column_width,
|_, _, _, _, _, _| {},
);
}
fn assign_block_size(&mut self, ctx: &LayoutContext) {
debug!("assign_block_size: assigning block_size for multicol");
let fragmentation_context = Some(FragmentationContext {
this_fragment_is_empty: true,
available_block_size: {
let style = &self.block_flow.fragment.style;
let size = match style.content_block_size() {
Size::Auto => None,
Size::LengthPercentage(ref lp) => lp.maybe_to_used_value(None),
};
let size = size.or_else(|| match style.max_block_size() {
MaxSize::None => None,
MaxSize::LengthPercentage(ref lp) => lp.maybe_to_used_value(None),
});
size.unwrap_or_else(|| {
// FIXME: do column balancing instead
// FIXME: (until column balancing) substract margins/borders/padding
LogicalSize::from_physical(
self.block_flow.base.writing_mode,
ctx.shared_context().viewport_size(),
)
.block
})
},
});
// Before layout, everything is in a single "column"
assert_eq!(self.block_flow.base.children.len(), 1);
let mut column = self.block_flow.base.children.pop_front_arc().unwrap();
// Pretend there is no children for this:
self.block_flow.assign_block_size(ctx);
loop {
let remaining = Arc::get_mut(&mut column)
.unwrap()
.fragment(ctx, fragmentation_context);
self.block_flow.base.children.push_back_arc(column);
column = match remaining {
Some(remaining) => remaining,
None => break,
};
}
}
fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
self.block_flow
.compute_stacking_relative_position(layout_context);
let pitch = LogicalSize::new(self.block_flow.base.writing_mode, self.column_pitch, Au(0));
let pitch = pitch.to_physical(self.block_flow.base.writing_mode);
for (i, child) in self.block_flow.base.children.iter_mut().enumerate() {
let point = &mut child.mut_base().stacking_relative_position;
*point = *point + Vector2D::new(pitch.width * i as i32, pitch.height * i as i32);
}
}
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
self.block_flow
.update_late_computed_inline_position_if_necessary(inline_position)
}
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
self.block_flow
.update_late_computed_block_position_if_necessary(block_position)
}
fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
debug!("build_display_list_multicol");
self.block_flow.build_display_list(state);
}
fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
self.block_flow.collect_stacking_contexts(state);
}
fn repair_style(&mut self, new_style: &ServoArc<ComputedValues>) {
self.block_flow.repair_style(new_style)
}
fn compute_overflow(&self) -> Overflow {
self.block_flow.compute_overflow()
}
fn contains_roots_of_absolute_flow_tree(&self) -> bool {
self.block_flow.contains_roots_of_absolute_flow_tree()
}
fn is_absolute_containing_block(&self) -> bool {
self.block_flow.is_absolute_containing_block()
}
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
self.block_flow.generated_containing_block_size(flow)
}
fn iterate_through_fragment_border_boxes(
&self,
iterator: &mut dyn FragmentBorderBoxIterator,
level: i32,
stacking_context_position: &Point2D<Au>,
) {
self.block_flow.iterate_through_fragment_border_boxes(
iterator,
level,
stacking_context_position,
);
}
fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
self.block_flow.mutate_fragments(mutator);
}
fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
self.block_flow.print_extra_flow_children(print_tree);
}
}
impl Flow for MulticolColumnFlow {
fn class(&self) -> FlowClass {
FlowClass::MulticolColumn
}
fn as_mut_block(&mut self) -> &mut BlockFlow {
&mut self.block_flow
}
fn as_block(&self) -> &BlockFlow {
&self.block_flow
}
fn bubble_inline_sizes(&mut self) {
self.block_flow.bubble_inline_sizes();
}
fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
debug!(
"assign_inline_sizes({}): assigning inline_size for flow",
"multicol column"
);
self.block_flow.assign_inline_sizes(layout_context);
}
fn assign_block_size(&mut self, ctx: &LayoutContext) {
debug!("assign_block_size: assigning block_size for multicol column");
self.block_flow.assign_block_size(ctx);
}
fn fragment(
&mut self,
layout_context: &LayoutContext,
fragmentation_context: Option<FragmentationContext>,
) -> Option<Arc<dyn Flow>> {
Flow::fragment(&mut self.block_flow, layout_context, fragmentation_context)
}
fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
self.block_flow
.compute_stacking_relative_position(layout_context)
}
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
self.block_flow
.update_late_computed_inline_position_if_necessary(inline_position)
}
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
self.block_flow
.update_late_computed_block_position_if_necessary(block_position)
}
fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
debug!("build_display_list_multicol column");
self.block_flow.build_display_list(state);
}
fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
self.block_flow.collect_stacking_contexts(state);
}
fn repair_style(&mut self, new_style: &ServoArc<ComputedValues>) {
self.block_flow.repair_style(new_style)
}
fn compute_overflow(&self) -> Overflow {
self.block_flow.compute_overflow()
}
fn contains_roots_of_absolute_flow_tree(&self) -> bool {
self.block_flow.contains_roots_of_absolute_flow_tree()
}
fn is_absolute_containing_block(&self) -> bool {
self.block_flow.is_absolute_containing_block()
}
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
self.block_flow.generated_containing_block_size(flow)
}
fn iterate_through_fragment_border_boxes(
&self,
iterator: &mut dyn FragmentBorderBoxIterator,
level: i32,
stacking_context_position: &Point2D<Au>,
) {
self.block_flow.iterate_through_fragment_border_boxes(
iterator,
level,
stacking_context_position,
);
}
fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
self.block_flow.mutate_fragments(mutator);
}
fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
self.block_flow.print_extra_flow_children(print_tree);
}
}
impl fmt::Debug for MulticolFlow {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "MulticolFlow: {:?}", self.block_flow)
}
}
impl fmt::Debug for MulticolColumnFlow {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "MulticolColumnFlow: {:?}", self.block_flow)
}
}

View file

@ -7,8 +7,6 @@ use libc::c_void;
use script_traits::UntrustedNodeAddress;
pub trait OpaqueNodeMethods {
/// 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;
}

View file

@ -1,232 +0,0 @@
/* 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 https://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.
#![allow(unsafe_code)]
use crate::block::BlockFlow;
use crate::context::LayoutContext;
use crate::flow::{Flow, GetBaseFlow};
use crate::flow_ref::FlowRef;
use crate::traversal::{AssignBSizes, AssignISizes, BubbleISizes};
use crate::traversal::{PostorderFlowTraversal, PreorderFlowTraversal};
use profile_traits::time::{self, profile, TimerMetadata};
use servo_config::opts;
use smallvec::SmallVec;
use std::mem;
use std::ptr;
use std::sync::atomic::{AtomicIsize, Ordering};
/// Traversal chunk size.
const CHUNK_SIZE: usize = 16;
pub type FlowList = SmallVec<[UnsafeFlow; CHUNK_SIZE]>;
/// Vtable + pointer representation of a Flow trait object.
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct UnsafeFlow(*const dyn Flow);
unsafe impl Sync for UnsafeFlow {}
unsafe impl Send for UnsafeFlow {}
fn null_unsafe_flow() -> UnsafeFlow {
UnsafeFlow(ptr::null::<BlockFlow>())
}
pub fn mut_owned_flow_to_unsafe_flow(flow: *mut FlowRef) -> UnsafeFlow {
unsafe { UnsafeFlow(&**flow) }
}
/// Information that we need stored in each flow.
pub struct FlowParallelInfo {
/// The number of children that still need work done.
pub children_count: AtomicIsize,
/// The address of the parent flow.
pub parent: UnsafeFlow,
}
impl FlowParallelInfo {
pub fn new() -> FlowParallelInfo {
FlowParallelInfo {
children_count: AtomicIsize::new(0),
parent: null_unsafe_flow(),
}
}
}
/// 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.
///
/// 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 bottom_up_flow(mut unsafe_flow: UnsafeFlow, assign_bsize_traversal: &AssignBSizes) {
loop {
// Get a real flow.
let flow: &mut dyn Flow = unsafe { mem::transmute(unsafe_flow) };
// Perform the appropriate traversal.
if assign_bsize_traversal.should_process(flow) {
assign_bsize_traversal.process(flow);
}
let base = flow.mut_base();
// Reset the count of children for the next layout traversal.
base.parallel
.children_count
.store(base.children.len() as isize, Ordering::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 dyn Flow = unsafe { &mut *(unsafe_parent.0 as *mut dyn Flow) };
let parent_base = parent.mut_base();
if parent_base
.parallel
.children_count
.fetch_sub(1, Ordering::Relaxed) ==
1
{
// We were the last child of our parent. Reflow our parent.
unsafe_flow = unsafe_parent
} else {
// Stop.
break;
}
}
}
fn top_down_flow<'scope>(
unsafe_flows: &[UnsafeFlow],
pool: &'scope rayon::ThreadPool,
scope: &rayon::ScopeFifo<'scope>,
assign_isize_traversal: &'scope AssignISizes,
assign_bsize_traversal: &'scope AssignBSizes,
) {
let mut discovered_child_flows = FlowList::new();
for unsafe_flow in unsafe_flows {
let mut had_children = false;
unsafe {
// Get a real flow.
let flow: &mut dyn Flow = mem::transmute(*unsafe_flow);
flow.mut_base().thread_id = pool.current_thread_index().unwrap() as u8;
if assign_isize_traversal.should_process(flow) {
// Perform the appropriate traversal.
assign_isize_traversal.process(flow);
}
// Possibly enqueue the children.
for kid in flow.mut_base().child_iter_mut() {
had_children = true;
discovered_child_flows.push(UnsafeFlow(kid));
}
}
// If there were no more children, start assigning block-sizes.
if !had_children {
bottom_up_flow(*unsafe_flow, &assign_bsize_traversal)
}
}
if discovered_child_flows.is_empty() {
return;
}
if discovered_child_flows.len() <= CHUNK_SIZE {
// We can handle all the children in this work unit.
top_down_flow(
&discovered_child_flows,
pool,
scope,
&assign_isize_traversal,
&assign_bsize_traversal,
);
} else {
// Spawn a new work unit for each chunk after the first.
let mut chunks = discovered_child_flows.chunks(CHUNK_SIZE);
let first_chunk = chunks.next();
for chunk in chunks {
let nodes = chunk.iter().cloned().collect::<FlowList>();
scope.spawn_fifo(move |scope| {
top_down_flow(
&nodes,
pool,
scope,
&assign_isize_traversal,
&assign_bsize_traversal,
);
});
}
if let Some(chunk) = first_chunk {
top_down_flow(
chunk,
pool,
scope,
&assign_isize_traversal,
&assign_bsize_traversal,
);
}
}
}
/// Run the main layout passes in parallel.
pub fn reflow(
root: &mut dyn Flow,
profiler_metadata: Option<TimerMetadata>,
time_profiler_chan: time::ProfilerChan,
context: &LayoutContext,
queue: &rayon::ThreadPool,
) {
if opts::get().bubble_inline_sizes_separately {
let bubble_inline_sizes = BubbleISizes {
layout_context: &context,
};
bubble_inline_sizes.traverse(root);
}
let assign_isize_traversal = &AssignISizes {
layout_context: &context,
};
let assign_bsize_traversal = &AssignBSizes {
layout_context: &context,
};
let nodes = [UnsafeFlow(root)];
queue.install(move || {
rayon::scope_fifo(move |scope| {
profile(
time::ProfilerCategory::LayoutParallelWarmup,
profiler_metadata,
time_profiler_chan,
move || {
top_down_flow(
&nodes,
queue,
scope,
assign_isize_traversal,
assign_bsize_traversal,
);
},
);
});
});
}

View file

@ -1,101 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! A persistent, thread-safe singly-linked list.
use std::sync::Arc;
pub struct PersistentList<T> {
head: PersistentListLink<T>,
length: usize,
}
struct PersistentListEntry<T> {
value: T,
next: PersistentListLink<T>,
}
type PersistentListLink<T> = Option<Arc<PersistentListEntry<T>>>;
impl<T> PersistentList<T>
where
T: Send + Sync,
{
#[inline]
pub fn new() -> PersistentList<T> {
PersistentList {
head: None,
length: 0,
}
}
#[inline]
pub fn len(&self) -> usize {
self.length
}
#[inline]
pub fn front(&self) -> Option<&T> {
self.head.as_ref().map(|head| &head.value)
}
#[inline]
pub fn prepend_elem(&self, value: T) -> PersistentList<T> {
PersistentList {
head: Some(Arc::new(PersistentListEntry {
value: value,
next: self.head.clone(),
})),
length: self.length + 1,
}
}
#[inline]
pub fn iter(&self) -> PersistentListIterator<T> {
// This could clone (and would not need the lifetime if it did), but then it would incur
// atomic operations on every call to `.next()`. Bad.
PersistentListIterator {
entry: self.head.as_ref().map(|head| &**head),
}
}
}
impl<T> Clone for PersistentList<T>
where
T: Send + Sync,
{
fn clone(&self) -> PersistentList<T> {
// This establishes the persistent nature of this list: we can clone a list by just cloning
// its head.
PersistentList {
head: self.head.clone(),
length: self.length,
}
}
}
pub struct PersistentListIterator<'a, T>
where
T: Send + Sync,
{
entry: Option<&'a PersistentListEntry<T>>,
}
impl<'a, T> Iterator for PersistentListIterator<'a, T>
where
T: Send + Sync + 'static,
{
type Item = &'a T;
#[inline]
fn next(&mut self) -> Option<&'a T> {
let entry = self.entry?;
let value = &entry.value;
self.entry = match entry.next {
None => None,
Some(ref entry) => Some(&**entry),
};
Some(value)
}
}

View file

@ -4,16 +4,11 @@
//! Utilities for querying the layout, as needed by the layout thread.
use crate::construct::ConstructionResult;
use crate::context::LayoutContext;
use crate::display_list::items::{DisplayList, OpaqueNode, ScrollOffsetMap};
use crate::display_list::IndexableText;
use crate::flow::{Flow, GetBaseFlow};
use crate::fragment::{Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo};
use crate::inline::InlineFragmentNodeFlags;
use crate::fragment::{Fragment, FragmentBorderBoxIterator};
use crate::opaque_node::OpaqueNodeMethods;
use crate::sequential;
use crate::wrapper::LayoutNodeLayoutData;
use app_units::Au;
use euclid::{Point2D, Rect, Size2D, Vector2D};
use ipc_channel::ipc::IpcSender;
@ -30,7 +25,6 @@ use script_layout_interface::{LayoutElementType, LayoutNodeType};
use script_traits::LayoutMsg as ConstellationMsg;
use script_traits::UntrustedNodeAddress;
use std::cmp::{max, min};
use std::ops::Deref;
use std::sync::{Arc, Mutex};
use style::computed_values::display::T as Display;
use style::computed_values::position::T as Position;
@ -366,24 +360,16 @@ impl FragmentBorderBoxIterator for MarginRetrievingFragmentBorderBoxIterator {
pub fn process_content_box_request(
requested_node: OpaqueNode,
layout_root: &mut dyn Flow,
) -> Option<Rect<Au>> {
// FIXME(pcwalton): This has not been updated to handle the stacking context relative
// stuff. So the position is wrong in most cases.
let mut iterator = UnioningFragmentBorderBoxIterator::new(requested_node);
sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator);
iterator.rect
UnioningFragmentBorderBoxIterator::new(requested_node).rect
}
pub fn process_content_boxes_request(
requested_node: OpaqueNode,
layout_root: &mut dyn Flow,
) -> Vec<Rect<Au>> {
// FIXME(pcwalton): This has not been updated to handle the stacking context relative
// stuff. So the position is wrong in most cases.
let mut iterator = CollectingFragmentBorderBoxIterator::new(requested_node);
sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator);
iterator.rects
CollectingFragmentBorderBoxIterator::new(requested_node).rects
}
struct FragmentLocatingFragmentIterator {
@ -581,45 +567,6 @@ impl FragmentBorderBoxIterator for ParentOffsetBorderBoxIterator {
if fragment.style.get_box().position == Position::Fixed {
self.parent_nodes.clear();
}
} else if let Some(node) = fragment.inline_context.as_ref().and_then(|inline_context| {
inline_context
.nodes
.iter()
.find(|node| node.address == self.node_address)
}) {
// TODO: Handle cases where the `offsetParent` is an inline
// element. This will likely be impossible until
// https://github.com/servo/servo/issues/13982 is fixed.
// Found a fragment in the flow tree whose inline context
// contains the DOM node we're looking for, i.e. the node
// is inline and contains this fragment.
match self.node_offset_box {
Some(NodeOffsetBoxInfo {
ref mut rectangle, ..
}) => {
*rectangle = rectangle.union(border_box);
},
None => {
// https://github.com/servo/servo/issues/13982 will
// cause this assertion to fail sometimes, so it's
// commented out for now.
/*assert!(node.flags.contains(FIRST_FRAGMENT_OF_ELEMENT),
"First fragment of inline node found wasn't its first fragment!");*/
self.node_offset_box = Some(NodeOffsetBoxInfo {
offset: border_box.origin,
rectangle: *border_box,
});
},
}
if node
.flags
.contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT)
{
self.has_processed_node = true;
}
} else if self.node_offset_box.is_none() {
// TODO(gw): Is there a less fragile way of checking whether this
// fragment is the body element, rather than just checking that
@ -636,8 +583,6 @@ impl FragmentBorderBoxIterator for ParentOffsetBorderBoxIterator {
// 2) Is static position *and* is a table or table cell
// 3) Is not static position
(true, _, _) |
(false, Position::Static, &SpecificFragmentInfo::Table) |
(false, Position::Static, &SpecificFragmentInfo::TableCell) |
(false, Position::Sticky, _) |
(false, Position::Absolute, _) |
(false, Position::Relative, _) |
@ -671,11 +616,8 @@ impl FragmentBorderBoxIterator for ParentOffsetBorderBoxIterator {
pub fn process_node_geometry_request(
requested_node: OpaqueNode,
layout_root: &mut dyn Flow,
) -> Rect<i32> {
let mut iterator = FragmentLocatingFragmentIterator::new(requested_node);
sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator);
iterator.client_rect
FragmentLocatingFragmentIterator::new(requested_node).client_rect
}
pub fn process_node_scroll_id_request<N: LayoutNode>(
@ -689,10 +631,8 @@ pub fn process_node_scroll_id_request<N: LayoutNode>(
/// https://drafts.csswg.org/cssom-view/#scrolling-area
pub fn process_node_scroll_area_request(
requested_node: OpaqueNode,
layout_root: &mut dyn Flow,
) -> Rect<i32> {
let mut iterator = UnioningFragmentScrollAreaIterator::new(requested_node);
sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator);
let iterator = UnioningFragmentScrollAreaIterator::new(requested_node);
match iterator.overflow_direction {
OverflowDirection::RightAndDown => {
let right = max(
@ -742,7 +682,6 @@ pub fn process_resolved_style_request<'a, N>(
node: N,
pseudo: &Option<PseudoElement>,
property: &PropertyId,
layout_root: &mut dyn Flow,
) -> String
where
N: LayoutNode,
@ -755,7 +694,7 @@ where
// We call process_resolved_style_request after performing a whole-document
// traversal, so in the common case, the element is styled.
if element.get_data().is_some() {
return process_resolved_style_request_internal(node, pseudo, property, layout_root);
return process_resolved_style_request_internal(node, pseudo, property);
}
// In a display: none subtree. No pseudo-element exists.
@ -791,7 +730,6 @@ fn process_resolved_style_request_internal<'a, N>(
requested_node: N,
pseudo: &Option<PseudoElement>,
property: &PropertyId,
layout_root: &mut dyn Flow,
) -> String
where
N: LayoutNode,
@ -842,24 +780,11 @@ where
let applies = true;
fn used_value_for_position_property<N: LayoutNode>(
layout_el: <N::ConcreteThreadSafeLayoutNode as ThreadSafeLayoutNode>::ConcreteThreadSafeLayoutElement,
layout_root: &mut dyn Flow,
_layout_el: <N::ConcreteThreadSafeLayoutNode as ThreadSafeLayoutNode>::ConcreteThreadSafeLayoutElement,
requested_node: N,
longhand_id: LonghandId,
) -> String {
let maybe_data = layout_el.borrow_layout_data();
let position = maybe_data.map_or(Point2D::zero(), |data| {
match (*data).flow_construction_result {
ConstructionResult::Flow(ref flow_ref, _) => flow_ref
.deref()
.base()
.stacking_relative_position
.to_point(),
// TODO(dzbarsky) search parents until we find node with a flow ref.
// https://github.com/servo/servo/issues/8307
_ => Point2D::zero(),
}
});
let position = Point2D::zero();
let property = match longhand_id {
LonghandId::Bottom => PositionProperty::Bottom,
LonghandId::Top => PositionProperty::Top,
@ -869,12 +794,11 @@ where
LonghandId::Height => PositionProperty::Height,
_ => unreachable!(),
};
let mut iterator = PositionRetrievingFragmentBorderBoxIterator::new(
let iterator = PositionRetrievingFragmentBorderBoxIterator::new(
requested_node.opaque(),
property,
position,
);
sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator);
iterator
.result
.map(|r| r.to_css_string())
@ -904,13 +828,12 @@ where
LonghandId::PaddingRight => (MarginPadding::Padding, Side::Right),
_ => unreachable!(),
};
let mut iterator = MarginRetrievingFragmentBorderBoxIterator::new(
let iterator = MarginRetrievingFragmentBorderBoxIterator::new(
requested_node.opaque(),
side,
margin_padding,
style.writing_mode,
);
sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator);
iterator
.result
.map(|r| r.to_css_string())
@ -920,12 +843,12 @@ where
LonghandId::Bottom | LonghandId::Top | LonghandId::Right | LonghandId::Left
if applies && positioned && style.get_box().display != Display::None =>
{
used_value_for_position_property(layout_el, layout_root, requested_node, longhand_id)
used_value_for_position_property(layout_el, requested_node, longhand_id)
},
LonghandId::Width | LonghandId::Height
if applies && style.get_box().display != Display::None =>
{
used_value_for_position_property(layout_el, layout_root, requested_node, longhand_id)
used_value_for_position_property(layout_el, requested_node, longhand_id)
},
// FIXME: implement used value computation for line-height
_ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
@ -934,10 +857,8 @@ where
pub fn process_offset_parent_query(
requested_node: OpaqueNode,
layout_root: &mut dyn Flow,
) -> OffsetParentResponse {
let mut iterator = ParentOffsetBorderBoxIterator::new(requested_node);
sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator);
let iterator = ParentOffsetBorderBoxIterator::new(requested_node);
let node_offset_box = iterator.node_offset_box;
let parent_info = iterator

View file

@ -1,193 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! Implements sequential traversals over the DOM and flow trees.
use crate::context::LayoutContext;
use crate::display_list::items::{self, CommonDisplayItem, DisplayItem, DisplayListSection};
use crate::display_list::{DisplayListBuildState, StackingContextCollectionState};
use crate::floats::SpeculatedFloatPlacement;
use crate::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils};
use crate::fragment::{CoordinateSystem, FragmentBorderBoxIterator};
use crate::generated_content::ResolveGeneratedContent;
use crate::incremental::RelayoutMode;
use crate::traversal::{AssignBSizes, AssignISizes, BubbleISizes, BuildDisplayList};
use crate::traversal::{InorderFlowTraversal, PostorderFlowTraversal, PreorderFlowTraversal};
use app_units::Au;
use euclid::{Point2D, Rect, Size2D, Vector2D};
use servo_config::opts;
use style::servo::restyle_damage::ServoRestyleDamage;
use webrender_api::units::LayoutPoint;
pub fn resolve_generated_content(root: &mut dyn Flow, layout_context: &LayoutContext) {
ResolveGeneratedContent::new(&layout_context).traverse(root, 0);
}
/// Run the main layout passes sequentially.
pub fn reflow(root: &mut dyn Flow, layout_context: &LayoutContext, relayout_mode: RelayoutMode) {
fn doit(
flow: &mut dyn Flow,
assign_inline_sizes: AssignISizes,
assign_block_sizes: AssignBSizes,
relayout_mode: RelayoutMode,
) {
// Force reflow children during this traversal. This is needed when we failed
// the float speculation of a block formatting context and need to fix it.
if relayout_mode == RelayoutMode::Force {
flow.mut_base()
.restyle_damage
.insert(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
}
if assign_inline_sizes.should_process(flow) {
assign_inline_sizes.process(flow);
}
for kid in flow.mut_base().child_iter_mut() {
doit(kid, assign_inline_sizes, assign_block_sizes, relayout_mode);
}
if assign_block_sizes.should_process(flow) {
assign_block_sizes.process(flow);
}
}
if opts::get().bubble_inline_sizes_separately {
let bubble_inline_sizes = BubbleISizes {
layout_context: &layout_context,
};
bubble_inline_sizes.traverse(root);
}
let assign_inline_sizes = AssignISizes {
layout_context: &layout_context,
};
let assign_block_sizes = AssignBSizes {
layout_context: &layout_context,
};
doit(root, assign_inline_sizes, assign_block_sizes, relayout_mode);
}
pub fn build_display_list_for_subtree<'a>(
flow_root: &mut dyn Flow,
layout_context: &'a LayoutContext,
background_color: webrender_api::ColorF,
client_size: Size2D<Au>,
) -> DisplayListBuildState<'a> {
let mut state = StackingContextCollectionState::new(layout_context.id);
flow_root.collect_stacking_contexts(&mut state);
let mut state = DisplayListBuildState::new(layout_context, state);
// Create a base rectangle for the page background based on the root
// background color.
let base = state.create_base_display_item(
Rect::new(Point2D::new(Au::new(0), Au::new(0)), client_size),
flow_root.as_block().fragment.node,
None,
DisplayListSection::BackgroundAndBorders,
);
state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new(
base,
webrender_api::RectangleDisplayItem {
color: background_color,
common: items::empty_common_item_properties(),
},
)));
let mut build_display_list = BuildDisplayList { state: state };
build_display_list.traverse(flow_root);
build_display_list.state
}
pub fn iterate_through_flow_tree_fragment_border_boxes(
root: &mut dyn Flow,
iterator: &mut dyn FragmentBorderBoxIterator,
) {
fn doit(
flow: &mut dyn Flow,
level: i32,
iterator: &mut dyn FragmentBorderBoxIterator,
stacking_context_position: &Point2D<Au>,
) {
flow.iterate_through_fragment_border_boxes(iterator, level, stacking_context_position);
for kid in flow.mut_base().child_iter_mut() {
let mut stacking_context_position = *stacking_context_position;
if kid.is_block_flow() && kid.as_block().fragment.establishes_stacking_context() {
stacking_context_position =
Point2D::new(kid.as_block().fragment.margin.inline_start, Au(0)) +
kid.base().stacking_relative_position +
stacking_context_position.to_vector();
let relative_position = kid
.as_block()
.stacking_relative_border_box(CoordinateSystem::Own);
if let Some(matrix) = kid.as_block().fragment.transform_matrix(&relative_position) {
let transform_matrix = matrix.transform_point2d(&LayoutPoint::zero()).unwrap();
stacking_context_position = stacking_context_position +
Vector2D::new(
Au::from_f32_px(transform_matrix.x),
Au::from_f32_px(transform_matrix.y),
)
}
}
doit(kid, level + 1, iterator, &stacking_context_position);
}
}
doit(root, 0, iterator, &Point2D::zero());
}
pub fn store_overflow(layout_context: &LayoutContext, flow: &mut dyn Flow) {
if !flow
.base()
.restyle_damage
.contains(ServoRestyleDamage::STORE_OVERFLOW)
{
return;
}
for kid in flow.mut_base().child_iter_mut() {
store_overflow(layout_context, kid);
}
flow.store_overflow(layout_context);
flow.mut_base()
.restyle_damage
.remove(ServoRestyleDamage::STORE_OVERFLOW);
}
/// Guesses how much inline size will be taken up by floats on the left and right sides of the
/// given flow. This is needed to speculatively calculate the inline sizes of block formatting
/// contexts. The speculation typically succeeds, but if it doesn't we have to lay it out again.
pub fn guess_float_placement(flow: &mut dyn Flow) {
if !flow
.base()
.restyle_damage
.intersects(ServoRestyleDamage::REFLOW)
{
return;
}
let mut floats_in = SpeculatedFloatPlacement::compute_floats_in_for_first_child(flow);
for kid in flow.mut_base().child_iter_mut() {
if kid
.base()
.flags
.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
{
// Do not propagate floats in or out, but do propogate between kids.
guess_float_placement(kid);
} else {
floats_in.compute_floats_in(kid);
kid.mut_base().speculated_float_placement_in = floats_in;
guess_float_placement(kid);
floats_in = kid.base().speculated_float_placement_out;
}
}
floats_in.compute_floats_out(flow);
flow.mut_base().speculated_float_placement_out = floats_in
}

File diff suppressed because it is too large Load diff

View file

@ -1,139 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! CSS table formatting contexts.
use crate::block::BlockFlow;
use crate::context::LayoutContext;
use crate::display_list::{
DisplayListBuildState, StackingContextCollectionFlags, StackingContextCollectionState,
};
use crate::flow::{Flow, FlowClass, OpaqueFlow};
use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
use app_units::Au;
use euclid::Point2D;
use gfx_traits::print_tree::PrintTree;
use std::fmt;
use style::logical_geometry::LogicalSize;
use style::properties::ComputedValues;
#[allow(unsafe_code)]
unsafe impl crate::flow::HasBaseFlow for TableCaptionFlow {}
/// A table formatting context.
#[repr(C)]
pub struct TableCaptionFlow {
pub block_flow: BlockFlow,
}
impl TableCaptionFlow {
pub fn from_fragment(fragment: Fragment) -> TableCaptionFlow {
TableCaptionFlow {
block_flow: BlockFlow::from_fragment(fragment),
}
}
}
impl Flow for TableCaptionFlow {
fn class(&self) -> FlowClass {
FlowClass::TableCaption
}
fn as_mut_block(&mut self) -> &mut BlockFlow {
&mut self.block_flow
}
fn as_block(&self) -> &BlockFlow {
&self.block_flow
}
fn bubble_inline_sizes(&mut self) {
self.block_flow.bubble_inline_sizes();
}
fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
debug!(
"assign_inline_sizes({}): assigning inline_size for flow",
"table_caption"
);
self.block_flow.assign_inline_sizes(layout_context);
}
fn assign_block_size(&mut self, layout_context: &LayoutContext) {
debug!("assign_block_size: assigning block_size for table_caption");
self.block_flow.assign_block_size(layout_context);
}
fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
self.block_flow
.compute_stacking_relative_position(layout_context)
}
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
self.block_flow
.update_late_computed_inline_position_if_necessary(inline_position)
}
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
self.block_flow
.update_late_computed_block_position_if_necessary(block_position)
}
fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
debug!("build_display_list_table_caption: same process as block flow");
self.block_flow.build_display_list(state);
}
fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
self.block_flow
.collect_stacking_contexts_for_block(state, StackingContextCollectionFlags::empty());
}
fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
self.block_flow.repair_style(new_style)
}
fn compute_overflow(&self) -> Overflow {
self.block_flow.compute_overflow()
}
fn contains_roots_of_absolute_flow_tree(&self) -> bool {
self.block_flow.contains_roots_of_absolute_flow_tree()
}
fn is_absolute_containing_block(&self) -> bool {
self.block_flow.is_absolute_containing_block()
}
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
self.block_flow.generated_containing_block_size(flow)
}
fn iterate_through_fragment_border_boxes(
&self,
iterator: &mut dyn FragmentBorderBoxIterator,
level: i32,
stacking_context_position: &Point2D<Au>,
) {
self.block_flow.iterate_through_fragment_border_boxes(
iterator,
level,
stacking_context_position,
)
}
fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
self.block_flow.mutate_fragments(mutator)
}
fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
self.block_flow.print_extra_flow_children(print_tree);
}
}
impl fmt::Debug for TableCaptionFlow {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "TableCaptionFlow: {:?}", self.block_flow)
}
}

View file

@ -1,506 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! CSS table formatting contexts.
use crate::block::{BlockFlow, ISizeAndMarginsComputer, MarginsMayCollapseFlag};
use crate::context::LayoutContext;
use crate::display_list::{
DisplayListBuildState, StackingContextCollectionFlags, StackingContextCollectionState,
};
use crate::flow::{Flow, FlowClass, FlowFlags, GetBaseFlow, OpaqueFlow};
use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
use crate::layout_debug;
use crate::table::InternalTable;
use crate::table_row::{CollapsedBorder, CollapsedBorderProvenance};
use app_units::Au;
use euclid::{Point2D, Rect, SideOffsets2D, Size2D};
use gfx_traits::print_tree::PrintTree;
use script_layout_interface::wrapper_traits::ThreadSafeLayoutNode;
use std::fmt;
use style::logical_geometry::{LogicalMargin, LogicalRect, LogicalSize, WritingMode};
use style::properties::ComputedValues;
use style::values::computed::length::Size;
use style::values::computed::Color;
use style::values::generics::box_::{VerticalAlign, VerticalAlignKeyword};
use style::values::specified::BorderStyle;
#[allow(unsafe_code)]
unsafe impl crate::flow::HasBaseFlow for TableCellFlow {}
/// A table formatting context.
#[derive(Serialize)]
#[repr(C)]
pub struct TableCellFlow {
/// Data common to all block flows.
pub block_flow: BlockFlow,
/// Border collapse information for the cell.
pub collapsed_borders: CollapsedBordersForCell,
/// The column span of this cell.
pub column_span: u32,
/// The rows spanned by this cell.
pub row_span: u32,
/// Whether this cell is visible. If false, the value of `empty-cells` means that we must not
/// display this cell.
pub visible: bool,
}
impl TableCellFlow {
pub fn from_fragment(fragment: Fragment) -> TableCellFlow {
TableCellFlow {
block_flow: BlockFlow::from_fragment(fragment),
collapsed_borders: CollapsedBordersForCell::new(),
column_span: 1,
row_span: 1,
visible: true,
}
}
pub fn from_node_fragment_and_visibility_flag<N: ThreadSafeLayoutNode>(
node: &N,
fragment: Fragment,
visible: bool,
) -> TableCellFlow {
TableCellFlow {
block_flow: BlockFlow::from_fragment(fragment),
collapsed_borders: CollapsedBordersForCell::new(),
column_span: node.get_colspan(),
row_span: node.get_rowspan(),
visible: visible,
}
}
pub fn fragment(&mut self) -> &Fragment {
&self.block_flow.fragment
}
pub fn mut_fragment(&mut self) -> &mut Fragment {
&mut self.block_flow.fragment
}
/// Assign block-size for table-cell 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_cell_base(&mut self, layout_context: &LayoutContext) {
let remaining = self.block_flow.assign_block_size_block_base(
layout_context,
None,
MarginsMayCollapseFlag::MarginsMayNotCollapse,
);
debug_assert!(remaining.is_none());
}
/// Position this cell's children according to vertical-align.
pub fn valign_children(&mut self) {
// Note to the reader: this code has been tested with negative margins.
// We end up with a "end" that's before the "start," but the math still works out.
let mut extents = None;
for kid in self.base().children.iter() {
let kid_base = kid.base();
if kid_base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) {
continue;
}
let start = kid_base.position.start.b -
kid_base
.collapsible_margins
.block_start_margin_for_noncollapsible_context();
let end = kid_base.position.start.b +
kid_base.position.size.block +
kid_base
.collapsible_margins
.block_end_margin_for_noncollapsible_context();
match extents {
Some((ref mut first_start, ref mut last_end)) => {
if start < *first_start {
*first_start = start
}
if end > *last_end {
*last_end = end
}
},
None => extents = Some((start, end)),
}
}
let (first_start, last_end) = match extents {
Some(extents) => extents,
None => return,
};
let kids_size = last_end - first_start;
let self_size = self.base().position.size.block -
self.block_flow.fragment.border_padding.block_start_end();
let kids_self_gap = self_size - kids_size;
// This offset should also account for VerticalAlign::baseline.
// Need max cell ascent from the first row of this cell.
let offset = match self.block_flow.fragment.style().get_box().vertical_align {
VerticalAlign::Keyword(VerticalAlignKeyword::Middle) => kids_self_gap / 2,
VerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => kids_self_gap,
_ => Au(0),
};
if offset == Au(0) {
return;
}
for kid in self.mut_base().children.iter_mut() {
let kid_base = kid.mut_base();
if !kid_base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) {
kid_base.position.start.b += offset
}
}
}
// Total block size of child
//
// Call after block size calculation
pub fn total_block_size(&mut self) -> Au {
// TODO: Percentage block-size
let specified = self
.fragment()
.style()
.content_block_size()
.to_used_value(Au(0))
.unwrap_or(Au(0));
specified + self.fragment().border_padding.block_start_end()
}
}
impl Flow for TableCellFlow {
fn class(&self) -> FlowClass {
FlowClass::TableCell
}
fn as_mut_table_cell(&mut self) -> &mut TableCellFlow {
self
}
fn as_table_cell(&self) -> &TableCellFlow {
self
}
fn as_mut_block(&mut self) -> &mut BlockFlow {
&mut self.block_flow
}
fn as_block(&self) -> &BlockFlow {
&self.block_flow
}
/// Minimum/preferred inline-sizes set by this function are used in automatic table layout
/// calculation.
fn bubble_inline_sizes(&mut self) {
let _scope = layout_debug_scope!(
"table_cell::bubble_inline_sizes {:x}",
self.block_flow.base.debug_id()
);
self.block_flow.bubble_inline_sizes_for_block(true);
let specified_inline_size = match self.block_flow.fragment.style().content_inline_size() {
Size::Auto => Au(0),
Size::LengthPercentage(ref lp) => lp.to_used_value(Au(0)),
};
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, layout_context: &LayoutContext) {
let _scope = layout_debug_scope!(
"table_cell::assign_inline_sizes {:x}",
self.block_flow.base.debug_id()
);
debug!(
"assign_inline_sizes({}): assigning inline_size for flow",
"table_cell"
);
let shared_context = layout_context.shared_context();
// 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.block_container_inline_size;
let inline_size_computer = InternalTable;
inline_size_computer.compute_used_inline_size(
&mut self.block_flow,
shared_context,
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 inline_end_content_edge = self.block_flow.base.block_container_inline_size -
self.block_flow.fragment.border_padding.inline_start_end() -
self.block_flow.fragment.border_box.size.inline;
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(
shared_context,
inline_start_content_edge,
inline_end_content_edge,
content_inline_size,
|_, _, _, _, _, _| {},
);
}
fn assign_block_size(&mut self, layout_context: &LayoutContext) {
debug!("assign_block_size: assigning block_size for table_cell");
self.assign_block_size_table_cell_base(layout_context);
}
fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
self.block_flow
.compute_stacking_relative_position(layout_context)
}
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
self.block_flow
.update_late_computed_inline_position_if_necessary(inline_position)
}
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
self.block_flow
.update_late_computed_block_position_if_necessary(block_position)
}
fn build_display_list(&mut self, _: &mut DisplayListBuildState) {
use style::servo::restyle_damage::ServoRestyleDamage;
// This is handled by TableCellStyleInfo::build_display_list()
// when the containing table builds its display list
// we skip setting the damage in TableCellStyleInfo::build_display_list()
// because we only have immutable access
self.block_flow
.fragment
.restyle_damage
.remove(ServoRestyleDamage::REPAINT);
}
fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
self.block_flow
.collect_stacking_contexts_for_block(state, StackingContextCollectionFlags::empty());
}
fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
self.block_flow.repair_style(new_style)
}
fn compute_overflow(&self) -> Overflow {
self.block_flow.compute_overflow()
}
fn contains_roots_of_absolute_flow_tree(&self) -> bool {
self.block_flow.contains_roots_of_absolute_flow_tree()
}
fn is_absolute_containing_block(&self) -> bool {
self.block_flow.is_absolute_containing_block()
}
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
self.block_flow.generated_containing_block_size(flow)
}
fn iterate_through_fragment_border_boxes(
&self,
iterator: &mut dyn FragmentBorderBoxIterator,
level: i32,
stacking_context_position: &Point2D<Au>,
) {
self.block_flow.iterate_through_fragment_border_boxes(
iterator,
level,
stacking_context_position,
)
}
fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
self.block_flow.mutate_fragments(mutator)
}
fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
self.block_flow.print_extra_flow_children(print_tree);
}
}
impl fmt::Debug for TableCellFlow {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "TableCellFlow: {:?}", self.block_flow)
}
}
#[derive(Clone, Copy, Debug, Serialize)]
pub struct CollapsedBordersForCell {
pub inline_start_border: CollapsedBorder,
pub inline_end_border: CollapsedBorder,
pub block_start_border: CollapsedBorder,
pub block_end_border: CollapsedBorder,
pub inline_start_width: Au,
pub inline_end_width: Au,
pub block_start_width: Au,
pub block_end_width: Au,
}
impl CollapsedBordersForCell {
fn new() -> CollapsedBordersForCell {
CollapsedBordersForCell {
inline_start_border: CollapsedBorder::new(),
inline_end_border: CollapsedBorder::new(),
block_start_border: CollapsedBorder::new(),
block_end_border: CollapsedBorder::new(),
inline_start_width: Au(0),
inline_end_width: Au(0),
block_start_width: Au(0),
block_end_width: Au(0),
}
}
fn should_paint_inline_start_border(&self) -> bool {
self.inline_start_border.provenance != CollapsedBorderProvenance::FromPreviousTableCell
}
fn should_paint_inline_end_border(&self) -> bool {
self.inline_end_border.provenance != CollapsedBorderProvenance::FromNextTableCell
}
fn should_paint_block_start_border(&self) -> bool {
self.block_start_border.provenance != CollapsedBorderProvenance::FromPreviousTableCell
}
fn should_paint_block_end_border(&self) -> bool {
self.block_end_border.provenance != CollapsedBorderProvenance::FromNextTableCell
}
pub fn adjust_border_widths_for_painting(&self, border_widths: &mut LogicalMargin<Au>) {
border_widths.inline_start = if !self.should_paint_inline_start_border() {
Au(0)
} else {
self.inline_start_border.width
};
border_widths.inline_end = if !self.should_paint_inline_end_border() {
Au(0)
} else {
self.inline_end_border.width
};
border_widths.block_start = if !self.should_paint_block_start_border() {
Au(0)
} else {
self.block_start_border.width
};
border_widths.block_end = if !self.should_paint_block_end_border() {
Au(0)
} else {
self.block_end_border.width
}
}
pub fn adjust_border_bounds_for_painting(
&self,
border_bounds: &mut Rect<Au>,
writing_mode: WritingMode,
) {
let inline_start_divisor = if self.should_paint_inline_start_border() {
2
} else {
-2
};
let inline_start_offset =
self.inline_start_width / 2 + self.inline_start_border.width / inline_start_divisor;
let inline_end_divisor = if self.should_paint_inline_end_border() {
2
} else {
-2
};
let inline_end_offset =
self.inline_end_width / 2 + self.inline_end_border.width / inline_end_divisor;
let block_start_divisor = if self.should_paint_block_start_border() {
2
} else {
-2
};
let block_start_offset =
self.block_start_width / 2 + self.block_start_border.width / block_start_divisor;
let block_end_divisor = if self.should_paint_block_end_border() {
2
} else {
-2
};
let block_end_offset =
self.block_end_width / 2 + self.block_end_border.width / block_end_divisor;
// FIXME(pcwalton): Get the real container size.
let mut logical_bounds =
LogicalRect::from_physical(writing_mode, *border_bounds, Size2D::new(Au(0), Au(0)));
logical_bounds.start.i = logical_bounds.start.i - inline_start_offset;
logical_bounds.start.b = logical_bounds.start.b - block_start_offset;
logical_bounds.size.inline =
logical_bounds.size.inline + inline_start_offset + inline_end_offset;
logical_bounds.size.block =
logical_bounds.size.block + block_start_offset + block_end_offset;
*border_bounds = logical_bounds.to_physical(writing_mode, Size2D::new(Au(0), Au(0)))
}
pub fn adjust_border_colors_and_styles_for_painting(
&self,
border_colors: &mut SideOffsets2D<Color>,
border_styles: &mut SideOffsets2D<BorderStyle>,
writing_mode: WritingMode,
) {
let logical_border_colors = LogicalMargin::new(
writing_mode,
self.block_start_border.color,
self.inline_end_border.color,
self.block_end_border.color,
self.inline_start_border.color,
);
*border_colors = logical_border_colors.to_physical(writing_mode);
let logical_border_styles = LogicalMargin::new(
writing_mode,
self.block_start_border.style,
self.inline_end_border.style,
self.block_end_border.style,
self.inline_start_border.style,
);
*border_styles = logical_border_styles.to_physical(writing_mode);
}
}

View file

@ -1,131 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! CSS table formatting contexts.
use crate::context::LayoutContext;
use crate::display_list::{DisplayListBuildState, StackingContextCollectionState};
use crate::flow::{BaseFlow, Flow, FlowClass, ForceNonfloatedFlag, OpaqueFlow};
use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
use crate::layout_debug;
use app_units::Au;
use euclid::Point2D;
use std::fmt;
use style::logical_geometry::LogicalSize;
use style::properties::ComputedValues;
use style::values::computed::Size;
#[allow(unsafe_code)]
unsafe impl crate::flow::HasBaseFlow for TableColGroupFlow {}
/// A table formatting context.
#[repr(C)]
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. (We use `LengthPercentageOrAuto` here in
/// lieu of `ColumnInlineSize` because column groups do not establish minimum or preferred
/// inline sizes.)
pub inline_sizes: Vec<Size>,
}
impl TableColGroupFlow {
pub fn from_fragments(fragment: Fragment, fragments: Vec<Fragment>) -> TableColGroupFlow {
let writing_mode = fragment.style().writing_mode;
TableColGroupFlow {
base: BaseFlow::new(
Some(fragment.style()),
writing_mode,
ForceNonfloatedFlag::ForceNonfloated,
),
fragment: Some(fragment),
cols: fragments,
inline_sizes: vec![],
}
}
}
impl Flow for TableColGroupFlow {
fn class(&self) -> FlowClass {
FlowClass::TableColGroup
}
fn as_mut_table_colgroup(&mut self) -> &mut TableColGroupFlow {
self
}
fn as_table_colgroup(&self) -> &TableColGroupFlow {
self
}
fn bubble_inline_sizes(&mut self) {
let _scope = layout_debug_scope!(
"table_colgroup::bubble_inline_sizes {:x}",
self.base.debug_id()
);
for fragment in &self.cols {
// Retrieve the specified value from the appropriate CSS property.
let inline_size = fragment.style().content_inline_size();
for _ in 0..fragment.column_span() {
self.inline_sizes.push(inline_size)
}
}
}
/// Table column inline-sizes are assigned in the table flow and propagated to table row flows
/// and/or rowgroup flows. Therefore, table colgroup flows do not need to assign inline-sizes.
fn assign_inline_sizes(&mut self, _: &LayoutContext) {}
/// Table columns do not have block-size.
fn assign_block_size(&mut self, _: &LayoutContext) {}
fn update_late_computed_inline_position_if_necessary(&mut self, _: Au) {}
fn update_late_computed_block_position_if_necessary(&mut self, _: Au) {}
// Table columns are invisible.
fn build_display_list(&mut self, _: &mut DisplayListBuildState) {}
fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
self.base.stacking_context_id = state.current_stacking_context_id;
self.base.clipping_and_scrolling = Some(state.current_clipping_and_scrolling);
}
fn repair_style(&mut self, _: &crate::ServoArc<ComputedValues>) {}
fn compute_overflow(&self) -> Overflow {
Overflow::new()
}
fn generated_containing_block_size(&self, _: OpaqueFlow) -> LogicalSize<Au> {
panic!("Table column groups can't be containing blocks!")
}
fn iterate_through_fragment_border_boxes(
&self,
_: &mut dyn FragmentBorderBoxIterator,
_: i32,
_: &Point2D<Au>,
) {
}
fn mutate_fragments(&mut self, _: &mut dyn FnMut(&mut Fragment)) {}
}
impl fmt::Debug 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"),
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,274 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! CSS table formatting contexts.
use crate::block::{BlockFlow, ISizeAndMarginsComputer};
use crate::context::LayoutContext;
use crate::display_list::{
DisplayListBuildState, StackingContextCollectionFlags, StackingContextCollectionState,
};
use crate::flow::{Flow, FlowClass, OpaqueFlow};
use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
use crate::layout_debug;
use crate::table::{ColumnIntrinsicInlineSize, InternalTable, TableLikeFlow};
use app_units::Au;
use euclid::Point2D;
use gfx_traits::print_tree::PrintTree;
use serde::{Serialize, Serializer};
use std::fmt;
use std::iter::{IntoIterator, Iterator, Peekable};
use style::computed_values::{border_collapse, border_spacing};
use style::logical_geometry::LogicalSize;
use style::properties::ComputedValues;
#[allow(unsafe_code)]
unsafe impl crate::flow::HasBaseFlow for TableRowGroupFlow {}
/// A table formatting context.
#[repr(C)]
pub struct TableRowGroupFlow {
/// Fields common to all block flows.
pub block_flow: BlockFlow,
/// Information about the intrinsic inline-sizes of each column.
pub column_intrinsic_inline_sizes: Vec<ColumnIntrinsicInlineSize>,
/// The spacing for this rowgroup.
pub spacing: border_spacing::T,
/// The final width of the borders in the inline direction for each cell, computed by the
/// entire table and pushed down into each row during inline size computation.
pub collapsed_inline_direction_border_widths_for_table: Vec<Au>,
/// The final width of the borders in the block direction for each cell, computed by the
/// entire table and pushed down into each row during inline size computation.
pub collapsed_block_direction_border_widths_for_table: Vec<Au>,
}
impl Serialize for TableRowGroupFlow {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.block_flow.serialize(serializer)
}
}
impl TableRowGroupFlow {
pub fn from_fragment(fragment: Fragment) -> TableRowGroupFlow {
TableRowGroupFlow {
block_flow: BlockFlow::from_fragment(fragment),
column_intrinsic_inline_sizes: Vec::new(),
spacing: border_spacing::T::zero(),
collapsed_inline_direction_border_widths_for_table: Vec::new(),
collapsed_block_direction_border_widths_for_table: Vec::new(),
}
}
pub fn populate_collapsed_border_spacing<'a, I>(
&mut self,
collapsed_inline_direction_border_widths_for_table: &[Au],
collapsed_block_direction_border_widths_for_table: &mut Peekable<I>,
) where
I: Iterator<Item = &'a Au>,
{
self.collapsed_inline_direction_border_widths_for_table
.clear();
self.collapsed_inline_direction_border_widths_for_table
.extend(
collapsed_inline_direction_border_widths_for_table
.into_iter()
.map(|x| *x),
);
for _ in 0..self.block_flow.base.children.len() {
if let Some(collapsed_block_direction_border_width_for_table) =
collapsed_block_direction_border_widths_for_table.next()
{
self.collapsed_block_direction_border_widths_for_table
.push(*collapsed_block_direction_border_width_for_table)
}
}
if let Some(collapsed_block_direction_border_width_for_table) =
collapsed_block_direction_border_widths_for_table.peek()
{
self.collapsed_block_direction_border_widths_for_table
.push(**collapsed_block_direction_border_width_for_table)
}
}
}
impl Flow for TableRowGroupFlow {
fn class(&self) -> FlowClass {
FlowClass::TableRowGroup
}
fn as_mut_table_rowgroup(&mut self) -> &mut TableRowGroupFlow {
self
}
fn as_table_rowgroup(&self) -> &TableRowGroupFlow {
self
}
fn as_mut_block(&mut self) -> &mut BlockFlow {
&mut self.block_flow
}
fn as_block(&self) -> &BlockFlow {
&self.block_flow
}
fn bubble_inline_sizes(&mut self) {
let _scope = layout_debug_scope!(
"table_rowgroup::bubble_inline_sizes {:x}",
self.block_flow.base.debug_id()
);
// Proper calculation of intrinsic sizes in table layout requires access to the entire
// table, which we don't have yet. Defer to our parent.
}
/// 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, layout_context: &LayoutContext) {
let _scope = layout_debug_scope!(
"table_rowgroup::assign_inline_sizes {:x}",
self.block_flow.base.debug_id()
);
debug!(
"assign_inline_sizes({}): assigning inline_size for flow",
"table_rowgroup"
);
let shared_context = layout_context.shared_context();
// The position was set to the containing block by the flow's parent.
let containing_block_inline_size = self.block_flow.base.block_container_inline_size;
let (inline_start_content_edge, inline_end_content_edge) = (Au(0), Au(0));
let content_inline_size = containing_block_inline_size;
let border_collapse = self
.block_flow
.fragment
.style
.get_inherited_table()
.border_collapse;
let inline_size_computer = InternalTable;
inline_size_computer.compute_used_inline_size(
&mut self.block_flow,
shared_context,
containing_block_inline_size,
);
let collapsed_inline_direction_border_widths_for_table =
&self.collapsed_inline_direction_border_widths_for_table;
let mut collapsed_block_direction_border_widths_for_table = self
.collapsed_block_direction_border_widths_for_table
.iter()
.peekable();
self.block_flow.propagate_assigned_inline_size_to_children(
shared_context,
inline_start_content_edge,
inline_end_content_edge,
content_inline_size,
|child_flow,
_child_index,
_content_inline_size,
_writing_mode,
_inline_start_margin_edge,
_inline_end_margin_edge| {
if border_collapse == border_collapse::T::Collapse {
let child_table_row = child_flow.as_mut_table_row();
child_table_row.populate_collapsed_border_spacing(
collapsed_inline_direction_border_widths_for_table,
&mut collapsed_block_direction_border_widths_for_table,
);
}
},
);
}
fn assign_block_size(&mut self, lc: &LayoutContext) {
debug!("assign_block_size: assigning block_size for table_rowgroup");
self.block_flow
.assign_block_size_for_table_like_flow(self.spacing.vertical(), lc);
}
fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
self.block_flow
.compute_stacking_relative_position(layout_context)
}
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
self.block_flow
.update_late_computed_inline_position_if_necessary(inline_position)
}
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
self.block_flow
.update_late_computed_block_position_if_necessary(block_position)
}
fn build_display_list(&mut self, _: &mut DisplayListBuildState) {
use style::servo::restyle_damage::ServoRestyleDamage;
// we skip setting the damage in TableCellStyleInfo::build_display_list()
// because we only have immutable access
self.block_flow
.fragment
.restyle_damage
.remove(ServoRestyleDamage::REPAINT);
}
fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
self.block_flow.collect_stacking_contexts_for_block(
state,
StackingContextCollectionFlags::POSITION_NEVER_CREATES_CONTAINING_BLOCK,
);
}
fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
self.block_flow.repair_style(new_style)
}
fn compute_overflow(&self) -> Overflow {
self.block_flow.compute_overflow()
}
fn contains_roots_of_absolute_flow_tree(&self) -> bool {
self.block_flow.contains_roots_of_absolute_flow_tree()
}
fn is_absolute_containing_block(&self) -> bool {
self.block_flow.is_absolute_containing_block()
}
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
self.block_flow.generated_containing_block_size(flow)
}
fn iterate_through_fragment_border_boxes(
&self,
iterator: &mut dyn FragmentBorderBoxIterator,
level: i32,
stacking_context_position: &Point2D<Au>,
) {
self.block_flow.iterate_through_fragment_border_boxes(
iterator,
level,
stacking_context_position,
)
}
fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
self.block_flow.mutate_fragments(mutator)
}
fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
self.block_flow.print_extra_flow_children(print_tree);
}
}
impl fmt::Debug for TableRowGroupFlow {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "TableRowGroupFlow: {:?}", self.block_flow)
}
}

View file

@ -1,996 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! CSS tables.
//!
//! This follows the "More Precise Definitions of Inline Layout and Table Layout" proposal written
//! by L. David Baron (Mozilla) here:
//!
//! http://dbaron.org/css/intrinsic/
//!
//! Hereafter this document is referred to as INTRINSIC.
use crate::block::{
AbsoluteNonReplaced, BlockFlow, FloatNonReplaced, ISizeAndMarginsComputer, ISizeConstraintInput,
};
use crate::block::{ISizeConstraintSolution, MarginsMayCollapseFlag};
use crate::context::LayoutContext;
use crate::display_list::StackingContextCollectionState;
use crate::display_list::{DisplayListBuildState, StackingContextCollectionFlags};
use crate::floats::FloatKind;
use crate::flow::{Flow, FlowClass, FlowFlags, ImmutableFlowUtils, OpaqueFlow};
use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
use crate::model::MaybeAuto;
use crate::table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize};
use app_units::Au;
use euclid::Point2D;
use gfx_traits::print_tree::PrintTree;
use std::cmp::{max, min};
use std::fmt;
use std::ops::Add;
use style::computed_values::{position, table_layout};
use style::context::SharedStyleContext;
use style::logical_geometry::{LogicalRect, LogicalSize};
use style::properties::ComputedValues;
use style::values::computed::Size;
use style::values::CSSFloat;
#[derive(Clone, Copy, Debug, Serialize)]
pub enum TableLayout {
Fixed,
Auto,
}
#[allow(unsafe_code)]
unsafe impl crate::flow::HasBaseFlow for TableWrapperFlow {}
/// A table wrapper flow based on a block formatting context.
#[derive(Serialize)]
#[repr(C)]
pub struct TableWrapperFlow {
pub block_flow: BlockFlow,
/// Intrinsic column inline sizes according to INTRINSIC § 4.1
pub column_intrinsic_inline_sizes: Vec<ColumnIntrinsicInlineSize>,
/// Table-layout property
pub table_layout: TableLayout,
}
impl TableWrapperFlow {
pub fn from_fragment(fragment: Fragment) -> TableWrapperFlow {
TableWrapperFlow::from_fragment_and_float_kind(fragment, None)
}
pub fn from_fragment_and_float_kind(
fragment: Fragment,
float_kind: Option<FloatKind>,
) -> TableWrapperFlow {
let mut block_flow = BlockFlow::from_fragment_and_float_kind(fragment, float_kind);
let table_layout =
if block_flow.fragment().style().get_table().table_layout == table_layout::T::Fixed {
TableLayout::Fixed
} else {
TableLayout::Auto
};
TableWrapperFlow {
block_flow: block_flow,
column_intrinsic_inline_sizes: vec![],
table_layout: table_layout,
}
}
fn border_padding_and_spacing(&mut self) -> (Au, Au) {
let (mut table_border_padding, mut spacing) = (Au(0), Au(0));
for kid in self.block_flow.base.child_iter_mut() {
if kid.is_table() {
let kid_table = kid.as_table();
spacing = kid_table.total_horizontal_spacing();
table_border_padding = kid_table
.block_flow
.fragment
.border_padding
.inline_start_end();
break;
}
}
(table_border_padding, spacing)
}
// Instructs our first child, which is the table itself, to compute its border and padding.
//
// This is a little weird because we're computing border/padding/margins for our child,
// when normally the child computes it itself. But it has to be this way because the
// padding will affect where we place the child. This is an odd artifact of the way that
// tables are separated into table flows and table wrapper flows.
fn compute_border_and_padding_of_table(&mut self) {
let available_inline_size = self.block_flow.base.block_container_inline_size;
for kid in self.block_flow.base.child_iter_mut() {
if !kid.is_table() {
continue;
}
let kid_table = kid.as_mut_table();
let kid_block_flow = &mut kid_table.block_flow;
kid_block_flow
.fragment
.compute_border_and_padding(available_inline_size);
kid_block_flow
.fragment
.compute_block_direction_margins(available_inline_size);
kid_block_flow
.fragment
.compute_inline_direction_margins(available_inline_size);
return;
}
}
/// Calculates table column sizes for automatic layout per INTRINSIC § 4.3.
fn calculate_table_column_sizes_for_automatic_layout(
&mut self,
intermediate_column_inline_sizes: &mut [IntermediateColumnInlineSize],
) {
let available_inline_size = self.available_inline_size();
// Compute all the guesses for the column sizes, and sum them.
let mut total_guess = AutoLayoutCandidateGuess::new();
let guesses: Vec<AutoLayoutCandidateGuess> = self
.column_intrinsic_inline_sizes
.iter()
.map(|column_intrinsic_inline_size| {
let guess = AutoLayoutCandidateGuess::from_column_intrinsic_inline_size(
column_intrinsic_inline_size,
available_inline_size,
);
total_guess = &total_guess + &guess;
guess
})
.collect();
// Assign inline sizes.
let selection =
SelectedAutoLayoutCandidateGuess::select(&total_guess, available_inline_size);
let mut total_used_inline_size = Au(0);
for (intermediate_column_inline_size, guess) in intermediate_column_inline_sizes
.iter_mut()
.zip(guesses.iter())
{
intermediate_column_inline_size.size = guess.calculate(selection);
intermediate_column_inline_size.percentage = 0.0;
total_used_inline_size = total_used_inline_size + intermediate_column_inline_size.size
}
// Distribute excess inline-size if necessary per INTRINSIC § 4.4.
//
// FIXME(pcwalton, spec): How do I deal with fractional excess?
let excess_inline_size = available_inline_size - total_used_inline_size;
if excess_inline_size > Au(0) &&
selection ==
SelectedAutoLayoutCandidateGuess::UsePreferredGuessAndDistributeExcessInlineSize
{
let mut info = ExcessInlineSizeDistributionInfo::new();
for column_intrinsic_inline_size in &self.column_intrinsic_inline_sizes {
info.update(column_intrinsic_inline_size)
}
let mut total_distributed_excess_size = Au(0);
for (intermediate_column_inline_size, column_intrinsic_inline_size) in
intermediate_column_inline_sizes
.iter_mut()
.zip(self.column_intrinsic_inline_sizes.iter())
{
info.distribute_excess_inline_size_to_column(
intermediate_column_inline_size,
column_intrinsic_inline_size,
excess_inline_size,
&mut total_distributed_excess_size,
)
}
total_used_inline_size = available_inline_size
}
self.set_inline_size(total_used_inline_size)
}
fn available_inline_size(&mut self) -> Au {
let available_inline_size = self.block_flow.fragment.border_box.size.inline;
let (table_border_padding, spacing) = self.border_padding_and_spacing();
// FIXME(pcwalton, spec): INTRINSIC § 8 does not properly define how to compute this, but
// says "the basic idea is the same as the shrink-to-fit width that CSS2.1 defines". So we
// just use the shrink-to-fit inline size.
let available_inline_size = match self.block_flow.fragment.style().content_inline_size() {
Size::Auto => {
self.block_flow
.get_shrink_to_fit_inline_size(available_inline_size) -
table_border_padding
},
// FIXME(mttr): This fixes #4421 without breaking our current reftests, but I'm not
// completely sure this is "correct".
//
// That said, `available_inline_size` is, as far as I can tell, equal to the table's
// computed width property (W) and is used from this point forward in a way that seems
// to correspond with CSS 2.1 § 17.5.2.2 under "Column and caption widths influence the
// final table width as follows: …"
_ => available_inline_size,
};
available_inline_size - spacing
}
fn set_inline_size(&mut self, total_used_inline_size: Au) {
let (table_border_padding, spacing) = self.border_padding_and_spacing();
self.block_flow.fragment.border_box.size.inline =
total_used_inline_size + table_border_padding + spacing;
self.block_flow.base.position.size.inline = total_used_inline_size +
table_border_padding +
spacing +
self.block_flow.fragment.margin.inline_start_end();
let writing_mode = self.block_flow.base.writing_mode;
let container_mode = self.block_flow.base.block_container_writing_mode;
if writing_mode.is_bidi_ltr() != container_mode.is_bidi_ltr() {
// If our "start" direction is different from our parent flow, then `border_box.start.i`
// depends on `border_box.size.inline`.
self.block_flow.fragment.border_box.start.i =
self.block_flow.base.block_container_inline_size -
self.block_flow.fragment.margin.inline_end -
self.block_flow.fragment.border_box.size.inline;
}
}
fn compute_used_inline_size(
&mut self,
shared_context: &SharedStyleContext,
parent_flow_inline_size: Au,
intermediate_column_inline_sizes: &[IntermediateColumnInlineSize],
) {
let (border_padding, spacing) = self.border_padding_and_spacing();
let minimum_width_of_all_columns = intermediate_column_inline_sizes.iter().fold(
border_padding + spacing,
|accumulator, intermediate_column_inline_sizes| {
accumulator + intermediate_column_inline_sizes.size
},
);
let preferred_width_of_all_columns = self.column_intrinsic_inline_sizes.iter().fold(
border_padding + spacing,
|accumulator, column_intrinsic_inline_sizes| {
accumulator + column_intrinsic_inline_sizes.preferred
},
);
// Delegate to the appropriate inline size computer to find the constraint inputs and write
// the constraint solutions in.
if self.block_flow.base.flags.is_float() {
let inline_size_computer = FloatedTable {
minimum_width_of_all_columns: minimum_width_of_all_columns,
preferred_width_of_all_columns: preferred_width_of_all_columns,
table_border_padding: border_padding,
};
let input = inline_size_computer.compute_inline_size_constraint_inputs(
&mut self.block_flow,
parent_flow_inline_size,
shared_context,
);
let solution =
inline_size_computer.solve_inline_size_constraints(&mut self.block_flow, &input);
inline_size_computer
.set_inline_size_constraint_solutions(&mut self.block_flow, solution);
inline_size_computer
.set_inline_position_of_flow_if_necessary(&mut self.block_flow, solution);
return;
}
if !self
.block_flow
.base
.flags
.contains(FlowFlags::INLINE_POSITION_IS_STATIC)
{
let inline_size_computer = AbsoluteTable {
minimum_width_of_all_columns: minimum_width_of_all_columns,
preferred_width_of_all_columns: preferred_width_of_all_columns,
table_border_padding: border_padding,
};
let input = inline_size_computer.compute_inline_size_constraint_inputs(
&mut self.block_flow,
parent_flow_inline_size,
shared_context,
);
let solution =
inline_size_computer.solve_inline_size_constraints(&mut self.block_flow, &input);
inline_size_computer
.set_inline_size_constraint_solutions(&mut self.block_flow, solution);
inline_size_computer
.set_inline_position_of_flow_if_necessary(&mut self.block_flow, solution);
return;
}
let inline_size_computer = Table {
minimum_width_of_all_columns: minimum_width_of_all_columns,
preferred_width_of_all_columns: preferred_width_of_all_columns,
table_border_padding: border_padding,
};
let input = inline_size_computer.compute_inline_size_constraint_inputs(
&mut self.block_flow,
parent_flow_inline_size,
shared_context,
);
let solution =
inline_size_computer.solve_inline_size_constraints(&mut self.block_flow, &input);
inline_size_computer.set_inline_size_constraint_solutions(&mut self.block_flow, solution);
inline_size_computer
.set_inline_position_of_flow_if_necessary(&mut self.block_flow, solution);
}
}
impl Flow for TableWrapperFlow {
fn class(&self) -> FlowClass {
FlowClass::TableWrapper
}
fn as_table_wrapper(&self) -> &TableWrapperFlow {
self
}
fn as_mut_block(&mut self) -> &mut BlockFlow {
&mut self.block_flow
}
fn as_block(&self) -> &BlockFlow {
&self.block_flow
}
fn mark_as_root(&mut self) {
self.block_flow.mark_as_root();
}
fn bubble_inline_sizes(&mut self) {
// Get the intrinsic column inline-sizes info from the table flow.
for kid in self.block_flow.base.child_iter_mut() {
debug_assert!(kid.is_table_caption() || kid.is_table());
if kid.is_table() {
let table = kid.as_table();
self.column_intrinsic_inline_sizes = table.column_intrinsic_inline_sizes.clone();
}
}
self.block_flow.bubble_inline_sizes();
}
fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
debug!(
"assign_inline_sizes({}): assigning inline_size for flow",
if self.block_flow.base.flags.is_float() {
"floated table_wrapper"
} else {
"table_wrapper"
}
);
let shared_context = layout_context.shared_context();
self.block_flow
.initialize_container_size_for_root(shared_context);
let mut intermediate_column_inline_sizes = self
.column_intrinsic_inline_sizes
.iter()
.map(
|column_intrinsic_inline_size| IntermediateColumnInlineSize {
size: column_intrinsic_inline_size.minimum_length,
percentage: column_intrinsic_inline_size.percentage,
},
)
.collect::<Vec<_>>();
// Our inline-size was set to the inline-size of the containing block by the flow's parent.
// Now compute the real value.
let containing_block_inline_size = self.block_flow.base.block_container_inline_size;
if self.block_flow.base.flags.is_float() {
self.block_flow
.float
.as_mut()
.unwrap()
.containing_inline_size = containing_block_inline_size;
}
// This has to be done before computing our inline size because `compute_used_inline_size`
// internally consults the border and padding of the table.
self.compute_border_and_padding_of_table();
self.compute_used_inline_size(
shared_context,
containing_block_inline_size,
&intermediate_column_inline_sizes,
);
match self.table_layout {
TableLayout::Auto => self.calculate_table_column_sizes_for_automatic_layout(
&mut intermediate_column_inline_sizes,
),
TableLayout::Fixed => {},
}
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;
let inline_end_content_edge = self.block_flow.fragment.border_padding.inline_end +
self.block_flow.fragment.margin.inline_end;
// In case of fixed layout, column inline-sizes are calculated in table flow.
let assigned_column_inline_sizes = match self.table_layout {
TableLayout::Fixed => None,
TableLayout::Auto => Some(
intermediate_column_inline_sizes
.iter()
.map(|sizes| ColumnComputedInlineSize { size: sizes.size })
.collect::<Vec<_>>(),
),
};
match assigned_column_inline_sizes {
None => self.block_flow.propagate_assigned_inline_size_to_children(
shared_context,
inline_start_content_edge,
inline_end_content_edge,
content_inline_size,
|_, _, _, _, _, _| {},
),
Some(ref assigned_column_inline_sizes) => {
self.block_flow.propagate_assigned_inline_size_to_children(
shared_context,
inline_start_content_edge,
inline_end_content_edge,
content_inline_size,
|child_flow, _, _, _, _, _| {
if child_flow.class() == FlowClass::Table {
child_flow.as_mut_table().column_computed_inline_sizes =
assigned_column_inline_sizes.to_vec();
}
},
)
},
}
}
fn assign_block_size(&mut self, layout_context: &LayoutContext) {
debug!("assign_block_size: assigning block_size for table_wrapper");
let remaining = self.block_flow.assign_block_size_block_base(
layout_context,
None,
MarginsMayCollapseFlag::MarginsMayNotCollapse,
);
debug_assert!(remaining.is_none());
}
fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
self.block_flow
.compute_stacking_relative_position(layout_context)
}
fn place_float_if_applicable<'a>(&mut self) {
self.block_flow.place_float_if_applicable()
}
fn assign_block_size_for_inorder_child_if_necessary(
&mut self,
layout_context: &LayoutContext,
parent_thread_id: u8,
content_box: LogicalRect<Au>,
) -> bool {
self.block_flow
.assign_block_size_for_inorder_child_if_necessary(
layout_context,
parent_thread_id,
content_box,
)
}
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
self.block_flow
.update_late_computed_inline_position_if_necessary(inline_position)
}
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
self.block_flow
.update_late_computed_block_position_if_necessary(block_position)
}
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
self.block_flow.generated_containing_block_size(flow)
}
fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
self.block_flow.build_display_list(state);
}
fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
self.block_flow.collect_stacking_contexts_for_block(
state,
StackingContextCollectionFlags::POSITION_NEVER_CREATES_CONTAINING_BLOCK |
StackingContextCollectionFlags::NEVER_CREATES_CLIP_SCROLL_NODE,
);
}
fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
self.block_flow.repair_style(new_style)
}
fn compute_overflow(&self) -> Overflow {
self.block_flow.compute_overflow()
}
fn iterate_through_fragment_border_boxes(
&self,
iterator: &mut dyn FragmentBorderBoxIterator,
level: i32,
stacking_context_position: &Point2D<Au>,
) {
self.block_flow.iterate_through_fragment_border_boxes(
iterator,
level,
stacking_context_position,
)
}
fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
self.block_flow.mutate_fragments(mutator)
}
fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
self.block_flow.print_extra_flow_children(print_tree);
}
fn positioning(&self) -> position::T {
self.block_flow.positioning()
}
}
impl fmt::Debug for TableWrapperFlow {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.block_flow.base.flags.is_float() {
write!(f, "TableWrapperFlow(Float): {:?}", self.block_flow)
} else {
write!(f, "TableWrapperFlow: {:?}", self.block_flow)
}
}
}
/// The layout "guesses" defined in INTRINSIC § 4.3.
struct AutoLayoutCandidateGuess {
/// The column inline-size assignment where each column is assigned its intrinsic minimum
/// inline-size.
minimum_guess: Au,
/// The column inline-size assignment where:
/// * A column with an intrinsic percentage inline-size greater than 0% is assigned the
/// larger of:
/// - Its intrinsic percentage inline-size times the assignable inline-size;
/// - Its intrinsic minimum inline-size;
/// * Other columns receive their intrinsic minimum inline-size.
minimum_percentage_guess: Au,
/// The column inline-size assignment where:
/// * Each column with an intrinsic percentage inline-size greater than 0% is assigned the
/// larger of:
/// - Its intrinsic percentage inline-size times the assignable inline-size;
/// - Its intrinsic minimum inline-size;
/// * Any other column that is constrained is assigned its intrinsic preferred inline-size;
/// * Other columns are assigned their intrinsic minimum inline-size.
minimum_specified_guess: Au,
/// The column inline-size assignment where:
/// * Each column with an intrinsic percentage inline-size greater than 0% is assigned the
/// larger of:
/// - Its intrinsic percentage inline-size times the assignable inline-size;
/// - Its intrinsic minimum inline-size;
/// * Other columns are assigned their intrinsic preferred inline-size.
preferred_guess: Au,
}
impl AutoLayoutCandidateGuess {
/// Creates a guess with all elements initialized to zero.
fn new() -> AutoLayoutCandidateGuess {
AutoLayoutCandidateGuess {
minimum_guess: Au(0),
minimum_percentage_guess: Au(0),
minimum_specified_guess: Au(0),
preferred_guess: Au(0),
}
}
/// Fills in the inline-size guesses for this column per INTRINSIC § 4.3.
fn from_column_intrinsic_inline_size(
column_intrinsic_inline_size: &ColumnIntrinsicInlineSize,
assignable_inline_size: Au,
) -> AutoLayoutCandidateGuess {
let minimum_percentage_guess = max(
assignable_inline_size.scale_by(column_intrinsic_inline_size.percentage),
column_intrinsic_inline_size.minimum_length,
);
AutoLayoutCandidateGuess {
minimum_guess: column_intrinsic_inline_size.minimum_length,
minimum_percentage_guess: minimum_percentage_guess,
// FIXME(pcwalton): We need the notion of *constrainedness* per INTRINSIC § 4 to
// implement this one correctly.
minimum_specified_guess: if column_intrinsic_inline_size.percentage > 0.0 {
minimum_percentage_guess
} else if column_intrinsic_inline_size.constrained {
column_intrinsic_inline_size.preferred
} else {
column_intrinsic_inline_size.minimum_length
},
preferred_guess: if column_intrinsic_inline_size.percentage > 0.0 {
minimum_percentage_guess
} else {
column_intrinsic_inline_size.preferred
},
}
}
/// Calculates the inline-size, interpolating appropriately based on the value of `selection`.
///
/// This does *not* distribute excess inline-size. That must be done later if necessary.
fn calculate(&self, selection: SelectedAutoLayoutCandidateGuess) -> Au {
match selection {
SelectedAutoLayoutCandidateGuess::UseMinimumGuess => self.minimum_guess,
SelectedAutoLayoutCandidateGuess::
InterpolateBetweenMinimumGuessAndMinimumPercentageGuess(weight) => {
interp(self.minimum_guess, self.minimum_percentage_guess, weight)
}
SelectedAutoLayoutCandidateGuess::
InterpolateBetweenMinimumPercentageGuessAndMinimumSpecifiedGuess(weight) => {
interp(self.minimum_percentage_guess, self.minimum_specified_guess, weight)
}
SelectedAutoLayoutCandidateGuess::
InterpolateBetweenMinimumSpecifiedGuessAndPreferredGuess(weight) => {
interp(self.minimum_specified_guess, self.preferred_guess, weight)
}
SelectedAutoLayoutCandidateGuess::UsePreferredGuessAndDistributeExcessInlineSize => {
self.preferred_guess
}
}
}
}
impl<'a> Add for &'a AutoLayoutCandidateGuess {
type Output = AutoLayoutCandidateGuess;
#[inline]
fn add(self, other: &AutoLayoutCandidateGuess) -> AutoLayoutCandidateGuess {
AutoLayoutCandidateGuess {
minimum_guess: self.minimum_guess + other.minimum_guess,
minimum_percentage_guess: self.minimum_percentage_guess +
other.minimum_percentage_guess,
minimum_specified_guess: self.minimum_specified_guess + other.minimum_specified_guess,
preferred_guess: self.preferred_guess + other.preferred_guess,
}
}
}
/// The `CSSFloat` member specifies the weight of the smaller of the two guesses, on a scale from
/// 0.0 to 1.0.
#[derive(Clone, Copy, Debug, PartialEq)]
enum SelectedAutoLayoutCandidateGuess {
UseMinimumGuess,
InterpolateBetweenMinimumGuessAndMinimumPercentageGuess(CSSFloat),
InterpolateBetweenMinimumPercentageGuessAndMinimumSpecifiedGuess(CSSFloat),
InterpolateBetweenMinimumSpecifiedGuessAndPreferredGuess(CSSFloat),
UsePreferredGuessAndDistributeExcessInlineSize,
}
impl SelectedAutoLayoutCandidateGuess {
/// See INTRINSIC § 4.3.
///
/// FIXME(pcwalton, INTRINSIC spec): INTRINSIC doesn't specify whether these are exclusive or
/// inclusive ranges.
fn select(
guess: &AutoLayoutCandidateGuess,
assignable_inline_size: Au,
) -> SelectedAutoLayoutCandidateGuess {
if assignable_inline_size < guess.minimum_guess {
SelectedAutoLayoutCandidateGuess::UseMinimumGuess
} else if assignable_inline_size < guess.minimum_percentage_guess {
let weight = weight(
guess.minimum_guess,
assignable_inline_size,
guess.minimum_percentage_guess,
);
SelectedAutoLayoutCandidateGuess::InterpolateBetweenMinimumGuessAndMinimumPercentageGuess(weight)
} else if assignable_inline_size < guess.minimum_specified_guess {
let weight = weight(
guess.minimum_percentage_guess,
assignable_inline_size,
guess.minimum_specified_guess,
);
SelectedAutoLayoutCandidateGuess::InterpolateBetweenMinimumPercentageGuessAndMinimumSpecifiedGuess(weight)
} else if assignable_inline_size < guess.preferred_guess {
let weight = weight(
guess.minimum_specified_guess,
assignable_inline_size,
guess.preferred_guess,
);
SelectedAutoLayoutCandidateGuess::InterpolateBetweenMinimumSpecifiedGuessAndPreferredGuess(weight)
} else {
SelectedAutoLayoutCandidateGuess::UsePreferredGuessAndDistributeExcessInlineSize
}
}
}
/// Computes the weight needed to linearly interpolate `middle` between two guesses `low` and
/// `high` as specified by INTRINSIC § 4.3.
fn weight(low: Au, middle: Au, high: Au) -> CSSFloat {
(middle - low).to_f32_px() / (high - low).to_f32_px()
}
/// Linearly interpolates between two guesses, as specified by INTRINSIC § 4.3.
fn interp(low: Au, high: Au, weight: CSSFloat) -> Au {
low + (high - low).scale_by(weight)
}
struct ExcessInlineSizeDistributionInfo {
preferred_inline_size_of_nonconstrained_columns_with_no_percentage: Au,
count_of_nonconstrained_columns_with_no_percentage: u32,
preferred_inline_size_of_constrained_columns_with_no_percentage: Au,
total_percentage: CSSFloat,
column_count: u32,
}
impl ExcessInlineSizeDistributionInfo {
fn new() -> ExcessInlineSizeDistributionInfo {
ExcessInlineSizeDistributionInfo {
preferred_inline_size_of_nonconstrained_columns_with_no_percentage: Au(0),
count_of_nonconstrained_columns_with_no_percentage: 0,
preferred_inline_size_of_constrained_columns_with_no_percentage: Au(0),
total_percentage: 0.0,
column_count: 0,
}
}
fn update(&mut self, column_intrinsic_inline_size: &ColumnIntrinsicInlineSize) {
if !column_intrinsic_inline_size.constrained &&
column_intrinsic_inline_size.percentage == 0.0
{
self.preferred_inline_size_of_nonconstrained_columns_with_no_percentage = self
.preferred_inline_size_of_nonconstrained_columns_with_no_percentage +
column_intrinsic_inline_size.preferred;
self.count_of_nonconstrained_columns_with_no_percentage += 1
}
if column_intrinsic_inline_size.constrained &&
column_intrinsic_inline_size.percentage == 0.0
{
self.preferred_inline_size_of_constrained_columns_with_no_percentage = self
.preferred_inline_size_of_constrained_columns_with_no_percentage +
column_intrinsic_inline_size.preferred
}
self.total_percentage += column_intrinsic_inline_size.percentage;
self.column_count += 1
}
/// Based on the information here, distributes excess inline-size to the given column per
/// INTRINSIC § 4.4.
///
/// `#[inline]` so the compiler will hoist out the branch, which is loop-invariant.
#[inline]
fn distribute_excess_inline_size_to_column(
&self,
intermediate_column_inline_size: &mut IntermediateColumnInlineSize,
column_intrinsic_inline_size: &ColumnIntrinsicInlineSize,
excess_inline_size: Au,
total_distributed_excess_size: &mut Au,
) {
let proportion =
if self.preferred_inline_size_of_nonconstrained_columns_with_no_percentage > Au(0) {
// FIXME(spec, pcwalton): Gecko and WebKit do *something* here when there are
// nonconstrained columns with no percentage *and* no preferred width. What do they
// do?
if !column_intrinsic_inline_size.constrained &&
column_intrinsic_inline_size.percentage == 0.0
{
column_intrinsic_inline_size.preferred.to_f32_px() /
self.preferred_inline_size_of_nonconstrained_columns_with_no_percentage
.to_f32_px()
} else {
0.0
}
} else if self.count_of_nonconstrained_columns_with_no_percentage > 0 {
1.0 / (self.count_of_nonconstrained_columns_with_no_percentage as CSSFloat)
} else if self.preferred_inline_size_of_constrained_columns_with_no_percentage > Au(0) {
column_intrinsic_inline_size.preferred.to_f32_px() /
self.preferred_inline_size_of_constrained_columns_with_no_percentage
.to_f32_px()
} else if self.total_percentage > 0.0 {
column_intrinsic_inline_size.percentage / self.total_percentage
} else {
1.0 / (self.column_count as CSSFloat)
};
// The `min` here has the effect of throwing away fractional excess at the end of the
// table.
let amount_to_distribute = min(
excess_inline_size.scale_by(proportion),
excess_inline_size - *total_distributed_excess_size,
);
*total_distributed_excess_size = *total_distributed_excess_size + amount_to_distribute;
intermediate_column_inline_size.size =
intermediate_column_inline_size.size + amount_to_distribute
}
}
/// An intermediate column size assignment.
struct IntermediateColumnInlineSize {
size: Au,
percentage: f32,
}
/// Returns the computed inline size of the table wrapper represented by `block`.
///
/// `table_border_padding` is the sum of the sizes of all border and padding in the inline
/// direction of the table contained within this table wrapper.
fn initial_computed_inline_size(
block: &mut BlockFlow,
containing_block_inline_size: Au,
minimum_width_of_all_columns: Au,
preferred_width_of_all_columns: Au,
table_border_padding: Au,
) -> MaybeAuto {
match block.fragment.style.content_inline_size() {
Size::Auto => {
if preferred_width_of_all_columns + table_border_padding <= containing_block_inline_size
{
MaybeAuto::Specified(preferred_width_of_all_columns + table_border_padding)
} else if minimum_width_of_all_columns > containing_block_inline_size {
MaybeAuto::Specified(minimum_width_of_all_columns)
} else {
MaybeAuto::Auto
}
},
Size::LengthPercentage(ref lp) => {
let used = lp.to_used_value(containing_block_inline_size);
MaybeAuto::Specified(max(
used - table_border_padding,
minimum_width_of_all_columns,
))
},
}
}
struct Table {
minimum_width_of_all_columns: Au,
preferred_width_of_all_columns: Au,
table_border_padding: Au,
}
impl ISizeAndMarginsComputer for Table {
fn compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au) {
block
.fragment
.compute_border_and_padding(containing_block_inline_size)
}
fn initial_computed_inline_size(
&self,
block: &mut BlockFlow,
parent_flow_inline_size: Au,
shared_context: &SharedStyleContext,
) -> MaybeAuto {
let containing_block_inline_size =
self.containing_block_inline_size(block, parent_flow_inline_size, shared_context);
initial_computed_inline_size(
block,
containing_block_inline_size,
self.minimum_width_of_all_columns,
self.preferred_width_of_all_columns,
self.table_border_padding,
)
}
fn solve_inline_size_constraints(
&self,
block: &mut BlockFlow,
input: &ISizeConstraintInput,
) -> ISizeConstraintSolution {
self.solve_block_inline_size_constraints(block, input)
}
}
struct FloatedTable {
minimum_width_of_all_columns: Au,
preferred_width_of_all_columns: Au,
table_border_padding: Au,
}
impl ISizeAndMarginsComputer for FloatedTable {
fn compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au) {
block
.fragment
.compute_border_and_padding(containing_block_inline_size)
}
fn initial_computed_inline_size(
&self,
block: &mut BlockFlow,
parent_flow_inline_size: Au,
shared_context: &SharedStyleContext,
) -> MaybeAuto {
let containing_block_inline_size =
self.containing_block_inline_size(block, parent_flow_inline_size, shared_context);
initial_computed_inline_size(
block,
containing_block_inline_size,
self.minimum_width_of_all_columns,
self.preferred_width_of_all_columns,
self.table_border_padding,
)
}
fn solve_inline_size_constraints(
&self,
block: &mut BlockFlow,
input: &ISizeConstraintInput,
) -> ISizeConstraintSolution {
FloatNonReplaced.solve_inline_size_constraints(block, input)
}
}
struct AbsoluteTable {
minimum_width_of_all_columns: Au,
preferred_width_of_all_columns: Au,
table_border_padding: Au,
}
impl ISizeAndMarginsComputer for AbsoluteTable {
fn compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au) {
block
.fragment
.compute_border_and_padding(containing_block_inline_size)
}
fn initial_computed_inline_size(
&self,
block: &mut BlockFlow,
parent_flow_inline_size: Au,
shared_context: &SharedStyleContext,
) -> MaybeAuto {
let containing_block_inline_size =
self.containing_block_inline_size(block, parent_flow_inline_size, shared_context);
initial_computed_inline_size(
block,
containing_block_inline_size,
self.minimum_width_of_all_columns,
self.preferred_width_of_all_columns,
self.table_border_padding,
)
}
fn containing_block_inline_size(
&self,
block: &mut BlockFlow,
parent_flow_inline_size: Au,
shared_context: &SharedStyleContext,
) -> Au {
AbsoluteNonReplaced.containing_block_inline_size(
block,
parent_flow_inline_size,
shared_context,
)
}
fn solve_inline_size_constraints(
&self,
block: &mut BlockFlow,
input: &ISizeConstraintInput,
) -> ISizeConstraintSolution {
AbsoluteNonReplaced.solve_inline_size_constraints(block, input)
}
fn set_inline_position_of_flow_if_necessary(
&self,
block: &mut BlockFlow,
solution: ISizeConstraintSolution,
) {
AbsoluteNonReplaced.set_inline_position_of_flow_if_necessary(block, solution);
}
}

View file

@ -1,18 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
#![cfg(target_pointer_width = "64")]
#[macro_use]
extern crate size_of_test;
use layout::Fragment;
use layout::SpecificFragmentInfo;
size_of_test!(test_size_of_fragment, Fragment, 176);
size_of_test!(
test_size_of_specific_fragment_info,
SpecificFragmentInfo,
24
);

View file

@ -1,798 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! Text layout.
use crate::context::LayoutFontContext;
use crate::fragment::{Fragment, ScannedTextFlags};
use crate::fragment::{ScannedTextFragmentInfo, SpecificFragmentInfo, UnscannedTextFragmentInfo};
use crate::inline::{InlineFragmentNodeFlags, InlineFragments};
use crate::linked_list::split_off_head;
use app_units::Au;
use gfx::font::{FontMetrics, FontRef, RunMetrics, ShapingFlags, ShapingOptions};
use gfx::text::glyph::ByteIndex;
use gfx::text::text_run::TextRun;
use gfx::text::util::{self, CompressionMode};
use range::Range;
use servo_atoms::Atom;
use std::borrow::ToOwned;
use std::collections::LinkedList;
use std::mem;
use std::sync::Arc;
use style::computed_values::text_rendering::T as TextRendering;
use style::computed_values::white_space::T as WhiteSpace;
use style::computed_values::word_break::T as WordBreak;
use style::logical_geometry::{LogicalSize, WritingMode};
use style::properties::style_structs::Font as FontStyleStruct;
use style::properties::ComputedValues;
use style::values::generics::text::LineHeight;
use style::values::specified::text::{TextTransform, TextTransformCase};
use unicode_bidi as bidi;
use unicode_script::{get_script, Script};
use xi_unicode::LineBreakLeafIter;
/// Returns the concatenated text of a list of unscanned text fragments.
fn text(fragments: &LinkedList<Fragment>) -> String {
// FIXME: Some of this work is later duplicated in split_first_fragment_at_newline_if_necessary
// and transform_text. This code should be refactored so that the all the scanning for
// newlines is done in a single pass.
let mut text = String::new();
for fragment in fragments {
if let SpecificFragmentInfo::UnscannedText(ref info) = fragment.specific {
if fragment.white_space().preserve_newlines() {
text.push_str(&info.text);
} else {
text.push_str(&info.text.replace("\n", " "));
}
}
}
text
}
/// A stack-allocated object for scanning an inline flow into `TextRun`-containing `TextFragment`s.
pub struct TextRunScanner {
pub clump: LinkedList<Fragment>,
}
impl TextRunScanner {
pub fn new() -> TextRunScanner {
TextRunScanner {
clump: LinkedList::new(),
}
}
pub fn scan_for_runs(
&mut self,
font_context: &mut LayoutFontContext,
mut fragments: LinkedList<Fragment>,
) -> InlineFragments {
debug!(
"TextRunScanner: scanning {} fragments for text runs...",
fragments.len()
);
debug_assert!(!fragments.is_empty());
// Calculate bidi embedding levels, so we can split bidirectional fragments for reordering.
let text = text(&fragments);
let para_level = fragments
.front()
.unwrap()
.style
.writing_mode
.to_bidi_level();
let bidi_info = bidi::BidiInfo::new(&text, Some(para_level));
// Optimization: If all the text is LTR, don't bother splitting on bidi levels.
let bidi_levels = if bidi_info.has_rtl() {
Some(&bidi_info.levels[..])
} else {
None
};
// FIXME(pcwalton): We want to be sure not to allocate multiple times, since this is a
// performance-critical spot, but this may overestimate and allocate too much memory.
let mut new_fragments = Vec::with_capacity(fragments.len());
let mut last_whitespace = false;
let mut paragraph_bytes_processed = 0;
// The first time we process a text run we will set this
// linebreaker. There is no way for the linebreaker to start
// with an empty state; you must give it its first input immediately.
//
// This linebreaker is shared across text runs, so we can know if
// there is a break at the beginning of a text run or clump, e.g.
// in the case of FooBar<span>Baz</span>
let mut linebreaker = None;
while !fragments.is_empty() {
// Create a clump.
split_first_fragment_at_newline_if_necessary(&mut fragments);
self.clump.append(&mut split_off_head(&mut fragments));
while !fragments.is_empty() &&
self.clump
.back()
.unwrap()
.can_merge_with_fragment(fragments.front().unwrap())
{
split_first_fragment_at_newline_if_necessary(&mut fragments);
self.clump.append(&mut split_off_head(&mut fragments));
}
// Flush that clump to the list of fragments we're building up.
last_whitespace = self.flush_clump_to_list(
font_context,
&mut new_fragments,
&mut paragraph_bytes_processed,
bidi_levels,
last_whitespace,
&mut linebreaker,
);
}
debug!("TextRunScanner: complete.");
InlineFragments {
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.
fn flush_clump_to_list(
&mut self,
mut font_context: &mut LayoutFontContext,
out_fragments: &mut Vec<Fragment>,
paragraph_bytes_processed: &mut usize,
bidi_levels: Option<&[bidi::Level]>,
mut last_whitespace: bool,
linebreaker: &mut Option<LineBreakLeafIter>,
) -> bool {
debug!(
"TextRunScanner: flushing {} fragments in range",
self.clump.len()
);
debug_assert!(!self.clump.is_empty());
match self.clump.front().unwrap().specific {
SpecificFragmentInfo::UnscannedText(_) => {},
_ => {
debug_assert!(
self.clump.len() == 1,
"WAT: can't coalesce non-text nodes in flush_clump_to_list()!"
);
out_fragments.push(self.clump.pop_front().unwrap());
return false;
},
}
// Concatenate all of the transformed strings together, saving the new character indices.
let mut mappings: Vec<RunMapping> = Vec::new();
let runs = {
let font_group;
let compression;
let text_transform;
let letter_spacing;
let word_spacing;
let text_rendering;
let word_break;
{
let in_fragment = self.clump.front().unwrap();
let font_style = in_fragment.style().clone_font();
let inherited_text_style = in_fragment.style().get_inherited_text();
font_group = font_context.font_group(font_style);
compression = match in_fragment.white_space() {
WhiteSpace::Normal | WhiteSpace::Nowrap => {
CompressionMode::CompressWhitespaceNewline
},
WhiteSpace::Pre | WhiteSpace::PreWrap => CompressionMode::CompressNone,
WhiteSpace::PreLine => CompressionMode::CompressWhitespace,
};
text_transform = inherited_text_style.text_transform;
letter_spacing = inherited_text_style.letter_spacing;
word_spacing = inherited_text_style.word_spacing.to_hash_key();
text_rendering = inherited_text_style.text_rendering;
word_break = inherited_text_style.word_break;
}
// First, transform/compress text of all the nodes.
let (mut run_info_list, mut run_info) = (Vec::new(), RunInfo::new());
let mut insertion_point = None;
for (fragment_index, in_fragment) in self.clump.iter().enumerate() {
debug!(" flushing {:?}", in_fragment);
let mut mapping = RunMapping::new(&run_info_list[..], fragment_index);
let text;
let selection;
match in_fragment.specific {
SpecificFragmentInfo::UnscannedText(ref text_fragment_info) => {
text = &text_fragment_info.text;
selection = text_fragment_info.selection;
},
_ => panic!("Expected an unscanned text fragment!"),
};
insertion_point = match selection {
Some(range) if range.is_empty() => {
// `range` is the range within the current fragment. To get the range
// within the text run, offset it by the length of the preceding fragments.
Some(range.begin() + ByteIndex(run_info.text.len() as isize))
},
_ => None,
};
let (mut start_position, mut end_position) = (0, 0);
for (byte_index, character) in text.char_indices() {
if !character.is_control() {
let font = font_group
.borrow_mut()
.find_by_codepoint(&mut font_context, character);
let bidi_level = match bidi_levels {
Some(levels) => levels[*paragraph_bytes_processed],
None => bidi::Level::ltr(),
};
// Break the run if the new character has a different explicit script than the
// previous characters.
//
// TODO: Special handling of paired punctuation characters.
// http://www.unicode.org/reports/tr24/#Common
let script = get_script(character);
let compatible_script = is_compatible(script, run_info.script);
if compatible_script && !is_specific(run_info.script) && is_specific(script)
{
run_info.script = script;
}
let selected = match selection {
Some(range) => range.contains(ByteIndex(byte_index as isize)),
None => false,
};
// Now, if necessary, flush the mapping we were building up.
let flush_run = !run_info.has_font(&font) ||
run_info.bidi_level != bidi_level ||
!compatible_script;
let new_mapping_needed = flush_run || mapping.selected != selected;
if new_mapping_needed {
// We ignore empty mappings at the very start of a fragment.
// The run info values are uninitialized at this point so
// flushing an empty mapping is pointless.
if end_position > 0 {
mapping.flush(
&mut mappings,
&mut run_info,
&**text,
compression,
text_transform,
&mut last_whitespace,
&mut start_position,
end_position,
);
}
if run_info.text.len() > 0 {
if flush_run {
run_info.flush(&mut run_info_list, &mut insertion_point);
run_info = RunInfo::new();
}
mapping = RunMapping::new(&run_info_list[..], fragment_index);
}
run_info.font = font;
run_info.bidi_level = bidi_level;
run_info.script = script;
mapping.selected = selected;
}
}
// Consume this character.
end_position += character.len_utf8();
*paragraph_bytes_processed += character.len_utf8();
}
// Flush the last mapping we created for this fragment to the list.
mapping.flush(
&mut mappings,
&mut run_info,
&**text,
compression,
text_transform,
&mut last_whitespace,
&mut start_position,
end_position,
);
}
// Push the final run info.
run_info.flush(&mut run_info_list, &mut insertion_point);
// Per CSS 2.1 § 16.4, "when the resultant space between two characters is not the same
// as the default space, user agents should not use ligatures." This ensures that, for
// example, `finally` with a wide `letter-spacing` renders as `f i n a l l y` and not
// `fi n a l l y`.
let mut flags = ShapingFlags::empty();
if letter_spacing.0.px() != 0. {
flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG);
}
if text_rendering == TextRendering::Optimizespeed {
flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG);
flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG)
}
if word_break == WordBreak::KeepAll {
flags.insert(ShapingFlags::KEEP_ALL_FLAG);
}
let options = ShapingOptions {
letter_spacing: if letter_spacing.0.px() == 0. {
None
} else {
Some(Au::from(letter_spacing.0))
},
word_spacing,
script: Script::Common,
flags: flags,
};
let mut result = Vec::with_capacity(run_info_list.len());
for run_info in run_info_list {
let mut options = options;
options.script = run_info.script;
if run_info.bidi_level.is_rtl() {
options.flags.insert(ShapingFlags::RTL_FLAG);
}
// If no font is found (including fallbacks), there's no way we can render.
let font = run_info
.font
.or_else(|| font_group.borrow_mut().first(&mut font_context))
.expect("No font found for text run!");
let (run, break_at_zero) = TextRun::new(
&mut *font.borrow_mut(),
run_info.text,
&options,
run_info.bidi_level,
linebreaker,
);
result.push((
ScannedTextRun {
run: Arc::new(run),
insertion_point: run_info.insertion_point,
},
break_at_zero,
))
}
result
};
// Make new fragments with the runs and adjusted text indices.
debug!("TextRunScanner: pushing {} fragment(s)", self.clump.len());
let mut mappings = mappings.into_iter().peekable();
let mut prev_fragments_to_meld = Vec::new();
for (logical_offset, old_fragment) in mem::replace(&mut self.clump, LinkedList::new())
.into_iter()
.enumerate()
{
let mut is_first_mapping_of_this_old_fragment = true;
loop {
match mappings.peek() {
Some(mapping) if mapping.old_fragment_index == logical_offset => {},
Some(_) | None => {
if is_first_mapping_of_this_old_fragment {
// There were no mappings for this unscanned fragment. Transfer its
// flags to the previous/next sibling elements instead.
if let Some(ref mut last_fragment) = out_fragments.last_mut() {
last_fragment.meld_with_next_inline_fragment(&old_fragment);
}
prev_fragments_to_meld.push(old_fragment);
}
break;
},
};
let mapping = mappings.next().unwrap();
let (scanned_run, break_at_zero) = runs[mapping.text_run_index].clone();
let mut byte_range = Range::new(
ByteIndex(mapping.byte_range.begin() as isize),
ByteIndex(mapping.byte_range.length() as isize),
);
let mut flags = ScannedTextFlags::empty();
if !break_at_zero && mapping.byte_range.begin() == 0 {
// If this is the first segment of the text run,
// and the text run doesn't break at zero, suppress line breaks
flags.insert(ScannedTextFlags::SUPPRESS_LINE_BREAK_BEFORE)
}
let text_size = old_fragment.border_box.size;
let requires_line_break_afterward_if_wrapping_on_newlines = scanned_run.run.text
[mapping.byte_range.begin()..mapping.byte_range.end()]
.ends_with('\n');
if requires_line_break_afterward_if_wrapping_on_newlines {
byte_range.extend_by(ByteIndex(-1)); // Trim the '\n'
flags.insert(
ScannedTextFlags::REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES,
);
}
if mapping.selected {
flags.insert(ScannedTextFlags::SELECTED);
}
let insertion_point =
if mapping.contains_insertion_point(scanned_run.insertion_point) {
scanned_run.insertion_point
} else {
None
};
let mut new_text_fragment_info = Box::new(ScannedTextFragmentInfo::new(
scanned_run.run,
byte_range,
text_size,
insertion_point,
flags,
));
let new_metrics = new_text_fragment_info.run.metrics_for_range(&byte_range);
let writing_mode = old_fragment.style.writing_mode;
let bounding_box_size = bounding_box_for_run_metrics(&new_metrics, writing_mode);
new_text_fragment_info.content_size = bounding_box_size;
let mut new_fragment = old_fragment.transform(
bounding_box_size,
SpecificFragmentInfo::ScannedText(new_text_fragment_info),
);
let is_last_mapping_of_this_old_fragment = match mappings.peek() {
Some(mapping) if mapping.old_fragment_index == logical_offset => false,
_ => true,
};
if let Some(ref mut context) = new_fragment.inline_context {
for node in &mut context.nodes {
if !is_last_mapping_of_this_old_fragment {
node.flags
.remove(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT);
}
if !is_first_mapping_of_this_old_fragment {
node.flags
.remove(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT);
}
}
}
for prev_fragment in prev_fragments_to_meld.drain(..) {
new_fragment.meld_with_prev_inline_fragment(&prev_fragment);
}
is_first_mapping_of_this_old_fragment = false;
out_fragments.push(new_fragment)
}
}
last_whitespace
}
}
#[inline]
fn bounding_box_for_run_metrics(
metrics: &RunMetrics,
writing_mode: WritingMode,
) -> LogicalSize<Au> {
// TODO: When the text-orientation property is supported, the block and inline directions may
// be swapped for horizontal glyphs in vertical lines.
LogicalSize::new(
writing_mode,
metrics.bounding_box.size.width,
metrics.bounding_box.size.height,
)
}
/// Returns the metrics of the font represented by the given `FontStyleStruct`.
///
/// `#[inline]` because often the caller only needs a few fields from the font metrics.
///
/// # Panics
///
/// Panics if no font can be found for the given font style.
#[inline]
pub fn font_metrics_for_style(
mut font_context: &mut LayoutFontContext,
style: crate::ServoArc<FontStyleStruct>,
) -> FontMetrics {
let font_group = font_context.font_group(style);
let font = font_group.borrow_mut().first(&mut font_context);
let font = font.as_ref().unwrap().borrow();
font.metrics.clone()
}
/// 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.size();
match style.get_inherited_text().line_height {
LineHeight::Normal => Au::from(metrics.line_gap),
LineHeight::Number(l) => font_size.scale_by(l.0),
LineHeight::Length(l) => Au::from(l),
}
}
fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList<Fragment>) {
if fragments.is_empty() {
return;
}
let new_fragment = {
let first_fragment = fragments.front_mut().unwrap();
let string_before;
let selection_before;
{
if !first_fragment.white_space().preserve_newlines() {
return;
}
let unscanned_text_fragment_info = match first_fragment.specific {
SpecificFragmentInfo::UnscannedText(ref mut unscanned_text_fragment_info) => {
unscanned_text_fragment_info
},
_ => return,
};
let position = match unscanned_text_fragment_info.text.find('\n') {
Some(position) if position < unscanned_text_fragment_info.text.len() - 1 => {
position
},
Some(_) | None => return,
};
string_before = unscanned_text_fragment_info.text[..(position + 1)].to_owned();
unscanned_text_fragment_info.text = unscanned_text_fragment_info.text[(position + 1)..]
.to_owned()
.into_boxed_str();
let offset = ByteIndex(string_before.len() as isize);
match unscanned_text_fragment_info.selection {
Some(ref mut selection) if selection.begin() >= offset => {
// Selection is entirely in the second fragment.
selection_before = None;
selection.shift_by(-offset);
},
Some(ref mut selection) if selection.end() > offset => {
// Selection is split across two fragments.
selection_before = Some(Range::new(selection.begin(), offset));
*selection = Range::new(ByteIndex(0), selection.end() - offset);
},
_ => {
// Selection is entirely in the first fragment.
selection_before = unscanned_text_fragment_info.selection;
unscanned_text_fragment_info.selection = None;
},
};
}
first_fragment.transform(
first_fragment.border_box.size,
SpecificFragmentInfo::UnscannedText(Box::new(UnscannedTextFragmentInfo::new(
string_before.into_boxed_str(),
selection_before,
))),
)
};
fragments.push_front(new_fragment);
}
/// Information about a text run that we're about to create. This is used in `scan_for_runs`.
struct RunInfo {
/// The text that will go in this text run.
text: String,
/// The insertion point in this text run, if applicable.
insertion_point: Option<ByteIndex>,
/// The font that the text should be rendered with.
font: Option<FontRef>,
/// The bidirection embedding level of this text run.
bidi_level: bidi::Level,
/// The Unicode script property of this text run.
script: Script,
}
impl RunInfo {
fn new() -> RunInfo {
RunInfo {
text: String::new(),
insertion_point: None,
font: None,
bidi_level: bidi::Level::ltr(),
script: Script::Common,
}
}
/// Finish processing this RunInfo and add it to the "done" list.
///
/// * `insertion_point`: The position of the insertion point, in characters relative to the start
/// of this text run.
fn flush(mut self, list: &mut Vec<RunInfo>, insertion_point: &mut Option<ByteIndex>) {
if let Some(idx) = *insertion_point {
let char_len = ByteIndex(self.text.len() as isize);
if idx <= char_len {
// The insertion point is in this text run.
self.insertion_point = insertion_point.take()
} else {
// Continue looking for the insertion point in the next text run.
*insertion_point = Some(idx - char_len)
}
}
list.push(self);
}
fn has_font(&self, font: &Option<FontRef>) -> bool {
fn identifier(font: &Option<FontRef>) -> Option<Atom> {
font.as_ref().map(|f| f.borrow().identifier())
}
identifier(&self.font) == identifier(font)
}
}
/// A mapping from a portion of an unscanned text fragment to the text run we're going to create
/// for it.
#[derive(Clone, Copy, Debug)]
struct RunMapping {
/// The range of byte indices within the text fragment.
byte_range: Range<usize>,
/// The index of the unscanned text fragment that this mapping corresponds to.
old_fragment_index: usize,
/// The index of the text run we're going to create.
text_run_index: usize,
/// Is the text in this fragment selected?
selected: bool,
}
impl RunMapping {
/// Given the current set of text runs, creates a run mapping for the next fragment.
/// `run_info_list` describes the set of runs we've seen already.
fn new(run_info_list: &[RunInfo], fragment_index: usize) -> RunMapping {
RunMapping {
byte_range: Range::new(0, 0),
old_fragment_index: fragment_index,
text_run_index: run_info_list.len(),
selected: false,
}
}
/// Flushes this run mapping to the list. `run_info` describes the text run that we're
/// currently working on. `text` refers to the text of this fragment.
fn flush(
mut self,
mappings: &mut Vec<RunMapping>,
run_info: &mut RunInfo,
text: &str,
compression: CompressionMode,
text_transform: TextTransform,
last_whitespace: &mut bool,
start_position: &mut usize,
end_position: usize,
) {
let was_empty = *start_position == end_position;
let old_byte_length = run_info.text.len();
*last_whitespace = util::transform_text(
&text[(*start_position)..end_position],
compression,
*last_whitespace,
&mut run_info.text,
);
// Account for `text-transform`. (Confusingly, this is not handled in "text
// transformation" above, but we follow Gecko in the naming.)
let is_first_run = *start_position == 0;
apply_style_transform_if_necessary(
&mut run_info.text,
old_byte_length,
text_transform,
*last_whitespace,
is_first_run,
);
*start_position = end_position;
let new_byte_length = run_info.text.len();
let is_empty = new_byte_length == old_byte_length;
// Don't save mappings that contain only discarded characters.
// (But keep ones that contained no characters to begin with, since they might have been
// generated by an empty flow to draw its borders/padding/insertion point.)
if is_empty && !was_empty {
return;
}
self.byte_range = Range::new(old_byte_length, new_byte_length - old_byte_length);
mappings.push(self)
}
/// Is the insertion point for this text run within this mapping?
///
/// NOTE: We treat the range as inclusive at both ends, since the insertion point can lie
/// before the first character *or* after the last character, and should be drawn even if the
/// text is empty.
fn contains_insertion_point(&self, insertion_point: Option<ByteIndex>) -> bool {
match insertion_point.map(ByteIndex::to_usize) {
None => false,
Some(idx) => self.byte_range.begin() <= idx && idx <= self.byte_range.end(),
}
}
}
/// Accounts for `text-transform`.
///
/// FIXME(#4311, pcwalton): Title-case mapping can change length of the string;
/// case mapping should be language-specific; `full-width`;
/// use graphemes instead of characters.
fn apply_style_transform_if_necessary(
string: &mut String,
first_character_position: usize,
text_transform: TextTransform,
last_whitespace: bool,
is_first_run: bool,
) {
match text_transform.case_ {
TextTransformCase::None => {},
TextTransformCase::Uppercase => {
let original = string[first_character_position..].to_owned();
string.truncate(first_character_position);
for ch in original.chars().flat_map(|ch| ch.to_uppercase()) {
string.push(ch);
}
},
TextTransformCase::Lowercase => {
let original = string[first_character_position..].to_owned();
string.truncate(first_character_position);
for ch in original.chars().flat_map(|ch| ch.to_lowercase()) {
string.push(ch);
}
},
TextTransformCase::Capitalize => {
let original = string[first_character_position..].to_owned();
string.truncate(first_character_position);
let mut capitalize_next_letter = is_first_run || last_whitespace;
for character in original.chars() {
// FIXME(#4311, pcwalton): Should be the CSS/Unicode notion of a *typographic
// letter unit*, not an *alphabetic* character:
//
// http://dev.w3.org/csswg/css-text/#typographic-letter-unit
if capitalize_next_letter && character.is_alphabetic() {
string.push(character.to_uppercase().next().unwrap());
capitalize_next_letter = false;
continue;
}
string.push(character);
// FIXME(#4311, pcwalton): Try UAX29 instead of just whitespace.
if character.is_whitespace() {
capitalize_next_letter = true
}
}
},
}
}
#[derive(Clone)]
struct ScannedTextRun {
run: Arc<TextRun>,
insertion_point: Option<ByteIndex>,
}
/// Can a character with script `b` continue a text run with script `a`?
fn is_compatible(a: Script, b: Script) -> bool {
a == b || !is_specific(a) || !is_specific(b)
}
/// Returns true if the script is not invalid or inherited.
fn is_specific(script: Script) -> bool {
script != Script::Common && script != Script::Inherited
}

View file

@ -2,21 +2,12 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Traversals over the DOM and flow trees, running the layout computations.
use crate::construct::FlowConstructor;
use crate::context::LayoutContext;
use crate::display_list::DisplayListBuildState;
use crate::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils};
use crate::wrapper::ThreadSafeLayoutNodeHelpers;
use crate::wrapper::{GetRawData, LayoutNodeLayoutData};
use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
use servo_config::opts;
use crate::wrapper::GetRawData;
use script_layout_interface::wrapper_traits::LayoutNode;
use style::context::{SharedStyleContext, StyleContext};
use style::data::ElementData;
use style::dom::{NodeInfo, TElement, TNode};
use style::selector_parser::RestyleDamage;
use style::servo::restyle_damage::ServoRestyleDamage;
use style::traversal::PerLevelTraversalData;
use style::traversal::{recalc_style_at, DomTraversal};
@ -25,13 +16,10 @@ pub struct RecalcStyleAndConstructFlows<'a> {
}
impl<'a> RecalcStyleAndConstructFlows<'a> {
/// Creates a traversal context, taking ownership of the shared layout context.
pub fn new(context: LayoutContext<'a>) -> Self {
RecalcStyleAndConstructFlows { context: context }
}
/// Consumes this traversal context, returning ownership of the shared layout
/// context to the caller.
pub fn destroy(self) -> LayoutContext<'a> {
self.context
}
@ -53,8 +41,6 @@ where
) where
F: FnMut(E::ConcreteNode),
{
// FIXME(pcwalton): Stop allocating here. Ideally this should just be
// done by the HTML parser.
unsafe { node.initialize_data() };
if !node.is_text_node() {
@ -65,14 +51,14 @@ where
}
fn process_postorder(&self, _style_context: &mut StyleContext<E>, node: E::ConcreteNode) {
construct_flows_at(&self.context, node);
if let Some(el) = node.as_element() {
unsafe {
el.unset_dirty_descendants();
}
}
}
fn text_node_needs_traversal(node: E::ConcreteNode, parent_data: &ElementData) -> bool {
// Text nodes never need styling. However, there are two cases they may need
// flow construction:
// (1) They child doesn't yet have layout data (preorder traversal initializes it).
// (2) The parent element has restyle damage (so the text flow also needs fixup).
node.get_raw_data().is_none() || !parent_data.damage.is_empty()
}
@ -80,271 +66,3 @@ where
&self.context.style_context
}
}
/// A top-down traversal.
pub trait PreorderFlowTraversal {
/// The operation to perform. Return true to continue or false to stop.
fn process(&self, flow: &mut dyn Flow);
/// Returns true if this node should be processed and false if neither this node nor its
/// descendants should be processed.
fn should_process_subtree(&self, _flow: &mut dyn Flow) -> bool {
true
}
/// Returns true if this node must be processed in-order. If this returns false,
/// we skip the operation for this node, but continue processing the descendants.
/// This is called *after* parent nodes are visited.
fn should_process(&self, _flow: &mut dyn Flow) -> bool {
true
}
/// Traverses the tree in preorder.
fn traverse(&self, flow: &mut dyn Flow) {
if !self.should_process_subtree(flow) {
return;
}
if self.should_process(flow) {
self.process(flow);
}
for kid in flow.mut_base().child_iter_mut() {
self.traverse(kid);
}
}
/// Traverse the Absolute flow tree in preorder.
///
/// Traverse all your direct absolute descendants, who will then traverse
/// their direct absolute descendants.
///
/// Return true if the traversal is to continue or false to stop.
fn traverse_absolute_flows(&self, flow: &mut dyn Flow) {
if self.should_process(flow) {
self.process(flow);
}
for descendant_link in flow.mut_base().abs_descendants.iter() {
self.traverse_absolute_flows(descendant_link)
}
}
}
/// A bottom-up traversal, with a optional in-order pass.
pub trait PostorderFlowTraversal {
/// The operation to perform. Return true to continue or false to stop.
fn process(&self, flow: &mut dyn Flow);
/// Returns false if this node must be processed in-order. If this returns false, we skip the
/// operation for this node, but continue processing the ancestors. This is called *after*
/// child nodes are visited.
fn should_process(&self, _flow: &mut dyn Flow) -> bool {
true
}
/// Traverses the tree in postorder.
fn traverse(&self, flow: &mut dyn Flow) {
for kid in flow.mut_base().child_iter_mut() {
self.traverse(kid);
}
if self.should_process(flow) {
self.process(flow);
}
}
}
/// An in-order (sequential only) traversal.
pub trait InorderFlowTraversal {
/// The operation to perform. Returns the level of the tree we're at.
fn process(&mut self, flow: &mut dyn Flow, level: u32);
/// Returns true if this node should be processed and false if neither this node nor its
/// descendants should be processed.
fn should_process_subtree(&mut self, _flow: &mut dyn Flow) -> bool {
true
}
/// Traverses the tree in-order.
fn traverse(&mut self, flow: &mut dyn Flow, level: u32) {
if !self.should_process_subtree(flow) {
return;
}
self.process(flow, level);
for kid in flow.mut_base().child_iter_mut() {
self.traverse(kid, level + 1);
}
}
}
/// A bottom-up, parallelizable traversal.
pub trait PostorderNodeMutTraversal<ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode> {
/// The operation to perform. Return true to continue or false to stop.
fn process(&mut self, node: &ConcreteThreadSafeLayoutNode);
}
/// The flow construction traversal, which builds flows for styled nodes.
#[inline]
#[allow(unsafe_code)]
fn construct_flows_at<N>(context: &LayoutContext, node: N)
where
N: LayoutNode,
{
debug!("construct_flows_at: {:?}", node);
// Construct flows for this node.
{
let tnode = node.to_threadsafe();
// Always reconstruct if incremental layout is turned off.
let nonincremental_layout = opts::get().nonincremental_layout;
if nonincremental_layout ||
tnode.restyle_damage() != RestyleDamage::empty() ||
node.as_element()
.map_or(false, |el| el.has_dirty_descendants())
{
let mut flow_constructor = FlowConstructor::new(context);
if nonincremental_layout || !flow_constructor.repair_if_possible(&tnode) {
flow_constructor.process(&tnode);
debug!(
"Constructed flow for {:?}: {:x}",
tnode,
tnode.flow_debug_id()
);
}
}
tnode
.mutate_layout_data()
.unwrap()
.flags
.insert(crate::data::LayoutDataFlags::HAS_BEEN_TRAVERSED);
}
if let Some(el) = node.as_element() {
unsafe {
el.unset_dirty_descendants();
}
}
}
/// The bubble-inline-sizes traversal, the first part of layout computation. This computes
/// preferred and intrinsic inline-sizes and bubbles them up the tree.
pub struct BubbleISizes<'a> {
pub layout_context: &'a LayoutContext<'a>,
}
impl<'a> PostorderFlowTraversal for BubbleISizes<'a> {
#[inline]
fn process(&self, flow: &mut dyn Flow) {
flow.bubble_inline_sizes();
flow.mut_base()
.restyle_damage
.remove(ServoRestyleDamage::BUBBLE_ISIZES);
}
#[inline]
fn should_process(&self, flow: &mut dyn Flow) -> bool {
flow.base()
.restyle_damage
.contains(ServoRestyleDamage::BUBBLE_ISIZES)
}
}
/// The assign-inline-sizes traversal. In Gecko this corresponds to `Reflow`.
#[derive(Clone, Copy)]
pub struct AssignISizes<'a> {
pub layout_context: &'a LayoutContext<'a>,
}
impl<'a> PreorderFlowTraversal for AssignISizes<'a> {
#[inline]
fn process(&self, flow: &mut dyn Flow) {
flow.assign_inline_sizes(self.layout_context);
}
#[inline]
fn should_process(&self, flow: &mut dyn Flow) -> bool {
flow.base()
.restyle_damage
.intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW)
}
}
/// The assign-block-sizes-and-store-overflow traversal, the last (and most expensive) part of
/// layout computation. Determines the final block-sizes for all layout objects and computes
/// positions. In Gecko this corresponds to `Reflow`.
#[derive(Clone, Copy)]
pub struct AssignBSizes<'a> {
pub layout_context: &'a LayoutContext<'a>,
}
impl<'a> PostorderFlowTraversal for AssignBSizes<'a> {
#[inline]
fn process(&self, flow: &mut dyn Flow) {
// Can't do anything with anything that floats might flow through until we reach their
// inorder parent.
//
// NB: We must return without resetting the restyle bits for these, as we haven't actually
// reflowed anything!
if flow.floats_might_flow_through() {
return;
}
flow.assign_block_size(self.layout_context);
}
#[inline]
fn should_process(&self, flow: &mut dyn Flow) -> bool {
let base = flow.base();
base.restyle_damage.intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW) &&
// The fragmentation countainer is responsible for calling
// Flow::fragment recursively
!base.flags.contains(FlowFlags::CAN_BE_FRAGMENTED)
}
}
pub struct ComputeStackingRelativePositions<'a> {
pub layout_context: &'a LayoutContext<'a>,
}
impl<'a> PreorderFlowTraversal for ComputeStackingRelativePositions<'a> {
#[inline]
fn should_process_subtree(&self, flow: &mut dyn Flow) -> bool {
flow.base()
.restyle_damage
.contains(ServoRestyleDamage::REPOSITION)
}
#[inline]
fn process(&self, flow: &mut dyn Flow) {
flow.compute_stacking_relative_position(self.layout_context);
flow.mut_base()
.restyle_damage
.remove(ServoRestyleDamage::REPOSITION)
}
}
pub struct BuildDisplayList<'a> {
pub state: DisplayListBuildState<'a>,
}
impl<'a> BuildDisplayList<'a> {
#[inline]
pub fn traverse(&mut self, flow: &mut dyn Flow) {
let parent_stacking_context_id = self.state.current_stacking_context_id;
self.state.current_stacking_context_id = flow.base().stacking_context_id;
let parent_clipping_and_scrolling = self.state.current_clipping_and_scrolling;
self.state.current_clipping_and_scrolling = flow.clipping_and_scrolling();
flow.build_display_list(&mut self.state);
flow.mut_base()
.restyle_damage
.remove(ServoRestyleDamage::REPAINT);
for kid in flow.mut_base().child_iter_mut() {
self.traverse(kid);
}
self.state.current_stacking_context_id = parent_stacking_context_id;
self.state.current_clipping_and_scrolling = parent_clipping_and_scrolling;
}
}

View file

@ -2,65 +2,10 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://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 `LayoutDom` 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 thread 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:
//!
//! * 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()`.
#![allow(unsafe_code)]
use crate::data::{LayoutData, LayoutDataFlags, StyleAndLayoutData};
use atomic_refcell::{AtomicRef, AtomicRefMut};
use crate::data::StyleAndLayoutData;
use script_layout_interface::wrapper_traits::GetLayoutData;
use script_layout_interface::wrapper_traits::{ThreadSafeLayoutElement, ThreadSafeLayoutNode};
use style::dom::{NodeInfo, TNode};
use style::selector_parser::RestyleDamage;
use style::values::computed::counters::ContentItem;
use style::values::generics::counters::Content;
pub trait LayoutNodeLayoutData {
/// Similar to borrow_data*, but returns the full PersistentLayoutData rather
/// than only the style::data::ElementData.
fn borrow_layout_data(&self) -> Option<AtomicRef<LayoutData>>;
fn mutate_layout_data(&self) -> Option<AtomicRefMut<LayoutData>>;
fn flow_debug_id(self) -> usize;
}
impl<T: GetLayoutData> LayoutNodeLayoutData for T {
fn borrow_layout_data(&self) -> Option<AtomicRef<LayoutData>> {
self.get_raw_data().map(|d| d.layout_data.borrow())
}
fn mutate_layout_data(&self) -> Option<AtomicRefMut<LayoutData>> {
self.get_raw_data().map(|d| d.layout_data.borrow_mut())
}
fn flow_debug_id(self) -> usize {
self.borrow_layout_data()
.map_or(0, |d| d.flow_construction_result.debug_id())
}
}
pub trait GetRawData {
fn get_raw_data(&self) -> Option<&StyleAndLayoutData>;
@ -74,101 +19,3 @@ impl<T: GetLayoutData> GetRawData for T {
})
}
}
pub trait ThreadSafeLayoutNodeHelpers {
/// Returns the layout data flags for this node.
fn flags(self) -> LayoutDataFlags;
/// Adds the given flags to this node.
fn insert_flags(self, new_flags: LayoutDataFlags);
/// Removes the given flags from this node.
fn remove_flags(self, flags: LayoutDataFlags);
/// If this is a text node, generated content, or a form element, copies out
/// its content. Otherwise, panics.
///
/// FIXME(pcwalton): This might have too much copying and/or allocation. Profile this.
fn text_content(&self) -> TextContent;
/// The RestyleDamage from any restyling, or RestyleDamage::rebuild_and_reflow() if this
/// is the first time layout is visiting this node. We implement this here, rather than
/// with the rest of the wrapper layer, because we need layout code to determine whether
/// layout has visited the node.
fn restyle_damage(self) -> RestyleDamage;
}
impl<T: ThreadSafeLayoutNode> ThreadSafeLayoutNodeHelpers for T {
fn flags(self) -> LayoutDataFlags {
self.borrow_layout_data().as_ref().unwrap().flags
}
fn insert_flags(self, new_flags: LayoutDataFlags) {
self.mutate_layout_data().unwrap().flags.insert(new_flags);
}
fn remove_flags(self, flags: LayoutDataFlags) {
self.mutate_layout_data().unwrap().flags.remove(flags);
}
fn text_content(&self) -> TextContent {
if self.get_pseudo_element_type().is_replaced_content() {
let style = self.as_element().unwrap().resolved_style();
return TextContent::GeneratedContent(match style.as_ref().get_counters().content {
Content::Items(ref value) => value.to_vec(),
_ => vec![],
});
}
TextContent::Text(self.node_text_content().into_boxed_str())
}
fn restyle_damage(self) -> RestyleDamage {
// We need the underlying node to potentially access the parent in the
// case of text nodes. This is safe as long as we don't let the parent
// escape and never access its descendants.
let mut node = unsafe { self.unsafe_get() };
// If this is a text node, use the parent element, since that's what
// controls our style.
if node.is_text_node() {
node = node.parent_node().unwrap();
debug_assert!(node.is_element());
}
let damage = {
let data = node.get_raw_data().unwrap();
if !data
.layout_data
.borrow()
.flags
.contains(crate::data::LayoutDataFlags::HAS_BEEN_TRAVERSED)
{
// We're reflowing a node that was styled for the first time and
// has never been visited by layout. Return rebuild_and_reflow,
// because that's what the code expects.
RestyleDamage::rebuild_and_reflow()
} else {
data.style_data.element_data.borrow().damage
}
};
damage
}
}
pub enum TextContent {
Text(Box<str>),
GeneratedContent(Vec<ContentItem>),
}
impl TextContent {
pub fn is_empty(&self) -> bool {
match *self {
TextContent::Text(_) => false,
TextContent::GeneratedContent(ref content) => content.is_empty(),
}
}
}

View file

@ -13,8 +13,6 @@ extern crate crossbeam_channel;
#[macro_use]
extern crate html5ever;
#[macro_use]
extern crate layout;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
@ -30,7 +28,7 @@ use crossbeam_channel::{unbounded, Receiver, Sender};
use embedder_traits::resources::{self, Resource};
use euclid::{Point2D, Rect, Size2D, TypedScale, TypedSize2D};
use fnv::FnvHashMap;
use fxhash::{FxHashMap, FxHashSet};
use fxhash::FxHashMap;
use gfx::font;
use gfx::font_cache_thread::FontCacheThread;
use gfx::font_context;
@ -38,19 +36,11 @@ use gfx_traits::{node_id_from_scroll_id, Epoch};
use histogram::Histogram;
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use ipc_channel::router::ROUTER;
use layout::animation;
use layout::construct::ConstructionResult;
use layout::context::malloc_size_of_persistent_local_context;
use layout::context::LayoutContext;
use layout::context::RegisteredPainter;
use layout::context::RegisteredPainters;
use layout::display_list::items::{OpaqueNode, WebRenderImageInfo};
use layout::display_list::{IndexableText, ToLayout, WebRenderDisplayListConverter};
use layout::flow::{Flow, GetBaseFlow, ImmutableFlowUtils, MutableOwnedFlowUtils};
use layout::display_list::items::OpaqueNode;
use layout::display_list::{IndexableText, WebRenderDisplayListConverter};
use layout::flow::{Flow, GetBaseFlow};
use layout::flow_ref::FlowRef;
use layout::incremental::{RelayoutMode, SpecialRestyleDamage};
use layout::layout_debug;
use layout::parallel;
use layout::query::{
process_content_box_request, process_content_boxes_request, LayoutRPCImpl, LayoutThreadData,
};
@ -59,11 +49,7 @@ use layout::query::{process_node_scroll_area_request, process_node_scroll_id_req
use layout::query::{
process_offset_parent_query, process_resolved_style_request, process_style_query,
};
use layout::sequential;
use layout::traversal::{
ComputeStackingRelativePositions, PreorderFlowTraversal, RecalcStyleAndConstructFlows,
};
use layout::wrapper::LayoutNodeLayoutData;
use layout::traversal::RecalcStyleAndConstructFlows;
use layout_traits::LayoutThreadFactory;
use libc::c_void;
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
@ -71,30 +57,29 @@ use metrics::{PaintTimeMetrics, ProfilerMetadataFactory, ProgressiveWebMetric};
use msg::constellation_msg::{
BackgroundHangMonitor, BackgroundHangMonitorRegister, HangAnnotation,
};
use msg::constellation_msg::{BrowsingContextId, MonitoredComponentId, TopLevelBrowsingContextId};
use msg::constellation_msg::{MonitoredComponentId, TopLevelBrowsingContextId};
use msg::constellation_msg::{LayoutHangAnnotation, MonitoredComponentType, PipelineId};
use net_traits::image_cache::{ImageCache, UsePlaceholder};
use net_traits::image_cache::ImageCache;
use parking_lot::RwLock;
use profile_traits::mem::{self as profile_mem, Report, ReportKind, ReportsChan};
use profile_traits::time::{self as profile_time, profile, TimerMetadata};
use profile_traits::time::{TimerMetadataFrameType, TimerMetadataReflowType};
use script_layout_interface::message::{LayoutThreadInit, Msg, NodesFromPointQueryType, Reflow};
use script_layout_interface::message::{LayoutThreadInit, Msg, NodesFromPointQueryType};
use script_layout_interface::message::{QueryMsg, ReflowComplete, ReflowGoal, ScriptReflow};
use script_layout_interface::rpc::TextIndexResponse;
use script_layout_interface::rpc::{LayoutRPC, OffsetParentResponse, StyleResponse};
use script_layout_interface::wrapper_traits::LayoutNode;
use script_traits::Painter;
use script_traits::{ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg};
use script_traits::{DrawAPaintImageResult, IFrameSizeMsg, PaintWorkletError, WindowSizeType};
use script_traits::{DrawAPaintImageResult, PaintWorkletError};
use script_traits::{ScrollState, UntrustedNodeAddress};
use selectors::Element;
use servo_arc::Arc as ServoArc;
use servo_atoms::Atom;
use servo_config::opts;
use servo_config::pref;
use servo_geometry::{DeviceIndependentPixel, MaxRect};
use servo_geometry::DeviceIndependentPixel;
use servo_url::ServoUrl;
use std::borrow::ToOwned;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
@ -111,7 +96,6 @@ use style::driver;
use style::error_reporting::RustLogReporter;
use style::global_style_data::{GLOBAL_STYLE_DATA, STYLE_THREAD_POOL};
use style::invalidation::element::restyle_hints::RestyleHint;
use style::logical_geometry::LogicalPoint;
use style::media_queries::{Device, MediaList, MediaType};
use style::properties::PropertyId;
use style::selector_parser::SnapshotMap;
@ -161,9 +145,6 @@ pub struct LayoutThread {
/// A means of communication with the background hang monitor.
background_hang_monitor: Box<dyn BackgroundHangMonitor>,
/// The channel on which messages can be sent to the constellation.
constellation_chan: IpcSender<ConstellationMsg>,
/// The channel on which messages can be sent to the script thread.
script_chan: IpcSender<ConstellationControlMsg>,
@ -173,9 +154,6 @@ pub struct LayoutThread {
/// The channel on which messages can be sent to the memory profiler.
mem_profiler_chan: profile_mem::ProfilerChan,
/// Reference to the script thread image cache.
image_cache: Arc<dyn ImageCache>,
/// Public interface to the font cache thread.
font_cache_thread: FontCacheThread,
@ -194,7 +172,7 @@ pub struct LayoutThread {
new_animations_sender: Sender<Animation>,
/// Receives newly-discovered animations.
new_animations_receiver: Receiver<Animation>,
_new_animations_receiver: Receiver<Animation>,
/// The number of Web fonts that have been requested but not yet loaded.
outstanding_web_fonts: Arc<AtomicUsize>,
@ -224,8 +202,6 @@ pub struct LayoutThread {
/// All the other elements of this struct are read-only.
rw_data: Arc<Mutex<LayoutThreadData>>,
webrender_image_cache: Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo>>>,
/// The executors for paint worklets.
registered_painters: RegisteredPaintersImpl,
@ -245,9 +221,6 @@ pub struct LayoutThread {
/// The time a layout query has waited before serviced by layout thread.
layout_query_waiting_time: Histogram,
/// The sizes of all iframes encountered during the last layout operation.
last_iframe_sizes: RefCell<HashMap<BrowsingContextId, TypedSize2D<f32, CSSPixel>>>,
/// Flag that indicates if LayoutThread is busy handling a request.
busy: Arc<AtomicBool>,
@ -302,7 +275,7 @@ impl LayoutThreadFactory for LayoutThread {
background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
constellation_chan: IpcSender<ConstellationMsg>,
script_chan: IpcSender<ConstellationControlMsg>,
image_cache: Arc<dyn ImageCache>,
_image_cache: Arc<dyn ImageCache>,
font_cache_thread: FontCacheThread,
time_profiler_chan: profile_time::ProfilerChan,
mem_profiler_chan: profile_mem::ProfilerChan,
@ -352,7 +325,6 @@ impl LayoutThreadFactory for LayoutThread {
background_hang_monitor,
constellation_chan,
script_chan,
image_cache.clone(),
font_cache_thread,
time_profiler_chan,
mem_profiler_chan.clone(),
@ -526,7 +498,6 @@ impl LayoutThread {
background_hang_monitor: Box<dyn BackgroundHangMonitor>,
constellation_chan: IpcSender<ConstellationMsg>,
script_chan: IpcSender<ConstellationControlMsg>,
image_cache: Arc<dyn ImageCache>,
font_cache_thread: FontCacheThread,
time_profiler_chan: profile_time::ProfilerChan,
mem_profiler_chan: profile_mem::ProfilerChan,
@ -574,11 +545,9 @@ impl LayoutThread {
pipeline_port: pipeline_receiver,
script_chan: script_chan.clone(),
background_hang_monitor,
constellation_chan: constellation_chan.clone(),
time_profiler_chan: time_profiler_chan,
mem_profiler_chan: mem_profiler_chan,
registered_painters: RegisteredPaintersImpl(Default::default()),
image_cache: image_cache.clone(),
font_cache_thread: font_cache_thread,
first_reflow: Cell::new(true),
font_cache_receiver: font_cache_receiver,
@ -586,7 +555,7 @@ impl LayoutThread {
parallel_flag: true,
generation: Cell::new(0),
new_animations_sender: new_animations_sender,
new_animations_receiver: new_animations_receiver,
_new_animations_receiver: new_animations_receiver,
outstanding_web_fonts: Arc::new(AtomicUsize::new(0)),
root_flow: RefCell::new(None),
document_shared_lock: None,
@ -614,7 +583,6 @@ impl LayoutThread {
nodes_from_point_response: vec![],
element_inner_text_response: String::new(),
})),
webrender_image_cache: Arc::new(RwLock::new(FnvHashMap::default())),
timer: if pref!(layout.animations.test.enabled) {
Timer::test_mode()
} else {
@ -622,7 +590,6 @@ impl LayoutThread {
},
paint_time_metrics: paint_time_metrics,
layout_query_waiting_time: Histogram::new(),
last_iframe_sizes: Default::default(),
busy,
load_webfonts_synchronously,
initial_window_size,
@ -655,7 +622,6 @@ impl LayoutThread {
fn build_layout_context<'a>(
&'a self,
guards: StylesheetGuards<'a>,
script_initiated_layout: bool,
snapshot_map: &'a SnapshotMap,
) -> LayoutContext<'a> {
let thread_local_style_context_creation_data =
@ -676,20 +642,6 @@ impl LayoutThread {
traversal_flags: TraversalFlags::empty(),
snapshot_map: snapshot_map,
},
image_cache: self.image_cache.clone(),
font_cache_thread: Mutex::new(self.font_cache_thread.clone()),
webrender_image_cache: self.webrender_image_cache.clone(),
pending_images: if script_initiated_layout {
Some(Mutex::new(vec![]))
} else {
None
},
newly_transitioning_nodes: if script_initiated_layout {
Some(Mutex::new(vec![]))
} else {
None
},
registered_painters: &self.registered_painters,
}
}
@ -862,23 +814,7 @@ impl LayoutThread {
Msg::SetFinalUrl(final_url) => {
self.url = final_url;
},
Msg::RegisterPaint(name, mut properties, painter) => {
debug!("Registering the painter");
let properties = properties
.drain(..)
.filter_map(|name| {
let id = PropertyId::parse_enabled_for_all_content(&*name).ok()?;
Some((name.clone(), id))
})
.filter(|&(_, ref id)| !id.is_shorthand())
.collect();
let registered_painter = RegisteredPainterImpl {
name: name.clone(),
properties,
painter,
};
self.registered_painters.0.insert(name, registered_painter);
},
Msg::RegisterPaint(_name, _properties, _painter) => {},
Msg::PrepareToExit(response_chan) => {
self.prepare_to_exit(response_chan);
return false;
@ -926,13 +862,6 @@ impl LayoutThread {
size: self.stylist.size_of(&mut ops),
});
// The LayoutThread has data in Persistent TLS...
reports.push(Report {
path: path![formatted_url, "layout-thread", "local-context"],
kind: ReportKind::ExplicitJemallocHeapSize,
size: malloc_size_of_persistent_local_context(&mut ops),
});
reports_chan.send(reports);
}
@ -1045,171 +974,35 @@ impl LayoutThread {
self.stylist.set_quirks_mode(quirks_mode);
}
fn try_get_layout_root<N: LayoutNode>(&self, node: N) -> Option<FlowRef> {
let result = node.mutate_layout_data()?.flow_construction_result.get();
let mut flow = match result {
ConstructionResult::Flow(mut flow, abs_descendants) => {
// Note: Assuming that the root has display 'static' (as per
// CSS Section 9.3.1). Otherwise, if it were absolutely
// positioned, it would return a reference to itself in
// `abs_descendants` and would lead to a circular reference.
// Set Root as CB for any remaining absolute descendants.
flow.set_absolute_descendants(abs_descendants);
flow
},
_ => return None,
};
FlowRef::deref_mut(&mut flow).mark_as_root();
Some(flow)
}
/// Performs layout constraint solving.
///
/// This corresponds to `Reflow()` in Gecko and `layout()` in WebKit/Blink and should be
/// benchmarked against those two. It is marked `#[inline(never)]` to aid profiling.
#[inline(never)]
fn solve_constraints(layout_root: &mut dyn Flow, layout_context: &LayoutContext) {
let _scope = layout_debug_scope!("solve_constraints");
sequential::reflow(layout_root, layout_context, RelayoutMode::Incremental);
}
/// Performs layout constraint solving in parallel.
///
/// This corresponds to `Reflow()` in Gecko and `layout()` in WebKit/Blink and should be
/// benchmarked against those two. It is marked `#[inline(never)]` to aid profiling.
#[inline(never)]
fn solve_constraints_parallel(
traversal: &rayon::ThreadPool,
layout_root: &mut dyn Flow,
profiler_metadata: Option<TimerMetadata>,
time_profiler_chan: profile_time::ProfilerChan,
layout_context: &LayoutContext,
) {
let _scope = layout_debug_scope!("solve_constraints_parallel");
// NOTE: this currently computes borders, so any pruning should separate that
// operation out.
parallel::reflow(
layout_root,
profiler_metadata,
time_profiler_chan,
layout_context,
traversal,
);
fn try_get_layout_root<N: LayoutNode>(&self, _node: N) -> Option<FlowRef> {
None
}
/// Computes the stacking-relative positions of all flows and, if the painting is dirty and the
/// reflow type need it, builds the display list.
fn compute_abs_pos_and_build_display_list(
&self,
data: &Reflow,
reflow_goal: &ReflowGoal,
document: Option<&ServoLayoutDocument>,
layout_root: &mut dyn Flow,
layout_context: &mut LayoutContext,
rw_data: &mut LayoutThreadData,
) {
let writing_mode = layout_root.base().writing_mode;
let (metadata, sender) = (self.profiler_metadata(), self.time_profiler_chan.clone());
profile(
profile_time::ProfilerCategory::LayoutDispListBuild,
metadata.clone(),
sender.clone(),
|| {
layout_root.mut_base().stacking_relative_position =
LogicalPoint::zero(writing_mode)
.to_physical(writing_mode, self.viewport_size)
.to_vector();
layout_root.mut_base().clip = data.page_clip_rect;
let traversal = ComputeStackingRelativePositions {
layout_context: layout_context,
};
traversal.traverse(layout_root);
if layout_root
.base()
.restyle_damage
.contains(ServoRestyleDamage::REPAINT) ||
rw_data.display_list.is_none()
{
if reflow_goal.needs_display_list() {
let background_color = get_root_flow_background_color(layout_root);
let mut build_state = sequential::build_display_list_for_subtree(
layout_root,
layout_context,
background_color,
data.page_clip_rect.size,
);
debug!("Done building display list.");
let root_size = {
let root_flow = layout_root.base();
if self.stylist.viewport_constraints().is_some() {
root_flow.position.size.to_physical(root_flow.writing_mode)
} else {
root_flow.overflow.scroll.size
}
};
let origin = Rect::new(Point2D::new(Au(0), Au(0)), root_size).to_layout();
build_state.root_stacking_context.bounds = origin;
build_state.root_stacking_context.overflow = origin;
if !build_state.iframe_sizes.is_empty() {
// build_state.iframe_sizes is only used here, so its okay to replace
// it with an empty vector
let iframe_sizes =
std::mem::replace(&mut build_state.iframe_sizes, vec![]);
// Collect the last frame's iframe sizes to compute any differences.
// Every frame starts with a fresh collection so that any removed
// iframes do not linger.
let last_iframe_sizes = std::mem::replace(
&mut *self.last_iframe_sizes.borrow_mut(),
HashMap::default(),
);
let mut size_messages = vec![];
for new_size in iframe_sizes {
// Only notify the constellation about existing iframes
// that have a new size, or iframes that did not previously
// exist.
if let Some(old_size) = last_iframe_sizes.get(&new_size.id) {
if *old_size != new_size.size {
size_messages.push(IFrameSizeMsg {
data: new_size,
type_: WindowSizeType::Resize,
});
}
} else {
size_messages.push(IFrameSizeMsg {
data: new_size,
type_: WindowSizeType::Initial,
});
}
self.last_iframe_sizes
.borrow_mut()
.insert(new_size.id, new_size.size);
}
if !size_messages.is_empty() {
let msg = ConstellationMsg::IFrameSizes(size_messages);
if let Err(e) = self.constellation_chan.send(msg) {
warn!("Layout resize to constellation failed ({}).", e);
}
}
}
rw_data.indexable_text = std::mem::replace(
&mut build_state.indexable_text,
IndexableText::default(),
);
rw_data.display_list = Some(build_state.to_display_list());
}
layout_root
.mut_base()
.restyle_damage
.remove(ServoRestyleDamage::REPAINT);
}
if !reflow_goal.needs_display() {
@ -1228,13 +1021,6 @@ impl LayoutThread {
let display_list = rw_data.display_list.as_mut().unwrap();
if self.dump_display_list {
display_list.print();
}
if self.dump_display_list_json {
println!("{}", serde_json::to_string_pretty(&display_list).unwrap());
}
debug!("Layout done!");
// TODO: Avoid the temporary conversion and build webrender sc/dl directly!
@ -1500,7 +1286,7 @@ impl LayoutThread {
self.stylist.flush(&guards, Some(element), Some(&map));
// Create a layout context for use throughout the following passes.
let mut layout_context = self.build_layout_context(guards.clone(), true, &map);
let mut layout_context = self.build_layout_context(guards.clone(), &map);
let (thread_pool, num_threads) = if self.parallel_flag {
(
@ -1579,12 +1365,9 @@ impl LayoutThread {
if let Some(mut root_flow) = self.root_flow.borrow().clone() {
self.perform_post_style_recalc_layout_passes(
&mut root_flow,
&data.reflow_info,
&data.reflow_goal,
Some(&document),
&mut rw_data,
&mut layout_context,
FxHashSet::default(),
);
}
@ -1593,7 +1376,6 @@ impl LayoutThread {
&data.reflow_goal,
&mut *rw_data,
&mut layout_context,
data.result.borrow_mut().as_mut().unwrap(),
);
}
@ -1602,32 +1384,14 @@ impl LayoutThread {
reflow_goal: &ReflowGoal,
rw_data: &mut LayoutThreadData,
context: &mut LayoutContext,
reflow_result: &mut ReflowComplete,
) {
let pending_images = match context.pending_images {
Some(ref pending) => std::mem::replace(&mut *pending.lock().unwrap(), vec![]),
None => vec![],
};
reflow_result.pending_images = pending_images;
let newly_transitioning_nodes = match context.newly_transitioning_nodes {
Some(ref nodes) => std::mem::replace(&mut *nodes.lock().unwrap(), vec![]),
None => vec![],
};
reflow_result.newly_transitioning_nodes = newly_transitioning_nodes;
let mut root_flow = match self.root_flow.borrow().clone() {
Some(root_flow) => root_flow,
None => return,
};
let root_flow = FlowRef::deref_mut(&mut root_flow);
match *reflow_goal {
ReflowGoal::LayoutQuery(ref querymsg, _) => match querymsg {
&QueryMsg::ContentBoxQuery(node) => {
rw_data.content_box_response = process_content_box_request(node, root_flow);
rw_data.content_box_response = process_content_box_request(node);
},
&QueryMsg::ContentBoxesQuery(node) => {
rw_data.content_boxes_response = process_content_boxes_request(node, root_flow);
rw_data.content_boxes_response = process_content_boxes_request(node);
},
&QueryMsg::TextIndexQuery(node, point_in_node) => {
let point_in_node = Point2D::new(
@ -1638,11 +1402,11 @@ impl LayoutThread {
TextIndexResponse(rw_data.indexable_text.text_index(node, point_in_node));
},
&QueryMsg::NodeGeometryQuery(node) => {
rw_data.client_rect_response = process_node_geometry_request(node, root_flow);
rw_data.client_rect_response = process_node_geometry_request(node);
},
&QueryMsg::NodeScrollGeometryQuery(node) => {
rw_data.scroll_area_response =
process_node_scroll_area_request(node, root_flow);
process_node_scroll_area_request(node);
},
&QueryMsg::NodeScrollIdQuery(node) => {
let node = unsafe { ServoLayoutNode::new(&node) };
@ -1652,10 +1416,10 @@ impl LayoutThread {
&QueryMsg::ResolvedStyleQuery(node, ref pseudo, ref property) => {
let node = unsafe { ServoLayoutNode::new(&node) };
rw_data.resolved_style_response =
process_resolved_style_request(context, node, pseudo, property, root_flow);
process_resolved_style_request(context, node, pseudo, property);
},
&QueryMsg::OffsetParentQuery(node) => {
rw_data.offset_parent_response = process_offset_parent_query(node, root_flow);
rw_data.offset_parent_response = process_offset_parent_query(node);
},
&QueryMsg::StyleQuery(node) => {
let node = unsafe { ServoLayoutNode::new(&node) };
@ -1737,203 +1501,62 @@ impl LayoutThread {
}
if let Some(mut root_flow) = self.root_flow.borrow().clone() {
let reflow_info = Reflow {
page_clip_rect: Rect::max_rect(),
};
// Unwrap here should not panic since self.root_flow is only ever set to Some(_)
// in handle_reflow() where self.document_shared_lock is as well.
let author_shared_lock = self.document_shared_lock.clone().unwrap();
let author_guard = author_shared_lock.read();
let ua_or_user_guard = UA_STYLESHEETS.shared_lock.read();
let guards = StylesheetGuards {
let _guards = StylesheetGuards {
author: &author_guard,
ua_or_user: &ua_or_user_guard,
};
let snapshots = SnapshotMap::new();
let mut layout_context = self.build_layout_context(guards, false, &snapshots);
let invalid_nodes = {
// Perform an abbreviated style recalc that operates without access to the DOM.
let animations = self.running_animations.read();
profile(
profile_time::ProfilerCategory::LayoutStyleRecalc,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| {
animation::recalc_style_for_animations::<ServoLayoutElement>(
&layout_context,
FlowRef::deref_mut(&mut root_flow),
&animations,
)
},
)
};
self.perform_post_style_recalc_layout_passes(
&mut root_flow,
&reflow_info,
&ReflowGoal::TickAnimations,
None,
&mut *rw_data,
&mut layout_context,
invalid_nodes,
);
assert!(layout_context.pending_images.is_none());
assert!(layout_context.newly_transitioning_nodes.is_none());
}
}
fn perform_post_style_recalc_layout_passes(
&self,
root_flow: &mut FlowRef,
data: &Reflow,
reflow_goal: &ReflowGoal,
document: Option<&ServoLayoutDocument>,
rw_data: &mut LayoutThreadData,
context: &mut LayoutContext,
invalid_nodes: FxHashSet<OpaqueNode>,
) {
{
let mut newly_transitioning_nodes = context
.newly_transitioning_nodes
.as_ref()
.map(|nodes| nodes.lock().unwrap());
let newly_transitioning_nodes =
newly_transitioning_nodes.as_mut().map(|nodes| &mut **nodes);
// Kick off animations if any were triggered, expire completed ones.
animation::update_animation_state::<ServoLayoutElement>(
&self.constellation_chan,
&self.script_chan,
&mut *self.running_animations.write(),
&mut *self.expired_animations.write(),
invalid_nodes,
newly_transitioning_nodes,
&self.new_animations_receiver,
self.id,
&self.timer,
);
}
profile(
profile_time::ProfilerCategory::LayoutRestyleDamagePropagation,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| {
// Call `compute_layout_damage` even in non-incremental mode, because it sets flags
// that are needed in both incremental and non-incremental traversals.
let damage = FlowRef::deref_mut(root_flow).compute_layout_damage();
if self.nonincremental_layout ||
damage.contains(SpecialRestyleDamage::REFLOW_ENTIRE_DOCUMENT)
{
FlowRef::deref_mut(root_flow).reflow_entire_document()
}
},
);
if self.trace_layout {
layout_debug::begin_trace(root_flow.clone());
}
// Resolve generated content.
profile(
profile_time::ProfilerCategory::LayoutGeneratedContent,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| sequential::resolve_generated_content(FlowRef::deref_mut(root_flow), &context),
);
// Guess float placement.
profile(
profile_time::ProfilerCategory::LayoutFloatPlacementSpeculation,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| sequential::guess_float_placement(FlowRef::deref_mut(root_flow)),
);
// Perform the primary layout passes over the flow tree to compute the locations of all
// the boxes.
if root_flow
.base()
.restyle_damage
.intersects(ServoRestyleDamage::REFLOW | ServoRestyleDamage::REFLOW_OUT_OF_FLOW)
{
profile(
profile_time::ProfilerCategory::LayoutMain,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| {
let profiler_metadata = self.profiler_metadata();
let thread_pool = if self.parallel_flag {
STYLE_THREAD_POOL.style_thread_pool.as_ref()
} else {
None
};
if let Some(pool) = thread_pool {
// Parallel mode.
LayoutThread::solve_constraints_parallel(
pool,
FlowRef::deref_mut(root_flow),
profiler_metadata,
self.time_profiler_chan.clone(),
&*context,
);
} else {
//Sequential mode
LayoutThread::solve_constraints(FlowRef::deref_mut(root_flow), &context)
}
},
);
}
profile(
profile_time::ProfilerCategory::LayoutStoreOverflow,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| {
sequential::store_overflow(context, FlowRef::deref_mut(root_flow) as &mut dyn Flow);
},
|| {},
);
self.perform_post_main_layout_passes(
data,
root_flow,
reflow_goal,
document,
rw_data,
context,
);
}
fn perform_post_main_layout_passes(
&self,
data: &Reflow,
mut root_flow: &mut FlowRef,
reflow_goal: &ReflowGoal,
document: Option<&ServoLayoutDocument>,
rw_data: &mut LayoutThreadData,
layout_context: &mut LayoutContext,
) {
// Build the display list if necessary, and send it to the painter.
self.compute_abs_pos_and_build_display_list(
data,
reflow_goal,
document,
FlowRef::deref_mut(&mut root_flow),
&mut *layout_context,
rw_data,
);
if self.trace_layout {
layout_debug::end_trace(self.generation.get());
}
if self.dump_flow_tree {
root_flow.print("Post layout flow tree".to_owned());
}
self.generation.set(self.generation.get() + 1);
}
@ -1945,10 +1568,6 @@ impl LayoutThread {
ServoRestyleDamage::REFLOW |
ServoRestyleDamage::REPOSITION,
);
for child in flow.mut_base().child_iter_mut() {
LayoutThread::reflow_all_nodes(child);
}
}
/// Returns profiling information which is passed to the time profiler.
@ -1975,51 +1594,6 @@ impl ProfilerMetadataFactory for LayoutThread {
}
}
// The default computed value for background-color is transparent (see
// http://dev.w3.org/csswg/css-backgrounds/#background-color). However, we
// need to propagate the background color from the root HTML/Body
// element (http://dev.w3.org/csswg/css-backgrounds/#special-backgrounds) if
// it is non-transparent. The phrase in the spec "If the canvas background
// is not opaque, what shows through is UA-dependent." is handled by rust-layers
// clearing the frame buffer to white. This ensures that setting a background
// color on an iframe element, while the iframe content itself has a default
// transparent background color is handled correctly.
fn get_root_flow_background_color(flow: &mut dyn Flow) -> webrender_api::ColorF {
let transparent = webrender_api::ColorF {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
};
if !flow.is_block_like() {
return transparent;
}
let block_flow = flow.as_mut_block();
let kid = match block_flow.base.children.iter_mut().next() {
None => return transparent,
Some(kid) => kid,
};
if !kid.is_block_like() {
return transparent;
}
let kid_block_flow = kid.as_block();
let color = kid_block_flow.fragment.style.resolve_color(
kid_block_flow
.fragment
.style
.get_background()
.background_color,
);
webrender_api::ColorF::new(
color.red_f32(),
color.green_f32(),
color.blue_f32(),
color.alpha_f32(),
)
}
fn get_ua_stylesheets() -> Result<UserAgentStylesheets, &'static str> {
fn parse_ua_stylesheet(
shared_lock: &SharedRwLock,
@ -2144,8 +1718,6 @@ impl Painter for RegisteredPainterImpl {
}
}
impl RegisteredPainter for RegisteredPainterImpl {}
struct RegisteredPaintersImpl(FnvHashMap<Atom, RegisteredPainterImpl>);
impl RegisteredSpeculativePainters for RegisteredPaintersImpl {
@ -2155,11 +1727,3 @@ impl RegisteredSpeculativePainters for RegisteredPaintersImpl {
.map(|painter| painter as &dyn RegisteredSpeculativePainter)
}
}
impl RegisteredPainters for RegisteredPaintersImpl {
fn get(&self, name: &Atom) -> Option<&dyn RegisteredPainter> {
self.0
.get(&name)
.map(|painter| painter as &dyn RegisteredPainter)
}
}