mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-25 19:25:43 +00:00
16beecaaf1
CSS-gradients should not only cover the content of an element but also the padding (but not the border). <!-- Please describe your changes on the following line: --> Thanks to @atouchet for catching this. <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes fix #17387 (github issue number if applicable). <!-- Either: --> - [x] These changes do not require tests because gradients are hard to test automatically but you can use [this gist](https://gist.github.com/pyfisch/fa263c8dfc57e5812fe8a4869ad61513) as a manual test. <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: 7a80192a820848f5ac04b9f4ffe2e860476579ba --HG-- extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear extra : subtree_revision : 69259da0089e03bbc881a356e7e2e13a1275272f
2918 lines
142 KiB
Rust
2918 lines
142 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||
|
||
//! Builds display lists from flows and fragments.
|
||
//!
|
||
//! Other browser engines sometimes call this "painting", but it is more accurately called display
|
||
//! list building, as the actual painting does not happen here—only deciding *what* we're going to
|
||
//! paint.
|
||
|
||
#![deny(unsafe_code)]
|
||
|
||
use app_units::{AU_PER_PX, Au};
|
||
use block::{BlockFlow, BlockStackingContextType};
|
||
use canvas_traits::{CanvasData, CanvasMsg, FromLayoutMsg};
|
||
use context::LayoutContext;
|
||
use euclid::{Transform3D, Point2D, Vector2D, Rect, SideOffsets2D, Size2D, TypedSize2D};
|
||
use flex::FlexFlow;
|
||
use flow::{BaseFlow, Flow, IS_ABSOLUTELY_POSITIONED};
|
||
use flow_ref::FlowRef;
|
||
use fragment::{CoordinateSystem, Fragment, ImageFragmentInfo, ScannedTextFragmentInfo};
|
||
use fragment::{SpecificFragmentInfo, TruncatedFragmentInfo};
|
||
use gfx::display_list;
|
||
use gfx::display_list::{BLUR_INFLATION_FACTOR, BaseDisplayItem, BorderDetails};
|
||
use gfx::display_list::{BorderDisplayItem, ImageBorder, NormalBorder};
|
||
use gfx::display_list::{BorderRadii, BoxShadowClipMode, BoxShadowDisplayItem, ClippingRegion};
|
||
use gfx::display_list::{DisplayItem, DisplayItemMetadata, DisplayList, DisplayListSection};
|
||
use gfx::display_list::{GradientDisplayItem, RadialGradientDisplayItem, IframeDisplayItem, ImageDisplayItem};
|
||
use gfx::display_list::{LineDisplayItem, OpaqueNode};
|
||
use gfx::display_list::{SolidColorDisplayItem, ScrollRoot, StackingContext, StackingContextType};
|
||
use gfx::display_list::{TextDisplayItem, TextOrientation, WebGLDisplayItem, WebRenderImageInfo};
|
||
use gfx_traits::{combine_id_with_fragment_type, FragmentType, StackingContextId};
|
||
use inline::{FIRST_FRAGMENT_OF_ELEMENT, InlineFlow, LAST_FRAGMENT_OF_ELEMENT};
|
||
use ipc_channel::ipc;
|
||
use list_item::ListItemFlow;
|
||
use model::{self, MaybeAuto};
|
||
use msg::constellation_msg::BrowsingContextId;
|
||
use net_traits::image::base::PixelFormat;
|
||
use net_traits::image_cache::UsePlaceholder;
|
||
use range::Range;
|
||
use script_layout_interface::wrapper_traits::PseudoElementType;
|
||
use servo_config::opts;
|
||
use servo_geometry::max_rect;
|
||
use servo_url::ServoUrl;
|
||
use std::{cmp, f32};
|
||
use std::collections::HashMap;
|
||
use std::default::Default;
|
||
use std::mem;
|
||
use std::sync::Arc;
|
||
use style::computed_values::{background_attachment, background_clip, background_origin};
|
||
use style::computed_values::{background_repeat, border_style, cursor};
|
||
use style::computed_values::{image_rendering, overflow_x, pointer_events, position, visibility};
|
||
use style::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode};
|
||
use style::properties::{self, ServoComputedValues};
|
||
use style::properties::longhands::border_image_repeat::computed_value::RepeatKeyword;
|
||
use style::properties::style_structs;
|
||
use style::servo::restyle_damage::REPAINT;
|
||
use style::values::{Either, RGBA};
|
||
use style::values::computed::{Gradient, GradientItem, LengthOrPercentage};
|
||
use style::values::computed::{LengthOrPercentageOrAuto, NumberOrPercentage, Position, Shadow};
|
||
use style::values::computed::image::{EndingShape, LineDirection};
|
||
use style::values::generics::background::BackgroundSize;
|
||
use style::values::generics::effects::Filter;
|
||
use style::values::generics::image::{Circle, Ellipse, EndingShape as GenericEndingShape};
|
||
use style::values::generics::image::{GradientItem as GenericGradientItem, GradientKind};
|
||
use style::values::generics::image::{Image, ShapeExtent};
|
||
use style::values::generics::image::PaintWorklet;
|
||
use style::values::specified::length::Percentage;
|
||
use style::values::specified::position::{X, Y};
|
||
use style_traits::CSSPixel;
|
||
use style_traits::cursor::Cursor;
|
||
use table_cell::CollapsedBordersForCell;
|
||
use webrender_helpers::{ToMixBlendMode, ToTransformStyle};
|
||
use webrender_traits::{ColorF, ClipId, GradientStop, RepeatMode, ScrollPolicy, TransformStyle};
|
||
|
||
trait ResolvePercentage {
|
||
fn resolve(&self, length: u32) -> u32;
|
||
}
|
||
|
||
impl ResolvePercentage for NumberOrPercentage {
|
||
fn resolve(&self, length: u32) -> u32 {
|
||
match *self {
|
||
NumberOrPercentage::Percentage(p) => {
|
||
(p.0 * length as f32).round() as u32
|
||
}
|
||
NumberOrPercentage::Number(n) => {
|
||
n.round() as u32
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
fn convert_repeat_mode(from: RepeatKeyword) -> RepeatMode {
|
||
match from {
|
||
RepeatKeyword::Stretch => RepeatMode::Stretch,
|
||
RepeatKeyword::Repeat => RepeatMode::Repeat,
|
||
RepeatKeyword::Round => RepeatMode::Round,
|
||
RepeatKeyword::Space => RepeatMode::Space,
|
||
}
|
||
}
|
||
|
||
fn establishes_containing_block_for_absolute(positioning: position::T) -> bool {
|
||
match positioning {
|
||
position::T::absolute | position::T::relative | position::T::fixed => true,
|
||
_ => false,
|
||
}
|
||
}
|
||
|
||
trait RgbColor {
|
||
fn rgb(r: u8, g: u8, b: u8) -> Self;
|
||
}
|
||
|
||
impl RgbColor for ColorF {
|
||
fn rgb(r: u8, g: u8, b: u8) -> Self {
|
||
ColorF {
|
||
r: (r as f32) / (255.0 as f32),
|
||
g: (g as f32) / (255.0 as f32),
|
||
b: (b as f32) / (255.0 as f32),
|
||
a: 1.0 as f32
|
||
}
|
||
}
|
||
}
|
||
|
||
static THREAD_TINT_COLORS: [ColorF; 8] = [
|
||
ColorF { r: 6.0 / 255.0, g: 153.0 / 255.0, b: 198.0 / 255.0, a: 0.7 },
|
||
ColorF { r: 255.0 / 255.0, g: 212.0 / 255.0, b: 83.0 / 255.0, a: 0.7 },
|
||
ColorF { r: 116.0 / 255.0, g: 29.0 / 255.0, b: 109.0 / 255.0, a: 0.7 },
|
||
ColorF { r: 204.0 / 255.0, g: 158.0 / 255.0, b: 199.0 / 255.0, a: 0.7 },
|
||
ColorF { r: 242.0 / 255.0, g: 46.0 / 255.0, b: 121.0 / 255.0, a: 0.7 },
|
||
ColorF { r: 116.0 / 255.0, g: 203.0 / 255.0, b: 196.0 / 255.0, a: 0.7 },
|
||
ColorF { r: 255.0 / 255.0, g: 249.0 / 255.0, b: 201.0 / 255.0, a: 0.7 },
|
||
ColorF { r: 137.0 / 255.0, g: 196.0 / 255.0, b: 78.0 / 255.0, a: 0.7 },
|
||
];
|
||
|
||
fn get_cyclic<T>(arr: &[T], index: usize) -> &T {
|
||
&arr[index % arr.len()]
|
||
}
|
||
|
||
#[derive(Debug)]
|
||
struct StackingContextInfo {
|
||
children: Vec<StackingContext>,
|
||
scroll_roots: Vec<ScrollRoot>,
|
||
}
|
||
|
||
impl StackingContextInfo {
|
||
fn new() -> StackingContextInfo {
|
||
StackingContextInfo {
|
||
children: Vec::new(),
|
||
scroll_roots: Vec::new(),
|
||
}
|
||
}
|
||
|
||
fn take_children(&mut self) -> Vec<StackingContext> {
|
||
mem::replace(&mut self.children, Vec::new())
|
||
}
|
||
}
|
||
|
||
pub struct DisplayListBuildState<'a> {
|
||
pub layout_context: &'a LayoutContext<'a>,
|
||
pub root_stacking_context: StackingContext,
|
||
pub items: HashMap<StackingContextId, Vec<DisplayItem>>,
|
||
stacking_context_info: HashMap<StackingContextId, StackingContextInfo>,
|
||
pub scroll_root_parents: HashMap<ClipId, ClipId>,
|
||
pub processing_scroll_root_element: bool,
|
||
|
||
/// The current stacking context id, used to keep track of state when building.
|
||
/// recursively building and processing the display list.
|
||
pub current_stacking_context_id: StackingContextId,
|
||
|
||
/// The current scroll root id, used to keep track of state when
|
||
/// recursively building and processing the display list.
|
||
pub current_scroll_root_id: ClipId,
|
||
|
||
/// The scroll root id of the first ancestor which defines a containing block.
|
||
/// This is necessary because absolutely positioned items should be clipped
|
||
/// by their containing block's scroll root.
|
||
pub containing_block_scroll_root_id: ClipId,
|
||
|
||
/// Vector containing iframe sizes, used to inform the constellation about
|
||
/// new iframe sizes
|
||
pub iframe_sizes: Vec<(BrowsingContextId, TypedSize2D<f32, CSSPixel>)>,
|
||
|
||
/// A stack of clips used to cull display list entries that are outside the
|
||
/// rendered region.
|
||
pub clip_stack: Vec<Rect<Au>>,
|
||
|
||
/// A stack of clips used to cull display list entries that are outside the
|
||
/// rendered region, but only collected at containing block boundaries.
|
||
pub containing_block_clip_stack: Vec<Rect<Au>>,
|
||
|
||
/// The current transform style of the stacking context.
|
||
current_transform_style: TransformStyle,
|
||
}
|
||
|
||
impl<'a> DisplayListBuildState<'a> {
|
||
pub fn new(layout_context: &'a LayoutContext) -> DisplayListBuildState<'a> {
|
||
DisplayListBuildState {
|
||
layout_context: layout_context,
|
||
root_stacking_context: StackingContext::root(layout_context.id),
|
||
items: HashMap::new(),
|
||
stacking_context_info: HashMap::new(),
|
||
scroll_root_parents: HashMap::new(),
|
||
processing_scroll_root_element: false,
|
||
current_stacking_context_id: StackingContextId::root(),
|
||
current_scroll_root_id: layout_context.id.root_scroll_node(),
|
||
containing_block_scroll_root_id: layout_context.id.root_scroll_node(),
|
||
iframe_sizes: Vec::new(),
|
||
clip_stack: Vec::new(),
|
||
containing_block_clip_stack: Vec::new(),
|
||
current_transform_style: TransformStyle::Flat,
|
||
}
|
||
}
|
||
|
||
fn add_display_item(&mut self, display_item: DisplayItem) {
|
||
let items = self.items.entry(display_item.stacking_context_id()).or_insert(Vec::new());
|
||
items.push(display_item);
|
||
}
|
||
|
||
fn add_stacking_context(&mut self,
|
||
parent_id: StackingContextId,
|
||
stacking_context: StackingContext) {
|
||
self.current_transform_style = stacking_context.transform_style;
|
||
let info = self.stacking_context_info
|
||
.entry(parent_id)
|
||
.or_insert(StackingContextInfo::new());
|
||
info.children.push(stacking_context);
|
||
}
|
||
|
||
fn has_scroll_root(&mut self, id: ClipId) -> bool {
|
||
self.scroll_root_parents.contains_key(&id)
|
||
}
|
||
|
||
fn add_scroll_root(&mut self, scroll_root: ScrollRoot, stacking_context_id: StackingContextId) {
|
||
self.scroll_root_parents.insert(scroll_root.id, scroll_root.parent_id);
|
||
let info = self.stacking_context_info
|
||
.entry(stacking_context_id)
|
||
.or_insert(StackingContextInfo::new());
|
||
info.scroll_roots.push(scroll_root);
|
||
}
|
||
|
||
fn parent_scroll_root_id(&self, scroll_root_id: ClipId) -> ClipId {
|
||
if scroll_root_id.is_root_scroll_node() {
|
||
return scroll_root_id;
|
||
}
|
||
|
||
debug_assert!(self.scroll_root_parents.contains_key(&scroll_root_id));
|
||
*self.scroll_root_parents.get(&scroll_root_id).unwrap()
|
||
}
|
||
|
||
fn create_base_display_item(&self,
|
||
bounds: &Rect<Au>,
|
||
clip: &ClippingRegion,
|
||
node: OpaqueNode,
|
||
cursor: Option<Cursor>,
|
||
section: DisplayListSection)
|
||
-> BaseDisplayItem {
|
||
let scroll_root_id = if (section == DisplayListSection::BackgroundAndBorders ||
|
||
section == DisplayListSection::BlockBackgroundsAndBorders) &&
|
||
self.processing_scroll_root_element {
|
||
self.parent_scroll_root_id(self.current_scroll_root_id)
|
||
} else {
|
||
self.current_scroll_root_id
|
||
};
|
||
|
||
BaseDisplayItem::new(&bounds,
|
||
DisplayItemMetadata {
|
||
node: node,
|
||
pointing: cursor,
|
||
},
|
||
clip,
|
||
section,
|
||
self.current_stacking_context_id,
|
||
scroll_root_id)
|
||
}
|
||
|
||
pub fn to_display_list(mut self) -> DisplayList {
|
||
let mut list = Vec::new();
|
||
let root_context = mem::replace(&mut self.root_stacking_context,
|
||
StackingContext::root(self.layout_context.id));
|
||
|
||
self.to_display_list_for_stacking_context(&mut list, root_context);
|
||
|
||
DisplayList {
|
||
list: list,
|
||
}
|
||
}
|
||
|
||
fn to_display_list_for_stacking_context(&mut self,
|
||
list: &mut Vec<DisplayItem>,
|
||
stacking_context: StackingContext) {
|
||
let mut child_items = self.items.remove(&stacking_context.id).unwrap_or(Vec::new());
|
||
child_items.sort_by(|a, b| a.base().section.cmp(&b.base().section));
|
||
child_items.reverse();
|
||
|
||
let mut info = self.stacking_context_info.remove(&stacking_context.id)
|
||
.unwrap_or_else(StackingContextInfo::new);
|
||
|
||
info.children.sort();
|
||
|
||
let pipeline_id = self.layout_context.id;
|
||
if stacking_context.context_type != StackingContextType::Real {
|
||
list.extend(info.scroll_roots.into_iter().map(|root| root.to_define_item(pipeline_id)));
|
||
self.to_display_list_for_items(list, child_items, info.children);
|
||
} else {
|
||
let (push_item, pop_item) = stacking_context.to_display_list_items(pipeline_id);
|
||
list.push(push_item);
|
||
list.extend(info.scroll_roots.into_iter().map(|root| root.to_define_item(pipeline_id)));
|
||
self.to_display_list_for_items(list, child_items, info.children);
|
||
list.push(pop_item);
|
||
}
|
||
}
|
||
|
||
fn to_display_list_for_items(&mut self,
|
||
list: &mut Vec<DisplayItem>,
|
||
mut child_items: Vec<DisplayItem>,
|
||
child_stacking_contexts: Vec<StackingContext>) {
|
||
// Properly order display items that make up a stacking context. "Steps" here
|
||
// refer to the steps in CSS 2.1 Appendix E.
|
||
// Steps 1 and 2: Borders and background for the root.
|
||
while child_items.last().map_or(false,
|
||
|child| child.section() == DisplayListSection::BackgroundAndBorders) {
|
||
list.push(child_items.pop().unwrap());
|
||
}
|
||
|
||
// Step 3: Positioned descendants with negative z-indices.
|
||
let mut child_stacking_contexts = child_stacking_contexts.into_iter().peekable();
|
||
while child_stacking_contexts.peek().map_or(false, |child| child.z_index < 0) {
|
||
let context = child_stacking_contexts.next().unwrap();
|
||
self.to_display_list_for_stacking_context(list, context);
|
||
}
|
||
|
||
// Step 4: Block backgrounds and borders.
|
||
while child_items.last().map_or(false,
|
||
|child| child.section() == DisplayListSection::BlockBackgroundsAndBorders) {
|
||
list.push(child_items.pop().unwrap());
|
||
}
|
||
|
||
// Step 5: Floats.
|
||
while child_stacking_contexts.peek().map_or(false,
|
||
|child| child.context_type == StackingContextType::PseudoFloat) {
|
||
let context = child_stacking_contexts.next().unwrap();
|
||
self.to_display_list_for_stacking_context(list, context);
|
||
}
|
||
|
||
// Step 6 & 7: Content and inlines that generate stacking contexts.
|
||
while child_items.last().map_or(false,
|
||
|child| child.section() == DisplayListSection::Content) {
|
||
list.push(child_items.pop().unwrap());
|
||
}
|
||
|
||
// Step 8 & 9: Positioned descendants with nonnegative, numeric z-indices.
|
||
for child in child_stacking_contexts {
|
||
self.to_display_list_for_stacking_context(list, child);
|
||
}
|
||
|
||
// Step 10: Outlines.
|
||
for item in child_items.drain(..) {
|
||
list.push(item);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// The logical width of an insertion point: at the moment, a one-pixel-wide line.
|
||
const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(1 * AU_PER_PX);
|
||
|
||
pub enum IdType {
|
||
StackingContext,
|
||
OverflowClip,
|
||
CSSClip,
|
||
}
|
||
|
||
pub trait FragmentDisplayListBuilding {
|
||
/// Adds the display items necessary to paint the background of this fragment to the display
|
||
/// list if necessary.
|
||
fn build_display_list_for_background_if_applicable(&self,
|
||
state: &mut DisplayListBuildState,
|
||
style: &ServoComputedValues,
|
||
display_list_section: DisplayListSection,
|
||
absolute_bounds: &Rect<Au>);
|
||
|
||
/// Computes the background size for an image with the given background area according to the
|
||
/// rules in CSS-BACKGROUNDS § 3.9.
|
||
fn compute_background_image_size(&self,
|
||
style: &ServoComputedValues,
|
||
bounds: &Rect<Au>,
|
||
image: &WebRenderImageInfo, index: usize)
|
||
-> Size2D<Au>;
|
||
|
||
/// Adds the display items necessary to paint the background image of this fragment to the
|
||
/// appropriate section of the display list.
|
||
fn build_display_list_for_background_image(&self,
|
||
state: &mut DisplayListBuildState,
|
||
style: &ServoComputedValues,
|
||
display_list_section: DisplayListSection,
|
||
absolute_bounds: &Rect<Au>,
|
||
clip: &ClippingRegion,
|
||
image_url: &ServoUrl,
|
||
background_index: usize);
|
||
|
||
/// Adds the display items necessary to paint a webrender image of this fragment to the
|
||
/// appropriate section of the display list.
|
||
fn build_display_list_for_webrender_image(&self,
|
||
state: &mut DisplayListBuildState,
|
||
style: &ServoComputedValues,
|
||
display_list_section: DisplayListSection,
|
||
absolute_bounds: &Rect<Au>,
|
||
clip: &ClippingRegion,
|
||
webrender_image: WebRenderImageInfo,
|
||
index: usize);
|
||
|
||
/// Adds the display items necessary to paint the background image created by this fragment's
|
||
/// worklet to the appropriate section of the display list.
|
||
fn build_display_list_for_background_paint_worklet(&self,
|
||
state: &mut DisplayListBuildState,
|
||
style: &ServoComputedValues,
|
||
display_list_section: DisplayListSection,
|
||
absolute_bounds: &Rect<Au>,
|
||
clip: &ClippingRegion,
|
||
paint_worklet: &PaintWorklet,
|
||
index: usize);
|
||
|
||
fn convert_linear_gradient(&self,
|
||
bounds: &Rect<Au>,
|
||
stops: &[GradientItem],
|
||
direction: &LineDirection,
|
||
repeating: bool)
|
||
-> display_list::Gradient;
|
||
|
||
fn convert_radial_gradient(&self,
|
||
bounds: &Rect<Au>,
|
||
stops: &[GradientItem],
|
||
shape: &EndingShape,
|
||
center: &Position,
|
||
repeating: bool)
|
||
-> display_list::RadialGradient;
|
||
|
||
/// Adds the display items necessary to paint the background linear gradient of this fragment
|
||
/// to the appropriate section of the display list.
|
||
fn build_display_list_for_background_gradient(&self,
|
||
state: &mut DisplayListBuildState,
|
||
display_list_section: DisplayListSection,
|
||
absolute_bounds: &Rect<Au>,
|
||
clip_bounds: &Rect<Au>,
|
||
clip: &ClippingRegion,
|
||
gradient: &Gradient,
|
||
style: &ServoComputedValues);
|
||
|
||
/// Adds the display items necessary to paint the borders of this fragment to a display list if
|
||
/// necessary.
|
||
fn build_display_list_for_borders_if_applicable(
|
||
&self,
|
||
state: &mut DisplayListBuildState,
|
||
style: &ServoComputedValues,
|
||
border_painting_mode: BorderPaintingMode,
|
||
bounds: &Rect<Au>,
|
||
display_list_section: DisplayListSection,
|
||
clip: &Rect<Au>);
|
||
|
||
/// Adds the display items necessary to paint the outline of this fragment to the display list
|
||
/// if necessary.
|
||
fn build_display_list_for_outline_if_applicable(&self,
|
||
state: &mut DisplayListBuildState,
|
||
style: &ServoComputedValues,
|
||
bounds: &Rect<Au>,
|
||
clip: &Rect<Au>);
|
||
|
||
/// Adds the display items necessary to paint the box shadow of this fragment to the display
|
||
/// list if necessary.
|
||
fn build_display_list_for_box_shadow_if_applicable(&self,
|
||
state: &mut DisplayListBuildState,
|
||
style: &ServoComputedValues,
|
||
display_list_section: DisplayListSection,
|
||
absolute_bounds: &Rect<Au>,
|
||
clip: &Rect<Au>);
|
||
|
||
/// Adds display items necessary to draw debug boxes around a scanned text fragment.
|
||
fn build_debug_borders_around_text_fragments(&self,
|
||
state: &mut DisplayListBuildState,
|
||
style: &ServoComputedValues,
|
||
stacking_relative_border_box: &Rect<Au>,
|
||
stacking_relative_content_box: &Rect<Au>,
|
||
text_fragment: &ScannedTextFragmentInfo,
|
||
clip: &Rect<Au>);
|
||
|
||
/// Adds display items necessary to draw debug boxes around this fragment.
|
||
fn build_debug_borders_around_fragment(&self,
|
||
state: &mut DisplayListBuildState,
|
||
stacking_relative_border_box: &Rect<Au>,
|
||
clip: &Rect<Au>);
|
||
|
||
/// Adds the display items for this fragment to the given display list.
|
||
///
|
||
/// Arguments:
|
||
///
|
||
/// * `state`: The display building state, including the display list currently
|
||
/// under construction and other metadata useful for constructing it.
|
||
/// * `dirty`: The dirty rectangle in the coordinate system of the owning flow.
|
||
/// * `stacking_relative_flow_origin`: Position of the origin of the owning flow with respect
|
||
/// to its nearest ancestor stacking context.
|
||
/// * `relative_containing_block_size`: The size of the containing block that
|
||
/// `position: relative` makes use of.
|
||
/// * `clip`: The region to clip the display items to.
|
||
fn build_display_list(&mut self,
|
||
state: &mut DisplayListBuildState,
|
||
stacking_relative_flow_origin: &Vector2D<Au>,
|
||
relative_containing_block_size: &LogicalSize<Au>,
|
||
relative_containing_block_mode: WritingMode,
|
||
border_painting_mode: BorderPaintingMode,
|
||
display_list_section: DisplayListSection,
|
||
clip: &Rect<Au>);
|
||
|
||
/// Builds the display items necessary to paint the selection and/or caret for this fragment,
|
||
/// if any.
|
||
fn build_display_items_for_selection_if_necessary(&self,
|
||
state: &mut DisplayListBuildState,
|
||
stacking_relative_border_box: &Rect<Au>,
|
||
display_list_section: DisplayListSection,
|
||
clip: &Rect<Au>);
|
||
|
||
/// Creates the text display item for one text fragment. This can be called multiple times for
|
||
/// one fragment if there are text shadows.
|
||
///
|
||
/// `text_shadow` will be `Some` if this is rendering a shadow.
|
||
fn build_display_list_for_text_fragment(&self,
|
||
state: &mut DisplayListBuildState,
|
||
text_fragment: &ScannedTextFragmentInfo,
|
||
stacking_relative_content_box: &Rect<Au>,
|
||
text_shadow: Option<&Shadow>,
|
||
clip: &Rect<Au>);
|
||
|
||
/// Creates the display item for a text decoration: underline, overline, or line-through.
|
||
fn build_display_list_for_text_decoration(&self,
|
||
state: &mut DisplayListBuildState,
|
||
color: &RGBA,
|
||
stacking_relative_box: &LogicalRect<Au>,
|
||
clip: &Rect<Au>,
|
||
blur_radius: Au);
|
||
|
||
/// A helper method that `build_display_list` calls to create per-fragment-type display items.
|
||
fn build_fragment_type_specific_display_items(&mut self,
|
||
state: &mut DisplayListBuildState,
|
||
stacking_relative_border_box: &Rect<Au>,
|
||
clip: &Rect<Au>);
|
||
|
||
/// Creates a stacking context for associated fragment.
|
||
fn create_stacking_context(&self,
|
||
id: StackingContextId,
|
||
base_flow: &BaseFlow,
|
||
scroll_policy: ScrollPolicy,
|
||
mode: StackingContextCreationMode,
|
||
parent_scroll_id: ClipId)
|
||
-> StackingContext;
|
||
|
||
|
||
/// The id of stacking context this fragment would create.
|
||
fn stacking_context_id(&self) -> StackingContextId;
|
||
|
||
fn unique_id(&self, id_type: IdType) -> u64;
|
||
|
||
fn fragment_type(&self) -> FragmentType;
|
||
}
|
||
|
||
fn handle_overlapping_radii(size: &Size2D<Au>, radii: &BorderRadii<Au>) -> BorderRadii<Au> {
|
||
// 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: Au, radius_b: Au, edge_length: Au) -> f32 {
|
||
let required = radius_a + radius_b;
|
||
|
||
if required <= edge_length {
|
||
1.0
|
||
} else {
|
||
edge_length.to_f32_px() / required.to_f32_px()
|
||
}
|
||
}
|
||
|
||
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 {
|
||
radii.scale_by(min_factor)
|
||
} else {
|
||
*radii
|
||
}
|
||
}
|
||
|
||
fn build_border_radius(abs_bounds: &Rect<Au>,
|
||
border_style: &style_structs::Border)
|
||
-> BorderRadii<Au> {
|
||
// 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.
|
||
|
||
handle_overlapping_radii(&abs_bounds.size, &BorderRadii {
|
||
top_left: model::specified_border_radius(border_style.border_top_left_radius,
|
||
abs_bounds.size),
|
||
top_right: model::specified_border_radius(border_style.border_top_right_radius,
|
||
abs_bounds.size),
|
||
bottom_right: model::specified_border_radius(border_style.border_bottom_right_radius,
|
||
abs_bounds.size),
|
||
bottom_left: model::specified_border_radius(border_style.border_bottom_left_radius,
|
||
abs_bounds.size),
|
||
})
|
||
}
|
||
|
||
/// Get the border radius for the rectangle inside of a rounded border. This is useful
|
||
/// for building the clip for the content inside the border.
|
||
fn build_border_radius_for_inner_rect(outer_rect: &Rect<Au>,
|
||
style: &ServoComputedValues)
|
||
-> BorderRadii<Au> {
|
||
let mut radii = build_border_radius(&outer_rect, style.get_border());
|
||
if radii.is_square() {
|
||
return radii;
|
||
}
|
||
|
||
// Since we are going to using the inner rectangle (outer rectangle minus
|
||
// border width), we need to adjust to border radius so that we are smaller
|
||
// rectangle with the same border curve.
|
||
let border_widths = style.logical_border_width().to_physical(style.writing_mode);
|
||
radii.top_left.width = cmp::max(Au(0), radii.top_left.width - border_widths.left);
|
||
radii.bottom_left.width = cmp::max(Au(0), radii.bottom_left.width - border_widths.left);
|
||
|
||
radii.top_right.width = cmp::max(Au(0), radii.top_right.width - border_widths.right);
|
||
radii.bottom_right.width = cmp::max(Au(0), radii.bottom_right.width - border_widths.right);
|
||
|
||
radii.top_left.height = cmp::max(Au(0), radii.top_left.height - border_widths.top);
|
||
radii.top_right.height = cmp::max(Au(0), radii.top_right.height - border_widths.top);
|
||
|
||
radii.bottom_left.height = cmp::max(Au(0), radii.bottom_left.height - border_widths.bottom);
|
||
radii.bottom_right.height = cmp::max(Au(0), radii.bottom_right.height - border_widths.bottom);
|
||
radii
|
||
}
|
||
|
||
fn convert_gradient_stops(gradient_items: &[GradientItem],
|
||
total_length: Au) -> Vec<GradientStop> {
|
||
// 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 {
|
||
GenericGradientItem::ColorStop(ref stop) => Some(*stop),
|
||
_ => 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(LengthOrPercentage::Percentage(Percentage(0.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(LengthOrPercentage::Percentage(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.
|
||
// Note: Remove the + 2 if fix_gradient_stops is changed.
|
||
let mut stops = Vec::with_capacity(stop_items.len() + 2);
|
||
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: start_offset,
|
||
end_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)
|
||
}
|
||
};
|
||
stops.push(GradientStop {
|
||
offset: offset,
|
||
color: stop.color.to_gfx_color()
|
||
})
|
||
}
|
||
stops
|
||
}
|
||
|
||
#[inline]
|
||
/// Duplicate the first and last stops if necessary.
|
||
///
|
||
/// Explanation by pyfisch:
|
||
/// If the last stop is at the same position as the previous stop the
|
||
/// last color is ignored by webrender. This differs from the spec
|
||
/// (I think so). The implementations of Chrome and Firefox seem
|
||
/// to have the same problem but work fine if the position of the last
|
||
/// stop is smaller than 100%. (Otherwise they ignore the last stop.)
|
||
///
|
||
/// Similarly the first stop is duplicated if it is not placed
|
||
/// at the start of the virtual gradient ray.
|
||
fn fix_gradient_stops(stops: &mut Vec<GradientStop>) {
|
||
if stops.first().unwrap().offset > 0.0 {
|
||
let color = stops.first().unwrap().color;
|
||
stops.insert(0, GradientStop {
|
||
offset: 0.0,
|
||
color: color,
|
||
})
|
||
}
|
||
if stops.last().unwrap().offset < 1.0 {
|
||
let color = stops.last().unwrap().color;
|
||
stops.push(GradientStop {
|
||
offset: 1.0,
|
||
color: color,
|
||
})
|
||
}
|
||
}
|
||
|
||
/// Returns the the distance to the nearest or farthest corner depending on the comperator.
|
||
fn get_distance_to_corner<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Au
|
||
where F: Fn(Au, Au) -> Au
|
||
{
|
||
let dist = get_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 get_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))
|
||
}
|
||
|
||
/// Returns the radius for an ellipse with the same ratio as if it was matched to the sides.
|
||
fn get_ellipse_radius<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
|
||
where F: Fn(Au, Au) -> Au
|
||
{
|
||
let dist = get_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 a circle if it was not explictly provided.
|
||
/// https://drafts.csswg.org/css-images-3/#typedef-size
|
||
fn convert_circle_size_keyword(keyword: ShapeExtent,
|
||
size: &Size2D<Au>,
|
||
center: &Point2D<Au>) -> Size2D<Au> {
|
||
let radius = match keyword {
|
||
ShapeExtent::ClosestSide | ShapeExtent::Contain => {
|
||
let dist = get_distance_to_sides(size, center, ::std::cmp::min);
|
||
::std::cmp::min(dist.width, dist.height)
|
||
}
|
||
ShapeExtent::FarthestSide => {
|
||
let dist = get_distance_to_sides(size, center, ::std::cmp::max);
|
||
::std::cmp::max(dist.width, dist.height)
|
||
}
|
||
ShapeExtent::ClosestCorner => {
|
||
get_distance_to_corner(size, center, ::std::cmp::min)
|
||
},
|
||
ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
|
||
get_distance_to_corner(size, center, ::std::cmp::max)
|
||
},
|
||
};
|
||
Size2D::new(radius, radius)
|
||
}
|
||
|
||
/// Determines the radius of an ellipse if it was not explictly provided.
|
||
/// https://drafts.csswg.org/css-images-3/#typedef-size
|
||
fn convert_ellipse_size_keyword(keyword: ShapeExtent,
|
||
size: &Size2D<Au>,
|
||
center: &Point2D<Au>) -> Size2D<Au> {
|
||
match keyword {
|
||
ShapeExtent::ClosestSide | ShapeExtent::Contain => {
|
||
get_distance_to_sides(size, center, ::std::cmp::min)
|
||
},
|
||
ShapeExtent::FarthestSide => {
|
||
get_distance_to_sides(size, center, ::std::cmp::max)
|
||
},
|
||
ShapeExtent::ClosestCorner => {
|
||
get_ellipse_radius(size, center, ::std::cmp::min)
|
||
},
|
||
ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
|
||
get_ellipse_radius(size, center, ::std::cmp::max)
|
||
},
|
||
}
|
||
}
|
||
|
||
impl FragmentDisplayListBuilding for Fragment {
|
||
fn build_display_list_for_background_if_applicable(&self,
|
||
state: &mut DisplayListBuildState,
|
||
style: &ServoComputedValues,
|
||
display_list_section: DisplayListSection,
|
||
absolute_bounds: &Rect<Au>) {
|
||
// Adjust the clipping region as necessary to account for `border-radius`.
|
||
let border_radii = build_border_radius(absolute_bounds, style.get_border());
|
||
let mut clip = ClippingRegion::max();
|
||
if !border_radii.is_square() {
|
||
clip.intersect_with_rounded_rect(absolute_bounds, &border_radii);
|
||
};
|
||
let background = style.get_background();
|
||
|
||
// FIXME: This causes a lot of background colors to be displayed when they are clearly not
|
||
// needed. We could use display list optimization to clean this up, but it still seems
|
||
// inefficient. What we really want is something like "nearest ancestor element that
|
||
// doesn't have a fragment".
|
||
let background_color = style.resolve_color(background.background_color);
|
||
|
||
// 'background-clip' determines the area within which the background is painted.
|
||
// http://dev.w3.org/csswg/css-backgrounds-3/#the-background-clip
|
||
let mut bounds = *absolute_bounds;
|
||
|
||
// This is the clip for the color (which is the last element in the bg array)
|
||
let color_clip = get_cyclic(&background.background_clip.0,
|
||
background.background_image.0.len() - 1);
|
||
|
||
match *color_clip {
|
||
background_clip::single_value::T::border_box => {}
|
||
background_clip::single_value::T::padding_box => {
|
||
let border = style.logical_border_width().to_physical(style.writing_mode);
|
||
bounds.origin.x = bounds.origin.x + border.left;
|
||
bounds.origin.y = bounds.origin.y + border.top;
|
||
bounds.size.width = bounds.size.width - border.horizontal();
|
||
bounds.size.height = bounds.size.height - border.vertical();
|
||
}
|
||
background_clip::single_value::T::content_box => {
|
||
let border_padding = self.border_padding.to_physical(style.writing_mode);
|
||
bounds.origin.x = bounds.origin.x + border_padding.left;
|
||
bounds.origin.y = bounds.origin.y + border_padding.top;
|
||
bounds.size.width = bounds.size.width - border_padding.horizontal();
|
||
bounds.size.height = bounds.size.height - border_padding.vertical();
|
||
}
|
||
}
|
||
|
||
let base = state.create_base_display_item(&bounds,
|
||
&clip,
|
||
self.node,
|
||
style.get_cursor(Cursor::Default),
|
||
display_list_section);
|
||
state.add_display_item(
|
||
DisplayItem::SolidColor(box SolidColorDisplayItem {
|
||
base: base,
|
||
color: background_color.to_gfx_color(),
|
||
}));
|
||
|
||
// The background image is painted on top of the background color.
|
||
// Implements background image, per spec:
|
||
// http://www.w3.org/TR/CSS21/colors.html#background
|
||
let background = style.get_background();
|
||
for (i, background_image) in background.background_image.0.iter().enumerate().rev() {
|
||
match *background_image {
|
||
Either::First(_) => {}
|
||
Either::Second(Image::Gradient(ref gradient)) => {
|
||
self.build_display_list_for_background_gradient(state,
|
||
display_list_section,
|
||
&absolute_bounds,
|
||
&bounds,
|
||
&clip,
|
||
gradient,
|
||
style);
|
||
}
|
||
Either::Second(Image::Url(ref image_url)) => {
|
||
if let Some(url) = image_url.url() {
|
||
self.build_display_list_for_background_image(state,
|
||
style,
|
||
display_list_section,
|
||
&bounds,
|
||
&clip,
|
||
url,
|
||
i);
|
||
}
|
||
}
|
||
Either::Second(Image::PaintWorklet(ref paint_worklet)) => {
|
||
self.build_display_list_for_background_paint_worklet(state,
|
||
style,
|
||
display_list_section,
|
||
&bounds,
|
||
&clip,
|
||
paint_worklet,
|
||
i);
|
||
}
|
||
Either::Second(Image::Rect(_)) => {
|
||
// TODO: Implement `-moz-image-rect`
|
||
}
|
||
Either::Second(Image::Element(_)) => {
|
||
// TODO: Implement `-moz-element`
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
fn compute_background_image_size(&self,
|
||
style: &ServoComputedValues,
|
||
bounds: &Rect<Au>,
|
||
image: &WebRenderImageInfo,
|
||
index: usize)
|
||
-> Size2D<Au> {
|
||
// If `image_aspect_ratio` < `bounds_aspect_ratio`, the image is tall; otherwise, it is
|
||
// wide.
|
||
let image_aspect_ratio = (image.width as f64) / (image.height as f64);
|
||
let bounds_aspect_ratio = bounds.size.width.to_f64_px() / bounds.size.height.to_f64_px();
|
||
let intrinsic_size = Size2D::new(Au::from_px(image.width as i32),
|
||
Au::from_px(image.height as i32));
|
||
let background_size = get_cyclic(&style.get_background().background_size.0, index).clone();
|
||
match (background_size, image_aspect_ratio < bounds_aspect_ratio) {
|
||
(BackgroundSize::Contain, false) | (BackgroundSize::Cover, true) => {
|
||
Size2D::new(bounds.size.width,
|
||
Au::from_f64_px(bounds.size.width.to_f64_px() / image_aspect_ratio))
|
||
}
|
||
|
||
(BackgroundSize::Contain, true) | (BackgroundSize::Cover, false) => {
|
||
Size2D::new(Au::from_f64_px(bounds.size.height.to_f64_px() * image_aspect_ratio),
|
||
bounds.size.height)
|
||
}
|
||
|
||
(BackgroundSize::Explicit { width, height: LengthOrPercentageOrAuto::Auto }, _) => {
|
||
let width = MaybeAuto::from_style(width, bounds.size.width)
|
||
.specified_or_default(intrinsic_size.width);
|
||
Size2D::new(width, Au::from_f64_px(width.to_f64_px() / image_aspect_ratio))
|
||
}
|
||
|
||
(BackgroundSize::Explicit { width: LengthOrPercentageOrAuto::Auto, height }, _) => {
|
||
let height = MaybeAuto::from_style(height, bounds.size.height)
|
||
.specified_or_default(intrinsic_size.height);
|
||
Size2D::new(Au::from_f64_px(height.to_f64_px() * image_aspect_ratio), height)
|
||
}
|
||
|
||
(BackgroundSize::Explicit { width, height }, _) => {
|
||
Size2D::new(MaybeAuto::from_style(width, bounds.size.width)
|
||
.specified_or_default(intrinsic_size.width),
|
||
MaybeAuto::from_style(height, bounds.size.height)
|
||
.specified_or_default(intrinsic_size.height))
|
||
}
|
||
}
|
||
}
|
||
|
||
fn build_display_list_for_background_image(&self,
|
||
state: &mut DisplayListBuildState,
|
||
style: &ServoComputedValues,
|
||
display_list_section: DisplayListSection,
|
||
absolute_bounds: &Rect<Au>,
|
||
clip: &ClippingRegion,
|
||
image_url: &ServoUrl,
|
||
index: usize) {
|
||
let webrender_image = state.layout_context
|
||
.get_webrender_image_for_url(self.node,
|
||
image_url.clone(),
|
||
UsePlaceholder::No);
|
||
|
||
if let Some(webrender_image) = webrender_image {
|
||
self.build_display_list_for_webrender_image(state,
|
||
style,
|
||
display_list_section,
|
||
absolute_bounds,
|
||
clip,
|
||
webrender_image,
|
||
index);
|
||
}
|
||
}
|
||
|
||
fn build_display_list_for_webrender_image(&self,
|
||
state: &mut DisplayListBuildState,
|
||
style: &ServoComputedValues,
|
||
display_list_section: DisplayListSection,
|
||
absolute_bounds: &Rect<Au>,
|
||
clip: &ClippingRegion,
|
||
webrender_image: WebRenderImageInfo,
|
||
index: usize) {
|
||
debug!("(building display list) building background image");
|
||
let background = style.get_background();
|
||
|
||
// Use `background-size` to get the size.
|
||
let mut bounds = *absolute_bounds;
|
||
let image_size = self.compute_background_image_size(style, &bounds,
|
||
&webrender_image, index);
|
||
|
||
// Clip.
|
||
//
|
||
// TODO: Check the bounds to see if a clip item is actually required.
|
||
let mut clip = clip.clone();
|
||
clip.intersect_rect(&bounds);
|
||
|
||
// Background image should be positioned on the padding box basis.
|
||
let border = style.logical_border_width().to_physical(style.writing_mode);
|
||
|
||
// Use 'background-origin' to get the origin value.
|
||
let origin = get_cyclic(&background.background_origin.0, index);
|
||
let (mut origin_x, mut origin_y) = match *origin {
|
||
background_origin::single_value::T::padding_box => {
|
||
(Au(0), Au(0))
|
||
}
|
||
background_origin::single_value::T::border_box => {
|
||
(-border.left, -border.top)
|
||
}
|
||
background_origin::single_value::T::content_box => {
|
||
let border_padding = self.border_padding.to_physical(self.style.writing_mode);
|
||
(border_padding.left - border.left, border_padding.top - border.top)
|
||
}
|
||
};
|
||
|
||
// Use `background-attachment` to get the initial virtual origin
|
||
let attachment = get_cyclic(&background.background_attachment.0, index);
|
||
let (virtual_origin_x, virtual_origin_y) = match *attachment {
|
||
background_attachment::single_value::T::scroll => {
|
||
(absolute_bounds.origin.x, absolute_bounds.origin.y)
|
||
}
|
||
background_attachment::single_value::T::fixed => {
|
||
// If the ‘background-attachment’ value for this image is ‘fixed’, then
|
||
// 'background-origin' has no effect.
|
||
origin_x = Au(0);
|
||
origin_y = Au(0);
|
||
(Au(0), Au(0))
|
||
}
|
||
};
|
||
|
||
let horiz_position = *get_cyclic(&background.background_position_x.0, index);
|
||
let vert_position = *get_cyclic(&background.background_position_y.0, index);
|
||
// Use `background-position` to get the offset.
|
||
let horizontal_position = horiz_position.to_used_value(bounds.size.width - image_size.width);
|
||
let vertical_position = vert_position.to_used_value(bounds.size.height - image_size.height);
|
||
|
||
// The anchor position for this background, based on both the background-attachment
|
||
// and background-position properties.
|
||
let anchor_origin_x = border.left + virtual_origin_x + origin_x + horizontal_position;
|
||
let anchor_origin_y = border.top + virtual_origin_y + origin_y + vertical_position;
|
||
|
||
let mut tile_spacing = Size2D::zero();
|
||
let mut stretch_size = image_size;
|
||
|
||
// Adjust origin and size based on background-repeat
|
||
let background_repeat = get_cyclic(&background.background_repeat.0, index);
|
||
match background_repeat.0 {
|
||
background_repeat::single_value::RepeatKeyword::NoRepeat => {
|
||
bounds.origin.x = anchor_origin_x;
|
||
bounds.size.width = image_size.width;
|
||
}
|
||
background_repeat::single_value::RepeatKeyword::Repeat => {
|
||
ImageFragmentInfo::tile_image(&mut bounds.origin.x,
|
||
&mut bounds.size.width,
|
||
anchor_origin_x,
|
||
image_size.width);
|
||
}
|
||
background_repeat::single_value::RepeatKeyword::Space => {
|
||
ImageFragmentInfo::tile_image_spaced(&mut bounds.origin.x,
|
||
&mut bounds.size.width,
|
||
&mut tile_spacing.width,
|
||
anchor_origin_x,
|
||
image_size.width);
|
||
|
||
}
|
||
background_repeat::single_value::RepeatKeyword::Round => {
|
||
ImageFragmentInfo::tile_image_round(&mut bounds.origin.x,
|
||
&mut bounds.size.width,
|
||
anchor_origin_x,
|
||
&mut stretch_size.width);
|
||
}
|
||
};
|
||
match background_repeat.1 {
|
||
background_repeat::single_value::RepeatKeyword::NoRepeat => {
|
||
bounds.origin.y = anchor_origin_y;
|
||
bounds.size.height = image_size.height;
|
||
}
|
||
background_repeat::single_value::RepeatKeyword::Repeat => {
|
||
ImageFragmentInfo::tile_image(&mut bounds.origin.y,
|
||
&mut bounds.size.height,
|
||
anchor_origin_y,
|
||
image_size.height);
|
||
}
|
||
background_repeat::single_value::RepeatKeyword::Space => {
|
||
ImageFragmentInfo::tile_image_spaced(&mut bounds.origin.y,
|
||
&mut bounds.size.height,
|
||
&mut tile_spacing.height,
|
||
anchor_origin_y,
|
||
image_size.height);
|
||
|
||
}
|
||
background_repeat::single_value::RepeatKeyword::Round => {
|
||
ImageFragmentInfo::tile_image_round(&mut bounds.origin.y,
|
||
&mut bounds.size.height,
|
||
anchor_origin_y,
|
||
&mut stretch_size.height);
|
||
}
|
||
};
|
||
|
||
// Create the image display item.
|
||
let base = state.create_base_display_item(&bounds,
|
||
&clip,
|
||
self.node,
|
||
style.get_cursor(Cursor::Default),
|
||
display_list_section);
|
||
|
||
debug!("(building display list) adding background image.");
|
||
state.add_display_item(DisplayItem::Image(box ImageDisplayItem {
|
||
base: base,
|
||
webrender_image: webrender_image,
|
||
image_data: None,
|
||
stretch_size: stretch_size,
|
||
tile_spacing: tile_spacing,
|
||
image_rendering: style.get_inheritedbox().image_rendering.clone(),
|
||
}));
|
||
|
||
}
|
||
|
||
fn build_display_list_for_background_paint_worklet(&self,
|
||
state: &mut DisplayListBuildState,
|
||
style: &ServoComputedValues,
|
||
display_list_section: DisplayListSection,
|
||
absolute_bounds: &Rect<Au>,
|
||
clip: &ClippingRegion,
|
||
paint_worklet: &PaintWorklet,
|
||
index: usize)
|
||
{
|
||
// TODO: check that this is the servo equivalent of "concrete object size".
|
||
// https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image
|
||
// https://drafts.csswg.org/css-images-3/#concrete-object-size
|
||
let size = self.content_box().size.to_physical(style.writing_mode);
|
||
let name = paint_worklet.name.clone();
|
||
|
||
// If the script thread has not added any paint worklet modules, there is nothing to do!
|
||
let executor = match state.layout_context.paint_worklet_executor {
|
||
Some(ref executor) => executor,
|
||
None => return debug!("Worklet {} called before any paint modules are added.", name),
|
||
};
|
||
|
||
// TODO: add a one-place cache to avoid drawing the paint image every time.
|
||
debug!("Drawing a paint image {}({},{}).", name, size.width.to_px(), size.height.to_px());
|
||
let mut image = match executor.draw_a_paint_image(name, size) {
|
||
Ok(image) => image,
|
||
Err(err) => return warn!("Error running paint worklet ({:?}).", err),
|
||
};
|
||
|
||
// Make sure the image has a webrender key.
|
||
state.layout_context.image_cache.set_webrender_image_key(&mut image);
|
||
|
||
debug!("Drew a paint image ({},{}).", image.width, image.height);
|
||
self.build_display_list_for_webrender_image(state,
|
||
style,
|
||
display_list_section,
|
||
absolute_bounds,
|
||
clip,
|
||
WebRenderImageInfo::from_image(&image),
|
||
index);
|
||
}
|
||
|
||
fn convert_linear_gradient(&self,
|
||
bounds: &Rect<Au>,
|
||
stops: &[GradientItem],
|
||
direction: &LineDirection,
|
||
repeating: bool)
|
||
-> display_list::Gradient {
|
||
let angle = match *direction {
|
||
LineDirection::Angle(angle) => angle.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 = (bounds.size.height.to_f32_px() /
|
||
bounds.size.width.to_f32_px()).atan();
|
||
match (horizontal, vertical) {
|
||
(X::Right, Y::Bottom)
|
||
=> f32::consts::PI - atan,
|
||
(X::Left, Y::Bottom)
|
||
=> f32::consts::PI + atan,
|
||
(X::Right, Y::Top)
|
||
=> atan,
|
||
(X::Left, Y::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 * bounds.size.width.to_f32_px()).abs() +
|
||
(dir.y * bounds.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 stops = convert_gradient_stops(stops, length);
|
||
|
||
// Only clamped gradients need to be fixed because in repeating gradients
|
||
// there is no "first" or "last" stop because they repeat infinitly in
|
||
// both directions, so the rendering is always correct.
|
||
if !repeating {
|
||
fix_gradient_stops(&mut stops);
|
||
}
|
||
|
||
let center = Point2D::new(bounds.size.width / 2, bounds.size.height / 2);
|
||
|
||
display_list::Gradient {
|
||
start_point: center - delta,
|
||
end_point: center + delta,
|
||
stops: stops,
|
||
repeating: repeating,
|
||
}
|
||
}
|
||
|
||
fn convert_radial_gradient(&self,
|
||
bounds: &Rect<Au>,
|
||
stops: &[GradientItem],
|
||
shape: &EndingShape,
|
||
center: &Position,
|
||
repeating: bool)
|
||
-> display_list::RadialGradient {
|
||
let center = Point2D::new(center.horizontal.to_used_value(bounds.size.width),
|
||
center.vertical.to_used_value(bounds.size.height));
|
||
let radius = match *shape {
|
||
GenericEndingShape::Circle(Circle::Radius(length)) => {
|
||
Size2D::new(length, length)
|
||
},
|
||
GenericEndingShape::Circle(Circle::Extent(extent)) => {
|
||
convert_circle_size_keyword(extent, &bounds.size, ¢er)
|
||
},
|
||
GenericEndingShape::Ellipse(Ellipse::Radii(x, y)) => {
|
||
Size2D::new(x.to_used_value(bounds.size.width), y.to_used_value(bounds.size.height))
|
||
},
|
||
GenericEndingShape::Ellipse(Ellipse::Extent(extent)) => {
|
||
convert_ellipse_size_keyword(extent, &bounds.size, ¢er)
|
||
},
|
||
};
|
||
|
||
let mut stops = convert_gradient_stops(stops, radius.width);
|
||
// Repeating gradients have no last stops that can be ignored. So
|
||
// fixup is not necessary but may actually break the gradient.
|
||
if !repeating {
|
||
fix_gradient_stops(&mut stops);
|
||
}
|
||
|
||
display_list::RadialGradient {
|
||
center: center,
|
||
radius: radius,
|
||
stops: stops,
|
||
repeating: repeating,
|
||
}
|
||
}
|
||
|
||
fn build_display_list_for_background_gradient(&self,
|
||
state: &mut DisplayListBuildState,
|
||
display_list_section: DisplayListSection,
|
||
absolute_bounds: &Rect<Au>,
|
||
clip_bounds: &Rect<Au>,
|
||
clip: &ClippingRegion,
|
||
gradient: &Gradient,
|
||
style: &ServoComputedValues) {
|
||
let mut clip = clip.clone();
|
||
clip.intersect_rect(clip_bounds);
|
||
|
||
let border = self.border_width().to_physical(style.writing_mode);
|
||
let mut bounds = *absolute_bounds;
|
||
bounds.origin.x = bounds.origin.x + border.left;
|
||
bounds.origin.y = bounds.origin.y + border.top;
|
||
bounds.size.width = bounds.size.width - border.horizontal();
|
||
bounds.size.height = bounds.size.height - border.vertical();
|
||
|
||
let base = state.create_base_display_item(&bounds,
|
||
&clip,
|
||
self.node,
|
||
style.get_cursor(Cursor::Default),
|
||
display_list_section);
|
||
|
||
let display_item = match gradient.kind {
|
||
GradientKind::Linear(ref angle_or_corner) => {
|
||
let gradient = self.convert_linear_gradient(&bounds,
|
||
&gradient.items[..],
|
||
angle_or_corner,
|
||
gradient.repeating);
|
||
DisplayItem::Gradient(box GradientDisplayItem {
|
||
base: base,
|
||
gradient: gradient,
|
||
})
|
||
}
|
||
GradientKind::Radial(ref shape, ref center) => {
|
||
let gradient = self.convert_radial_gradient(&bounds,
|
||
&gradient.items[..],
|
||
shape,
|
||
center,
|
||
gradient.repeating);
|
||
DisplayItem::RadialGradient(box RadialGradientDisplayItem {
|
||
base: base,
|
||
gradient: gradient,
|
||
})
|
||
}
|
||
};
|
||
state.add_display_item(display_item);
|
||
}
|
||
|
||
fn build_display_list_for_box_shadow_if_applicable(&self,
|
||
state: &mut DisplayListBuildState,
|
||
style: &ServoComputedValues,
|
||
display_list_section: DisplayListSection,
|
||
absolute_bounds: &Rect<Au>,
|
||
clip: &Rect<Au>) {
|
||
// NB: According to CSS-BACKGROUNDS, box shadows render in *reverse* order (front to back).
|
||
for box_shadow in style.get_effects().box_shadow.0.iter().rev() {
|
||
let bounds =
|
||
shadow_bounds(&absolute_bounds.translate(&Vector2D::new(box_shadow.offset_x,
|
||
box_shadow.offset_y)),
|
||
box_shadow.blur_radius,
|
||
box_shadow.spread_radius);
|
||
|
||
// TODO(pcwalton): Multiple border radii; elliptical border radii.
|
||
let base = state.create_base_display_item(&bounds,
|
||
&ClippingRegion::from_rect(&clip),
|
||
self.node,
|
||
style.get_cursor(Cursor::Default),
|
||
display_list_section);
|
||
state.add_display_item(DisplayItem::BoxShadow(box BoxShadowDisplayItem {
|
||
base: base,
|
||
box_bounds: *absolute_bounds,
|
||
color: style.resolve_color(box_shadow.color).to_gfx_color(),
|
||
offset: Vector2D::new(box_shadow.offset_x, box_shadow.offset_y),
|
||
blur_radius: box_shadow.blur_radius,
|
||
spread_radius: box_shadow.spread_radius,
|
||
border_radius: model::specified_border_radius(style.get_border()
|
||
.border_top_left_radius,
|
||
absolute_bounds.size).width,
|
||
clip_mode: if box_shadow.inset {
|
||
BoxShadowClipMode::Inset
|
||
} else {
|
||
BoxShadowClipMode::Outset
|
||
},
|
||
}));
|
||
}
|
||
}
|
||
|
||
fn build_display_list_for_borders_if_applicable(
|
||
&self,
|
||
state: &mut DisplayListBuildState,
|
||
style: &ServoComputedValues,
|
||
border_painting_mode: BorderPaintingMode,
|
||
bounds: &Rect<Au>,
|
||
display_list_section: DisplayListSection,
|
||
clip: &Rect<Au>) {
|
||
let mut border = style.logical_border_width();
|
||
|
||
match border_painting_mode {
|
||
BorderPaintingMode::Separate => {}
|
||
BorderPaintingMode::Collapse(collapsed_borders) => {
|
||
collapsed_borders.adjust_border_widths_for_painting(&mut border)
|
||
}
|
||
BorderPaintingMode::Hidden => return,
|
||
}
|
||
if border.is_zero() {
|
||
return
|
||
}
|
||
|
||
let border_style_struct = style.get_border();
|
||
let mut colors = SideOffsets2D::new(border_style_struct.border_top_color,
|
||
border_style_struct.border_right_color,
|
||
border_style_struct.border_bottom_color,
|
||
border_style_struct.border_left_color);
|
||
let mut border_style = SideOffsets2D::new(border_style_struct.border_top_style,
|
||
border_style_struct.border_right_style,
|
||
border_style_struct.border_bottom_style,
|
||
border_style_struct.border_left_style);
|
||
if let BorderPaintingMode::Collapse(collapsed_borders) = border_painting_mode {
|
||
collapsed_borders.adjust_border_colors_and_styles_for_painting(&mut colors,
|
||
&mut border_style,
|
||
style.writing_mode);
|
||
}
|
||
|
||
let colors = SideOffsets2D::new(style.resolve_color(colors.top),
|
||
style.resolve_color(colors.right),
|
||
style.resolve_color(colors.bottom),
|
||
style.resolve_color(colors.left));
|
||
|
||
// If this border collapses, then we draw outside the boundaries we were given.
|
||
let mut bounds = *bounds;
|
||
if let BorderPaintingMode::Collapse(collapsed_borders) = border_painting_mode {
|
||
collapsed_borders.adjust_border_bounds_for_painting(&mut bounds, style.writing_mode)
|
||
}
|
||
|
||
// Append the border to the display list.
|
||
let base = state.create_base_display_item(&bounds,
|
||
&ClippingRegion::from_rect(&clip),
|
||
self.node,
|
||
style.get_cursor(Cursor::Default),
|
||
display_list_section);
|
||
|
||
match border_style_struct.border_image_source {
|
||
Either::First(_) => {
|
||
state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
|
||
base: base,
|
||
border_widths: border.to_physical(style.writing_mode),
|
||
details: BorderDetails::Normal(NormalBorder {
|
||
color: SideOffsets2D::new(colors.top.to_gfx_color(),
|
||
colors.right.to_gfx_color(),
|
||
colors.bottom.to_gfx_color(),
|
||
colors.left.to_gfx_color()),
|
||
style: border_style,
|
||
radius: build_border_radius(&bounds, border_style_struct),
|
||
}),
|
||
}));
|
||
}
|
||
Either::Second(Image::Gradient(ref gradient)) => {
|
||
match gradient.kind {
|
||
GradientKind::Linear(angle_or_corner) => {
|
||
let grad = self.convert_linear_gradient(&bounds,
|
||
&gradient.items[..],
|
||
&angle_or_corner,
|
||
gradient.repeating);
|
||
|
||
state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
|
||
base: base,
|
||
border_widths: border.to_physical(style.writing_mode),
|
||
details: BorderDetails::Gradient(display_list::GradientBorder {
|
||
gradient: grad,
|
||
|
||
// TODO(gw): Support border-image-outset
|
||
outset: SideOffsets2D::zero(),
|
||
}),
|
||
}));
|
||
}
|
||
GradientKind::Radial(ref shape, ref center) => {
|
||
let grad = self.convert_radial_gradient(&bounds,
|
||
&gradient.items[..],
|
||
shape,
|
||
center,
|
||
gradient.repeating);
|
||
state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
|
||
base: base,
|
||
border_widths: border.to_physical(style.writing_mode),
|
||
details: BorderDetails::RadialGradient(
|
||
display_list::RadialGradientBorder {
|
||
gradient: grad,
|
||
|
||
// TODO(gw): Support border-image-outset
|
||
outset: SideOffsets2D::zero(),
|
||
}),
|
||
}));
|
||
}
|
||
}
|
||
}
|
||
Either::Second(Image::PaintWorklet(..)) => {
|
||
// TODO: Handle border-image with `paint()`.
|
||
}
|
||
Either::Second(Image::Rect(..)) => {
|
||
// TODO: Handle border-image with `-moz-image-rect`.
|
||
}
|
||
Either::Second(Image::Element(..)) => {
|
||
// TODO: Handle border-image with `-moz-element`.
|
||
}
|
||
Either::Second(Image::Url(ref image_url)) => {
|
||
if let Some(url) = image_url.url() {
|
||
let webrender_image = state.layout_context
|
||
.get_webrender_image_for_url(self.node,
|
||
url.clone(),
|
||
UsePlaceholder::No);
|
||
if let Some(webrender_image) = webrender_image {
|
||
let corners = &border_style_struct.border_image_slice.offsets;
|
||
|
||
state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
|
||
base: base,
|
||
border_widths: border.to_physical(style.writing_mode),
|
||
details: BorderDetails::Image(ImageBorder {
|
||
image: webrender_image,
|
||
fill: border_style_struct.border_image_slice.fill,
|
||
slice: SideOffsets2D::new(corners.0.resolve(webrender_image.height),
|
||
corners.1.resolve(webrender_image.width),
|
||
corners.2.resolve(webrender_image.height),
|
||
corners.3.resolve(webrender_image.width)),
|
||
// TODO(gw): Support border-image-outset
|
||
outset: SideOffsets2D::zero(),
|
||
repeat_horizontal: convert_repeat_mode(border_style_struct.border_image_repeat.0),
|
||
repeat_vertical: convert_repeat_mode(border_style_struct.border_image_repeat.1),
|
||
}),
|
||
}));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
fn build_display_list_for_outline_if_applicable(&self,
|
||
state: &mut DisplayListBuildState,
|
||
style: &ServoComputedValues,
|
||
bounds: &Rect<Au>,
|
||
clip: &Rect<Au>) {
|
||
use style::values::Either;
|
||
|
||
let width = style.get_outline().outline_width;
|
||
if width == Au(0) {
|
||
return
|
||
}
|
||
|
||
let outline_style = match style.get_outline().outline_style {
|
||
Either::First(_auto) => border_style::T::solid,
|
||
Either::Second(border_style::T::none) => return,
|
||
Either::Second(border_style) => border_style
|
||
};
|
||
|
||
// Outlines are not accounted for in the dimensions of the border box, so adjust the
|
||
// absolute bounds.
|
||
let mut bounds = *bounds;
|
||
let offset = width + style.get_outline().outline_offset;
|
||
bounds.origin.x = bounds.origin.x - offset;
|
||
bounds.origin.y = bounds.origin.y - offset;
|
||
bounds.size.width = bounds.size.width + offset + offset;
|
||
bounds.size.height = bounds.size.height + offset + offset;
|
||
|
||
// Append the outline to the display list.
|
||
let color = style.resolve_color(style.get_outline().outline_color).to_gfx_color();
|
||
let base = state.create_base_display_item(&bounds,
|
||
&ClippingRegion::from_rect(&clip),
|
||
self.node,
|
||
style.get_cursor(Cursor::Default),
|
||
DisplayListSection::Outlines);
|
||
state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
|
||
base: base,
|
||
border_widths: SideOffsets2D::new_all_same(width),
|
||
details: BorderDetails::Normal(NormalBorder {
|
||
color: SideOffsets2D::new_all_same(color),
|
||
style: SideOffsets2D::new_all_same(outline_style),
|
||
radius: Default::default(),
|
||
}),
|
||
}));
|
||
}
|
||
|
||
fn build_debug_borders_around_text_fragments(&self,
|
||
state: &mut DisplayListBuildState,
|
||
style: &ServoComputedValues,
|
||
stacking_relative_border_box: &Rect<Au>,
|
||
stacking_relative_content_box: &Rect<Au>,
|
||
text_fragment: &ScannedTextFragmentInfo,
|
||
clip: &Rect<Au>) {
|
||
// FIXME(pcwalton, #2795): Get the real container size.
|
||
let container_size = Size2D::zero();
|
||
|
||
// Compute the text fragment bounds and draw a border surrounding them.
|
||
let base = state.create_base_display_item(stacking_relative_border_box,
|
||
&ClippingRegion::from_rect(&clip),
|
||
self.node,
|
||
style.get_cursor(Cursor::Default),
|
||
DisplayListSection::Content);
|
||
state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
|
||
base: base,
|
||
border_widths: SideOffsets2D::new_all_same(Au::from_px(1)),
|
||
details: BorderDetails::Normal(NormalBorder {
|
||
color: SideOffsets2D::new_all_same(ColorF::rgb(0, 0, 200)),
|
||
style: SideOffsets2D::new_all_same(border_style::T::solid),
|
||
radius: Default::default(),
|
||
}),
|
||
}));
|
||
|
||
// Draw a rectangle representing the baselines.
|
||
let mut baseline = LogicalRect::from_physical(self.style.writing_mode,
|
||
*stacking_relative_content_box,
|
||
container_size);
|
||
baseline.start.b = baseline.start.b + text_fragment.run.ascent();
|
||
baseline.size.block = Au(0);
|
||
let baseline = baseline.to_physical(self.style.writing_mode, container_size);
|
||
|
||
let base = state.create_base_display_item(&baseline,
|
||
&ClippingRegion::from_rect(&clip),
|
||
self.node,
|
||
style.get_cursor(Cursor::Default),
|
||
DisplayListSection::Content);
|
||
state.add_display_item(DisplayItem::Line(box LineDisplayItem {
|
||
base: base,
|
||
color: ColorF::rgb(0, 200, 0),
|
||
style: border_style::T::dashed,
|
||
}));
|
||
}
|
||
|
||
fn build_debug_borders_around_fragment(&self,
|
||
state: &mut DisplayListBuildState,
|
||
stacking_relative_border_box: &Rect<Au>,
|
||
clip: &Rect<Au>) {
|
||
// This prints a debug border around the border of this fragment.
|
||
let base = state.create_base_display_item(stacking_relative_border_box,
|
||
&ClippingRegion::from_rect(&clip),
|
||
self.node,
|
||
self.style.get_cursor(Cursor::Default),
|
||
DisplayListSection::Content);
|
||
state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
|
||
base: base,
|
||
border_widths: SideOffsets2D::new_all_same(Au::from_px(1)),
|
||
details: BorderDetails::Normal(NormalBorder {
|
||
color: SideOffsets2D::new_all_same(ColorF::rgb(0, 0, 200)),
|
||
style: SideOffsets2D::new_all_same(border_style::T::solid),
|
||
radius: Default::default(),
|
||
}),
|
||
}));
|
||
}
|
||
|
||
fn build_display_items_for_selection_if_necessary(&self,
|
||
state: &mut DisplayListBuildState,
|
||
stacking_relative_border_box: &Rect<Au>,
|
||
display_list_section: DisplayListSection,
|
||
clip: &Rect<Au>) {
|
||
let scanned_text_fragment_info = match self.specific {
|
||
SpecificFragmentInfo::ScannedText(ref scanned_text_fragment_info) => {
|
||
scanned_text_fragment_info
|
||
}
|
||
_ => return,
|
||
};
|
||
|
||
// Draw a highlighted background if the text is selected.
|
||
//
|
||
// TODO: Allow non-text fragments to be selected too.
|
||
if scanned_text_fragment_info.selected() {
|
||
let style = self.selected_style();
|
||
let background_color = style.resolve_color(style.get_background().background_color);
|
||
let base = state.create_base_display_item(stacking_relative_border_box,
|
||
&ClippingRegion::from_rect(&clip),
|
||
self.node,
|
||
self.style.get_cursor(Cursor::Default),
|
||
display_list_section);
|
||
state.add_display_item(
|
||
DisplayItem::SolidColor(box SolidColorDisplayItem {
|
||
base: base,
|
||
color: background_color.to_gfx_color(),
|
||
}));
|
||
}
|
||
|
||
// Draw a caret at the insertion point.
|
||
let insertion_point_index = match scanned_text_fragment_info.insertion_point {
|
||
Some(insertion_point_index) => insertion_point_index,
|
||
None => return,
|
||
};
|
||
let range = Range::new(scanned_text_fragment_info.range.begin(),
|
||
insertion_point_index - scanned_text_fragment_info.range.begin());
|
||
let advance = scanned_text_fragment_info.run.advance_for_range(&range);
|
||
|
||
let insertion_point_bounds;
|
||
let cursor;
|
||
if !self.style.writing_mode.is_vertical() {
|
||
insertion_point_bounds =
|
||
Rect::new(Point2D::new(stacking_relative_border_box.origin.x + advance,
|
||
stacking_relative_border_box.origin.y),
|
||
Size2D::new(INSERTION_POINT_LOGICAL_WIDTH,
|
||
stacking_relative_border_box.size.height));
|
||
cursor = Cursor::Text;
|
||
} else {
|
||
insertion_point_bounds =
|
||
Rect::new(Point2D::new(stacking_relative_border_box.origin.x,
|
||
stacking_relative_border_box.origin.y + advance),
|
||
Size2D::new(stacking_relative_border_box.size.width,
|
||
INSERTION_POINT_LOGICAL_WIDTH));
|
||
cursor = Cursor::VerticalText;
|
||
};
|
||
|
||
let base = state.create_base_display_item(&insertion_point_bounds,
|
||
&ClippingRegion::from_rect(&clip),
|
||
self.node,
|
||
self.style.get_cursor(cursor),
|
||
display_list_section);
|
||
state.add_display_item(DisplayItem::SolidColor(box SolidColorDisplayItem {
|
||
base: base,
|
||
color: self.style().get_color().color.to_gfx_color(),
|
||
}));
|
||
}
|
||
|
||
fn build_display_list(&mut self,
|
||
state: &mut DisplayListBuildState,
|
||
stacking_relative_flow_origin: &Vector2D<Au>,
|
||
relative_containing_block_size: &LogicalSize<Au>,
|
||
relative_containing_block_mode: WritingMode,
|
||
border_painting_mode: BorderPaintingMode,
|
||
display_list_section: DisplayListSection,
|
||
clip: &Rect<Au>) {
|
||
self.restyle_damage.remove(REPAINT);
|
||
if self.style().get_inheritedbox().visibility != visibility::T::visible {
|
||
return
|
||
}
|
||
|
||
// Compute the fragment position relative to the parent stacking context. If the fragment
|
||
// itself establishes a stacking context, then the origin of its position will be (0, 0)
|
||
// for the purposes of this computation.
|
||
let stacking_relative_border_box =
|
||
self.stacking_relative_border_box(stacking_relative_flow_origin,
|
||
relative_containing_block_size,
|
||
relative_containing_block_mode,
|
||
CoordinateSystem::Own);
|
||
|
||
debug!("Fragment::build_display_list at rel={:?}, abs={:?}, flow origin={:?}: {:?}",
|
||
self.border_box,
|
||
stacking_relative_border_box,
|
||
stacking_relative_flow_origin,
|
||
self);
|
||
|
||
// Check the clip rect. If there's nothing to render at all, don't even construct display
|
||
// list items.
|
||
let empty_rect = !clip.intersects(&stacking_relative_border_box);
|
||
if self.is_primary_fragment() && !empty_rect {
|
||
// Add shadows, background, borders, and outlines, if applicable.
|
||
if let Some(ref inline_context) = self.inline_context {
|
||
for node in inline_context.nodes.iter().rev() {
|
||
self.build_display_list_for_background_if_applicable(
|
||
state,
|
||
&*node.style,
|
||
display_list_section,
|
||
&stacking_relative_border_box);
|
||
self.build_display_list_for_box_shadow_if_applicable(
|
||
state,
|
||
&*node.style,
|
||
display_list_section,
|
||
&stacking_relative_border_box,
|
||
clip);
|
||
|
||
let mut style = node.style.clone();
|
||
properties::modify_border_style_for_inline_sides(
|
||
&mut style,
|
||
node.flags.contains(FIRST_FRAGMENT_OF_ELEMENT),
|
||
node.flags.contains(LAST_FRAGMENT_OF_ELEMENT));
|
||
self.build_display_list_for_borders_if_applicable(
|
||
state,
|
||
&*style,
|
||
border_painting_mode,
|
||
&stacking_relative_border_box,
|
||
display_list_section,
|
||
clip);
|
||
|
||
self.build_display_list_for_outline_if_applicable(
|
||
state,
|
||
&*node.style,
|
||
&stacking_relative_border_box,
|
||
clip);
|
||
}
|
||
}
|
||
|
||
if !self.is_scanned_text_fragment() {
|
||
self.build_display_list_for_background_if_applicable(state,
|
||
&*self.style,
|
||
display_list_section,
|
||
&stacking_relative_border_box);
|
||
self.build_display_list_for_box_shadow_if_applicable(state,
|
||
&*self.style,
|
||
display_list_section,
|
||
&stacking_relative_border_box,
|
||
clip);
|
||
self.build_display_list_for_borders_if_applicable(state,
|
||
&*self.style,
|
||
border_painting_mode,
|
||
&stacking_relative_border_box,
|
||
display_list_section,
|
||
clip);
|
||
self.build_display_list_for_outline_if_applicable(state,
|
||
&*self.style,
|
||
&stacking_relative_border_box,
|
||
clip);
|
||
}
|
||
}
|
||
|
||
if self.is_primary_fragment() {
|
||
// Paint the selection point if necessary. Even an empty text fragment may have an
|
||
// insertion point, so we do this even if `empty_rect` is true.
|
||
self.build_display_items_for_selection_if_necessary(state,
|
||
&stacking_relative_border_box,
|
||
display_list_section,
|
||
clip);
|
||
}
|
||
|
||
if empty_rect {
|
||
return
|
||
}
|
||
|
||
debug!("Fragment::build_display_list: intersected. Adding display item...");
|
||
|
||
// Create special per-fragment-type display items.
|
||
self.build_fragment_type_specific_display_items(state,
|
||
&stacking_relative_border_box,
|
||
clip);
|
||
|
||
if opts::get().show_debug_fragment_borders {
|
||
self.build_debug_borders_around_fragment(state, &stacking_relative_border_box, clip)
|
||
}
|
||
}
|
||
|
||
fn build_fragment_type_specific_display_items(&mut self,
|
||
state: &mut DisplayListBuildState,
|
||
stacking_relative_border_box: &Rect<Au>,
|
||
clip: &Rect<Au>) {
|
||
// Compute the context box position relative to the parent stacking context.
|
||
let stacking_relative_content_box =
|
||
self.stacking_relative_content_box(stacking_relative_border_box);
|
||
|
||
match self.specific {
|
||
SpecificFragmentInfo::TruncatedFragment(box TruncatedFragmentInfo {
|
||
text_info: Some(ref text_fragment),
|
||
..
|
||
}) |
|
||
SpecificFragmentInfo::ScannedText(box ref text_fragment) => {
|
||
// Create items for shadows.
|
||
//
|
||
// NB: According to CSS-BACKGROUNDS, text shadows render in *reverse* order (front
|
||
// to back).
|
||
|
||
for text_shadow in self.style.get_inheritedtext().text_shadow.0.iter().rev() {
|
||
self.build_display_list_for_text_fragment(state,
|
||
&*text_fragment,
|
||
&stacking_relative_content_box,
|
||
Some(text_shadow),
|
||
clip);
|
||
}
|
||
|
||
// Create the main text display item.
|
||
self.build_display_list_for_text_fragment(state,
|
||
&*text_fragment,
|
||
&stacking_relative_content_box,
|
||
None,
|
||
clip);
|
||
|
||
if opts::get().show_debug_fragment_borders {
|
||
self.build_debug_borders_around_text_fragments(state,
|
||
self.style(),
|
||
stacking_relative_border_box,
|
||
&stacking_relative_content_box,
|
||
&*text_fragment,
|
||
clip);
|
||
}
|
||
}
|
||
SpecificFragmentInfo::Generic |
|
||
SpecificFragmentInfo::GeneratedContent(..) |
|
||
SpecificFragmentInfo::Table |
|
||
SpecificFragmentInfo::TableCell |
|
||
SpecificFragmentInfo::TableRow |
|
||
SpecificFragmentInfo::TableWrapper |
|
||
SpecificFragmentInfo::Multicol |
|
||
SpecificFragmentInfo::MulticolColumn |
|
||
SpecificFragmentInfo::InlineBlock(_) |
|
||
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
|
||
SpecificFragmentInfo::InlineAbsolute(_) |
|
||
SpecificFragmentInfo::TruncatedFragment(_) |
|
||
SpecificFragmentInfo::Svg(_) => {
|
||
if opts::get().show_debug_fragment_borders {
|
||
self.build_debug_borders_around_fragment(state,
|
||
stacking_relative_border_box,
|
||
clip);
|
||
}
|
||
}
|
||
SpecificFragmentInfo::Iframe(ref fragment_info) => {
|
||
if !stacking_relative_content_box.is_empty() {
|
||
let base = state.create_base_display_item(
|
||
&stacking_relative_content_box,
|
||
&ClippingRegion::from_rect(clip),
|
||
self.node,
|
||
self.style.get_cursor(Cursor::Default),
|
||
DisplayListSection::Content);
|
||
let item = DisplayItem::Iframe(box IframeDisplayItem {
|
||
base: base,
|
||
iframe: fragment_info.pipeline_id,
|
||
});
|
||
|
||
let size = Size2D::new(item.bounds().size.width.to_f32_px(),
|
||
item.bounds().size.height.to_f32_px());
|
||
state.iframe_sizes.push((fragment_info.browsing_context_id, TypedSize2D::from_untyped(&size)));
|
||
|
||
state.add_display_item(item);
|
||
}
|
||
}
|
||
SpecificFragmentInfo::Image(ref mut image_fragment) => {
|
||
// Place the image into the display list.
|
||
if let Some(ref image) = image_fragment.image {
|
||
let base = state.create_base_display_item(
|
||
&stacking_relative_content_box,
|
||
&ClippingRegion::from_rect(clip),
|
||
self.node,
|
||
self.style.get_cursor(Cursor::Default),
|
||
DisplayListSection::Content);
|
||
state.add_display_item(DisplayItem::Image(box ImageDisplayItem {
|
||
base: base,
|
||
webrender_image: WebRenderImageInfo::from_image(image),
|
||
image_data: Some(Arc::new(image.bytes.clone())),
|
||
stretch_size: stacking_relative_content_box.size,
|
||
tile_spacing: Size2D::zero(),
|
||
image_rendering: self.style.get_inheritedbox().image_rendering.clone(),
|
||
}));
|
||
}
|
||
}
|
||
SpecificFragmentInfo::Canvas(ref canvas_fragment_info) => {
|
||
let computed_width = canvas_fragment_info.dom_width.to_px();
|
||
let computed_height = canvas_fragment_info.dom_height.to_px();
|
||
|
||
let canvas_data = match canvas_fragment_info.ipc_renderer {
|
||
Some(ref ipc_renderer) => {
|
||
let ipc_renderer = ipc_renderer.lock().unwrap();
|
||
let (sender, receiver) = ipc::channel().unwrap();
|
||
ipc_renderer.send(CanvasMsg::FromLayout(
|
||
FromLayoutMsg::SendData(sender))).unwrap();
|
||
receiver.recv().unwrap()
|
||
},
|
||
None => return,
|
||
};
|
||
|
||
let base = state.create_base_display_item(
|
||
&stacking_relative_content_box,
|
||
&ClippingRegion::from_rect(clip),
|
||
self.node,
|
||
self.style.get_cursor(Cursor::Default),
|
||
DisplayListSection::Content);
|
||
let display_item = match canvas_data {
|
||
CanvasData::Image(canvas_data) => {
|
||
DisplayItem::Image(box ImageDisplayItem {
|
||
base: base,
|
||
webrender_image: WebRenderImageInfo {
|
||
width: computed_width as u32,
|
||
height: computed_height as u32,
|
||
format: PixelFormat::BGRA8,
|
||
key: Some(canvas_data.image_key),
|
||
},
|
||
image_data: None,
|
||
stretch_size: stacking_relative_content_box.size,
|
||
tile_spacing: Size2D::zero(),
|
||
image_rendering: image_rendering::T::auto,
|
||
})
|
||
}
|
||
CanvasData::WebGL(context_id) => {
|
||
DisplayItem::WebGL(box WebGLDisplayItem {
|
||
base: base,
|
||
context_id: context_id,
|
||
})
|
||
}
|
||
};
|
||
|
||
state.add_display_item(display_item);
|
||
}
|
||
SpecificFragmentInfo::UnscannedText(_) => {
|
||
panic!("Shouldn't see unscanned fragments here.")
|
||
}
|
||
SpecificFragmentInfo::TableColumn(_) => {
|
||
panic!("Shouldn't see table column fragments here.")
|
||
}
|
||
}
|
||
}
|
||
|
||
fn stacking_context_id(&self) -> StackingContextId {
|
||
StackingContextId::new(self.unique_id(IdType::StackingContext))
|
||
}
|
||
|
||
fn create_stacking_context(&self,
|
||
id: StackingContextId,
|
||
base_flow: &BaseFlow,
|
||
scroll_policy: ScrollPolicy,
|
||
mode: StackingContextCreationMode,
|
||
parent_scroll_id: ClipId)
|
||
-> StackingContext {
|
||
let border_box =
|
||
self.stacking_relative_border_box(&base_flow.stacking_relative_position,
|
||
&base_flow.early_absolute_position_info
|
||
.relative_containing_block_size,
|
||
base_flow.early_absolute_position_info
|
||
.relative_containing_block_mode,
|
||
CoordinateSystem::Parent);
|
||
// First, compute the offset of our border box (including relative positioning)
|
||
// from our flow origin, since that is what `BaseFlow::overflow` is relative to.
|
||
let border_box_offset =
|
||
border_box.translate(&-base_flow.stacking_relative_position).origin;
|
||
// Then, using that, compute our overflow region relative to our border box.
|
||
let overflow = base_flow.overflow.paint.translate(&-border_box_offset.to_vector());
|
||
|
||
// Create the filter pipeline.
|
||
let effects = self.style().get_effects();
|
||
let mut filters = effects.filter.clone().0.into_vec();
|
||
if effects.opacity != 1.0 {
|
||
filters.push(Filter::Opacity(effects.opacity))
|
||
}
|
||
|
||
let context_type = match mode {
|
||
StackingContextCreationMode::PseudoFloat => StackingContextType::PseudoFloat,
|
||
StackingContextCreationMode::PseudoPositioned => StackingContextType::PseudoPositioned,
|
||
_ => StackingContextType::Real,
|
||
};
|
||
|
||
StackingContext::new(id,
|
||
context_type,
|
||
&border_box,
|
||
&overflow,
|
||
self.effective_z_index(),
|
||
filters.into(),
|
||
self.style().get_effects().mix_blend_mode.to_mix_blend_mode(),
|
||
self.transform_matrix(&border_box),
|
||
self.style().get_used_transform_style().to_transform_style(),
|
||
self.perspective_matrix(&border_box),
|
||
scroll_policy,
|
||
parent_scroll_id)
|
||
}
|
||
|
||
fn build_display_list_for_text_fragment(&self,
|
||
state: &mut DisplayListBuildState,
|
||
text_fragment: &ScannedTextFragmentInfo,
|
||
stacking_relative_content_box: &Rect<Au>,
|
||
text_shadow: Option<&Shadow>,
|
||
clip: &Rect<Au>) {
|
||
// TODO(emilio): Allow changing more properties by ::selection
|
||
let text_color = if let Some(shadow) = text_shadow {
|
||
// If we're painting a shadow, paint the text the same color as the shadow.
|
||
self.style().resolve_color(shadow.color)
|
||
} else if text_fragment.selected() {
|
||
// Otherwise, paint the text with the color as described in its styling.
|
||
self.selected_style().get_color().color
|
||
} else {
|
||
self.style().get_color().color
|
||
};
|
||
let offset = text_shadow.map(|s| Vector2D::new(s.offset_x, s.offset_y)).unwrap_or_else(Vector2D::zero);
|
||
let shadow_blur_radius = text_shadow.map(|s| s.blur_radius).unwrap_or(Au(0));
|
||
|
||
// Determine the orientation and cursor to use.
|
||
let (orientation, cursor) = if self.style.writing_mode.is_vertical() {
|
||
// TODO: Distinguish between 'sideways-lr' and 'sideways-rl' writing modes in CSS
|
||
// Writing Modes Level 4.
|
||
(TextOrientation::SidewaysRight, Cursor::VerticalText)
|
||
} else {
|
||
(TextOrientation::Upright, Cursor::Text)
|
||
};
|
||
|
||
// Compute location of the baseline.
|
||
//
|
||
// FIXME(pcwalton): Get the real container size.
|
||
let container_size = Size2D::zero();
|
||
let metrics = &text_fragment.run.font_metrics;
|
||
let stacking_relative_content_box = stacking_relative_content_box.translate(&offset);
|
||
let baseline_origin = stacking_relative_content_box.origin +
|
||
LogicalPoint::new(self.style.writing_mode,
|
||
Au(0),
|
||
metrics.ascent).to_physical(self.style.writing_mode,
|
||
container_size).to_vector();
|
||
|
||
// Create the text display item.
|
||
let base = state.create_base_display_item(&stacking_relative_content_box,
|
||
&ClippingRegion::from_rect(&clip),
|
||
self.node,
|
||
self.style().get_cursor(cursor),
|
||
DisplayListSection::Content);
|
||
state.add_display_item(DisplayItem::Text(box TextDisplayItem {
|
||
base: base,
|
||
text_run: text_fragment.run.clone(),
|
||
range: text_fragment.range,
|
||
text_color: text_color.to_gfx_color(),
|
||
orientation: orientation,
|
||
baseline_origin: baseline_origin,
|
||
blur_radius: shadow_blur_radius,
|
||
}));
|
||
|
||
// Create display items for text decorations.
|
||
let mut text_decorations = self.style()
|
||
.get_inheritedtext()
|
||
._servo_text_decorations_in_effect;
|
||
// Note that the text decoration colors are always the same as the text color.
|
||
text_decorations.underline = text_decorations.underline.map(|_| text_color);
|
||
text_decorations.overline = text_decorations.overline.map(|_| text_color);
|
||
text_decorations.line_through = text_decorations.line_through.map(|_| text_color);
|
||
|
||
let stacking_relative_content_box =
|
||
LogicalRect::from_physical(self.style.writing_mode,
|
||
stacking_relative_content_box,
|
||
container_size);
|
||
if let Some(ref underline_color) = text_decorations.underline {
|
||
let mut stacking_relative_box = stacking_relative_content_box;
|
||
stacking_relative_box.start.b = stacking_relative_content_box.start.b +
|
||
metrics.ascent - metrics.underline_offset;
|
||
stacking_relative_box.size.block = metrics.underline_size;
|
||
self.build_display_list_for_text_decoration(state,
|
||
underline_color,
|
||
&stacking_relative_box,
|
||
clip,
|
||
shadow_blur_radius);
|
||
}
|
||
|
||
if let Some(ref overline_color) = text_decorations.overline {
|
||
let mut stacking_relative_box = stacking_relative_content_box;
|
||
stacking_relative_box.size.block = metrics.underline_size;
|
||
self.build_display_list_for_text_decoration(state,
|
||
overline_color,
|
||
&stacking_relative_box,
|
||
clip,
|
||
shadow_blur_radius);
|
||
}
|
||
|
||
if let Some(ref line_through_color) = text_decorations.line_through {
|
||
let mut stacking_relative_box = stacking_relative_content_box;
|
||
stacking_relative_box.start.b = stacking_relative_box.start.b + metrics.ascent -
|
||
metrics.strikeout_offset;
|
||
stacking_relative_box.size.block = metrics.strikeout_size;
|
||
self.build_display_list_for_text_decoration(state,
|
||
line_through_color,
|
||
&stacking_relative_box,
|
||
clip,
|
||
shadow_blur_radius);
|
||
}
|
||
}
|
||
|
||
fn build_display_list_for_text_decoration(&self,
|
||
state: &mut DisplayListBuildState,
|
||
color: &RGBA,
|
||
stacking_relative_box: &LogicalRect<Au>,
|
||
clip: &Rect<Au>,
|
||
blur_radius: Au) {
|
||
// Perhaps surprisingly, text decorations are box shadows. This is because they may need
|
||
// to have blur in the case of `text-shadow`, and this doesn't hurt performance because box
|
||
// shadows are optimized into essentially solid colors if there is no need for the blur.
|
||
//
|
||
// FIXME(pcwalton, #2795): Get the real container size.
|
||
let container_size = Size2D::zero();
|
||
let stacking_relative_box = stacking_relative_box.to_physical(self.style.writing_mode,
|
||
container_size);
|
||
let base = state.create_base_display_item(
|
||
&shadow_bounds(&stacking_relative_box, blur_radius, Au(0)),
|
||
&ClippingRegion::from_rect(&clip),
|
||
self.node,
|
||
self.style.get_cursor(Cursor::Default),
|
||
DisplayListSection::Content);
|
||
state.add_display_item(DisplayItem::BoxShadow(box BoxShadowDisplayItem {
|
||
base: base,
|
||
box_bounds: stacking_relative_box,
|
||
color: color.to_gfx_color(),
|
||
offset: Vector2D::zero(),
|
||
blur_radius: blur_radius,
|
||
spread_radius: Au(0),
|
||
border_radius: Au(0),
|
||
clip_mode: BoxShadowClipMode::None,
|
||
}));
|
||
}
|
||
|
||
fn unique_id(&self, id_type: IdType) -> u64 {
|
||
let fragment_type = self.fragment_type();
|
||
let id = match id_type {
|
||
IdType::StackingContext | IdType::OverflowClip => self.node.id() as usize,
|
||
IdType::CSSClip => self as *const _ as usize,
|
||
};
|
||
combine_id_with_fragment_type(id, fragment_type) as u64
|
||
}
|
||
|
||
fn fragment_type(&self) -> FragmentType {
|
||
match self.pseudo {
|
||
PseudoElementType::Normal => FragmentType::FragmentBody,
|
||
PseudoElementType::Before(_) => FragmentType::BeforePseudoContent,
|
||
PseudoElementType::After(_) => FragmentType::AfterPseudoContent,
|
||
PseudoElementType::DetailsSummary(_) => FragmentType::FragmentBody,
|
||
PseudoElementType::DetailsContent(_) => FragmentType::FragmentBody,
|
||
}
|
||
}
|
||
}
|
||
|
||
pub trait BlockFlowDisplayListBuilding {
|
||
fn collect_stacking_contexts_for_block(&mut self, state: &mut DisplayListBuildState);
|
||
|
||
fn transform_clip_to_coordinate_space(&mut self,
|
||
state: &mut DisplayListBuildState,
|
||
preserved_state: &mut PreservedDisplayListState);
|
||
fn setup_clipping_for_block(&mut self,
|
||
state: &mut DisplayListBuildState,
|
||
preserved_state: &mut PreservedDisplayListState,
|
||
stacking_context_type: BlockStackingContextType)
|
||
-> ClipId;
|
||
fn setup_scroll_root_for_overflow(&mut self,
|
||
state: &mut DisplayListBuildState,
|
||
preserved_state: &mut PreservedDisplayListState,
|
||
border_box: &Rect<Au>);
|
||
fn setup_scroll_root_for_css_clip(&mut self,
|
||
state: &mut DisplayListBuildState,
|
||
preserved_state: &mut PreservedDisplayListState,
|
||
stacking_relative_border_box: &Rect<Au>);
|
||
fn create_pseudo_stacking_context_for_block(&mut self,
|
||
parent_stacking_context_id: StackingContextId,
|
||
parent_scroll_root_id: ClipId,
|
||
state: &mut DisplayListBuildState);
|
||
fn create_real_stacking_context_for_block(&mut self,
|
||
parent_stacking_context_id: StackingContextId,
|
||
parent_scroll_root_id: ClipId,
|
||
state: &mut DisplayListBuildState);
|
||
fn build_display_list_for_block(&mut self,
|
||
state: &mut DisplayListBuildState,
|
||
border_painting_mode: BorderPaintingMode);
|
||
}
|
||
|
||
/// This structure manages ensuring that modification to DisplayListBuildState
|
||
/// is only temporary. It's useful for moving recursively down the flow tree
|
||
/// and ensuring that the state is restored for siblings. To use this structure,
|
||
/// we must call PreservedDisplayListState::restore in order to restore the state.
|
||
/// TODO(mrobinson): It would be nice to use RAII here to avoid having to call restore.
|
||
pub struct PreservedDisplayListState {
|
||
stacking_context_id: StackingContextId,
|
||
scroll_root_id: ClipId,
|
||
containing_block_scroll_root_id: ClipId,
|
||
clips_pushed: usize,
|
||
containing_block_clips_pushed: usize,
|
||
transform_style: TransformStyle,
|
||
}
|
||
|
||
impl PreservedDisplayListState {
|
||
fn new(state: &mut DisplayListBuildState) -> PreservedDisplayListState {
|
||
PreservedDisplayListState {
|
||
stacking_context_id: state.current_stacking_context_id,
|
||
scroll_root_id: state.current_scroll_root_id,
|
||
containing_block_scroll_root_id: state.containing_block_scroll_root_id,
|
||
clips_pushed: 0,
|
||
containing_block_clips_pushed: 0,
|
||
transform_style: state.current_transform_style,
|
||
}
|
||
}
|
||
|
||
fn switch_to_containing_block_clip(&mut self, state: &mut DisplayListBuildState) {
|
||
let clip = state.containing_block_clip_stack.last().cloned().unwrap_or_else(max_rect);
|
||
state.clip_stack.push(clip);
|
||
self.clips_pushed += 1;
|
||
}
|
||
|
||
fn restore(self, state: &mut DisplayListBuildState) {
|
||
state.current_stacking_context_id = self.stacking_context_id;
|
||
state.current_scroll_root_id = self.scroll_root_id;
|
||
state.containing_block_scroll_root_id = self.containing_block_scroll_root_id;
|
||
|
||
let truncate_length = state.clip_stack.len() - self.clips_pushed;
|
||
state.clip_stack.truncate(truncate_length);
|
||
|
||
let truncate_length = state.containing_block_clip_stack.len() -
|
||
self.containing_block_clips_pushed;
|
||
state.containing_block_clip_stack.truncate(truncate_length);
|
||
|
||
state.current_transform_style = self.transform_style;
|
||
}
|
||
|
||
fn push_clip(&mut self,
|
||
state: &mut DisplayListBuildState,
|
||
clip: &Rect<Au>,
|
||
positioning: position::T) {
|
||
let mut clip = *clip;
|
||
if positioning != position::T::fixed {
|
||
if let Some(old_clip) = state.clip_stack.last() {
|
||
clip = old_clip.intersection(&clip).unwrap_or_else(Rect::zero);
|
||
}
|
||
}
|
||
|
||
state.clip_stack.push(clip);
|
||
self.clips_pushed += 1;
|
||
|
||
if position::T::absolute == positioning {
|
||
state.containing_block_clip_stack.push(clip);
|
||
self.containing_block_clips_pushed += 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
impl BlockFlowDisplayListBuilding for BlockFlow {
|
||
fn transform_clip_to_coordinate_space(&mut self,
|
||
state: &mut DisplayListBuildState,
|
||
preserved_state: &mut PreservedDisplayListState) {
|
||
if state.clip_stack.is_empty() {
|
||
return;
|
||
}
|
||
let border_box = self.stacking_relative_position(CoordinateSystem::Parent);
|
||
let transform = match self.fragment.transform_matrix(&border_box) {
|
||
Some(transform) => transform,
|
||
None => return,
|
||
};
|
||
|
||
let perspective = self.fragment.perspective_matrix(&border_box)
|
||
.unwrap_or_else(Transform3D::identity);
|
||
let transform = transform.pre_mul(&perspective).inverse();
|
||
|
||
let origin = &border_box.origin;
|
||
let transform_clip = |clip: &Rect<Au>| {
|
||
if *clip == max_rect() {
|
||
return *clip;
|
||
}
|
||
|
||
match transform {
|
||
Some(transform) if transform.m13 != 0.0 || transform.m23 != 0.0 => {
|
||
// We cannot properly handle perspective transforms, because there may be a
|
||
// situation where an element is transformed from outside the clip into the
|
||
// clip region. Here we don't have enough information to detect when that is
|
||
// happening. For the moment we just punt on trying to optimize the display
|
||
// list for those cases.
|
||
max_rect()
|
||
}
|
||
Some(transform) => {
|
||
let clip = Rect::new(Point2D::new((clip.origin.x - origin.x).to_f32_px(),
|
||
(clip.origin.y - origin.y).to_f32_px()),
|
||
Size2D::new(clip.size.width.to_f32_px(),
|
||
clip.size.height.to_f32_px()));
|
||
|
||
let clip = transform.transform_rect(&clip);
|
||
|
||
Rect::new(Point2D::new(Au::from_f32_px(clip.origin.x),
|
||
Au::from_f32_px(clip.origin.y)),
|
||
Size2D::new(Au::from_f32_px(clip.size.width),
|
||
Au::from_f32_px(clip.size.height)))
|
||
}
|
||
None => Rect::zero(),
|
||
}
|
||
};
|
||
|
||
if let Some(clip) = state.clip_stack.last().cloned() {
|
||
state.clip_stack.push(transform_clip(&clip));
|
||
preserved_state.clips_pushed += 1;
|
||
}
|
||
|
||
if let Some(clip) = state.containing_block_clip_stack.last().cloned() {
|
||
state.containing_block_clip_stack.push(transform_clip(&clip));
|
||
preserved_state.containing_block_clips_pushed += 1;
|
||
}
|
||
}
|
||
|
||
fn collect_stacking_contexts_for_block(&mut self, state: &mut DisplayListBuildState) {
|
||
let mut preserved_state = PreservedDisplayListState::new(state);
|
||
|
||
let block_stacking_context_type = self.block_stacking_context_type();
|
||
self.base.stacking_context_id = match block_stacking_context_type {
|
||
BlockStackingContextType::NonstackingContext => state.current_stacking_context_id,
|
||
BlockStackingContextType::PseudoStackingContext |
|
||
BlockStackingContextType::StackingContext => self.fragment.stacking_context_id(),
|
||
};
|
||
state.current_stacking_context_id = self.base.stacking_context_id;
|
||
|
||
// We are getting the id of the scroll root that contains us here, not the id of
|
||
// any scroll root that we create. If we create a scroll root, its id will be
|
||
// stored in state.current_scroll_root_id. If we should create a stacking context,
|
||
// we don't want it to be clipped by its own scroll root.
|
||
let containing_scroll_root_id = self.setup_clipping_for_block(state,
|
||
&mut preserved_state,
|
||
block_stacking_context_type);
|
||
|
||
if establishes_containing_block_for_absolute(self.positioning()) {
|
||
state.containing_block_scroll_root_id = state.current_scroll_root_id;
|
||
}
|
||
|
||
match block_stacking_context_type {
|
||
BlockStackingContextType::NonstackingContext => {
|
||
self.base.collect_stacking_contexts_for_children(state);
|
||
}
|
||
BlockStackingContextType::PseudoStackingContext => {
|
||
self.create_pseudo_stacking_context_for_block(preserved_state.stacking_context_id,
|
||
containing_scroll_root_id,
|
||
state);
|
||
}
|
||
BlockStackingContextType::StackingContext => {
|
||
self.create_real_stacking_context_for_block(preserved_state.stacking_context_id,
|
||
containing_scroll_root_id,
|
||
state);
|
||
}
|
||
}
|
||
|
||
preserved_state.restore(state);
|
||
}
|
||
|
||
fn setup_clipping_for_block(&mut self,
|
||
state: &mut DisplayListBuildState,
|
||
preserved_state: &mut PreservedDisplayListState,
|
||
stacking_context_type: BlockStackingContextType)
|
||
-> ClipId {
|
||
// If this block is absolutely positioned, we should be clipped and positioned by
|
||
// the scroll root of our nearest ancestor that establishes a containing block.
|
||
let containing_scroll_root_id = match self.positioning() {
|
||
position::T::absolute => {
|
||
preserved_state.switch_to_containing_block_clip(state);
|
||
state.current_scroll_root_id = state.containing_block_scroll_root_id;
|
||
state.containing_block_scroll_root_id
|
||
}
|
||
position::T::fixed => {
|
||
preserved_state.push_clip(state, &max_rect(), position::T::fixed);
|
||
state.current_scroll_root_id
|
||
}
|
||
_ => state.current_scroll_root_id,
|
||
};
|
||
self.base.scroll_root_id = Some(containing_scroll_root_id);
|
||
|
||
let coordinate_system = if self.fragment.establishes_stacking_context() {
|
||
CoordinateSystem::Own
|
||
} else {
|
||
CoordinateSystem::Parent
|
||
};
|
||
|
||
let stacking_relative_border_box = self.fragment.stacking_relative_border_box(
|
||
&self.base.stacking_relative_position,
|
||
&self.base.early_absolute_position_info.relative_containing_block_size,
|
||
self.base.early_absolute_position_info.relative_containing_block_mode,
|
||
coordinate_system);
|
||
|
||
if stacking_context_type == BlockStackingContextType::StackingContext {
|
||
self.transform_clip_to_coordinate_space(state, preserved_state);
|
||
}
|
||
|
||
self.setup_scroll_root_for_overflow(state, preserved_state, &stacking_relative_border_box);
|
||
self.setup_scroll_root_for_css_clip(state, preserved_state, &stacking_relative_border_box);
|
||
self.base.clip = state.clip_stack.last().cloned().unwrap_or_else(max_rect);
|
||
|
||
match self.positioning() {
|
||
position::T::absolute | position::T::relative | position::T::fixed =>
|
||
state.containing_block_scroll_root_id = state.current_scroll_root_id,
|
||
_ => {}
|
||
}
|
||
|
||
containing_scroll_root_id
|
||
}
|
||
|
||
fn setup_scroll_root_for_overflow(&mut self,
|
||
state: &mut DisplayListBuildState,
|
||
preserved_state: &mut PreservedDisplayListState,
|
||
border_box: &Rect<Au>) {
|
||
if !self.overflow_style_may_require_scroll_root() {
|
||
return;
|
||
}
|
||
|
||
let content_box = self.fragment.stacking_relative_content_box(&border_box);
|
||
let has_scrolling_overflow =
|
||
self.base.overflow.scroll.origin != Point2D::zero() ||
|
||
self.base.overflow.scroll.size.width > content_box.size.width ||
|
||
self.base.overflow.scroll.size.height > content_box.size.height ||
|
||
overflow_x::T::hidden == self.fragment.style.get_box().overflow_x ||
|
||
overflow_x::T::hidden == self.fragment.style.get_box().overflow_y;
|
||
|
||
self.mark_scrolling_overflow(has_scrolling_overflow);
|
||
if !has_scrolling_overflow {
|
||
return;
|
||
}
|
||
|
||
// If we already have a scroll root for this flow, just return. This can happen
|
||
// when fragments map to more than one flow, such as in the case of table
|
||
// wrappers. We just accept the first scroll root in that case.
|
||
let new_scroll_root_id = ClipId::new(self.fragment.unique_id(IdType::OverflowClip),
|
||
state.layout_context.id.to_webrender());
|
||
if state.has_scroll_root(new_scroll_root_id) {
|
||
return;
|
||
}
|
||
|
||
let clip_rect = Rect::new(Point2D::zero(), content_box.size);
|
||
let mut clip = ClippingRegion::from_rect(&clip_rect);
|
||
|
||
let border_radii = build_border_radius_for_inner_rect(&border_box,
|
||
&self.fragment.style);
|
||
if !border_radii.is_square() {
|
||
clip.intersect_with_rounded_rect(&clip_rect, &border_radii)
|
||
}
|
||
|
||
let content_size = self.base.overflow.scroll.origin + self.base.overflow.scroll.size;
|
||
let mut content_size = Size2D::new(content_size.x, content_size.y);
|
||
if overflow_x::T::hidden == self.fragment.style.get_box().overflow_x {
|
||
content_size.width = content_box.size.width;
|
||
}
|
||
|
||
if overflow_x::T::hidden == self.fragment.style.get_box().overflow_y {
|
||
content_size.height = content_box.size.height;
|
||
}
|
||
|
||
if overflow_x::T::hidden == self.fragment.style.get_box().overflow_y ||
|
||
overflow_x::T::hidden == self.fragment.style.get_box().overflow_x {
|
||
preserved_state.push_clip(state, &border_box, self.positioning());
|
||
}
|
||
|
||
let clip_rect = Rect::new(Point2D::zero(), content_box.size);
|
||
let mut clip = ClippingRegion::from_rect(&clip_rect);
|
||
let radii = build_border_radius_for_inner_rect(&border_box, &self.fragment.style);
|
||
if !radii.is_square() {
|
||
clip.intersect_with_rounded_rect(&clip_rect, &radii)
|
||
}
|
||
|
||
let parent_id = self.scroll_root_id(state.layout_context.id);
|
||
state.add_scroll_root(
|
||
ScrollRoot {
|
||
id: new_scroll_root_id,
|
||
parent_id: parent_id,
|
||
clip: clip,
|
||
content_rect: Rect::new(content_box.origin, content_size),
|
||
},
|
||
self.base.stacking_context_id
|
||
);
|
||
|
||
self.base.scroll_root_id = Some(new_scroll_root_id);
|
||
state.current_scroll_root_id = new_scroll_root_id;
|
||
}
|
||
|
||
/// Adds a scroll root for a block to take the `clip` property into account
|
||
/// per CSS 2.1 § 11.1.2.
|
||
fn setup_scroll_root_for_css_clip(&mut self,
|
||
state: &mut DisplayListBuildState,
|
||
preserved_state: &mut PreservedDisplayListState,
|
||
stacking_relative_border_box: &Rect<Au>) {
|
||
// Account for `clip` per CSS 2.1 § 11.1.2.
|
||
let style_clip_rect = match self.fragment.style().get_effects().clip {
|
||
Either::First(style_clip_rect) => style_clip_rect,
|
||
_ => return,
|
||
};
|
||
|
||
let clip_origin = Point2D::new(stacking_relative_border_box.origin.x +
|
||
style_clip_rect.left.unwrap_or(Au(0)),
|
||
stacking_relative_border_box.origin.y +
|
||
style_clip_rect.top.unwrap_or(Au(0)));
|
||
let right = style_clip_rect.right.unwrap_or(stacking_relative_border_box.size.width);
|
||
let bottom = style_clip_rect.bottom.unwrap_or(stacking_relative_border_box.size.height);
|
||
let clip_size = Size2D::new(right - clip_origin.x, bottom - clip_origin.y);
|
||
|
||
// We use the node id to create scroll roots for overflow properties, so we
|
||
// use the fragment address to do the same for CSS clipping.
|
||
// TODO(mrobinson): This should be more resilient while maintaining the space
|
||
// efficiency of ScrollRootId.
|
||
let new_scroll_root_id = ClipId::new(self.fragment.unique_id(IdType::CSSClip),
|
||
state.layout_context.id.to_webrender());
|
||
|
||
// If we already have a scroll root for this flow, just return. This can happen
|
||
// when fragments map to more than one flow, such as in the case of table
|
||
// wrappers. We just accept the first scroll root in that case.
|
||
if state.has_scroll_root(new_scroll_root_id) {
|
||
return;
|
||
}
|
||
|
||
let content_rect = Rect::new(clip_origin, clip_size);
|
||
preserved_state.push_clip(state, &content_rect, self.positioning());
|
||
|
||
let parent_id = self.scroll_root_id(state.layout_context.id);
|
||
state.add_scroll_root(
|
||
ScrollRoot {
|
||
id: new_scroll_root_id,
|
||
parent_id: parent_id,
|
||
clip: ClippingRegion::from_rect(&Rect::new(Point2D::zero(), clip_size)),
|
||
content_rect: content_rect,
|
||
},
|
||
self.base.stacking_context_id
|
||
);
|
||
|
||
self.base.scroll_root_id = Some(new_scroll_root_id);
|
||
state.current_scroll_root_id = new_scroll_root_id;
|
||
}
|
||
|
||
fn create_pseudo_stacking_context_for_block(&mut self,
|
||
parent_stacking_context_id: StackingContextId,
|
||
parent_scroll_root_id: ClipId,
|
||
state: &mut DisplayListBuildState) {
|
||
let creation_mode = if self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) ||
|
||
self.fragment.style.get_box().position != position::T::static_ {
|
||
StackingContextCreationMode::PseudoPositioned
|
||
} else {
|
||
assert!(self.base.flags.is_float());
|
||
StackingContextCreationMode::PseudoFloat
|
||
};
|
||
|
||
let new_context = self.fragment.create_stacking_context(self.base.stacking_context_id,
|
||
&self.base,
|
||
ScrollPolicy::Scrollable,
|
||
creation_mode,
|
||
parent_scroll_root_id);
|
||
state.add_stacking_context(parent_stacking_context_id, new_context);
|
||
|
||
self.base.collect_stacking_contexts_for_children(state);
|
||
|
||
let children = state.stacking_context_info.get_mut(&self.base.stacking_context_id)
|
||
.map(|info| info.take_children());
|
||
if let Some(children) = children {
|
||
for child in children {
|
||
if child.context_type == StackingContextType::PseudoFloat {
|
||
state.add_stacking_context(self.base.stacking_context_id, child);
|
||
} else {
|
||
state.add_stacking_context(parent_stacking_context_id, child);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
fn create_real_stacking_context_for_block(&mut self,
|
||
parent_stacking_context_id: StackingContextId,
|
||
parent_scroll_root_id: ClipId,
|
||
state: &mut DisplayListBuildState) {
|
||
let scroll_policy = if self.is_fixed() {
|
||
ScrollPolicy::Fixed
|
||
} else {
|
||
ScrollPolicy::Scrollable
|
||
};
|
||
|
||
let stacking_context = self.fragment.create_stacking_context(
|
||
self.base.stacking_context_id,
|
||
&self.base,
|
||
scroll_policy,
|
||
StackingContextCreationMode::Normal,
|
||
parent_scroll_root_id);
|
||
|
||
state.add_stacking_context(parent_stacking_context_id, stacking_context);
|
||
self.base.collect_stacking_contexts_for_children(state);
|
||
}
|
||
|
||
fn build_display_list_for_block(&mut self,
|
||
state: &mut DisplayListBuildState,
|
||
border_painting_mode: BorderPaintingMode) {
|
||
let background_border_section = if self.base.flags.is_float() {
|
||
DisplayListSection::BackgroundAndBorders
|
||
} else if self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) {
|
||
if self.fragment.establishes_stacking_context() {
|
||
DisplayListSection::BackgroundAndBorders
|
||
} else {
|
||
DisplayListSection::BlockBackgroundsAndBorders
|
||
}
|
||
} else {
|
||
DisplayListSection::BlockBackgroundsAndBorders
|
||
};
|
||
|
||
state.processing_scroll_root_element = self.has_scrolling_overflow();
|
||
|
||
// Add the box that starts the block context.
|
||
self.fragment
|
||
.build_display_list(state,
|
||
&self.base.stacking_relative_position,
|
||
&self.base
|
||
.early_absolute_position_info
|
||
.relative_containing_block_size,
|
||
self.base
|
||
.early_absolute_position_info
|
||
.relative_containing_block_mode,
|
||
border_painting_mode,
|
||
background_border_section,
|
||
&self.base.clip);
|
||
|
||
self.base.build_display_items_for_debugging_tint(state, self.fragment.node);
|
||
|
||
state.processing_scroll_root_element = false;
|
||
}
|
||
|
||
}
|
||
|
||
pub trait InlineFlowDisplayListBuilding {
|
||
fn collect_stacking_contexts_for_inline(&mut self, state: &mut DisplayListBuildState);
|
||
fn build_display_list_for_inline_fragment_at_index(&mut self,
|
||
state: &mut DisplayListBuildState,
|
||
index: usize);
|
||
fn build_display_list_for_inline(&mut self, state: &mut DisplayListBuildState);
|
||
}
|
||
|
||
impl InlineFlowDisplayListBuilding for InlineFlow {
|
||
fn collect_stacking_contexts_for_inline(&mut self, state: &mut DisplayListBuildState) {
|
||
self.base.stacking_context_id = state.current_stacking_context_id;
|
||
self.base.scroll_root_id = Some(state.current_scroll_root_id);
|
||
self.base.clip = state.clip_stack.last().cloned().unwrap_or_else(max_rect);
|
||
|
||
for mut fragment in self.fragments.fragments.iter_mut() {
|
||
let previous_containing_block_scroll_root_id = state.containing_block_scroll_root_id;
|
||
if establishes_containing_block_for_absolute(fragment.style.get_box().position) {
|
||
state.containing_block_scroll_root_id = state.current_scroll_root_id;
|
||
}
|
||
|
||
match fragment.specific {
|
||
SpecificFragmentInfo::InlineBlock(ref mut block_flow) => {
|
||
let block_flow = FlowRef::deref_mut(&mut block_flow.flow_ref);
|
||
block_flow.collect_stacking_contexts(state);
|
||
}
|
||
SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut block_flow) => {
|
||
let block_flow = FlowRef::deref_mut(&mut block_flow.flow_ref);
|
||
block_flow.collect_stacking_contexts(state);
|
||
}
|
||
SpecificFragmentInfo::InlineAbsolute(ref mut block_flow) => {
|
||
let block_flow = FlowRef::deref_mut(&mut block_flow.flow_ref);
|
||
block_flow.collect_stacking_contexts(state);
|
||
}
|
||
_ if fragment.establishes_stacking_context() => {
|
||
fragment.stacking_context_id = fragment.stacking_context_id();
|
||
|
||
let current_stacking_context_id = state.current_stacking_context_id;
|
||
let stacking_context = fragment.create_stacking_context(fragment.stacking_context_id,
|
||
&self.base,
|
||
ScrollPolicy::Scrollable,
|
||
StackingContextCreationMode::Normal,
|
||
state.current_scroll_root_id);
|
||
|
||
state.add_stacking_context(current_stacking_context_id,
|
||
stacking_context);
|
||
}
|
||
_ => fragment.stacking_context_id = state.current_stacking_context_id,
|
||
}
|
||
state.containing_block_scroll_root_id = previous_containing_block_scroll_root_id;
|
||
}
|
||
|
||
}
|
||
|
||
fn build_display_list_for_inline_fragment_at_index(&mut self,
|
||
state: &mut DisplayListBuildState,
|
||
index: usize) {
|
||
let fragment = self.fragments.fragments.get_mut(index).unwrap();
|
||
fragment.build_display_list(state,
|
||
&self.base.stacking_relative_position,
|
||
&self.base
|
||
.early_absolute_position_info
|
||
.relative_containing_block_size,
|
||
self.base
|
||
.early_absolute_position_info
|
||
.relative_containing_block_mode,
|
||
BorderPaintingMode::Separate,
|
||
DisplayListSection::Content,
|
||
&self.base.clip);
|
||
}
|
||
|
||
fn build_display_list_for_inline(&mut self, state: &mut DisplayListBuildState) {
|
||
// TODO(#228): Once we form lines and have their cached bounds, we can be smarter and
|
||
// not recurse on a line if nothing in it can intersect the dirty region.
|
||
debug!("Flow: building display list for {} inline fragments", self.fragments.len());
|
||
|
||
// We iterate using an index here, because we want to avoid doing a doing
|
||
// a double-borrow of self (one mutable for the method call and one immutable
|
||
// for the self.fragments.fragment iterator itself).
|
||
for index in 0..self.fragments.fragments.len() {
|
||
let (establishes_stacking_context, stacking_context_id) = {
|
||
let fragment = self.fragments.fragments.get(index).unwrap();
|
||
(self.base.stacking_context_id != fragment.stacking_context_id,
|
||
fragment.stacking_context_id)
|
||
};
|
||
|
||
let parent_stacking_context_id = state.current_stacking_context_id;
|
||
if establishes_stacking_context {
|
||
state.current_stacking_context_id = stacking_context_id;
|
||
}
|
||
|
||
self.build_display_list_for_inline_fragment_at_index(state, index);
|
||
|
||
if establishes_stacking_context {
|
||
state.current_stacking_context_id = parent_stacking_context_id
|
||
}
|
||
}
|
||
|
||
if !self.fragments.fragments.is_empty() {
|
||
self.base.build_display_items_for_debugging_tint(state,
|
||
self.fragments.fragments[0].node);
|
||
}
|
||
}
|
||
}
|
||
|
||
pub trait ListItemFlowDisplayListBuilding {
|
||
fn build_display_list_for_list_item(&mut self, state: &mut DisplayListBuildState);
|
||
}
|
||
|
||
impl ListItemFlowDisplayListBuilding for ListItemFlow {
|
||
fn build_display_list_for_list_item(&mut self, state: &mut DisplayListBuildState) {
|
||
// Draw the marker, if applicable.
|
||
for marker in &mut self.marker_fragments {
|
||
marker.build_display_list(state,
|
||
&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,
|
||
BorderPaintingMode::Separate,
|
||
DisplayListSection::Content,
|
||
&self.block_flow.base.clip);
|
||
}
|
||
|
||
// Draw the rest of the block.
|
||
self.block_flow.build_display_list_for_block(state, BorderPaintingMode::Separate)
|
||
}
|
||
}
|
||
|
||
pub trait FlexFlowDisplayListBuilding {
|
||
fn build_display_list_for_flex(&mut self, state: &mut DisplayListBuildState);
|
||
}
|
||
|
||
impl FlexFlowDisplayListBuilding for FlexFlow {
|
||
fn build_display_list_for_flex(&mut self, state: &mut DisplayListBuildState) {
|
||
// Draw the rest of the block.
|
||
self.as_mut_block().build_display_list_for_block(state, BorderPaintingMode::Separate)
|
||
}
|
||
}
|
||
|
||
trait BaseFlowDisplayListBuilding {
|
||
fn build_display_items_for_debugging_tint(&self,
|
||
state: &mut DisplayListBuildState,
|
||
node: OpaqueNode);
|
||
}
|
||
|
||
impl BaseFlowDisplayListBuilding for BaseFlow {
|
||
fn build_display_items_for_debugging_tint(&self,
|
||
state: &mut DisplayListBuildState,
|
||
node: OpaqueNode) {
|
||
if !opts::get().show_debug_parallel_layout {
|
||
return
|
||
}
|
||
|
||
let thread_id = self.thread_id;
|
||
let stacking_context_relative_bounds =
|
||
Rect::new(self.stacking_relative_position.to_point(),
|
||
self.position.size.to_physical(self.writing_mode));
|
||
|
||
let mut color = THREAD_TINT_COLORS[thread_id as usize % THREAD_TINT_COLORS.len()];
|
||
color.a = 1.0;
|
||
let base = state.create_base_display_item(
|
||
&stacking_context_relative_bounds.inflate(Au::from_px(2), Au::from_px(2)),
|
||
&ClippingRegion::from_rect(&self.clip),
|
||
node,
|
||
None,
|
||
DisplayListSection::Content);
|
||
state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
|
||
base: base,
|
||
border_widths: SideOffsets2D::new_all_same(Au::from_px(2)),
|
||
details: BorderDetails::Normal(NormalBorder {
|
||
color: SideOffsets2D::new_all_same(color),
|
||
style: SideOffsets2D::new_all_same(border_style::T::solid),
|
||
radius: BorderRadii::all_same(Au(0)),
|
||
}),
|
||
}));
|
||
}
|
||
}
|
||
|
||
trait ServoComputedValuesCursorUtility {
|
||
fn get_cursor(&self, default_cursor: Cursor) -> Option<Cursor>;
|
||
}
|
||
|
||
impl ServoComputedValuesCursorUtility for ServoComputedValues {
|
||
/// Gets the cursor to use given the specific ServoComputedValues. `default_cursor` specifies
|
||
/// the cursor to use if `cursor` is `auto`. Typically, this will be `PointerCursor`, but for
|
||
/// text display items it may be `TextCursor` or `VerticalTextCursor`.
|
||
#[inline]
|
||
fn get_cursor(&self, default_cursor: Cursor) -> Option<Cursor> {
|
||
match (self.get_pointing().pointer_events, self.get_pointing().cursor) {
|
||
(pointer_events::T::none, _) => None,
|
||
(pointer_events::T::auto, cursor::Keyword::Auto) => Some(default_cursor),
|
||
(pointer_events::T::auto, cursor::Keyword::Cursor(cursor)) => Some(cursor),
|
||
}
|
||
}
|
||
}
|
||
|
||
// A helper data structure for gradients.
|
||
#[derive(Copy, Clone)]
|
||
struct StopRun {
|
||
start_offset: f32,
|
||
end_offset: f32,
|
||
start_index: usize,
|
||
stop_count: usize,
|
||
}
|
||
|
||
fn position_to_offset(position: LengthOrPercentage, total_length: Au) -> f32 {
|
||
match position {
|
||
LengthOrPercentage::Length(Au(length)) => length as f32 / total_length.0 as f32,
|
||
LengthOrPercentage::Percentage(percentage) => percentage.0 as f32,
|
||
LengthOrPercentage::Calc(calc) => {
|
||
calc.to_used_value(Some(total_length)).unwrap().0 as f32 / total_length.0 as f32
|
||
},
|
||
}
|
||
}
|
||
|
||
/// Adjusts `content_rect` as necessary for the given spread, and blur so that the resulting
|
||
/// bounding rect contains all of a shadow's ink.
|
||
fn shadow_bounds(content_rect: &Rect<Au>, blur_radius: Au, spread_radius: Au) -> Rect<Au> {
|
||
let inflation = spread_radius + blur_radius * BLUR_INFLATION_FACTOR;
|
||
content_rect.inflate(inflation, inflation)
|
||
}
|
||
|
||
/// Allows a CSS color to be converted into a graphics color.
|
||
pub trait ToGfxColor {
|
||
/// Converts a CSS color to a graphics color.
|
||
fn to_gfx_color(&self) -> ColorF;
|
||
}
|
||
|
||
impl ToGfxColor for RGBA {
|
||
fn to_gfx_color(&self) -> ColorF {
|
||
ColorF::new(self.red_f32(), self.green_f32(), self.blue_f32(), self.alpha_f32())
|
||
}
|
||
}
|
||
|
||
/// Describes how to paint the borders.
|
||
#[derive(Copy, Clone)]
|
||
pub enum BorderPaintingMode<'a> {
|
||
/// Paint borders separately (`border-collapse: separate`).
|
||
Separate,
|
||
/// Paint collapsed borders.
|
||
Collapse(&'a CollapsedBordersForCell),
|
||
/// Paint no borders.
|
||
Hidden,
|
||
}
|
||
|
||
#[derive(Copy, Clone, PartialEq)]
|
||
pub enum StackingContextCreationMode {
|
||
Normal,
|
||
PseudoPositioned,
|
||
PseudoFloat,
|
||
}
|