Remove legacy layout (layout 2013) (#35943)

We were already not compiling it and not running tests on it by default.
So it's simpler to just completely remove it.

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Oriol Brufau 2025-03-13 08:26:57 +01:00 committed by GitHub
parent f93006af95
commit 7594dc6991
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17224 changed files with 23 additions and 1584835 deletions

110
Cargo.lock generated
View file

@ -4143,50 +4143,6 @@ version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]]
name = "layout_2013"
version = "0.0.1"
dependencies = [
"app_units",
"atomic_refcell",
"base",
"bitflags 2.9.0",
"canvas_traits",
"embedder_traits",
"euclid",
"fnv",
"fonts",
"html5ever",
"ipc-channel",
"log",
"malloc_size_of_derive",
"net_traits",
"parking_lot",
"pixels",
"profile_traits",
"range",
"rayon",
"script_layout_interface",
"script_traits",
"serde",
"serde_json",
"servo_arc",
"servo_config",
"servo_geometry",
"servo_malloc_size_of",
"servo_url",
"smallvec",
"style",
"style_traits",
"stylo_atoms",
"tracing",
"unicode-bidi",
"unicode-script",
"webrender_api",
"webrender_traits",
"xi-unicode",
]
[[package]]
name = "layout_2020"
version = "0.0.1"
@ -4233,44 +4189,6 @@ dependencies = [
"xi-unicode",
]
[[package]]
name = "layout_thread_2013"
version = "0.0.1"
dependencies = [
"app_units",
"base",
"embedder_traits",
"euclid",
"fnv",
"fonts",
"fonts_traits",
"fxhash",
"ipc-channel",
"layout_2013",
"log",
"metrics",
"net_traits",
"parking_lot",
"profile_traits",
"rayon",
"script",
"script_layout_interface",
"script_traits",
"servo_allocator",
"servo_arc",
"servo_config",
"servo_malloc_size_of",
"servo_url",
"style",
"style_traits",
"stylo_atoms",
"time",
"tracing",
"url",
"webrender_api",
"webrender_traits",
]
[[package]]
name = "layout_thread_2020"
version = "0.0.1"
@ -4423,7 +4341,6 @@ dependencies = [
"gstreamer",
"ipc-channel",
"keyboard-types",
"layout_thread_2013",
"layout_thread_2020",
"libservo",
"log",
@ -4555,7 +4472,7 @@ dependencies = [
[[package]]
name = "malloc_size_of"
version = "0.0.1"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#4ff13d4fca78189988f1fa3d7351d10369e5c532"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#3c39c913c848eac591f4412243acbdf86439e0e1"
dependencies = [
"app_units",
"cssparser",
@ -6575,7 +6492,7 @@ dependencies = [
[[package]]
name = "selectors"
version = "0.26.0"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#4ff13d4fca78189988f1fa3d7351d10369e5c532"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#3c39c913c848eac591f4412243acbdf86439e0e1"
dependencies = [
"bitflags 2.9.0",
"cssparser",
@ -6860,7 +6777,7 @@ dependencies = [
[[package]]
name = "servo_arc"
version = "0.4.0"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#4ff13d4fca78189988f1fa3d7351d10369e5c532"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#3c39c913c848eac591f4412243acbdf86439e0e1"
dependencies = [
"serde",
"stable_deref_trait",
@ -7292,7 +7209,7 @@ dependencies = [
[[package]]
name = "style"
version = "0.0.1"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#4ff13d4fca78189988f1fa3d7351d10369e5c532"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#3c39c913c848eac591f4412243acbdf86439e0e1"
dependencies = [
"app_units",
"arrayvec",
@ -7350,7 +7267,7 @@ dependencies = [
[[package]]
name = "style_derive"
version = "0.0.1"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#4ff13d4fca78189988f1fa3d7351d10369e5c532"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#3c39c913c848eac591f4412243acbdf86439e0e1"
dependencies = [
"darling",
"proc-macro2",
@ -7380,7 +7297,7 @@ dependencies = [
[[package]]
name = "style_traits"
version = "0.0.1"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#4ff13d4fca78189988f1fa3d7351d10369e5c532"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#3c39c913c848eac591f4412243acbdf86439e0e1"
dependencies = [
"app_units",
"bitflags 2.9.0",
@ -7402,7 +7319,7 @@ dependencies = [
[[package]]
name = "stylo_atoms"
version = "0.1.0"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#4ff13d4fca78189988f1fa3d7351d10369e5c532"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#3c39c913c848eac591f4412243acbdf86439e0e1"
dependencies = [
"string_cache",
"string_cache_codegen",
@ -7411,12 +7328,12 @@ dependencies = [
[[package]]
name = "stylo_config"
version = "0.1.0"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#4ff13d4fca78189988f1fa3d7351d10369e5c532"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#3c39c913c848eac591f4412243acbdf86439e0e1"
[[package]]
name = "stylo_dom"
version = "0.1.0"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#4ff13d4fca78189988f1fa3d7351d10369e5c532"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#3c39c913c848eac591f4412243acbdf86439e0e1"
dependencies = [
"bitflags 2.9.0",
"malloc_size_of",
@ -7425,7 +7342,7 @@ dependencies = [
[[package]]
name = "stylo_static_prefs"
version = "0.1.0"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#4ff13d4fca78189988f1fa3d7351d10369e5c532"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#3c39c913c848eac591f4412243acbdf86439e0e1"
[[package]]
name = "subtle"
@ -7792,7 +7709,7 @@ dependencies = [
[[package]]
name = "to_shmem"
version = "0.1.0"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#4ff13d4fca78189988f1fa3d7351d10369e5c532"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#3c39c913c848eac591f4412243acbdf86439e0e1"
dependencies = [
"cssparser",
"servo_arc",
@ -7805,7 +7722,7 @@ dependencies = [
[[package]]
name = "to_shmem_derive"
version = "0.1.0"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#4ff13d4fca78189988f1fa3d7351d10369e5c532"
source = "git+https://github.com/servo/stylo?branch=2025-03-11#3c39c913c848eac591f4412243acbdf86439e0e1"
dependencies = [
"darling",
"proc-macro2",
@ -8093,9 +8010,6 @@ name = "unicode-bidi"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
dependencies = [
"serde",
]
[[package]]
name = "unicode-ident"

View file

@ -20,9 +20,6 @@ pub struct Opts {
/// after they have loaded.
pub wait_for_stable_image: bool,
/// Whether or not the legacy layout system is enabled.
pub legacy_layout: bool,
/// `None` to disable the time profiler or `Some` to enable it with:
///
/// - an interval in seconds to cause it to produce output on that interval.
@ -191,7 +188,6 @@ impl Default for Opts {
fn default() -> Self {
Self {
wait_for_stable_image: false,
legacy_layout: false,
time_profiling: None,
time_profiler_trace_path: None,
nonincremental_layout: false,

View file

@ -23,7 +23,6 @@ pub fn set(preferences: Preferences) {
// DOM CSS style accessors.
stylo_config::set_bool("layout.unimplemented", preferences.layout_unimplemented);
stylo_config::set_i32("layout.threads", preferences.layout_threads as i32);
stylo_config::set_bool("layout.legacy_layout", preferences.layout_legacy_layout);
stylo_config::set_bool("layout.flexbox.enabled", preferences.layout_flexbox_enabled);
stylo_config::set_bool("layout.columns.enabled", preferences.layout_columns_enabled);
stylo_config::set_bool("layout.grid.enabled", preferences.layout_grid_enabled);
@ -197,7 +196,6 @@ pub struct Preferences {
pub layout_container_queries_enabled: bool,
pub layout_css_transition_behavior_enabled: bool,
pub layout_flexbox_enabled: bool,
pub layout_legacy_layout: bool,
pub layout_threads: i64,
pub layout_unimplemented: bool,
pub layout_writing_mode_enabled: bool,
@ -363,7 +361,6 @@ impl Preferences {
layout_css_transition_behavior_enabled: true,
layout_flexbox_enabled: true,
layout_grid_enabled: false,
layout_legacy_layout: false,
// TODO(mrobinson): This should likely be based on the number of processors.
layout_threads: 3,
layout_unimplemented: false,

View file

@ -1,57 +0,0 @@
[package]
name = "layout_2013"
version.workspace = true
authors.workspace = true
license.workspace = true
edition.workspace = true
publish.workspace = true
rust-version.workspace = true
[lib]
name = "layout_2013"
path = "lib.rs"
test = true
doctest = false
[features]
tracing = ["dep:tracing"]
[dependencies]
app_units = { workspace = true }
atomic_refcell = { workspace = true }
base = { workspace = true }
bitflags = { workspace = true }
canvas_traits = { workspace = true }
embedder_traits = { workspace = true }
euclid = { workspace = true }
fnv = { workspace = true }
fonts = { path = "../fonts" }
html5ever = { workspace = true }
ipc-channel = { workspace = true }
log = { workspace = true }
malloc_size_of = { workspace = true }
malloc_size_of_derive = { workspace = true }
net_traits = { workspace = true }
pixels = { path = "../pixels" }
parking_lot = { workspace = true }
profile_traits = { workspace = true }
range = { path = "../range" }
rayon = { workspace = true }
script_layout_interface = { workspace = true }
script_traits = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
servo_arc = { workspace = true }
stylo_atoms = { workspace = true }
servo_config = { path = "../config" }
servo_geometry = { path = "../geometry" }
servo_url = { path = "../url" }
smallvec = { workspace = true, features = ["union"] }
style = { workspace = true }
style_traits = { workspace = true }
tracing = { workspace = true, optional = true }
unicode-bidi = { workspace = true, features = ["with_serde"] }
unicode-script = { workspace = true }
webrender_api = { workspace = true }
webrender_traits = { workspace = true }
xi-unicode = { workspace = true }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,152 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Data needed by layout.
use std::collections::HashMap;
use std::hash::BuildHasherDefault;
use std::sync::{Arc, Mutex};
use std::thread;
use base::id::PipelineId;
use fnv::FnvHasher;
use fonts::FontContext;
use net_traits::image_cache::{
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, UsePlaceholder,
};
use parking_lot::RwLock;
use script_layout_interface::{PendingImage, PendingImageState};
use script_traits::Painter;
use servo_url::{ImmutableOrigin, ServoUrl};
use style::context::{RegisteredSpeculativePainter, SharedStyleContext};
use stylo_atoms::Atom;
use crate::display_list::items::{OpaqueNode, WebRenderImageInfo};
type WebrenderImageCache =
HashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo, BuildHasherDefault<FnvHasher>>;
/// Layout information shared among all workers. This must be thread-safe.
pub struct LayoutContext<'a> {
/// The pipeline id of this LayoutContext.
pub id: PipelineId,
/// The origin of this layout context.
pub origin: ImmutableOrigin,
/// Bits shared by the layout and style system.
pub style_context: SharedStyleContext<'a>,
/// Reference to the script thread image cache.
pub image_cache: Arc<dyn ImageCache>,
/// A FontContext to be used during layout.
pub font_context: Arc<FontContext>,
/// A cache of WebRender image info.
pub webrender_image_cache: Arc<RwLock<WebrenderImageCache>>,
/// Paint worklets
pub registered_painters: &'a dyn RegisteredPainters,
/// A list of in-progress image loads to be shared with the script thread.
pub pending_images: Mutex<Vec<PendingImage>>,
}
impl Drop for LayoutContext<'_> {
fn drop(&mut self) {
if !thread::panicking() {
assert!(self.pending_images.lock().unwrap().is_empty());
}
}
}
impl LayoutContext<'_> {
#[inline(always)]
pub fn shared_context(&self) -> &SharedStyleContext {
&self.style_context
}
pub fn get_or_request_image_or_meta(
&self,
node: OpaqueNode,
url: ServoUrl,
use_placeholder: UsePlaceholder,
) -> Option<ImageOrMetadataAvailable> {
// Check for available image or start tracking.
let cache_result = self.image_cache.get_cached_image_status(
url.clone(),
self.origin.clone(),
None,
use_placeholder,
);
match cache_result {
ImageCacheResult::Available(img_or_meta) => Some(img_or_meta),
// Image has been requested, is still pending. Return no image for this paint loop.
// When the image loads it will trigger a reflow and/or repaint.
ImageCacheResult::Pending(id) => {
let image = PendingImage {
state: PendingImageState::PendingResponse,
node: node.into(),
id,
origin: self.origin.clone(),
};
self.pending_images.lock().unwrap().push(image);
None
},
// Not yet requested - request image or metadata from the cache
ImageCacheResult::ReadyForRequest(id) => {
let image = PendingImage {
state: PendingImageState::Unrequested(url),
node: node.into(),
id,
origin: self.origin.clone(),
};
self.pending_images.lock().unwrap().push(image);
None
},
// Image failed to load, so just return nothing
ImageCacheResult::LoadError => None,
}
}
pub fn get_webrender_image_for_url(
&self,
node: OpaqueNode,
url: ServoUrl,
use_placeholder: UsePlaceholder,
) -> Option<WebRenderImageInfo> {
if let Some(existing_webrender_image) = self
.webrender_image_cache
.read()
.get(&(url.clone(), use_placeholder))
{
return Some(*existing_webrender_image);
}
match self.get_or_request_image_or_meta(node, url.clone(), use_placeholder) {
Some(ImageOrMetadataAvailable::ImageAvailable { image, .. }) => {
let image_info = WebRenderImageInfo::from_image(&image);
if image_info.key.is_none() {
Some(image_info)
} else {
let mut webrender_image_cache = self.webrender_image_cache.write();
webrender_image_cache.insert((url, use_placeholder), image_info);
Some(image_info)
}
},
None | Some(ImageOrMetadataAvailable::MetadataAvailable(..)) => None,
}
}
}
/// A registered painter
pub trait RegisteredPainter: RegisteredSpeculativePainter + Painter {}
/// A set of registered painters
pub trait RegisteredPainters: Sync {
/// Look up a painter
fn get(&self, name: &Atom) -> Option<&dyn RegisteredPainter>;
}

View file

@ -1,47 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use atomic_refcell::AtomicRefCell;
use bitflags::bitflags;
use script_layout_interface::wrapper_traits::LayoutDataTrait;
use crate::construct::ConstructionResult;
/// Data that layout associates with a node.
#[derive(Clone, Default)]
pub struct InnerLayoutData {
/// The current results of flow construction for this node. This is either a
/// flow or a `ConstructionItem`. See comments in `construct.rs` for more
/// details.
pub flow_construction_result: ConstructionResult,
pub before_flow_construction_result: ConstructionResult,
pub after_flow_construction_result: ConstructionResult,
pub details_summary_flow_construction_result: ConstructionResult,
pub details_content_flow_construction_result: ConstructionResult,
/// Various flags.
pub flags: LayoutDataFlags,
}
bitflags! {
#[derive(Clone, Copy, Default)]
pub struct LayoutDataFlags: u8 {
/// Whether a flow has been newly constructed.
const HAS_NEWLY_CONSTRUCTED_FLOW = 0x01;
/// Whether this node has been traversed by layout.
const HAS_BEEN_TRAVERSED = 0x02;
}
}
/// A wrapper for [`InnerLayoutData`]. This is necessary to give the entire data
/// structure interior mutability, as we will need to mutate the layout data of
/// non-mutable DOM nodes.
#[derive(Clone, Default)]
pub struct LayoutData(pub AtomicRefCell<InnerLayoutData>);
// The implementation of this trait allows the data to be stored in the DOM.
impl LayoutDataTrait for LayoutData {}

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,184 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use app_units::Au;
use euclid::default::{Point2D, Rect, SideOffsets2D, Size2D, Vector2D};
use style::color::{AbsoluteColor, ColorSpace};
use style::computed_values::image_rendering::T as ImageRendering;
use style::computed_values::mix_blend_mode::T as MixBlendMode;
use style::computed_values::transform_style::T as TransformStyle;
use style::values::computed::{BorderStyle, Filter};
use style::values::specified::border::BorderImageRepeatKeyword;
use webrender_api as wr;
pub trait ToLayout {
type Type;
fn to_layout(&self) -> Self::Type;
}
pub trait FilterToLayout {
type Type;
fn to_layout(&self, current_color: &AbsoluteColor) -> Self::Type;
}
impl ToLayout for BorderStyle {
type Type = wr::BorderStyle;
fn to_layout(&self) -> Self::Type {
match *self {
BorderStyle::None => wr::BorderStyle::None,
BorderStyle::Solid => wr::BorderStyle::Solid,
BorderStyle::Double => wr::BorderStyle::Double,
BorderStyle::Dotted => wr::BorderStyle::Dotted,
BorderStyle::Dashed => wr::BorderStyle::Dashed,
BorderStyle::Hidden => wr::BorderStyle::Hidden,
BorderStyle::Groove => wr::BorderStyle::Groove,
BorderStyle::Ridge => wr::BorderStyle::Ridge,
BorderStyle::Inset => wr::BorderStyle::Inset,
BorderStyle::Outset => wr::BorderStyle::Outset,
}
}
}
impl FilterToLayout for Filter {
type Type = wr::FilterOp;
fn to_layout(&self, current_color: &AbsoluteColor) -> Self::Type {
match *self {
Filter::Blur(radius) => wr::FilterOp::Blur(radius.px(), radius.px()),
Filter::Brightness(amount) => wr::FilterOp::Brightness(amount.0),
Filter::Contrast(amount) => wr::FilterOp::Contrast(amount.0),
Filter::Grayscale(amount) => wr::FilterOp::Grayscale(amount.0),
Filter::HueRotate(angle) => wr::FilterOp::HueRotate(angle.radians()),
Filter::Invert(amount) => wr::FilterOp::Invert(amount.0),
Filter::Opacity(amount) => wr::FilterOp::Opacity(amount.0.into(), amount.0),
Filter::Saturate(amount) => wr::FilterOp::Saturate(amount.0),
Filter::Sepia(amount) => wr::FilterOp::Sepia(amount.0),
Filter::DropShadow(ref shadow) => wr::FilterOp::DropShadow(wr::Shadow {
blur_radius: shadow.blur.px(),
offset: wr::units::LayoutVector2D::new(
shadow.horizontal.px(),
shadow.vertical.px(),
),
color: shadow
.color
.clone()
.resolve_to_absolute(current_color)
.to_layout(),
}),
// Statically check that Url is impossible.
Filter::Url(ref url) => match *url {},
}
}
}
impl ToLayout for ImageRendering {
type Type = wr::ImageRendering;
fn to_layout(&self) -> Self::Type {
match *self {
ImageRendering::Auto => wr::ImageRendering::Auto,
ImageRendering::CrispEdges => wr::ImageRendering::CrispEdges,
ImageRendering::Pixelated => wr::ImageRendering::Pixelated,
}
}
}
impl ToLayout for MixBlendMode {
type Type = wr::MixBlendMode;
fn to_layout(&self) -> Self::Type {
match *self {
MixBlendMode::Normal => wr::MixBlendMode::Normal,
MixBlendMode::Multiply => wr::MixBlendMode::Multiply,
MixBlendMode::Screen => wr::MixBlendMode::Screen,
MixBlendMode::Overlay => wr::MixBlendMode::Overlay,
MixBlendMode::Darken => wr::MixBlendMode::Darken,
MixBlendMode::Lighten => wr::MixBlendMode::Lighten,
MixBlendMode::ColorDodge => wr::MixBlendMode::ColorDodge,
MixBlendMode::ColorBurn => wr::MixBlendMode::ColorBurn,
MixBlendMode::HardLight => wr::MixBlendMode::HardLight,
MixBlendMode::SoftLight => wr::MixBlendMode::SoftLight,
MixBlendMode::Difference => wr::MixBlendMode::Difference,
MixBlendMode::Exclusion => wr::MixBlendMode::Exclusion,
MixBlendMode::Hue => wr::MixBlendMode::Hue,
MixBlendMode::Saturation => wr::MixBlendMode::Saturation,
MixBlendMode::Color => wr::MixBlendMode::Color,
MixBlendMode::Luminosity => wr::MixBlendMode::Luminosity,
MixBlendMode::PlusLighter => wr::MixBlendMode::PlusLighter,
}
}
}
impl ToLayout for TransformStyle {
type Type = wr::TransformStyle;
fn to_layout(&self) -> Self::Type {
match *self {
TransformStyle::Flat => wr::TransformStyle::Flat,
TransformStyle::Preserve3d => wr::TransformStyle::Preserve3D,
}
}
}
impl ToLayout for AbsoluteColor {
type Type = wr::ColorF;
fn to_layout(&self) -> Self::Type {
let rgba = self.to_color_space(ColorSpace::Srgb);
wr::ColorF::new(
rgba.components.0.clamp(0.0, 1.0),
rgba.components.1.clamp(0.0, 1.0),
rgba.components.2.clamp(0.0, 1.0),
rgba.alpha,
)
}
}
impl ToLayout for Point2D<Au> {
type Type = wr::units::LayoutPoint;
fn to_layout(&self) -> Self::Type {
wr::units::LayoutPoint::new(self.x.to_f32_px(), self.y.to_f32_px())
}
}
impl ToLayout for Rect<Au> {
type Type = wr::units::LayoutRect;
fn to_layout(&self) -> Self::Type {
wr::units::LayoutRect::from_origin_and_size(self.origin.to_layout(), self.size.to_layout())
}
}
impl ToLayout for SideOffsets2D<Au> {
type Type = wr::units::LayoutSideOffsets;
fn to_layout(&self) -> Self::Type {
wr::units::LayoutSideOffsets::new(
self.top.to_f32_px(),
self.right.to_f32_px(),
self.bottom.to_f32_px(),
self.left.to_f32_px(),
)
}
}
impl ToLayout for Size2D<Au> {
type Type = wr::units::LayoutSize;
fn to_layout(&self) -> Self::Type {
wr::units::LayoutSize::new(self.width.to_f32_px(), self.height.to_f32_px())
}
}
impl ToLayout for Vector2D<Au> {
type Type = wr::units::LayoutVector2D;
fn to_layout(&self) -> Self::Type {
wr::units::LayoutVector2D::new(self.x.to_f32_px(), self.y.to_f32_px())
}
}
impl ToLayout for BorderImageRepeatKeyword {
type Type = wr::RepeatMode;
fn to_layout(&self) -> Self::Type {
match *self {
BorderImageRepeatKeyword::Stretch => wr::RepeatMode::Stretch,
BorderImageRepeatKeyword::Repeat => wr::RepeatMode::Repeat,
BorderImageRepeatKeyword::Round => wr::RepeatMode::Round,
BorderImageRepeatKeyword::Space => wr::RepeatMode::Space,
}
}
}

View file

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

View file

@ -1,761 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Servo heavily uses display lists, which are retained-mode lists of painting commands to
//! perform. Using a list instead of painting elements in immediate mode allows transforms, hit
//! testing, and invalidation to be performed using the same primitives as painting. It also allows
//! Servo to aggressively cull invisible and out-of-bounds painting elements, to reduce overdraw.
//!
//! Display items describe relatively high-level drawing operations (for example, entire borders
//! and shadows instead of lines and blur operations), to reduce the amount of allocation required.
//! They are therefore not exactly analogous to constructs like Skia pictures, which consist of
//! low-level drawing primitives.
use std::cmp::Ordering;
use std::collections::HashMap;
use std::{f32, fmt};
use base::id::PipelineId;
use base::print_tree::PrintTree;
use embedder_traits::Cursor;
use euclid::{SideOffsets2D, Vector2D};
use pixels::Image;
use serde::Serialize;
use servo_geometry::MaxRect;
use style::computed_values::_servo_top_layer::T as InTopLayer;
pub use style::dom::OpaqueNode;
use webrender_api as wr;
use webrender_api::units::{LayoutPixel, LayoutRect, LayoutTransform};
use webrender_api::{
BorderRadius, ClipChainId, ClipMode, CommonItemProperties, ComplexClipRegion, ExternalScrollId,
FilterOp, GlyphInstance, GradientStop, ImageKey, MixBlendMode, PrimitiveFlags, Shadow,
SpatialId, StickyOffsetBounds, TransformStyle,
};
use webrender_traits::display_list::{AxesScrollSensitivity, ScrollTreeNodeId};
use super::StackingContextId;
/// The factor that we multiply the blur radius by in order to inflate the boundaries of display
/// items that involve a blur. This ensures that the display item boundaries include all the ink.
pub static BLUR_INFLATION_FACTOR: i32 = 3;
/// An index into the vector of ClipScrollNodes. During WebRender conversion these nodes
/// are given ClipIds.
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
pub struct ClipScrollNodeIndex(usize);
impl ClipScrollNodeIndex {
pub fn root_scroll_node() -> ClipScrollNodeIndex {
ClipScrollNodeIndex(1)
}
pub fn root_reference_frame() -> ClipScrollNodeIndex {
ClipScrollNodeIndex(0)
}
pub fn new(index: usize) -> ClipScrollNodeIndex {
assert_ne!(index, 0, "Use the root_reference_frame constructor");
assert_ne!(index, 1, "Use the root_scroll_node constructor");
ClipScrollNodeIndex(index)
}
pub fn is_root_scroll_node(&self) -> bool {
*self == Self::root_scroll_node()
}
pub fn to_define_item(&self) -> DisplayItem {
DisplayItem::DefineClipScrollNode(Box::new(DefineClipScrollNodeItem {
base: BaseDisplayItem::empty(),
node_index: *self,
}))
}
pub fn to_index(self) -> usize {
self.0
}
}
/// A set of indices into the clip scroll node vector for a given item.
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
pub struct ClippingAndScrolling {
pub scrolling: ClipScrollNodeIndex,
pub clipping: Option<ClipScrollNodeIndex>,
}
impl ClippingAndScrolling {
pub fn simple(scrolling: ClipScrollNodeIndex) -> ClippingAndScrolling {
ClippingAndScrolling {
scrolling,
clipping: None,
}
}
pub fn new(scrolling: ClipScrollNodeIndex, clipping: ClipScrollNodeIndex) -> Self {
ClippingAndScrolling {
scrolling,
clipping: Some(clipping),
}
}
}
#[derive(Serialize)]
pub struct DisplayList {
pub list: Vec<DisplayItem>,
pub clip_scroll_nodes: Vec<ClipScrollNode>,
}
impl DisplayList {
/// Return the bounds of this display list based on the dimensions of the root
/// stacking context.
pub fn bounds(&self) -> LayoutRect {
match self.list.first() {
Some(DisplayItem::PushStackingContext(item)) => item.stacking_context.bounds,
Some(_) => unreachable!("Root element of display list not stacking context."),
None => LayoutRect::zero(),
}
}
pub fn print(&self) {
let mut print_tree = PrintTree::new("Display List".to_owned());
self.print_with_tree(&mut print_tree);
}
pub fn print_with_tree(&self, print_tree: &mut PrintTree) {
print_tree.new_level("ClipScrollNodes".to_owned());
for node in &self.clip_scroll_nodes {
print_tree.add_item(format!("{:?}", node));
}
print_tree.end_level();
print_tree.new_level("Items".to_owned());
for item in &self.list {
print_tree.add_item(format!(
"{:?} StackingContext: {:?} {:?}",
item,
item.base().stacking_context_id,
item.clipping_and_scrolling()
));
}
print_tree.end_level();
}
}
/// Display list sections that make up a stacking context. Each section here refers
/// to the steps in CSS 2.1 Appendix E.
///
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub enum DisplayListSection {
BackgroundAndBorders,
BlockBackgroundsAndBorders,
Content,
Outlines,
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub enum StackingContextType {
Real,
PseudoPositioned,
PseudoFloat,
}
#[derive(Clone, Serialize)]
/// Represents one CSS stacking context, which may or may not have a hardware layer.
pub struct StackingContext {
/// The ID of this StackingContext for uniquely identifying it.
pub id: StackingContextId,
/// The type of this StackingContext. Used for collecting and sorting.
pub context_type: StackingContextType,
/// The position and size of this stacking context.
pub bounds: LayoutRect,
/// The overflow rect for this stacking context in its coordinate system.
pub overflow: LayoutRect,
/// The `z-index` for this stacking context.
pub z_index: i32,
/// Whether this is the top layer.
pub in_top_layer: InTopLayer,
/// CSS filters to be applied to this stacking context (including opacity).
pub filters: Vec<FilterOp>,
/// The blend mode with which this stacking context blends with its backdrop.
pub mix_blend_mode: MixBlendMode,
/// A transform to be applied to this stacking context.
pub transform: Option<LayoutTransform>,
/// The transform style of this stacking context.
pub transform_style: TransformStyle,
/// The perspective matrix to be applied to children.
pub perspective: Option<LayoutTransform>,
/// The clip and scroll info for this StackingContext.
pub parent_clipping_and_scrolling: ClippingAndScrolling,
/// The index of the reference frame that this stacking context establishes.
pub established_reference_frame: Option<ClipScrollNodeIndex>,
}
impl StackingContext {
/// Creates a new stacking context.
#[inline]
#[allow(clippy::too_many_arguments)]
pub fn new(
id: StackingContextId,
context_type: StackingContextType,
bounds: LayoutRect,
overflow: LayoutRect,
z_index: i32,
in_top_layer: InTopLayer,
filters: Vec<FilterOp>,
mix_blend_mode: MixBlendMode,
transform: Option<LayoutTransform>,
transform_style: TransformStyle,
perspective: Option<LayoutTransform>,
parent_clipping_and_scrolling: ClippingAndScrolling,
established_reference_frame: Option<ClipScrollNodeIndex>,
) -> StackingContext {
if let Some(ref t) = transform {
// These are used as scale values by webrender, and it can't handle
// divisors of 0 when scaling.
assert_ne!(t.m11, 0.);
assert_ne!(t.m22, 0.);
}
StackingContext {
id,
context_type,
bounds,
overflow,
z_index,
in_top_layer,
filters,
mix_blend_mode,
transform,
transform_style,
perspective,
parent_clipping_and_scrolling,
established_reference_frame,
}
}
#[inline]
pub fn root() -> StackingContext {
StackingContext::new(
StackingContextId::root(),
StackingContextType::Real,
LayoutRect::zero(),
LayoutRect::zero(),
0,
InTopLayer::None,
vec![],
MixBlendMode::Normal,
None,
TransformStyle::Flat,
None,
ClippingAndScrolling::simple(ClipScrollNodeIndex::root_scroll_node()),
None,
)
}
pub fn to_display_list_items(self) -> (DisplayItem, DisplayItem) {
let mut base_item = BaseDisplayItem::empty();
base_item.stacking_context_id = self.id;
base_item.clipping_and_scrolling = self.parent_clipping_and_scrolling;
let pop_item = DisplayItem::PopStackingContext(Box::new(PopStackingContextItem {
base: base_item.clone(),
stacking_context_id: self.id,
established_reference_frame: self.established_reference_frame.is_some(),
}));
let push_item = DisplayItem::PushStackingContext(Box::new(PushStackingContextItem {
base: base_item,
stacking_context: self,
}));
(push_item, pop_item)
}
}
impl Ord for StackingContext {
fn cmp(&self, other: &Self) -> Ordering {
if self.in_top_layer == InTopLayer::Top {
if other.in_top_layer == InTopLayer::Top {
return Ordering::Equal;
} else {
return Ordering::Greater;
}
} else if other.in_top_layer == InTopLayer::Top {
return Ordering::Less;
}
if self.z_index != 0 || other.z_index != 0 {
return self.z_index.cmp(&other.z_index);
}
match (self.context_type, other.context_type) {
(StackingContextType::PseudoFloat, StackingContextType::PseudoFloat) => Ordering::Equal,
(StackingContextType::PseudoFloat, _) => Ordering::Less,
(_, StackingContextType::PseudoFloat) => Ordering::Greater,
(_, _) => Ordering::Equal,
}
}
}
impl PartialOrd for StackingContext {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Eq for StackingContext {}
impl PartialEq for StackingContext {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl fmt::Debug for StackingContext {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let type_string = if self.context_type == StackingContextType::Real {
"StackingContext"
} else {
"Pseudo-StackingContext"
};
write!(
f,
"{} at {:?} with overflow {:?}: {:?}",
type_string, self.bounds, self.overflow, self.id
)
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct StickyFrameData {
pub margins: SideOffsets2D<Option<f32>, LayoutPixel>,
pub vertical_offset_bounds: StickyOffsetBounds,
pub horizontal_offset_bounds: StickyOffsetBounds,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
pub enum ClipType {
Rounded(ComplexClipRegion),
Rect,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum ClipScrollNodeType {
Placeholder,
ScrollFrame(AxesScrollSensitivity, ExternalScrollId),
StickyFrame(StickyFrameData),
Clip(ClipType),
}
/// Defines a clip scroll node.
#[derive(Clone, Debug, Serialize)]
pub struct ClipScrollNode {
/// The index of the parent of this ClipScrollNode.
pub parent_index: ClipScrollNodeIndex,
/// The position of this scroll root's frame in the parent stacking context.
pub clip: ClippingRegion,
/// The rect of the contents that can be scrolled inside of the scroll root.
pub content_rect: LayoutRect,
/// The type of this ClipScrollNode.
pub node_type: ClipScrollNodeType,
/// The WebRender spatial id of this node assigned during WebRender conversion.
pub scroll_node_id: Option<ScrollTreeNodeId>,
/// The WebRender clip id of this node assigned during WebRender conversion.
pub clip_chain_id: Option<ClipChainId>,
}
impl ClipScrollNode {
pub fn placeholder() -> ClipScrollNode {
ClipScrollNode {
parent_index: ClipScrollNodeIndex(0),
clip: ClippingRegion::from_rect(LayoutRect::zero()),
content_rect: LayoutRect::zero(),
node_type: ClipScrollNodeType::Placeholder,
scroll_node_id: None,
clip_chain_id: None,
}
}
pub fn is_placeholder(&self) -> bool {
self.node_type == ClipScrollNodeType::Placeholder
}
pub fn rounded(
clip_rect: LayoutRect,
radii: BorderRadius,
parent_index: ClipScrollNodeIndex,
) -> ClipScrollNode {
let complex_region = ComplexClipRegion {
rect: clip_rect,
radii,
mode: ClipMode::Clip,
};
ClipScrollNode {
parent_index,
clip: ClippingRegion::from_rect(clip_rect),
content_rect: LayoutRect::zero(), // content_rect isn't important for clips.
node_type: ClipScrollNodeType::Clip(ClipType::Rounded(complex_region)),
scroll_node_id: None,
clip_chain_id: None,
}
}
}
/// One drawing command in the list.
#[derive(Clone, Serialize)]
pub enum DisplayItem {
Rectangle(Box<CommonDisplayItem<wr::RectangleDisplayItem>>),
Text(Box<CommonDisplayItem<wr::TextDisplayItem, Vec<GlyphInstance>>>),
Image(Box<CommonDisplayItem<wr::ImageDisplayItem>>),
RepeatingImage(Box<CommonDisplayItem<wr::RepeatingImageDisplayItem>>),
Border(Box<CommonDisplayItem<wr::BorderDisplayItem, Vec<GradientStop>>>),
Gradient(Box<CommonDisplayItem<wr::GradientDisplayItem, Vec<GradientStop>>>),
RadialGradient(Box<CommonDisplayItem<wr::RadialGradientDisplayItem, Vec<GradientStop>>>),
Line(Box<CommonDisplayItem<wr::LineDisplayItem>>),
BoxShadow(Box<CommonDisplayItem<wr::BoxShadowDisplayItem>>),
PushTextShadow(Box<PushTextShadowDisplayItem>),
PopAllTextShadows(Box<PopAllTextShadowsDisplayItem>),
Iframe(Box<IframeDisplayItem>),
PushStackingContext(Box<PushStackingContextItem>),
PopStackingContext(Box<PopStackingContextItem>),
DefineClipScrollNode(Box<DefineClipScrollNodeItem>),
}
/// Information common to all display items.
#[derive(Clone, Serialize)]
pub struct BaseDisplayItem {
/// Metadata attached to this display item.
pub metadata: DisplayItemMetadata,
/// The clip rectangle to use for this item.
pub clip_rect: LayoutRect,
/// The section of the display list that this item belongs to.
pub section: DisplayListSection,
/// The id of the stacking context this item belongs to.
pub stacking_context_id: StackingContextId,
/// The clip and scroll info for this item.
pub clipping_and_scrolling: ClippingAndScrolling,
}
impl BaseDisplayItem {
#[inline(always)]
pub fn new(
metadata: DisplayItemMetadata,
clip_rect: LayoutRect,
section: DisplayListSection,
stacking_context_id: StackingContextId,
clipping_and_scrolling: ClippingAndScrolling,
) -> BaseDisplayItem {
BaseDisplayItem {
metadata,
clip_rect,
section,
stacking_context_id,
clipping_and_scrolling,
}
}
#[inline(always)]
pub fn empty() -> BaseDisplayItem {
BaseDisplayItem {
metadata: DisplayItemMetadata {
node: OpaqueNode(0),
unique_id: 0,
cursor: None,
},
// Create a rectangle of maximal size.
clip_rect: LayoutRect::max_rect(),
section: DisplayListSection::Content,
stacking_context_id: StackingContextId::root(),
clipping_and_scrolling: ClippingAndScrolling::simple(
ClipScrollNodeIndex::root_scroll_node(),
),
}
}
}
pub fn empty_common_item_properties() -> CommonItemProperties {
CommonItemProperties {
clip_rect: LayoutRect::max_rect(),
clip_chain_id: ClipChainId::INVALID,
spatial_id: SpatialId::root_scroll_node(wr::PipelineId::dummy()),
flags: PrimitiveFlags::empty(),
}
}
/// A clipping region for a display item. Currently, this can describe rectangles, rounded
/// rectangles (for `border-radius`), or arbitrary intersections of the two. Arbitrary transforms
/// are not supported because those are handled by the higher-level `StackingContext` abstraction.
#[derive(Clone, PartialEq, Serialize)]
pub struct ClippingRegion {
/// The main rectangular region. This does not include any corners.
pub main: LayoutRect,
}
impl ClippingRegion {
/// Returns an empty clipping region that, if set, will result in no pixels being visible.
#[inline]
pub fn empty() -> ClippingRegion {
ClippingRegion {
main: LayoutRect::zero(),
}
}
/// Returns an all-encompassing clipping region that clips no pixels out.
#[inline]
pub fn max() -> ClippingRegion {
ClippingRegion {
main: LayoutRect::max_rect(),
}
}
/// Returns a clipping region that represents the given rectangle.
#[inline]
pub fn from_rect(rect: LayoutRect) -> ClippingRegion {
ClippingRegion { main: rect }
}
}
impl fmt::Debug for ClippingRegion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if *self == ClippingRegion::max() {
write!(f, "ClippingRegion::Max")
} else if *self == ClippingRegion::empty() {
write!(f, "ClippingRegion::Empty")
} else {
write!(f, "ClippingRegion(Rect={:?})", self.main,)
}
}
}
/// Metadata attached to each display item. This is useful for performing auxiliary threads with
/// the display list involving hit testing: finding the originating DOM node and determining the
/// cursor to use when the element is hovered over.
#[derive(Clone, Copy, Serialize)]
pub struct DisplayItemMetadata {
/// The DOM node from which this display item originated.
pub node: OpaqueNode,
/// The unique fragment id of the fragment of this item.
pub unique_id: u64,
/// The value of the `cursor` property when the mouse hovers over this display item. If `None`,
/// this display item is ineligible for pointer events (`pointer-events: none`).
pub cursor: Option<Cursor>,
}
#[derive(Clone, Eq, PartialEq, Serialize)]
pub enum TextOrientation {
Upright,
SidewaysLeft,
SidewaysRight,
}
/// Paints an iframe.
#[derive(Clone, Serialize)]
pub struct IframeDisplayItem {
pub base: BaseDisplayItem,
pub iframe: PipelineId,
pub bounds: LayoutRect,
}
#[derive(Clone, Serialize)]
pub struct CommonDisplayItem<T, U = ()> {
pub base: BaseDisplayItem,
pub item: T,
pub data: U,
}
impl<T> CommonDisplayItem<T> {
pub fn new(base: BaseDisplayItem, item: T) -> Box<CommonDisplayItem<T>> {
Box::new(CommonDisplayItem {
base,
item,
data: (),
})
}
}
impl<T, U> CommonDisplayItem<T, U> {
pub fn with_data(base: BaseDisplayItem, item: T, data: U) -> Box<CommonDisplayItem<T, U>> {
Box::new(CommonDisplayItem { base, item, data })
}
}
/// Defines a text shadow that affects all items until the paired PopTextShadow.
#[derive(Clone, Serialize)]
pub struct PushTextShadowDisplayItem {
/// Fields common to all display items.
pub base: BaseDisplayItem,
pub shadow: Shadow,
}
/// Defines a text shadow that affects all items until the next PopTextShadow.
#[derive(Clone, Serialize)]
pub struct PopAllTextShadowsDisplayItem {
/// Fields common to all display items.
pub base: BaseDisplayItem,
}
/// Defines a stacking context.
#[derive(Clone, Serialize)]
pub struct PushStackingContextItem {
/// Fields common to all display items.
pub base: BaseDisplayItem,
pub stacking_context: StackingContext,
}
/// Defines a stacking context.
#[derive(Clone, Serialize)]
pub struct PopStackingContextItem {
/// Fields common to all display items.
pub base: BaseDisplayItem,
pub stacking_context_id: StackingContextId,
pub established_reference_frame: bool,
}
/// Starts a group of items inside a particular scroll root.
#[derive(Clone, Serialize)]
pub struct DefineClipScrollNodeItem {
/// Fields common to all display items.
pub base: BaseDisplayItem,
/// The scroll root that this item starts.
pub node_index: ClipScrollNodeIndex,
}
impl DisplayItem {
pub fn base(&self) -> &BaseDisplayItem {
match *self {
DisplayItem::Rectangle(ref rect) => &rect.base,
DisplayItem::Text(ref text) => &text.base,
DisplayItem::Image(ref image_item) => &image_item.base,
DisplayItem::RepeatingImage(ref image_item) => &image_item.base,
DisplayItem::Border(ref border) => &border.base,
DisplayItem::Gradient(ref gradient) => &gradient.base,
DisplayItem::RadialGradient(ref gradient) => &gradient.base,
DisplayItem::Line(ref line) => &line.base,
DisplayItem::BoxShadow(ref box_shadow) => &box_shadow.base,
DisplayItem::PushTextShadow(ref push_text_shadow) => &push_text_shadow.base,
DisplayItem::PopAllTextShadows(ref pop_text_shadow) => &pop_text_shadow.base,
DisplayItem::Iframe(ref iframe) => &iframe.base,
DisplayItem::PushStackingContext(ref stacking_context) => &stacking_context.base,
DisplayItem::PopStackingContext(ref item) => &item.base,
DisplayItem::DefineClipScrollNode(ref item) => &item.base,
}
}
pub fn clipping_and_scrolling(&self) -> ClippingAndScrolling {
self.base().clipping_and_scrolling
}
pub fn stacking_context_id(&self) -> StackingContextId {
self.base().stacking_context_id
}
pub fn section(&self) -> DisplayListSection {
self.base().section
}
pub fn bounds(&self) -> LayoutRect {
match *self {
DisplayItem::Rectangle(ref item) => item.item.common.clip_rect,
DisplayItem::Text(ref item) => item.item.bounds,
DisplayItem::Image(ref item) => item.item.bounds,
DisplayItem::RepeatingImage(ref item) => item.item.bounds,
DisplayItem::Border(ref item) => item.item.bounds,
DisplayItem::Gradient(ref item) => item.item.bounds,
DisplayItem::RadialGradient(ref item) => item.item.bounds,
DisplayItem::Line(ref item) => item.item.area,
DisplayItem::BoxShadow(ref item) => item.item.box_bounds,
DisplayItem::PushTextShadow(_) => LayoutRect::zero(),
DisplayItem::PopAllTextShadows(_) => LayoutRect::zero(),
DisplayItem::Iframe(ref item) => item.bounds,
DisplayItem::PushStackingContext(ref item) => item.stacking_context.bounds,
DisplayItem::PopStackingContext(_) => LayoutRect::zero(),
DisplayItem::DefineClipScrollNode(_) => LayoutRect::zero(),
}
}
}
impl fmt::Debug for DisplayItem {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let DisplayItem::PushStackingContext(ref item) = *self {
return write!(f, "PushStackingContext({:?})", item.stacking_context);
}
if let DisplayItem::PopStackingContext(ref item) = *self {
return write!(f, "PopStackingContext({:?}", item.stacking_context_id);
}
if let DisplayItem::DefineClipScrollNode(ref item) = *self {
return write!(f, "DefineClipScrollNode({:?}", item.node_index);
}
write!(
f,
"{} @ {:?} {:?}",
match *self {
DisplayItem::Rectangle(_) => "Rectangle",
DisplayItem::Text(_) => "Text",
DisplayItem::Image(_) => "Image",
DisplayItem::RepeatingImage(_) => "RepeatingImage",
DisplayItem::Border(_) => "Border",
DisplayItem::Gradient(_) => "Gradient",
DisplayItem::RadialGradient(_) => "RadialGradient",
DisplayItem::Line(_) => "Line",
DisplayItem::BoxShadow(_) => "BoxShadow",
DisplayItem::PushTextShadow(_) => "PushTextShadow",
DisplayItem::PopAllTextShadows(_) => "PopTextShadow",
DisplayItem::Iframe(_) => "Iframe",
DisplayItem::PushStackingContext(_) |
DisplayItem::PopStackingContext(_) |
DisplayItem::DefineClipScrollNode(_) => "",
},
self.bounds(),
self.base().clip_rect
)
}
}
#[derive(Clone, Copy, Serialize)]
pub struct WebRenderImageInfo {
pub width: u32,
pub height: u32,
pub key: Option<ImageKey>,
}
impl WebRenderImageInfo {
#[inline]
pub fn from_image(image: &Image) -> WebRenderImageInfo {
WebRenderImageInfo {
width: image.width,
height: image.height,
key: image.id,
}
}
}
/// The type of the scroll offset list. This is only populated if WebRender is in use.
pub type ScrollOffsetMap = HashMap<ExternalScrollId, Vector2D<f32, LayoutPixel>>;

View file

@ -1,41 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use malloc_size_of_derive::MallocSizeOf;
use serde::{Deserialize, Serialize};
pub use self::builder::{
BorderPaintingMode, DisplayListBuildState, IndexableText, StackingContextCollectionFlags,
StackingContextCollectionState,
};
pub use self::conversions::{FilterToLayout, ToLayout};
mod background;
mod border;
mod builder;
pub(crate) mod conversions;
mod gradient;
pub mod items;
mod webrender_helpers;
/// A unique ID for every stacking context.
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub struct StackingContextId(
/// The identifier for this StackingContext, derived from the Flow's memory address
/// and fragment type. As a space optimization, these are combined into a single word.
pub u64,
);
impl StackingContextId {
/// Returns the stacking context ID for the outer document/layout root.
#[inline]
pub fn root() -> StackingContextId {
StackingContextId(0)
}
pub fn next(&self) -> StackingContextId {
let StackingContextId(id) = *self;
StackingContextId(id + 1)
}
}

View file

@ -1,553 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
// TODO(gw): This contains helper traits and implementations for converting Servo display lists
// into WebRender display lists. In the future, this step should be completely removed.
// This might be achieved by sharing types between WR and Servo display lists, or
// completely converting layout to directly generate WebRender display lists, for example.
use base::WebRenderEpochToU16;
use base::id::PipelineId;
use log::trace;
use webrender_api::units::{LayoutPoint, LayoutSize, LayoutVector2D};
use webrender_api::{
self, ClipChainId, ClipId, CommonItemProperties, DisplayItem as WrDisplayItem,
DisplayListBuilder, Epoch, HasScrollLinkedEffect, PrimitiveFlags, PropertyBinding, RasterSpace,
ReferenceFrameKind, SpaceAndClipInfo, SpatialId, SpatialTreeItemKey,
};
use webrender_traits::display_list::{
AxesScrollSensitivity, CompositorDisplayListInfo, ScrollSensitivity, ScrollTreeNodeId,
ScrollableNodeInfo,
};
use crate::display_list::items::{
BaseDisplayItem, ClipScrollNode, ClipScrollNodeType, ClipType, DisplayItem, DisplayList,
StackingContextType,
};
struct ClipScrollState<'a> {
clip_scroll_nodes: &'a mut Vec<ClipScrollNode>,
compositor_info: CompositorDisplayListInfo,
stacking_context_offset: Vec<LayoutVector2D>,
}
impl<'a> ClipScrollState<'a> {
fn new(
clip_scroll_nodes: &'a mut Vec<ClipScrollNode>,
compositor_info: CompositorDisplayListInfo,
) -> Self {
let mut state = ClipScrollState {
clip_scroll_nodes,
compositor_info,
stacking_context_offset: Vec::new(),
};
// We need to register the WebRender root reference frame and root scroll node ids
// here manually, because WebRender and the CompositorDisplayListInfo create them
// automatically. We also follow the "old" WebRender API for clip/scroll for now,
// hence both arrays are initialized based on FIRST_SPATIAL_NODE_INDEX, while
// FIRST_CLIP_NODE_INDEX is not taken into account.
state.clip_scroll_nodes[0].scroll_node_id =
Some(state.compositor_info.root_reference_frame_id);
state.clip_scroll_nodes[1].scroll_node_id = Some(state.compositor_info.root_scroll_node_id);
let root_clip_chain = ClipChainId::INVALID;
state.add_clip_node_mapping(0, root_clip_chain);
state.add_clip_node_mapping(1, root_clip_chain);
state
}
fn webrender_clip_id_for_index(&mut self, index: usize) -> ClipChainId {
self.clip_scroll_nodes[index]
.clip_chain_id
.expect("Tried to access WebRender ClipId before definining it.")
}
fn webrender_spatial_id_for_index(&mut self, index: usize) -> SpatialId {
self.clip_scroll_nodes[index]
.scroll_node_id
.expect("Tried to use WebRender parent SpatialId before it was defined.")
.spatial_id
}
fn add_clip_node_mapping(&mut self, index: usize, webrender_id: ClipChainId) {
self.clip_scroll_nodes[index].clip_chain_id = Some(webrender_id);
}
fn scroll_node_id_from_index(&self, index: usize) -> ScrollTreeNodeId {
self.clip_scroll_nodes[index]
.scroll_node_id
.expect("Tried to use WebRender parent SpatialId before it was defined.")
}
fn register_spatial_node(
&mut self,
index: usize,
spatial_id: SpatialId,
parent_index: Option<usize>,
scroll_info: Option<ScrollableNodeInfo>,
) {
let parent_scroll_node_id = parent_index.map(|index| self.scroll_node_id_from_index(index));
self.clip_scroll_nodes[index].scroll_node_id =
Some(self.compositor_info.scroll_tree.add_scroll_tree_node(
parent_scroll_node_id.as_ref(),
spatial_id,
scroll_info,
));
}
fn add_spatial_node_mapping_to_parent_index(&mut self, index: usize, parent_index: usize) {
self.clip_scroll_nodes[index].scroll_node_id =
self.clip_scroll_nodes[parent_index].scroll_node_id
}
pub fn define_clip_chain<I>(
&self,
builder: &mut DisplayListBuilder,
parent: ClipChainId,
clips: I,
) -> ClipChainId
where
I: IntoIterator<Item = ClipId>,
I::IntoIter: ExactSizeIterator + Clone,
{
// We use INVALID to mean "no clipping", but that cannot be passed as an argument
// to `define_clip_chain()`, so it must be converted into `None`.
let parent = match parent {
ClipChainId::INVALID => None,
parent => Some(parent),
};
builder.define_clip_chain(parent, clips)
}
fn stacking_context_offset(&self) -> LayoutVector2D {
self.stacking_context_offset
.last()
.cloned()
.unwrap_or_default()
}
fn push_stacking_context_offset(&mut self, offset: LayoutVector2D) {
self.stacking_context_offset.push(offset);
}
fn pop_stacking_context_offset(&mut self) {
self.stacking_context_offset.pop();
}
}
/// Contentful paint, for the purpose of
/// <https://w3c.github.io/paint-timing/#first-contentful-paint>
/// (i.e. the display list contains items of type text,
/// image, non-white canvas or SVG). Used by metrics.
pub struct IsContentful(pub bool);
impl DisplayList {
pub fn convert_to_webrender(
&mut self,
pipeline_id: PipelineId,
viewport_size: LayoutSize,
epoch: Epoch,
dump_display_list: bool,
) -> (DisplayListBuilder, CompositorDisplayListInfo, IsContentful) {
let webrender_pipeline = pipeline_id.into();
let mut builder = DisplayListBuilder::new(webrender_pipeline);
builder.begin();
if dump_display_list {
builder.dump_serialized_display_list();
}
let content_size = self.bounds().size();
let mut state = ClipScrollState::new(
&mut self.clip_scroll_nodes,
CompositorDisplayListInfo::new(
viewport_size,
content_size,
webrender_pipeline,
epoch,
AxesScrollSensitivity {
x: ScrollSensitivity::ScriptAndInputEvents,
y: ScrollSensitivity::ScriptAndInputEvents,
},
),
);
let mut is_contentful = IsContentful(false);
for item in &mut self.list {
is_contentful.0 |= item.convert_to_webrender(&mut state, &mut builder).0;
}
(builder, state.compositor_info, is_contentful)
}
}
impl DisplayItem {
fn get_spatial_tree_item_key(
&self,
builder: &DisplayListBuilder,
node_index: usize,
) -> SpatialTreeItemKey {
let pipeline_tag = ((builder.pipeline_id.0 as u64) << 32) | builder.pipeline_id.1 as u64;
SpatialTreeItemKey::new(pipeline_tag, node_index as u64)
}
fn convert_to_webrender(
&mut self,
state: &mut ClipScrollState,
builder: &mut DisplayListBuilder,
) -> IsContentful {
// Note: for each time of a display item, if we register one of `clip_ids` or `spatial_ids`,
// we also register the other one as inherited from the current state or the stack.
// This is not an ideal behavior, but it is compatible with the old WebRender model
// of the clip-scroll tree.
let clip_and_scroll_indices = self.base().clipping_and_scrolling;
trace!("converting {:?}", clip_and_scroll_indices);
let current_scrolling_index = clip_and_scroll_indices.scrolling.to_index();
let current_scroll_node_id = state.scroll_node_id_from_index(current_scrolling_index);
let internal_clip_id = clip_and_scroll_indices
.clipping
.unwrap_or(clip_and_scroll_indices.scrolling);
let current_clip_chain_id = state.webrender_clip_id_for_index(internal_clip_id.to_index());
let hit_test_bounds = self.bounds().intersection(&self.base().clip_rect);
let stacking_context_offset = state.stacking_context_offset();
let build_common_item_properties = |base: &BaseDisplayItem| {
CommonItemProperties {
clip_rect: base.clip_rect.translate(stacking_context_offset),
spatial_id: current_scroll_node_id.spatial_id,
clip_chain_id: current_clip_chain_id,
// TODO(gw): Make use of the WR backface visibility functionality.
flags: PrimitiveFlags::default(),
}
};
let mut push_hit_test = |base: &BaseDisplayItem| {
let bounds = match hit_test_bounds {
Some(bounds) => bounds,
None => return,
}
.translate(stacking_context_offset);
let cursor = match base.metadata.cursor {
Some(cursor) => cursor,
None => return,
};
let hit_test_index = state.compositor_info.add_hit_test_info(
base.metadata.node.0 as u64,
Some(cursor),
current_scroll_node_id,
);
builder.push_hit_test(
bounds,
current_clip_chain_id,
current_scroll_node_id.spatial_id,
PrimitiveFlags::default(),
(hit_test_index as u64, state.compositor_info.epoch.as_u16()),
);
};
match *self {
DisplayItem::Rectangle(ref mut item) => {
let mut rect_item = item.item;
rect_item.common = build_common_item_properties(&item.base);
rect_item.bounds = item.item.bounds.translate(stacking_context_offset);
push_hit_test(&item.base);
builder.push_item(&WrDisplayItem::Rectangle(rect_item));
IsContentful(false)
},
DisplayItem::Text(ref mut item) => {
let mut text_item = item.item;
text_item.bounds = text_item.bounds.translate(stacking_context_offset);
text_item.common = build_common_item_properties(&item.base);
push_hit_test(&item.base);
builder.push_text(
&text_item.common,
text_item.bounds,
&item.data,
text_item.font_key,
text_item.color,
text_item.glyph_options,
);
IsContentful(true)
},
DisplayItem::Image(ref mut item) => {
let mut image_item = item.item;
image_item.common = build_common_item_properties(&item.base);
image_item.bounds = item.item.bounds.translate(stacking_context_offset);
push_hit_test(&item.base);
builder.push_item(&WrDisplayItem::Image(image_item));
IsContentful(true)
},
DisplayItem::RepeatingImage(ref mut item) => {
let mut image_item = item.item;
image_item.common = build_common_item_properties(&item.base);
image_item.bounds = item.item.bounds.translate(stacking_context_offset);
push_hit_test(&item.base);
builder.push_item(&WrDisplayItem::RepeatingImage(image_item));
IsContentful(true)
},
DisplayItem::Border(ref mut item) => {
let mut border_item = item.item;
border_item.common = build_common_item_properties(&item.base);
border_item.bounds = item.item.bounds.translate(stacking_context_offset);
push_hit_test(&item.base);
if !item.data.is_empty() {
builder.push_stops(item.data.as_ref());
}
builder.push_item(&WrDisplayItem::Border(border_item));
IsContentful(false)
},
DisplayItem::Gradient(ref mut item) => {
let mut gradient_item = item.item;
gradient_item.common = build_common_item_properties(&item.base);
gradient_item.bounds = item.item.bounds.translate(stacking_context_offset);
push_hit_test(&item.base);
builder.push_stops(item.data.as_ref());
builder.push_item(&WrDisplayItem::Gradient(gradient_item));
IsContentful(false)
},
DisplayItem::RadialGradient(ref mut item) => {
let mut gradient_item = item.item;
gradient_item.common = build_common_item_properties(&item.base);
gradient_item.bounds = item.item.bounds.translate(stacking_context_offset);
push_hit_test(&item.base);
builder.push_stops(item.data.as_ref());
builder.push_item(&WrDisplayItem::RadialGradient(gradient_item));
IsContentful(false)
},
DisplayItem::Line(ref mut item) => {
let mut line_item = item.item;
line_item.common = build_common_item_properties(&item.base);
line_item.area = item.item.area.translate(stacking_context_offset);
push_hit_test(&item.base);
builder.push_item(&WrDisplayItem::Line(line_item));
IsContentful(false)
},
DisplayItem::BoxShadow(ref mut item) => {
let mut shadow_item = item.item;
shadow_item.common = build_common_item_properties(&item.base);
shadow_item.box_bounds = item.item.box_bounds.translate(stacking_context_offset);
push_hit_test(&item.base);
builder.push_item(&WrDisplayItem::BoxShadow(shadow_item));
IsContentful(false)
},
DisplayItem::PushTextShadow(ref mut item) => {
let common = build_common_item_properties(&item.base);
push_hit_test(&item.base);
builder.push_shadow(
&SpaceAndClipInfo {
spatial_id: common.spatial_id,
clip_chain_id: common.clip_chain_id,
},
item.shadow,
true,
);
IsContentful(false)
},
DisplayItem::PopAllTextShadows(_) => {
builder.push_item(&WrDisplayItem::PopAllShadows);
IsContentful(false)
},
DisplayItem::Iframe(ref mut item) => {
let common = build_common_item_properties(&item.base);
push_hit_test(&item.base);
builder.push_iframe(
item.bounds.translate(stacking_context_offset),
common.clip_rect,
&SpaceAndClipInfo {
spatial_id: common.spatial_id,
clip_chain_id: common.clip_chain_id,
},
item.iframe.into(),
true,
);
IsContentful(false)
},
DisplayItem::PushStackingContext(ref item) => {
let stacking_context = &item.stacking_context;
debug_assert_eq!(stacking_context.context_type, StackingContextType::Real);
//let mut info = LayoutPrimitiveInfo::new(stacking_context.bounds);
let mut bounds = stacking_context.bounds;
let spatial_id =
if let Some(frame_index) = stacking_context.established_reference_frame {
let (transform, ref_frame) =
match (stacking_context.transform, stacking_context.perspective) {
(None, Some(p)) => (
p,
ReferenceFrameKind::Perspective {
scrolling_relative_to: None,
},
),
(Some(t), None) => (
t,
ReferenceFrameKind::Transform {
is_2d_scale_translation: false,
should_snap: false,
paired_with_perspective: false,
},
),
(Some(t), Some(p)) => (
p.then(&t),
ReferenceFrameKind::Perspective {
scrolling_relative_to: None,
},
),
(None, None) => unreachable!(),
};
let index = frame_index.to_index();
let new_spatial_id = builder.push_reference_frame(
stacking_context.bounds.min + state.stacking_context_offset(),
current_scroll_node_id.spatial_id,
stacking_context.transform_style,
PropertyBinding::Value(transform),
ref_frame,
self.get_spatial_tree_item_key(builder, index),
);
state.add_clip_node_mapping(index, current_clip_chain_id);
state.register_spatial_node(
index,
new_spatial_id,
Some(current_scrolling_index),
None,
);
bounds.min = LayoutPoint::zero();
new_spatial_id
} else {
current_scroll_node_id.spatial_id
};
// TODO(jdm): WebRender now requires us to create stacking context items
// with the IS_BLEND_CONTAINER flag enabled if any children
// of the stacking context have a blend mode applied.
// This will require additional tracking during layout
// before we start collecting stacking contexts so that
// information will be available when we reach this point.
state.push_stacking_context_offset(
(bounds.min + state.stacking_context_offset()).to_vector(),
);
builder.push_stacking_context(
LayoutPoint::zero(),
spatial_id,
PrimitiveFlags::default(),
None,
stacking_context.transform_style,
stacking_context.mix_blend_mode,
&stacking_context.filters,
&[],
&[],
RasterSpace::Screen,
Default::default(),
None, // snapshot
);
IsContentful(false)
},
DisplayItem::PopStackingContext(ref item) => {
state.pop_stacking_context_offset();
builder.pop_stacking_context();
if item.established_reference_frame {
builder.pop_reference_frame();
}
IsContentful(false)
},
DisplayItem::DefineClipScrollNode(ref mut item) => {
let index = item.node_index.to_index();
let node = state.clip_scroll_nodes[index].clone();
let item_rect = node.clip.main.translate(stacking_context_offset);
let parent_index = node.parent_index.to_index();
let parent_spatial_id = state.webrender_spatial_id_for_index(parent_index);
let parent_clip_chain_id = state.webrender_clip_id_for_index(parent_index);
match node.node_type {
ClipScrollNodeType::Clip(clip_type) => {
let clip_id = match clip_type {
ClipType::Rect => {
builder.define_clip_rect(parent_spatial_id, item_rect)
},
ClipType::Rounded(complex) => {
builder.define_clip_rounded_rect(parent_spatial_id, complex)
},
};
let clip_chain_id =
state.define_clip_chain(builder, parent_clip_chain_id, [clip_id]);
state.add_clip_node_mapping(index, clip_chain_id);
state.add_spatial_node_mapping_to_parent_index(index, parent_index);
},
ClipScrollNodeType::ScrollFrame(scroll_sensitivity, external_id) => {
let clip_id = builder.define_clip_rect(parent_spatial_id, item_rect);
let clip_chain_id =
state.define_clip_chain(builder, parent_clip_chain_id, [clip_id]);
state.add_clip_node_mapping(index, clip_chain_id);
let spatial_id = builder.define_scroll_frame(
parent_spatial_id,
external_id,
node.content_rect,
item_rect,
LayoutVector2D::zero(), /* external_scroll_offset */
0, /* scroll_offset_generation */
HasScrollLinkedEffect::No,
self.get_spatial_tree_item_key(builder, index),
);
state.register_spatial_node(
index,
spatial_id,
Some(parent_index),
Some(ScrollableNodeInfo {
external_id,
scrollable_size: node.content_rect.size() - item_rect.size(),
scroll_sensitivity,
offset: LayoutVector2D::zero(),
}),
);
},
ClipScrollNodeType::StickyFrame(ref sticky_data) => {
// TODO: Add define_sticky_frame_with_parent to WebRender.
let id = builder.define_sticky_frame(
parent_spatial_id,
item_rect,
sticky_data.margins,
sticky_data.vertical_offset_bounds,
sticky_data.horizontal_offset_bounds,
LayoutVector2D::zero(), /* previously_applied_offset */
self.get_spatial_tree_item_key(builder, index),
None, /* transform */
);
state.add_clip_node_mapping(index, parent_clip_chain_id);
state.register_spatial_node(index, id, Some(current_scrolling_index), None);
},
ClipScrollNodeType::Placeholder => {
unreachable!("Found DefineClipScrollNode for Placeholder type node.");
},
};
IsContentful(false)
},
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,627 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cmp::{max, min};
use std::fmt;
use app_units::{Au, MAX_AU};
use log::debug;
use serde::Serialize;
use style::computed_values::clear::T as StyleClear;
use style::computed_values::float::T as StyleFloat;
use style::logical_geometry::{LogicalRect, LogicalSize, WritingMode};
use style::properties::ComputedValues;
use crate::block::FormattingContextType;
use crate::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils};
use crate::persistent_list::PersistentList;
/// The kind of float: left or right.
#[derive(Clone, Copy, Debug, Serialize)]
pub enum FloatKind {
Left,
Right,
}
impl FloatKind {
pub fn from_property_and_writing_mode(
property: StyleFloat,
writing_mode: WritingMode,
) -> Option<FloatKind> {
Some(match property {
StyleFloat::None => return None,
StyleFloat::Left => Self::Left,
StyleFloat::Right => Self::Right,
StyleFloat::InlineStart if writing_mode.is_bidi_ltr() => Self::Left,
StyleFloat::InlineStart => Self::Right,
StyleFloat::InlineEnd if writing_mode.is_bidi_ltr() => Self::Right,
StyleFloat::InlineEnd => Self::Left,
})
}
}
/// The kind of clearance: left, right, or both.
#[derive(Clone, Copy)]
pub enum ClearType {
Left,
Right,
Both,
}
impl ClearType {
pub fn from_style(style: &ComputedValues) -> Option<Self> {
Some(match style.get_box().clear {
StyleClear::None => return None,
StyleClear::Left => Self::Left,
StyleClear::Right => Self::Right,
StyleClear::Both => Self::Both,
// FIXME: these should check the writing mode of the containing block.
StyleClear::InlineStart if style.writing_mode.is_bidi_ltr() => Self::Left,
StyleClear::InlineStart => Self::Right,
StyleClear::InlineEnd if style.writing_mode.is_bidi_ltr() => Self::Right,
StyleClear::InlineEnd => Self::Left,
})
}
}
/// Information about a single float.
#[derive(Clone, Copy)]
struct Float {
/// The boundaries of this float.
bounds: LogicalRect<Au>,
/// The kind of float: left or right.
kind: FloatKind,
}
impl fmt::Debug for Float {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "bounds={:?} kind={:?}", self.bounds, self.kind)
}
}
/// Information about the floats next to a flow.
#[derive(Clone)]
struct FloatList {
/// Information about each of the floats here.
floats: PersistentList<Float>,
/// Cached copy of the maximum block-start offset of the float.
max_block_start: Option<Au>,
}
impl FloatList {
fn new() -> FloatList {
FloatList {
floats: PersistentList::new(),
max_block_start: None,
}
}
/// Returns true if the list is allocated and false otherwise. If false, there are guaranteed
/// not to be any floats.
fn is_present(&self) -> bool {
self.floats.len() > 0
}
}
impl fmt::Debug for FloatList {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"max_block_start={:?} floats={}",
self.max_block_start,
self.floats.len()
)?;
for float in self.floats.iter() {
write!(f, " {:?}", float)?;
}
Ok(())
}
}
/// All the information necessary to place a float.
pub struct PlacementInfo {
/// The dimensions of the float.
pub size: LogicalSize<Au>,
/// The minimum block-start of the float, as determined by earlier elements.
pub ceiling: Au,
/// The maximum inline-end position of the float, generally determined by the containing block.
pub max_inline_size: Au,
/// The kind of float.
pub kind: FloatKind,
}
impl fmt::Debug for PlacementInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"size={:?} ceiling={:?} max_inline_size={:?} kind={:?}",
self.size, self.ceiling, self.max_inline_size, self.kind
)
}
}
fn range_intersect(
block_start_1: Au,
block_end_1: Au,
block_start_2: Au,
block_end_2: Au,
) -> (Au, Au) {
(
max(block_start_1, block_start_2),
min(block_end_1, block_end_2),
)
}
/// Encapsulates information about floats. This is optimized to avoid allocation if there are
/// no floats, and to avoid copying when translating the list of floats downward.
#[derive(Clone)]
pub struct Floats {
/// The list of floats.
list: FloatList,
/// The offset of the flow relative to the first float.
offset: LogicalSize<Au>,
/// The writing mode of these floats.
pub writing_mode: WritingMode,
}
impl fmt::Debug for Floats {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if !self.list.is_present() {
write!(f, "[empty]")
} else {
write!(f, "offset={:?} floats={:?}", self.offset, self.list)
}
}
}
impl Floats {
/// Creates a new `Floats` object.
pub fn new(writing_mode: WritingMode) -> Floats {
Floats {
list: FloatList::new(),
offset: LogicalSize::zero(writing_mode),
writing_mode,
}
}
/// Adjusts the recorded offset of the flow relative to the first float.
pub fn translate(&mut self, delta: LogicalSize<Au>) {
self.offset = self.offset + delta
}
/// Returns the position of the last float in flow coordinates.
pub fn last_float_pos(&self) -> Option<LogicalRect<Au>> {
self.list
.floats
.front()
.map(|float| float.bounds.translate_by_size(self.offset))
}
/// Returns a rectangle that encloses the region from block-start to block-start + block-size,
/// with inline-size small enough that it doesn't collide with any floats. max_x is the
/// inline-size beyond which floats have no effect. (Generally this is the containing block
/// inline-size.)
pub fn available_rect(
&self,
block_start: Au,
block_size: Au,
max_x: Au,
) -> Option<LogicalRect<Au>> {
let list = &self.list;
let block_start = block_start - self.offset.block;
debug!("available_rect: trying to find space at {:?}", block_start);
// Relevant dimensions for the inline-end-most inline-start float
let mut max_inline_start = Au(0) - self.offset.inline;
let mut l_block_start = None;
let mut l_block_end = None;
// Relevant dimensions for the inline-start-most inline-end float
let mut min_inline_end = max_x - self.offset.inline;
let mut r_block_start = None;
let mut r_block_end = None;
// Find the float collisions for the given range in the block direction.
for float in list.floats.iter() {
debug!("available_rect: Checking for collision against float");
let float_pos = float.bounds.start;
let float_size = float.bounds.size;
debug!("float_pos: {:?}, float_size: {:?}", float_pos, float_size);
match float.kind {
FloatKind::Left
if float_pos.i + float_size.inline > max_inline_start &&
float_pos.b + float_size.block > block_start &&
float_pos.b < block_start + block_size =>
{
max_inline_start = float_pos.i + float_size.inline;
l_block_start = Some(float_pos.b);
l_block_end = Some(float_pos.b + float_size.block);
debug!(
"available_rect: collision with inline_start float: new \
max_inline_start is {:?}",
max_inline_start
);
},
FloatKind::Right
if float_pos.i < min_inline_end &&
float_pos.b + float_size.block > block_start &&
float_pos.b < block_start + block_size =>
{
min_inline_end = float_pos.i;
r_block_start = Some(float_pos.b);
r_block_end = Some(float_pos.b + float_size.block);
debug!(
"available_rect: collision with inline_end float: new min_inline_end \
is {:?}",
min_inline_end
);
},
FloatKind::Left | FloatKind::Right => {},
}
}
// Extend the vertical range of the rectangle to the closest floats.
// If there are floats on both sides, take the intersection of the
// two areas. Also make sure we never return a block-start smaller than the
// given upper bound.
let (block_start, block_end) =
match (r_block_start, r_block_end, l_block_start, l_block_end) {
(
Some(r_block_start),
Some(r_block_end),
Some(l_block_start),
Some(l_block_end),
) => range_intersect(
max(block_start, r_block_start),
r_block_end,
max(block_start, l_block_start),
l_block_end,
),
(None, None, Some(l_block_start), Some(l_block_end)) => {
(max(block_start, l_block_start), l_block_end)
},
(Some(r_block_start), Some(r_block_end), None, None) => {
(max(block_start, r_block_start), r_block_end)
},
(None, None, None, None) => return None,
_ => panic!("Reached unreachable state when computing float area"),
};
// FIXME(eatkinson): This assertion is too strong and fails in some cases. It is OK to
// return negative inline-sizes since we check against that inline-end away, but we should
// still understand why they occur and add a stronger assertion here.
// assert!(max_inline-start < min_inline-end);
assert!(block_start <= block_end, "Float position error");
Some(LogicalRect::new(
self.writing_mode,
max_inline_start + self.offset.inline,
block_start + self.offset.block,
min_inline_end - max_inline_start,
block_end - block_start,
))
}
/// Adds a new float to the list.
pub fn add_float(&mut self, info: &PlacementInfo) {
let new_info = PlacementInfo {
size: info.size,
ceiling: match self.list.max_block_start {
None => info.ceiling,
Some(max_block_start) => max(info.ceiling, max_block_start + self.offset.block),
},
max_inline_size: info.max_inline_size,
kind: info.kind,
};
debug!("add_float: added float with info {:?}", new_info);
let new_float = Float {
bounds: LogicalRect::from_point_size(
self.writing_mode,
self.place_between_floats(&new_info).start - self.offset,
info.size,
),
kind: info.kind,
};
self.list.floats = self.list.floats.prepend_elem(new_float);
self.list.max_block_start = match self.list.max_block_start {
None => Some(new_float.bounds.start.b),
Some(max_block_start) => Some(max(max_block_start, new_float.bounds.start.b)),
}
}
/// Given the three sides of the bounding rectangle in the block-start direction, finds the
/// largest block-size that will result in the rectangle not colliding with any floats. Returns
/// `None` if that block-size is infinite.
fn max_block_size_for_bounds(
&self,
inline_start: Au,
block_start: Au,
inline_size: Au,
) -> Option<Au> {
let list = &self.list;
let block_start = block_start - self.offset.block;
let inline_start = inline_start - self.offset.inline;
let mut max_block_size = None;
for float in list.floats.iter() {
if float.bounds.start.b + float.bounds.size.block > block_start &&
float.bounds.start.i + float.bounds.size.inline > inline_start &&
float.bounds.start.i < inline_start + inline_size
{
let new_y = float.bounds.start.b;
max_block_size = Some(min(max_block_size.unwrap_or(new_y), new_y));
}
}
max_block_size.map(|h| h + self.offset.block)
}
/// Given placement information, finds the closest place a fragment can be positioned without
/// colliding with any floats.
pub fn place_between_floats(&self, info: &PlacementInfo) -> LogicalRect<Au> {
debug!("place_between_floats: Placing object with {:?}", info.size);
// If no floats, use this fast path.
if !self.list.is_present() {
match info.kind {
FloatKind::Left => {
return LogicalRect::new(
self.writing_mode,
Au(0),
info.ceiling,
info.max_inline_size,
MAX_AU,
);
},
FloatKind::Right => {
return LogicalRect::new(
self.writing_mode,
info.max_inline_size - info.size.inline,
info.ceiling,
info.max_inline_size,
MAX_AU,
);
},
}
}
// Can't go any higher than previous floats or previous elements in the document.
let mut float_b = info.ceiling;
loop {
let maybe_location =
self.available_rect(float_b, info.size.block, info.max_inline_size);
debug!(
"place_float: got available rect: {:?} for block-pos: {:?}",
maybe_location, float_b
);
match maybe_location {
// If there are no floats blocking us, return the current location
// TODO(eatkinson): integrate with overflow
None => {
return match info.kind {
FloatKind::Left => LogicalRect::new(
self.writing_mode,
Au(0),
float_b,
info.max_inline_size,
MAX_AU,
),
FloatKind::Right => LogicalRect::new(
self.writing_mode,
info.max_inline_size - info.size.inline,
float_b,
info.max_inline_size,
MAX_AU,
),
};
},
Some(rect) => {
assert_ne!(
rect.start.b + rect.size.block,
float_b,
"Non-terminating float placement"
);
// Place here if there is enough room
if rect.size.inline >= info.size.inline {
let block_size = self.max_block_size_for_bounds(
rect.start.i,
rect.start.b,
rect.size.inline,
);
let block_size = block_size.unwrap_or(MAX_AU);
return match info.kind {
FloatKind::Left => LogicalRect::new(
self.writing_mode,
rect.start.i,
float_b,
rect.size.inline,
block_size,
),
FloatKind::Right => LogicalRect::new(
self.writing_mode,
rect.start.i + rect.size.inline - info.size.inline,
float_b,
rect.size.inline,
block_size,
),
};
}
// Try to place at the next-lowest location.
// Need to be careful of fencepost errors.
float_b = rect.start.b + rect.size.block;
},
}
}
}
pub fn clearance(&self, clear: ClearType) -> Au {
let list = &self.list;
let mut clearance = Au(0);
for float in list.floats.iter() {
match (clear, float.kind) {
(ClearType::Left, FloatKind::Left) |
(ClearType::Right, FloatKind::Right) |
(ClearType::Both, _) => {
let b = self.offset.block + float.bounds.start.b + float.bounds.size.block;
clearance = max(clearance, b);
},
_ => {},
}
}
clearance
}
pub fn is_present(&self) -> bool {
self.list.is_present()
}
}
/// The speculated inline sizes of floats flowing through or around a flow (depending on whether
/// the flow is a block formatting context). These speculations are always *upper bounds*; the
/// actual inline sizes might be less. Note that this implies that a speculated value of zero is a
/// guarantee that there will be no floats on that side.
///
/// This is used for two purposes: (a) determining whether we can lay out blocks in parallel; (b)
/// guessing the inline-sizes of block formatting contexts in an effort to lay them out in
/// parallel.
#[derive(Clone, Copy)]
pub struct SpeculatedFloatPlacement {
/// The estimated inline size (an upper bound) of the left floats flowing through this flow.
pub left: Au,
/// The estimated inline size (an upper bound) of the right floats flowing through this flow.
pub right: Au,
}
impl fmt::Debug for SpeculatedFloatPlacement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "L {:?} R {:?}", self.left, self.right)
}
}
impl SpeculatedFloatPlacement {
/// Returns a `SpeculatedFloatPlacement` objects with both left and right speculated inline
/// sizes initialized to zero.
pub fn zero() -> SpeculatedFloatPlacement {
SpeculatedFloatPlacement {
left: Au(0),
right: Au(0),
}
}
/// Given the speculated inline size of the floats out for the inorder predecessor of this
/// flow, computes the speculated inline size of the floats flowing in.
pub fn compute_floats_in(&mut self, flow: &mut dyn Flow) {
let base_flow = flow.base();
if base_flow.flags.contains(FlowFlags::CLEARS_LEFT) {
self.left = Au(0)
}
if base_flow.flags.contains(FlowFlags::CLEARS_RIGHT) {
self.right = Au(0)
}
}
/// Given the speculated inline size of the floats out for this flow's last child, computes the
/// speculated inline size of the floats out for this flow.
pub fn compute_floats_out(&mut self, flow: &mut dyn Flow) {
if flow.is_block_like() {
let block_flow = flow.as_block();
if block_flow.formatting_context_type() != FormattingContextType::None {
*self = block_flow.base.speculated_float_placement_in;
} else {
if self.left > Au(0) || self.right > Au(0) {
let speculated_inline_content_edge_offsets =
block_flow.fragment.guess_inline_content_edge_offsets();
if self.left > Au(0) && speculated_inline_content_edge_offsets.start > Au(0) {
self.left += speculated_inline_content_edge_offsets.start
}
if self.right > Au(0) && speculated_inline_content_edge_offsets.end > Au(0) {
self.right += speculated_inline_content_edge_offsets.end
}
}
self.left = max(
self.left,
block_flow.base.speculated_float_placement_in.left,
);
self.right = max(
self.right,
block_flow.base.speculated_float_placement_in.right,
);
}
}
let base_flow = flow.base();
if !base_flow.flags.is_float() {
return;
}
let mut float_inline_size = base_flow.intrinsic_inline_sizes.preferred_inline_size;
if float_inline_size == Au(0) && flow.is_block_like() {
// Hack: If the size of the float is not fixed, then there's no
// way we can guess at its size now. So just pick an arbitrary
// nonzero value (in this case, 1px) so that the layout
// traversal logic will know that objects later in the document
// might flow around this float.
let inline_size = flow.as_block().fragment.style.content_inline_size();
let fixed =
inline_size.is_definitely_zero() || inline_size.maybe_to_used_value(None).is_some();
if !fixed {
float_inline_size = Au::from_px(1)
}
}
match base_flow.flags.float_kind() {
None => {},
Some(FloatKind::Left) => self.left += float_inline_size,
Some(FloatKind::Right) => self.right += float_inline_size,
}
}
/// Given a flow, computes the speculated inline size of the floats in of its first child.
pub fn compute_floats_in_for_first_child(
parent_flow: &mut dyn Flow,
) -> SpeculatedFloatPlacement {
if !parent_flow.is_block_like() {
return parent_flow.base().speculated_float_placement_in;
}
let parent_block_flow = parent_flow.as_block();
if parent_block_flow.formatting_context_type() != FormattingContextType::None {
return SpeculatedFloatPlacement::zero();
}
let mut placement = parent_block_flow.base.speculated_float_placement_in;
let speculated_inline_content_edge_offsets = parent_block_flow
.fragment
.guess_inline_content_edge_offsets();
if speculated_inline_content_edge_offsets.start > Au(0) {
placement.left = if placement.left > speculated_inline_content_edge_offsets.start {
placement.left - speculated_inline_content_edge_offsets.start
} else {
Au(0)
}
}
if speculated_inline_content_edge_offsets.end > Au(0) {
placement.right = if placement.right > speculated_inline_content_edge_offsets.end {
placement.right - speculated_inline_content_edge_offsets.end
} else {
Au(0)
}
}
placement
}
}

File diff suppressed because it is too large Load diff

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,47 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#![deny(unsafe_code)]
mod block;
pub mod construct;
pub mod context;
pub mod data;
pub mod display_list;
mod flex;
mod floats;
pub mod flow;
mod flow_list;
pub mod flow_ref;
mod fragment;
mod generated_content;
pub mod incremental;
mod inline;
mod linked_list;
mod list_item;
mod model;
mod multicol;
pub mod parallel;
mod persistent_list;
pub mod query;
pub mod sequential;
mod table;
mod table_caption;
mod table_cell;
mod table_colgroup;
mod table_row;
mod table_rowgroup;
mod table_wrapper;
mod text;
mod text_run;
pub mod traversal;
pub mod wrapper;
// For unit tests:
// We can't use servo_arc for everything in layout, because the Flow stuff uses
// weak references.
use servo_arc::Arc as ServoArc;
pub use self::data::LayoutData;
pub use crate::fragment::{Fragment, SpecificFragmentInfo};

View file

@ -1,21 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Utility functions for doubly-linked lists.
use std::collections::LinkedList;
use std::mem;
/// Splits the head off a list in O(1) time, and returns the head.
pub fn split_off_head<T>(list: &mut LinkedList<T>) -> LinkedList<T> {
let tail = list.split_off(1);
mem::replace(list, tail)
}
/// Prepends the items in the other list to this one, leaving the other list empty.
#[inline]
pub fn prepend_from<T>(this: &mut LinkedList<T>, other: &mut LinkedList<T>) {
other.append(this);
mem::swap(this, other);
}

View file

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

View file

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

View file

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

View file

@ -1,239 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Implements parallel traversals over the DOM and flow trees.
//!
//! This code is highly unsafe. Keep this file small and easy to audit.
#![allow(unsafe_code)]
use std::sync::atomic::{AtomicIsize, Ordering};
use std::{mem, ptr};
use profile_traits::time::{self, TimerMetadata};
use profile_traits::time_profile;
use smallvec::SmallVec;
use crate::block::BlockFlow;
use crate::context::LayoutContext;
use crate::flow::{Flow, GetBaseFlow};
use crate::flow_ref::FlowRef;
use crate::traversal::{AssignBSizes, AssignISizes, PostorderFlowTraversal, PreorderFlowTraversal};
/// Traversal chunk size.
const CHUNK_SIZE: usize = 16;
pub type FlowList = SmallVec<[UnsafeFlow; CHUNK_SIZE]>;
/// Vtable + pointer representation of a Flow trait object.
#[derive(Clone, Copy, Eq)]
pub struct UnsafeFlow(*const dyn Flow);
unsafe impl Sync for UnsafeFlow {}
unsafe impl Send for UnsafeFlow {}
impl PartialEq for UnsafeFlow {
#[allow(clippy::ptr_eq)]
fn eq(&self, other: &Self) -> bool {
// Compare the pointers explicitly to avoid a clippy error
self.0 as *const u8 == other.0 as *const u8
}
}
fn null_unsafe_flow() -> UnsafeFlow {
UnsafeFlow(ptr::null::<BlockFlow>())
}
#[allow(clippy::not_unsafe_ptr_arg_deref)] // It has an unsafe block inside
pub fn mut_owned_flow_to_unsafe_flow(flow: *mut FlowRef) -> UnsafeFlow {
unsafe { UnsafeFlow(&**flow) }
}
/// Information that we need stored in each flow.
pub struct FlowParallelInfo {
/// The number of children that still need work done.
pub children_count: AtomicIsize,
/// The address of the parent flow.
pub parent: UnsafeFlow,
}
impl FlowParallelInfo {
pub fn new() -> FlowParallelInfo {
FlowParallelInfo {
children_count: AtomicIsize::new(0),
parent: null_unsafe_flow(),
}
}
}
impl Default for FlowParallelInfo {
fn default() -> Self {
Self::new()
}
}
/// Process current flow and potentially traverse its ancestors.
///
/// If we are the last child that finished processing, recursively process
/// our parent. Else, stop. Also, stop at the root.
///
/// Thus, if we start with all the leaves of a tree, we end up traversing
/// the whole tree bottom-up because each parent will be processed exactly
/// once (by the last child that finishes processing).
///
/// The only communication between siblings is that they both
/// fetch-and-subtract the parent's children count.
fn bottom_up_flow(mut unsafe_flow: UnsafeFlow, assign_bsize_traversal: &AssignBSizes) {
loop {
// Get a real flow.
let flow: &mut dyn Flow = unsafe { mem::transmute(unsafe_flow) };
// Perform the appropriate traversal.
if assign_bsize_traversal.should_process(flow) {
assign_bsize_traversal.process(flow);
}
let base = flow.mut_base();
// Reset the count of children for the next layout traversal.
base.parallel
.children_count
.store(base.children.len() as isize, Ordering::Relaxed);
// Possibly enqueue the parent.
let unsafe_parent = base.parallel.parent;
if unsafe_parent == null_unsafe_flow() {
// We're done!
break;
}
// No, we're not at the root yet. Then are we the last child
// of our parent to finish processing? If so, we can continue
// on with our parent; otherwise, we've gotta wait.
let parent: &mut dyn Flow = unsafe { &mut *(unsafe_parent.0 as *mut dyn Flow) };
let parent_base = parent.mut_base();
if parent_base
.parallel
.children_count
.fetch_sub(1, Ordering::Relaxed) ==
1
{
// We were the last child of our parent. Reflow our parent.
unsafe_flow = unsafe_parent
} else {
// Stop.
break;
}
}
}
fn top_down_flow<'scope>(
unsafe_flows: &[UnsafeFlow],
pool: &'scope rayon::ThreadPool,
scope: &rayon::ScopeFifo<'scope>,
assign_isize_traversal: &'scope AssignISizes,
assign_bsize_traversal: &'scope AssignBSizes,
) {
let mut discovered_child_flows = FlowList::new();
for unsafe_flow in unsafe_flows {
let mut had_children = false;
unsafe {
// Get a real flow.
let flow: &mut dyn Flow = mem::transmute(*unsafe_flow);
flow.mut_base().thread_id = pool.current_thread_index().unwrap() as u8;
if assign_isize_traversal.should_process(flow) {
// Perform the appropriate traversal.
assign_isize_traversal.process(flow);
}
// Possibly enqueue the children.
for kid in flow.mut_base().child_iter_mut() {
had_children = true;
discovered_child_flows.push(UnsafeFlow(kid));
}
}
// If there were no more children, start assigning block-sizes.
if !had_children {
bottom_up_flow(*unsafe_flow, assign_bsize_traversal)
}
}
if discovered_child_flows.is_empty() {
return;
}
if discovered_child_flows.len() <= CHUNK_SIZE {
// We can handle all the children in this work unit.
top_down_flow(
&discovered_child_flows,
pool,
scope,
assign_isize_traversal,
assign_bsize_traversal,
);
} else {
// Spawn a new work unit for each chunk after the first.
let mut chunks = discovered_child_flows.chunks(CHUNK_SIZE);
let first_chunk = chunks.next();
for chunk in chunks {
let nodes = chunk.iter().cloned().collect::<FlowList>();
scope.spawn_fifo(move |scope| {
top_down_flow(
&nodes,
pool,
scope,
assign_isize_traversal,
assign_bsize_traversal,
);
});
}
if let Some(chunk) = first_chunk {
top_down_flow(
chunk,
pool,
scope,
assign_isize_traversal,
assign_bsize_traversal,
);
}
}
}
/// Run the main layout passes in parallel.
pub fn reflow(
root: &mut dyn Flow,
profiler_metadata: Option<TimerMetadata>,
time_profiler_chan: time::ProfilerChan,
context: &LayoutContext,
queue: &rayon::ThreadPool,
) {
let assign_isize_traversal = &AssignISizes {
layout_context: context,
};
let assign_bsize_traversal = &AssignBSizes {
layout_context: context,
};
let nodes = [UnsafeFlow(root)];
queue.install(move || {
rayon::scope_fifo(move |scope| {
time_profile!(
time::ProfilerCategory::LayoutParallelWarmup,
profiler_metadata,
time_profiler_chan,
move || {
top_down_flow(
&nodes,
queue,
scope,
assign_isize_traversal,
assign_bsize_traversal,
);
},
);
});
});
}

View file

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

File diff suppressed because it is too large Load diff

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,150 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! CSS table formatting contexts.
use std::fmt;
use app_units::Au;
use base::print_tree::PrintTree;
use euclid::default::Point2D;
use log::{debug, trace};
use style::logical_geometry::LogicalSize;
use style::properties::ComputedValues;
use crate::block::BlockFlow;
use crate::context::LayoutContext;
use crate::display_list::{
DisplayListBuildState, StackingContextCollectionFlags, StackingContextCollectionState,
};
use crate::flow::{Flow, FlowClass, OpaqueFlow};
use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
#[allow(unsafe_code)]
unsafe impl crate::flow::HasBaseFlow for TableCaptionFlow {}
/// A table formatting context.
#[repr(C)]
pub struct TableCaptionFlow {
pub block_flow: BlockFlow,
}
impl TableCaptionFlow {
pub fn from_fragment(fragment: Fragment) -> TableCaptionFlow {
TableCaptionFlow {
block_flow: BlockFlow::from_fragment(fragment),
}
}
}
impl Flow for TableCaptionFlow {
fn class(&self) -> FlowClass {
FlowClass::TableCaption
}
fn as_mut_block(&mut self) -> &mut BlockFlow {
&mut self.block_flow
}
fn as_block(&self) -> &BlockFlow {
&self.block_flow
}
fn bubble_inline_sizes(&mut self) {
self.block_flow.bubble_inline_sizes();
}
fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
debug!(
"assign_inline_sizes({}): assigning inline_size for flow",
"table_caption"
);
trace!("TableCaptionFlow before assigning: {:?}", &self);
self.block_flow.assign_inline_sizes(layout_context);
trace!("TableCaptionFlow after assigning: {:?}", &self);
}
fn assign_block_size(&mut self, layout_context: &LayoutContext) {
debug!("assign_block_size: assigning block_size for table_caption");
trace!("TableCaptionFlow before assigning: {:?}", &self);
self.block_flow.assign_block_size(layout_context);
trace!("TableCaptionFlow after assigning: {:?}", &self);
}
fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
self.block_flow
.compute_stacking_relative_position(layout_context)
}
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
self.block_flow
.update_late_computed_inline_position_if_necessary(inline_position)
}
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
self.block_flow
.update_late_computed_block_position_if_necessary(block_position)
}
fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
debug!("build_display_list_table_caption: same process as block flow");
self.block_flow.build_display_list(state);
}
fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
self.block_flow
.collect_stacking_contexts_for_block(state, StackingContextCollectionFlags::empty());
}
fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
self.block_flow.repair_style(new_style)
}
fn compute_overflow(&self) -> Overflow {
self.block_flow.compute_overflow()
}
fn contains_roots_of_absolute_flow_tree(&self) -> bool {
self.block_flow.contains_roots_of_absolute_flow_tree()
}
fn is_absolute_containing_block(&self) -> bool {
self.block_flow.is_absolute_containing_block()
}
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
self.block_flow.generated_containing_block_size(flow)
}
fn iterate_through_fragment_border_boxes(
&self,
iterator: &mut dyn FragmentBorderBoxIterator,
level: i32,
stacking_context_position: &Point2D<Au>,
) {
self.block_flow.iterate_through_fragment_border_boxes(
iterator,
level,
stacking_context_position,
)
}
fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
self.block_flow.mutate_fragments(mutator)
}
fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
self.block_flow.print_extra_flow_children(print_tree);
}
}
impl fmt::Debug for TableCaptionFlow {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "TableCaptionFlow: {:?}", self.block_flow)
}
}

View file

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

View file

@ -1,127 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! CSS table formatting contexts.
use std::fmt;
use app_units::Au;
use euclid::default::Point2D;
use style::logical_geometry::LogicalSize;
use style::properties::ComputedValues;
use style::values::computed::Size;
use crate::context::LayoutContext;
use crate::display_list::{DisplayListBuildState, StackingContextCollectionState};
use crate::flow::{BaseFlow, Flow, FlowClass, ForceNonfloatedFlag, OpaqueFlow};
use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
#[allow(unsafe_code)]
unsafe impl crate::flow::HasBaseFlow for TableColGroupFlow {}
/// A table formatting context.
#[repr(C)]
pub struct TableColGroupFlow {
/// Data common to all flows.
pub base: BaseFlow,
/// The associated fragment.
pub fragment: Option<Fragment>,
/// The table column fragments
pub cols: Vec<Fragment>,
/// The specified inline-sizes of table columns. (We use `LengthPercentageOrAuto` here in
/// lieu of `ColumnInlineSize` because column groups do not establish minimum or preferred
/// inline sizes.)
pub inline_sizes: Vec<Size>,
}
impl TableColGroupFlow {
pub fn from_fragments(fragment: Fragment, fragments: Vec<Fragment>) -> TableColGroupFlow {
let writing_mode = fragment.style().writing_mode;
TableColGroupFlow {
base: BaseFlow::new(
Some(fragment.style()),
writing_mode,
ForceNonfloatedFlag::ForceNonfloated,
),
fragment: Some(fragment),
cols: fragments,
inline_sizes: vec![],
}
}
}
impl Flow for TableColGroupFlow {
fn class(&self) -> FlowClass {
FlowClass::TableColGroup
}
fn as_mut_table_colgroup(&mut self) -> &mut TableColGroupFlow {
self
}
fn as_table_colgroup(&self) -> &TableColGroupFlow {
self
}
fn bubble_inline_sizes(&mut self) {
for fragment in &self.cols {
// Retrieve the specified value from the appropriate CSS property.
let inline_size = fragment.style().content_inline_size();
for _ in 0..fragment.column_span() {
self.inline_sizes.push(inline_size.clone())
}
}
}
/// Table column inline-sizes are assigned in the table flow and propagated to table row flows
/// and/or rowgroup flows. Therefore, table colgroup flows do not need to assign inline-sizes.
fn assign_inline_sizes(&mut self, _: &LayoutContext) {}
/// Table columns do not have block-size.
fn assign_block_size(&mut self, _: &LayoutContext) {}
fn update_late_computed_inline_position_if_necessary(&mut self, _: Au) {}
fn update_late_computed_block_position_if_necessary(&mut self, _: Au) {}
// Table columns are invisible.
fn build_display_list(&mut self, _: &mut DisplayListBuildState) {}
fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
self.base.stacking_context_id = state.current_stacking_context_id;
self.base.clipping_and_scrolling = Some(state.current_clipping_and_scrolling);
}
fn repair_style(&mut self, _: &crate::ServoArc<ComputedValues>) {}
fn compute_overflow(&self) -> Overflow {
Overflow::new()
}
fn generated_containing_block_size(&self, _: OpaqueFlow) -> LogicalSize<Au> {
panic!("Table column groups can't be containing blocks!")
}
fn iterate_through_fragment_border_boxes(
&self,
_: &mut dyn FragmentBorderBoxIterator,
_: i32,
_: &Point2D<Au>,
) {
}
fn mutate_fragments(&mut self, _: &mut dyn FnMut(&mut Fragment)) {}
}
impl fmt::Debug for TableColGroupFlow {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.fragment {
Some(ref rb) => write!(f, "TableColGroupFlow: {:?}", rb),
None => write!(f, "TableColGroupFlow"),
}
}
}

File diff suppressed because it is too large Load diff

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,444 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::cmp::max;
use std::slice::Iter;
use std::sync::Arc;
use app_units::Au;
use fonts::{
ByteIndex, FontMetrics, FontRef, GlyphRun, GlyphStore, RunMetrics, ShapingFlags, ShapingOptions,
};
use log::debug;
use range::Range;
use serde::{Deserialize, Serialize};
use style::str::char_is_whitespace;
use unicode_bidi as bidi;
use webrender_api::FontInstanceKey;
use xi_unicode::LineBreakLeafIter;
thread_local! {
static INDEX_OF_FIRST_GLYPH_RUN_CACHE: Cell<Option<(*const TextRun, ByteIndex, usize)>> =
const { Cell::new(None) }
}
/// A single "paragraph" of text in one font size and style.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct TextRun {
/// The UTF-8 string represented by this text run.
pub text: Arc<String>,
pub pt_size: Au,
pub font_metrics: FontMetrics,
pub font_key: FontInstanceKey,
/// The glyph runs that make up this text run.
pub glyphs: Arc<Vec<GlyphRun>>,
pub bidi_level: bidi::Level,
pub extra_word_spacing: Au,
}
impl Drop for TextRun {
fn drop(&mut self) {
// Invalidate the glyph run cache if it was our text run that got freed.
INDEX_OF_FIRST_GLYPH_RUN_CACHE.with(|index_of_first_glyph_run_cache| {
if let Some((text_run_ptr, _, _)) = index_of_first_glyph_run_cache.get() {
if text_run_ptr == (self as *const TextRun) {
index_of_first_glyph_run_cache.set(None);
}
}
})
}
}
pub struct NaturalWordSliceIterator<'a> {
glyphs: &'a [GlyphRun],
index: usize,
range: Range<ByteIndex>,
reverse: bool,
}
/// A "slice" of a text run is a series of contiguous glyphs that all belong to the same glyph
/// store. Line breaking strategies yield these.
pub struct TextRunSlice<'a> {
/// The glyph store that the glyphs in this slice belong to.
pub glyphs: &'a GlyphStore,
/// The byte index that this slice begins at, relative to the start of the *text run*.
pub offset: ByteIndex,
/// The range that these glyphs encompass, relative to the start of the *glyph store*.
pub range: Range<ByteIndex>,
}
impl TextRunSlice<'_> {
/// Returns the range that these glyphs encompass, relative to the start of the *text run*.
#[inline]
pub fn text_run_range(&self) -> Range<ByteIndex> {
let mut range = self.range;
range.shift_by(self.offset);
range
}
}
impl<'a> Iterator for NaturalWordSliceIterator<'a> {
type Item = TextRunSlice<'a>;
// inline(always) due to the inefficient rt failures messing up inline heuristics, I think.
#[inline(always)]
fn next(&mut self) -> Option<TextRunSlice<'a>> {
let slice_glyphs;
if self.reverse {
if self.index == 0 {
return None;
}
self.index -= 1;
slice_glyphs = &self.glyphs[self.index];
} else {
if self.index >= self.glyphs.len() {
return None;
}
slice_glyphs = &self.glyphs[self.index];
self.index += 1;
}
let mut byte_range = self.range.intersect(&slice_glyphs.range);
let slice_range_begin = slice_glyphs.range.begin();
byte_range.shift_by(-slice_range_begin);
if !byte_range.is_empty() {
Some(TextRunSlice {
glyphs: &slice_glyphs.glyph_store,
offset: slice_range_begin,
range: byte_range,
})
} else {
None
}
}
}
pub struct CharacterSliceIterator<'a> {
text: &'a str,
glyph_run: Option<&'a GlyphRun>,
glyph_run_iter: Iter<'a, GlyphRun>,
range: Range<ByteIndex>,
}
impl<'a> Iterator for CharacterSliceIterator<'a> {
type Item = TextRunSlice<'a>;
// inline(always) due to the inefficient rt failures messing up inline heuristics, I think.
#[inline(always)]
fn next(&mut self) -> Option<TextRunSlice<'a>> {
let glyph_run = self.glyph_run?;
debug_assert!(!self.range.is_empty());
let byte_start = self.range.begin();
let byte_len = match self.text[byte_start.to_usize()..].chars().next() {
Some(ch) => ByteIndex(ch.len_utf8() as isize),
None => unreachable!(), // XXX refactor?
};
self.range.adjust_by(byte_len, -byte_len);
if self.range.is_empty() {
// We're done.
self.glyph_run = None
} else if self.range.intersect(&glyph_run.range).is_empty() {
// Move on to the next glyph run.
self.glyph_run = self.glyph_run_iter.next();
}
let index_within_glyph_run = byte_start - glyph_run.range.begin();
Some(TextRunSlice {
glyphs: &glyph_run.glyph_store,
offset: glyph_run.range.begin(),
range: Range::new(index_within_glyph_run, byte_len),
})
}
}
impl<'a> TextRun {
/// Constructs a new text run. Also returns if there is a line break at the beginning
pub fn new(
font: FontRef,
font_key: FontInstanceKey,
text: String,
options: &ShapingOptions,
bidi_level: bidi::Level,
breaker: &mut Option<LineBreakLeafIter>,
) -> (TextRun, bool) {
let (glyphs, break_at_zero) =
TextRun::break_and_shape(font.clone(), &text, options, breaker);
(
TextRun {
text: Arc::new(text),
font_metrics: font.metrics.clone(),
font_key,
pt_size: font.descriptor.pt_size,
glyphs: Arc::new(glyphs),
bidi_level,
extra_word_spacing: Au(0),
},
break_at_zero,
)
}
pub fn break_and_shape(
font: FontRef,
text: &str,
options: &ShapingOptions,
breaker: &mut Option<LineBreakLeafIter>,
) -> (Vec<GlyphRun>, bool) {
let mut glyphs = vec![];
let mut slice = 0..0;
let mut finished = false;
let mut break_at_zero = false;
if breaker.is_none() {
if text.is_empty() {
return (glyphs, true);
}
*breaker = Some(LineBreakLeafIter::new(text, 0));
}
let breaker = breaker.as_mut().unwrap();
let mut push_range = |range: &std::ops::Range<usize>, options: &ShapingOptions| {
glyphs.push(GlyphRun {
glyph_store: font.shape_text(&text[range.clone()], options),
range: Range::new(
ByteIndex(range.start as isize),
ByteIndex(range.len() as isize),
),
});
};
while !finished {
let (idx, _is_hard_break) = breaker.next(text);
if idx == text.len() {
finished = true;
}
if idx == 0 {
break_at_zero = true;
}
// Extend the slice to the next UAX#14 line break opportunity.
slice.end = idx;
let word = &text[slice.clone()];
// Split off any trailing whitespace into a separate glyph run.
let mut whitespace = slice.end..slice.end;
let mut rev_char_indices = word.char_indices().rev().peekable();
let ends_with_newline = rev_char_indices.peek().is_some_and(|&(_, c)| c == '\n');
if let Some((i, _)) = rev_char_indices
.take_while(|&(_, c)| char_is_whitespace(c))
.last()
{
whitespace.start = slice.start + i;
slice.end = whitespace.start;
} else if idx != text.len() && options.flags.contains(ShapingFlags::KEEP_ALL_FLAG) {
// If there's no whitespace and word-break is set to
// keep-all, try increasing the slice.
continue;
}
if !slice.is_empty() {
push_range(&slice, options);
}
if !whitespace.is_empty() {
let mut options = *options;
options
.flags
.insert(ShapingFlags::IS_WHITESPACE_SHAPING_FLAG);
// The breaker breaks after every newline, so either there is none,
// or there is exactly one at the very end. In the latter case,
// split it into a different run. That's because shaping considers
// a newline to have the same advance as a space, but during layout
// we want to treat the newline as having no advance.
if ends_with_newline {
whitespace.end -= 1;
if !whitespace.is_empty() {
push_range(&whitespace, &options);
}
whitespace.start = whitespace.end;
whitespace.end += 1;
}
push_range(&whitespace, &options);
}
slice.start = whitespace.end;
}
(glyphs, break_at_zero)
}
pub fn ascent(&self) -> Au {
self.font_metrics.ascent
}
pub fn advance_for_range(&self, range: &Range<ByteIndex>) -> Au {
if range.is_empty() {
return Au(0);
}
// TODO(Issue #199): alter advance direction for RTL
// TODO(Issue #98): using inter-char and inter-word spacing settings when measuring text
self.natural_word_slices_in_range(range)
.fold(Au(0), |advance, slice| {
advance +
slice
.glyphs
.advance_for_byte_range(&slice.range, self.extra_word_spacing)
})
}
pub fn metrics_for_range(&self, range: &Range<ByteIndex>) -> RunMetrics {
RunMetrics::new(
self.advance_for_range(range),
self.font_metrics.ascent,
self.font_metrics.descent,
)
}
pub fn metrics_for_slice(
&self,
glyphs: &GlyphStore,
slice_range: &Range<ByteIndex>,
) -> RunMetrics {
RunMetrics::new(
glyphs.advance_for_byte_range(slice_range, self.extra_word_spacing),
self.font_metrics.ascent,
self.font_metrics.descent,
)
}
pub fn min_width_for_range(&self, range: &Range<ByteIndex>) -> Au {
debug!("iterating outer range {:?}", range);
self.natural_word_slices_in_range(range)
.fold(Au(0), |max_piece_width, slice| {
debug!("iterated on {:?}[{:?}]", slice.offset, slice.range);
max(max_piece_width, self.advance_for_range(&slice.range))
})
}
pub fn minimum_splittable_inline_size(&self, range: &Range<ByteIndex>) -> Au {
match self.natural_word_slices_in_range(range).next() {
None => Au(0),
Some(slice) => self.advance_for_range(&slice.range),
}
}
/// Returns the index of the first glyph run containing the given character index.
fn index_of_first_glyph_run_containing(&self, index: ByteIndex) -> Option<usize> {
let self_ptr = self as *const TextRun;
INDEX_OF_FIRST_GLYPH_RUN_CACHE.with(|index_of_first_glyph_run_cache| {
if let Some((last_text_run, last_index, last_result)) =
index_of_first_glyph_run_cache.get()
{
if last_text_run == self_ptr && last_index == index {
return Some(last_result);
}
}
if let Ok(result) = self
.glyphs
.binary_search_by(|current| current.compare(&index))
{
index_of_first_glyph_run_cache.set(Some((self_ptr, index, result)));
Some(result)
} else {
None
}
})
}
pub fn on_glyph_run_boundary(&self, index: ByteIndex) -> bool {
if let Some(glyph_index) = self.index_of_first_glyph_run_containing(index) {
self.glyphs[glyph_index].range.begin() == index
} else {
true
}
}
/// Returns the index in the range of the first glyph advancing over given advance
pub fn range_index_of_advance(&self, range: &Range<ByteIndex>, advance: Au) -> usize {
// TODO(Issue #199): alter advance direction for RTL
// TODO(Issue #98): using inter-char and inter-word spacing settings when measuring text
let mut remaining = advance;
self.natural_word_slices_in_range(range)
.map(|slice| {
let (slice_index, slice_advance) = slice.glyphs.range_index_of_advance(
&slice.range,
remaining,
self.extra_word_spacing,
);
remaining -= slice_advance;
slice_index
})
.sum()
}
/// Returns an iterator that will iterate over all slices of glyphs that represent natural
/// words in the given range.
pub fn natural_word_slices_in_range(
&'a self,
range: &Range<ByteIndex>,
) -> NaturalWordSliceIterator<'a> {
let index = match self.index_of_first_glyph_run_containing(range.begin()) {
None => self.glyphs.len(),
Some(index) => index,
};
NaturalWordSliceIterator {
glyphs: &self.glyphs[..],
index,
range: *range,
reverse: false,
}
}
/// Returns an iterator that over natural word slices in visual order (left to right or
/// right to left, depending on the bidirectional embedding level).
pub fn natural_word_slices_in_visual_order(
&'a self,
range: &Range<ByteIndex>,
) -> NaturalWordSliceIterator<'a> {
// Iterate in reverse order if bidi level is RTL.
let reverse = self.bidi_level.is_rtl();
let index = if reverse {
match self.index_of_first_glyph_run_containing(range.end() - ByteIndex(1)) {
Some(i) => i + 1, // In reverse mode, index points one past the next element.
None => 0,
}
} else {
match self.index_of_first_glyph_run_containing(range.begin()) {
Some(i) => i,
None => self.glyphs.len(),
}
};
NaturalWordSliceIterator {
glyphs: &self.glyphs[..],
index,
range: *range,
reverse,
}
}
/// Returns an iterator that will iterate over all slices of glyphs that represent individual
/// characters in the given range.
pub fn character_slices_in_range(
&'a self,
range: &Range<ByteIndex>,
) -> CharacterSliceIterator<'a> {
let index = match self.index_of_first_glyph_run_containing(range.begin()) {
None => self.glyphs.len(),
Some(index) => index,
};
let mut glyph_run_iter = self.glyphs[index..].iter();
let first_glyph_run = glyph_run_iter.next();
CharacterSliceIterator {
text: &self.text,
glyph_run: first_glyph_run,
glyph_run_iter,
range: *range,
}
}
}

View file

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

View file

@ -1,162 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! A safe wrapper for DOM nodes that prevents layout from mutating the DOM, from letting DOM nodes
//! escape, and from generally doing anything that it isn't supposed to. This is accomplished via
//! a simple whitelist of allowed operations, along with some lifetime magic to prevent nodes from
//! escaping.
//!
//! As a security wrapper is only as good as its whitelist, be careful when adding operations to
//! this list. The cardinal rules are:
//!
//! 1. Layout is not allowed to mutate the DOM.
//!
//! 2. Layout is not allowed to see anything with `LayoutDom` in the name, because it could hang
//! onto these objects and cause use-after-free.
//!
//! When implementing wrapper functions, be careful that you do not touch the borrow flags, or you
//! will race and cause spurious thread failure. (Note that I do not believe these races are
//! exploitable, but they'll result in brokenness nonetheless.)
//!
//! Rules of the road for this file:
//!
//! * Do not call any methods on DOM nodes without checking to see whether they use borrow flags.
//!
//! o Instead of `get_attr()`, use `.get_attr_val_for_layout()`.
//!
//! o Instead of `html_element_in_html_document()`, use
//! `html_element_in_html_document_for_layout()`.
#![allow(unsafe_code)]
use atomic_refcell::{AtomicRef, AtomicRefMut};
use script_layout_interface::wrapper_traits::{
LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
};
use style::dom::{NodeInfo, TElement, TNode};
use style::selector_parser::RestyleDamage;
use style::values::computed::counters::ContentItem;
use style::values::generics::counters::Content;
use crate::data::{InnerLayoutData, LayoutData, LayoutDataFlags};
pub trait ThreadSafeLayoutNodeHelpers<'dom> {
fn borrow_layout_data(self) -> Option<AtomicRef<'dom, InnerLayoutData>>;
fn mutate_layout_data(self) -> Option<AtomicRefMut<'dom, InnerLayoutData>>;
/// Returns the layout data flags for this node.
fn flags(self) -> LayoutDataFlags;
/// Adds the given flags to this node.
fn insert_flags(self, new_flags: LayoutDataFlags);
/// Removes the given flags from this node.
fn remove_flags(self, flags: LayoutDataFlags);
/// If this is a text node, generated content, or a form element, copies out
/// its content. Otherwise, panics.
///
/// FIXME(pcwalton): This might have too much copying and/or allocation. Profile this.
fn text_content(&self) -> TextContent;
/// The RestyleDamage from any restyling, or RestyleDamage::rebuild_and_reflow() if this
/// is the first time layout is visiting this node. We implement this here, rather than
/// with the rest of the wrapper layer, because we need layout code to determine whether
/// layout has visited the node.
fn restyle_damage(self) -> RestyleDamage;
}
impl<'dom, T> ThreadSafeLayoutNodeHelpers<'dom> for T
where
T: ThreadSafeLayoutNode<'dom>,
{
fn borrow_layout_data(self) -> Option<AtomicRef<'dom, InnerLayoutData>> {
self.layout_data()
.map(|data| data.downcast_ref::<LayoutData>().unwrap().0.borrow())
}
fn mutate_layout_data(self) -> Option<AtomicRefMut<'dom, InnerLayoutData>> {
self.layout_data()
.and_then(|data| data.downcast_ref::<LayoutData>())
.map(|data| data.0.borrow_mut())
}
fn flags(self) -> LayoutDataFlags {
self.borrow_layout_data().as_ref().unwrap().flags
}
fn insert_flags(self, new_flags: LayoutDataFlags) {
self.mutate_layout_data().unwrap().flags.insert(new_flags);
}
fn remove_flags(self, flags: LayoutDataFlags) {
self.mutate_layout_data().unwrap().flags.remove(flags);
}
fn text_content(&self) -> TextContent {
if self.get_pseudo_element_type().is_replaced_content() {
let style = self.as_element().unwrap().resolved_style();
return TextContent::GeneratedContent(match style.as_ref().get_counters().content {
Content::Items(ref value) => value.items.to_vec(),
_ => vec![],
});
}
TextContent::Text(self.node_text_content().into_owned().into_boxed_str())
}
fn restyle_damage(self) -> RestyleDamage {
// We need the underlying node to potentially access the parent in the
// case of text nodes. This is safe as long as we don't let the parent
// escape and never access its descendants.
let mut node = self.unsafe_get();
// If this is a text node, use the parent element, since that's what
// controls our style.
if node.is_text_node() {
node = node.parent_node().unwrap();
debug_assert!(node.is_element());
}
let damage = {
let (layout_data, style_data) = match (node.layout_data(), node.style_data()) {
(Some(layout_data), Some(style_data)) => (layout_data, style_data),
_ => panic!(
"could not get style and layout data for <{}>",
node.as_element().unwrap().local_name()
),
};
let layout_data = layout_data.downcast_ref::<LayoutData>().unwrap().0.borrow();
if !layout_data
.flags
.contains(crate::data::LayoutDataFlags::HAS_BEEN_TRAVERSED)
{
// We're reflowing a node that was styled for the first time and
// has never been visited by layout. Return rebuild_and_reflow,
// because that's what the code expects.
RestyleDamage::rebuild_and_reflow()
} else {
style_data.element_data.borrow().damage
}
};
damage
}
}
pub enum TextContent {
Text(Box<str>),
GeneratedContent(Vec<ContentItem>),
}
impl TextContent {
pub fn is_empty(&self) -> bool {
match *self {
TextContent::Text(_) => false,
TextContent::GeneratedContent(ref content) => content.is_empty(),
}
}
}

View file

@ -182,7 +182,7 @@ where
return None;
}
// TODO: This is the what the legacy layout system does, but really if Servo
// TODO: This is the what the legacy layout system did, but really if Servo
// supports any `<object>` that's an image, it should support those with URLs
// and `type` attributes with image mime types.
let element = self.to_threadsafe().as_element()?;

View file

@ -1,49 +0,0 @@
[package]
name = "layout_thread_2013"
version.workspace = true
authors.workspace = true
license.workspace = true
edition.workspace = true
publish.workspace = true
rust-version.workspace = true
[lib]
name = "layout_thread_2013"
path = "lib.rs"
[features]
tracing = ["dep:tracing", "layout/tracing"]
[dependencies]
app_units = { workspace = true }
base = { workspace = true }
embedder_traits = { workspace = true }
euclid = { workspace = true }
fnv = { workspace = true }
fxhash = { workspace = true }
fonts = { path = "../fonts" }
fonts_traits = { workspace = true }
ipc-channel = { workspace = true }
layout = { path = "../layout", package = "layout_2013" }
log = { workspace = true }
malloc_size_of = { workspace = true }
metrics = { path = "../metrics" }
net_traits = { workspace = true }
parking_lot = { workspace = true }
profile_traits = { workspace = true }
rayon = { workspace = true }
script = { path = "../script" }
script_layout_interface = { workspace = true }
script_traits = { workspace = true }
servo_allocator = { path = "../allocator" }
servo_arc = { workspace = true }
stylo_atoms = { workspace = true }
servo_config = { path = "../config" }
servo_url = { path = "../url" }
style = { workspace = true }
style_traits = { workspace = true }
time = { workspace = true }
tracing = { workspace = true, optional = true }
url = { workspace = true }
webrender_api = { workspace = true }
webrender_traits = { workspace = true }

File diff suppressed because it is too large Load diff

View file

@ -107,7 +107,6 @@ def add_css_properties_attributes(css_properties_json, parser):
MAPPING = [
["layout.unimplemented", "layout_unimplemented"],
["layout.threads", "layout_threads"],
["layout.legacy_layout", "layout_legacy_layout"],
["layout.flexbox.enabled", "layout_flexbox_enabled"],
["layout.columns.enabled", "layout_columns_enabled"],
["layout.grid.enabled", "layout_grid_enabled"],

View file

@ -27,7 +27,6 @@ debugmozjs = ["script/debugmozjs"]
background_hang_monitor = ["background_hang_monitor/sampler"]
jitspew = ["script/jitspew"]
js_backtrace = ["script/js_backtrace"]
layout_2013 = ["dep:layout_thread_2013"]
media-gstreamer = ["servo-media-gstreamer", "gstreamer"]
multiview = ["compositing/multiview", "constellation/multiview"]
native-bluetooth = ["bluetooth/native-bluetooth"]
@ -35,7 +34,7 @@ no-wgl = ["mozangle/egl", "mozangle/build_dlls", "surfman/sm-angle-default", "we
dynamic_freetype = ["webrender/dynamic_freetype"]
profilemozjs = ["script/profilemozjs"]
refcell_backtrace = ["script/refcell_backtrace"]
tracing = ["dep:tracing", "compositing/tracing", "constellation/tracing", "fonts/tracing", "layout_thread_2013?/tracing", "layout_thread_2020/tracing", "profile_traits/tracing", "script/tracing"]
tracing = ["dep:tracing", "compositing/tracing", "constellation/tracing", "fonts/tracing", "layout_thread_2020/tracing", "profile_traits/tracing", "script/tracing"]
webdriver = ["webdriver_server"]
webgl_backtrace = [
"script/webgl_backtrace",
@ -79,7 +78,6 @@ gleam = { workspace = true }
gstreamer = { workspace = true, optional = true }
ipc-channel = { workspace = true }
keyboard-types = { workspace = true }
layout_thread_2013 = { path = "../layout_thread", optional = true }
layout_thread_2020 = { path = "../layout_thread_2020" }
log = { workspace = true }
media = { path = "../media" }

View file

@ -77,8 +77,6 @@ use gleam::gl::RENDERER;
use ipc_channel::ipc::{self, IpcSender};
use ipc_channel::router::ROUTER;
pub use keyboard_types::*;
#[cfg(feature = "layout_2013")]
pub use layout_thread_2013;
use log::{Log, Metadata, Record, debug, warn};
use media::{GlApi, NativeDisplay, WindowGLContext};
use net::protocols::ProtocolRegistry;
@ -86,7 +84,6 @@ use net::resource_thread::new_resource_threads;
use profile::{mem as profile_mem, time as profile_time};
use profile_traits::{mem, time};
use script::{JSEngineSetup, ServiceWorkerManager};
use script_layout_interface::LayoutFactory;
use script_traits::{ScriptToConstellationChan, WindowSizeData};
use servo_config::opts::Opts;
use servo_config::prefs::Preferences;
@ -996,22 +993,6 @@ fn create_compositor_channel(
(compositor_proxy, CompositorReceiver { receiver })
}
fn get_layout_factory(legacy_layout: bool) -> Arc<dyn LayoutFactory> {
cfg_if::cfg_if! {
if #[cfg(feature = "layout_2013")] {
if legacy_layout {
return Arc::new(layout_thread_2013::LayoutFactoryImpl());
}
} else {
if legacy_layout {
panic!("Runtime option `legacy_layout` was enabled, but the `layout_2013` \
feature was not enabled at compile time! ");
}
}
}
Arc::new(layout_thread_2020::LayoutFactoryImpl())
}
#[allow(clippy::too_many_arguments)]
fn create_constellation(
user_agent: Cow<'static, str>,
@ -1083,7 +1064,7 @@ fn create_constellation(
wgpu_image_map,
};
let layout_factory: Arc<dyn LayoutFactory> = get_layout_factory(opts::get().legacy_layout);
let layout_factory = Arc::new(layout_thread_2020::LayoutFactoryImpl());
Constellation::<script::ScriptThread, script::ServiceWorkerManager>::start(
initial_state,
@ -1161,8 +1142,7 @@ pub fn run_content_process(token: String) {
set_logger(content.script_to_constellation_chan().clone());
let background_hang_monitor_register = content.register_with_background_hang_monitor();
let layout_factory: Arc<dyn LayoutFactory> =
get_layout_factory(opts::get().legacy_layout);
let layout_factory = Arc::new(layout_thread_2020::LayoutFactoryImpl());
content.start_all::<script::ScriptThread>(
true,

View file

@ -41,7 +41,6 @@ debugmozjs = ["libservo/debugmozjs"]
default = ["max_log_level", "webdriver", "webxr", "webgpu"]
jitspew = ["libservo/jitspew"]
js_backtrace = ["libservo/js_backtrace"]
layout_2013 = ["libservo/layout_2013"]
max_log_level = ["log/release_max_level_info"]
media-gstreamer = ["libservo/media-gstreamer"]
multiview = ["libservo/multiview"]

View file

@ -180,7 +180,6 @@ pub(crate) fn parse_command_line_arguments(args: Vec<String>) -> ArgumentParsing
let (app_name, args) = args.split_first().unwrap();
let mut opts = Options::new();
opts.optflag("", "legacy-layout", "Use the legacy layout engine");
opts.optopt(
"o",
"output",
@ -539,11 +538,6 @@ pub(crate) fn parse_command_line_arguments(args: Vec<String>) -> ArgumentParsing
preferences.set_value(pref_name, pref_value);
}
let legacy_layout = opt_match.opt_present("legacy-layout");
if legacy_layout {
preferences.layout_legacy_layout = true;
}
if let Some(layout_threads) = layout_threads {
preferences.layout_threads = layout_threads as i64;
}
@ -600,7 +594,6 @@ pub(crate) fn parse_command_line_arguments(args: Vec<String>) -> ArgumentParsing
let opts = Opts {
debug: debug_options.clone(),
wait_for_stable_image,
legacy_layout,
time_profiling,
time_profiler_trace_path: opt_match.opt_str("profiler-trace-path"),
nonincremental_layout,

View file

@ -164,7 +164,6 @@ class MachCommands(CommandBase):
"constellation",
"fonts",
"hyper_serde",
"layout_2013",
"layout_2020",
"net",
"net_traits",

View file

@ -23,20 +23,17 @@ from enum import Enum, Flag, auto
class Layout(Flag):
none = 0
layout2013 = auto()
layout2020 = auto()
@staticmethod
def all():
return Layout.layout2013 | Layout.layout2020
return Layout.layout2020
def to_string(self):
if Layout.all() in self:
return "all"
elif Layout.layout2020 in self:
return "2020"
elif Layout.layout2013 in self:
return "2013"
else:
return "none"
@ -141,8 +138,6 @@ def handle_modifier(config: JobConfig, s: str) -> Optional[JobConfig]:
config.profile = "production"
if "bencher" in s:
config.bencher = True
if "wpt-2013" in s:
config.wpt_layout = Layout.layout2013
elif "wpt-2020" in s:
config.wpt_layout = Layout.layout2020
elif "wpt" in s:
@ -239,7 +234,7 @@ class TestParser(unittest.TestCase):
{
"name": "Linux (Unit Tests, WPT, Bencher)",
"workflow": "linux",
"wpt_layout": "2020",
"wpt_layout": "all",
"profile": "release",
"unit_tests": True,
'build_libservo': False,
@ -298,7 +293,7 @@ class TestParser(unittest.TestCase):
]})
def test_job_merging(self):
self.assertDictEqual(json.loads(Config("linux-wpt-2020 linux-wpt-2013").to_json()),
self.assertDictEqual(json.loads(Config("linux-wpt-2020").to_json()),
{'fail_fast': False,
'matrix': [{
'bencher': False,

View file

@ -31,8 +31,6 @@ def create_parser():
help="Run under chaos mode in rr until a failure is captured")
parser.add_argument('--pref', default=[], action="append", dest="prefs",
help="Pass preferences to servo")
parser.add_argument('--legacy-layout', '--layout-2013', '--with-layout-2013', default=False,
action="store_true", help="Use expected results for the legacy layout engine")
parser.add_argument('--log-servojson', action="append", type=mozlog.commandline.log_file,
help="Servo's JSON logger of unexpected results")
parser.add_argument('--always-succeed', default=False, action="store_true",
@ -48,17 +46,5 @@ def create_parser():
return parser
def update_args_for_legacy_layout(kwargs: dict):
def override_metadata_path(url_base, metadata_path):
test_root = kwargs["test_paths"][url_base]
kwargs["test_paths"][url_base] = wptrunner.wptcommandline.TestRoot(
test_root.tests_path,
os.path.join(WPT_PATH, *metadata_path)
)
override_metadata_path("/", ["meta-legacy-layout"])
override_metadata_path("/_mozilla/", ["mozilla", "meta-legacy-layout"])
override_metadata_path("/_webgl/", ["webgl", "meta-legacy-layout"])
def run_tests():
return test.run_tests()

View file

@ -18,7 +18,7 @@ from typing import List, NamedTuple, Optional, Union
import mozlog
import mozlog.formatters
from . import SERVO_ROOT, WPT_PATH, WPT_TOOLS_PATH, update_args_for_legacy_layout
from . import SERVO_ROOT, WPT_PATH, WPT_TOOLS_PATH
from .grouping_formatter import (
ServoFormatter, ServoHandler,
UnexpectedResult, UnexpectedSubtestResult
@ -40,11 +40,7 @@ def set_if_none(args: dict, key: str, value):
def run_tests(default_binary_path: str, **kwargs):
legacy_layout = kwargs.pop("legacy_layout")
message = f"Running WPT tests with {default_binary_path}"
if legacy_layout:
message += " (legacy layout)"
print(message)
print(f"Running WPT tests with {default_binary_path}")
# By default, Rayon selects the number of worker threads based on the
# available CPU count. This doesn't work very well when running tests on CI,
@ -87,8 +83,6 @@ def run_tests(default_binary_path: str, **kwargs):
kwargs.setdefault("binary_args", [])
if prefs:
kwargs["binary_args"] += ["--pref=" + pref for pref in prefs]
if legacy_layout:
kwargs["binary_args"].append("--legacy-layout")
if not kwargs.get("no_default_test_types"):
test_types = {
@ -104,9 +98,6 @@ def run_tests(default_binary_path: str, **kwargs):
wptcommandline.check_args(kwargs)
if legacy_layout:
update_args_for_legacy_layout(kwargs)
mozlog.commandline.log_formatters["servo"] = (
ServoFormatter,
"Servo's grouping output formatter",

View file

@ -12,7 +12,7 @@ from wptrunner.update import setup_logging, WPTUpdate # noqa: F401
from wptrunner.update.base import exit_unclean # noqa: F401
from wptrunner import wptcommandline # noqa: F401
from . import WPT_PATH, update_args_for_legacy_layout
from . import WPT_PATH
from . import manifestupdate
TEST_ROOT = os.path.join(WPT_PATH, 'tests')
@ -109,9 +109,6 @@ def update_tests(**kwargs) -> int:
if hasattr(wptcommandline, 'check_paths'):
wptcommandline.check_paths(kwargs["test_paths"])
if kwargs.pop("legacy_layout"):
update_args_for_legacy_layout(kwargs)
if kwargs.get('sync', False):
return do_sync(**kwargs)
@ -125,8 +122,4 @@ def run_update(**kwargs) -> bool:
def create_parser(**_kwargs):
parser = wptcommandline.create_parser_update()
parser.add_argument("--legacy-layout", "--layout-2013", "--with-layout-2013",
default=False, action="store_true",
help="Use expected results for the legacy layout engine")
return parser
return wptcommandline.create_parser_update()

View file

@ -9,9 +9,7 @@ files = [
"./components/net/tests/parsable_mime/text",
"./resources/hsts_preload.json",
"./tests/wpt/meta/MANIFEST.json",
"./tests/wpt/meta-legacy-layout/MANIFEST.json",
"./tests/wpt/mozilla/meta/MANIFEST.json",
"./tests/wpt/mozilla/meta-legacy-layout/MANIFEST.json",
# Long encoded string
"./tests/wpt/mozilla/tests/mozilla/resources/brotli.py",
"./tests/wpt/webgl/meta/MANIFEST.json",
@ -45,11 +43,8 @@ directories = [
"./third_party",
# Cache files generated by wptrunner which fail the EOF newlines check.
"./tests/wpt/meta/.cache",
"./tests/wpt/meta-legacy-layout/.cache",
"./tests/wpt/mozilla/meta/.cache",
"./tests/wpt/mozilla/meta-legacy-layout/.cache",
"./tests/wpt/webgl/meta/.cache",
"./tests/wpt/webgl/meta-legacy-layout/.cache",
]
# Directories that are checked for correct file extension

View file

@ -1,2 +0,0 @@
[cross-partition-navigation.tentative.https.html]
expected: TIMEOUT

View file

@ -1,2 +0,0 @@
[cross-partition-worker-creation.tentative.https.html]
expected: TIMEOUT

View file

@ -1,2 +0,0 @@
[cross-partition.tentative.https.html]
expected: TIMEOUT

View file

@ -1,58 +0,0 @@
[Blob-constructor-endings.html]
[The "endings" options property is used]
expected: FAIL
[Input CRCR with endings 'native']
expected: FAIL
[Input LFCRLFCR with endings 'native']
expected: FAIL
[Invalid "endings" value: null]
expected: FAIL
[Invalid "endings" value: {}]
expected: FAIL
[Input CR with endings 'native']
expected: FAIL
[CR/LF in adjacent input strings]
expected: FAIL
[Input CRLFCRLF with endings 'native']
expected: FAIL
[Invalid "endings" value: ""]
expected: FAIL
[Input LFCR with endings 'native']
expected: FAIL
[Invalid "endings" value: "Transparent"]
expected: FAIL
[Input CRLFCR with endings 'native']
expected: FAIL
[Input CRLFLF with endings 'native']
expected: FAIL
[Input CRCRLF with endings 'native']
expected: FAIL
[Input CRLF with endings 'native']
expected: FAIL
[Invalid "endings" value: "NATIVE"]
expected: FAIL
[Invalid "endings" value: 0]
expected: FAIL
[Invalid "endings" value: "invalidEnumValue"]
expected: FAIL
[Exception propagation from options]
expected: FAIL

View file

@ -1,14 +0,0 @@
[Blob-constructor.any.worker.html]
[options properties should be accessed in lexicographic order.]
expected: FAIL
[Passing a Float16Array as element of the blobParts array should work.]
expected: FAIL
[Blob-constructor.any.html]
[options properties should be accessed in lexicographic order.]
expected: FAIL
[Passing a Float16Array as element of the blobParts array should work.]
expected: FAIL

View file

@ -1,8 +0,0 @@
[Blob-stream.any.html]
[Reading Blob.stream() with BYOB reader]
expected: FAIL
[Blob-stream.any.worker.html]
[Reading Blob.stream() with BYOB reader]
expected: FAIL

View file

@ -1,58 +0,0 @@
[File-constructor-endings.html]
[The "endings" options property is used]
expected: FAIL
[Input CRCR with endings 'native']
expected: FAIL
[Input LFCRLFCR with endings 'native']
expected: FAIL
[Invalid "endings" value: null]
expected: FAIL
[Invalid "endings" value: {}]
expected: FAIL
[Input CR with endings 'native']
expected: FAIL
[CR/LF in adjacent input strings]
expected: FAIL
[Input CRLFCRLF with endings 'native']
expected: FAIL
[Invalid "endings" value: ""]
expected: FAIL
[Input LFCR with endings 'native']
expected: FAIL
[Invalid "endings" value: "Transparent"]
expected: FAIL
[Input CRLFCR with endings 'native']
expected: FAIL
[Input CRLFLF with endings 'native']
expected: FAIL
[Input CRCRLF with endings 'native']
expected: FAIL
[Input CRLF with endings 'native']
expected: FAIL
[Invalid "endings" value: "NATIVE"]
expected: FAIL
[Invalid "endings" value: 0]
expected: FAIL
[Invalid "endings" value: "invalidEnumValue"]
expected: FAIL
[Exception propagation from options]
expected: FAIL

View file

@ -1,8 +0,0 @@
[File-constructor.any.worker.html]
[No replacement when using special character in fileName]
expected: FAIL
[File-constructor.any.html]
[No replacement when using special character in fileName]
expected: FAIL

View file

@ -1,37 +0,0 @@
[send-file-form-controls.html]
[Upload file-for-upload-in-form-ESC-[\x1b\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-BS-[\x08\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-VT-[\x0b\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-LF-CR-[\n\r\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-FF-[\x0c\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-LF-[\n\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-CR-LF-[\r\n\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-SPACE-[ \].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-CR-[\r\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-HT-[\t\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-NUL-[\x00\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-DEL-[\].txt (ASCII) in UTF-8 form]
expected: FAIL

View file

@ -1,19 +0,0 @@
[send-file-form-iso-2022-jp.html]
[Upload file-for-upload-in-form-☺😂.txt (windows-1252) in ISO-2022-JP form]
expected: FAIL
[Upload file-for-upload-in-form-ABC~‾¥≈¤・・•∙·☼★星🌟星★☼·∙•・・¤≈¥‾~XYZ.txt (Unicode) in ISO-2022-JP form]
expected: FAIL
[Upload file-for-upload-in-form-.txt (x-user-defined) in ISO-2022-JP form]
expected: FAIL
[Upload file-for-upload-in-form-★星★.txt (JIS X 0201 and JIS X 0208) in ISO-2022-JP form]
expected: FAIL
[Upload file-for-upload-in-form.txt (ASCII) in ISO-2022-JP form]
expected: FAIL
[Upload file-for-upload-in-form-☺😂.txt (Unicode) in ISO-2022-JP form]
expected: FAIL

View file

@ -1,82 +0,0 @@
[send-file-form-punctuation.html]
[Upload file-for-upload-in-form-CIRCUMFLEX-ACCENT-[^\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-QUOTATION-MARK-["\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-LEFT-SQUARE-BRACKET-[[\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-EQUALS-SIGN-[=\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-TILDE-[~\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-LEFT-PARENTHESIS-[(\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-VERTICAL-LINE-[|\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-ASTERISK-[*\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-RIGHT-PARENTHESIS-[)\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-PERCENT-SIGN-[%\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-DOLLAR-SIGN-[$\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-COMMA-[,\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-RIGHT-CURLY-BRACKET-[}\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-QUESTION-MARK-[?\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-SOLIDUS-[/\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-LEFT-CURLY-BRACKET-[{\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-APOSTROPHE-['\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-RIGHT-SQUARE-BRACKET-[\]\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-COLON-[:\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload 'file-for-upload-in-form-single-quoted.txt' (ASCII) in UTF-8 form]
expected: FAIL
[Upload "file-for-upload-in-form-double-quoted.txt" (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-SEMICOLON-[;\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-AMPERSAND-[&\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-PLUS-SIGN-[+\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-REVERSE-SOLIDUS-[\\\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-EXCLAMATION-MARK-[!\].txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-FULL-STOP-[.\].txt (ASCII) in UTF-8 form]
expected: FAIL

View file

@ -1,19 +0,0 @@
[send-file-form-utf-8.html]
[Upload file-for-upload-in-form-☺😂.txt (windows-1252) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-★星★.txt (JIS X 0201 and JIS X 0208) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-.txt (x-user-defined) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-ABC~‾¥≈¤・・•∙·☼★星🌟星★☼·∙•・・¤≈¥‾~XYZ.txt (Unicode) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form.txt (ASCII) in UTF-8 form]
expected: FAIL
[Upload file-for-upload-in-form-☺😂.txt (Unicode) in UTF-8 form]
expected: FAIL

View file

@ -1,19 +0,0 @@
[send-file-form-windows-1252.html]
[Upload file-for-upload-in-form-ABC~‾¥≈¤・・•∙·☼★星🌟星★☼·∙•・・¤≈¥‾~XYZ.txt (Unicode) in windows-1252 form]
expected: FAIL
[Upload file-for-upload-in-form.txt (ASCII) in windows-1252 form]
expected: FAIL
[Upload file-for-upload-in-form-☺😂.txt (Unicode) in windows-1252 form]
expected: FAIL
[Upload file-for-upload-in-form-★星★.txt (JIS X 0201 and JIS X 0208) in windows-1252 form]
expected: FAIL
[Upload file-for-upload-in-form-☺😂.txt (windows-1252) in windows-1252 form]
expected: FAIL
[Upload file-for-upload-in-form-.txt (x-user-defined) in windows-1252 form]
expected: FAIL

View file

@ -1,19 +0,0 @@
[send-file-form-x-user-defined.html]
[Upload file-for-upload-in-form.txt (ASCII) in x-user-defined form]
expected: FAIL
[Upload file-for-upload-in-form-☺😂.txt (Unicode) in x-user-defined form]
expected: FAIL
[Upload file-for-upload-in-form-★星★.txt (JIS X 0201 and JIS X 0208) in x-user-defined form]
expected: FAIL
[Upload file-for-upload-in-form-☺😂.txt (windows-1252) in x-user-defined form]
expected: FAIL
[Upload file-for-upload-in-form-.txt (x-user-defined) in x-user-defined form]
expected: FAIL
[Upload file-for-upload-in-form-ABC~‾¥≈¤・・•∙·☼★星🌟星★☼·∙•・・¤≈¥‾~XYZ.txt (Unicode) in x-user-defined form]
expected: FAIL

View file

@ -1,4 +0,0 @@
[send-file-form.html]
[Upload file-for-upload-in-form.txt (ASCII) in UTF-8 form]
expected: FAIL

View file

@ -1,74 +0,0 @@
[send-file-formdata-controls.any.worker.html]
[Upload file-for-upload-in-form-NUL-[\x00\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-BS-[\x08\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-VT-[\x0b\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-LF-[\n\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-LF-CR-[\n\r\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-CR-[\r\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-CR-LF-[\r\n\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-HT-[\t\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-FF-[\x0c\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-DEL-[\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-ESC-[\x1b\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-SPACE-[ \].txt (ASCII) in fetch with FormData]
expected: FAIL
[send-file-formdata-controls.any.html]
[Upload file-for-upload-in-form-NUL-[\x00\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-BS-[\x08\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-VT-[\x0b\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-LF-[\n\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-LF-CR-[\n\r\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-CR-[\r\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-CR-LF-[\r\n\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-HT-[\t\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-FF-[\x0c\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-DEL-[\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-ESC-[\x1b\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-SPACE-[ \].txt (ASCII) in fetch with FormData]
expected: FAIL

View file

@ -1,164 +0,0 @@
[send-file-formdata-punctuation.any.html]
[Upload file-for-upload-in-form-QUOTATION-MARK-["\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload "file-for-upload-in-form-double-quoted.txt" (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-REVERSE-SOLIDUS-[\\\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-EXCLAMATION-MARK-[!\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-DOLLAR-SIGN-[$\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-PERCENT-SIGN-[%\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-AMPERSAND-[&\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-APOSTROPHE-['\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-LEFT-PARENTHESIS-[(\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-RIGHT-PARENTHESIS-[)\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-ASTERISK-[*\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-PLUS-SIGN-[+\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-COMMA-[,\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-FULL-STOP-[.\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-SOLIDUS-[/\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-COLON-[:\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-SEMICOLON-[;\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-EQUALS-SIGN-[=\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-QUESTION-MARK-[?\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-CIRCUMFLEX-ACCENT-[^\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-LEFT-SQUARE-BRACKET-[[\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-RIGHT-SQUARE-BRACKET-[\]\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-LEFT-CURLY-BRACKET-[{\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-VERTICAL-LINE-[|\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-RIGHT-CURLY-BRACKET-[}\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-TILDE-[~\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload 'file-for-upload-in-form-single-quoted.txt' (ASCII) in fetch with FormData]
expected: FAIL
[send-file-formdata-punctuation.any.worker.html]
[Upload file-for-upload-in-form-QUOTATION-MARK-["\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload "file-for-upload-in-form-double-quoted.txt" (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-REVERSE-SOLIDUS-[\\\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-EXCLAMATION-MARK-[!\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-DOLLAR-SIGN-[$\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-PERCENT-SIGN-[%\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-AMPERSAND-[&\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-APOSTROPHE-['\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-LEFT-PARENTHESIS-[(\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-RIGHT-PARENTHESIS-[)\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-ASTERISK-[*\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-PLUS-SIGN-[+\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-COMMA-[,\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-FULL-STOP-[.\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-SOLIDUS-[/\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-COLON-[:\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-SEMICOLON-[;\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-EQUALS-SIGN-[=\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-QUESTION-MARK-[?\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-CIRCUMFLEX-ACCENT-[^\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-LEFT-SQUARE-BRACKET-[[\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-RIGHT-SQUARE-BRACKET-[\]\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-LEFT-CURLY-BRACKET-[{\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-VERTICAL-LINE-[|\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-RIGHT-CURLY-BRACKET-[}\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-TILDE-[~\].txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload 'file-for-upload-in-form-single-quoted.txt' (ASCII) in fetch with FormData]
expected: FAIL

View file

@ -1,38 +0,0 @@
[send-file-formdata-utf-8.any.html]
[Upload file-for-upload-in-form.txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-.txt (x-user-defined) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-☺😂.txt (windows-1252) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-★星★.txt (JIS X 0201 and JIS X 0208) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-☺😂.txt (Unicode) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-ABC~‾¥≈¤・・•∙·☼★星🌟星★☼·∙•・・¤≈¥‾~XYZ.txt (Unicode) in fetch with FormData]
expected: FAIL
[send-file-formdata-utf-8.any.worker.html]
[Upload file-for-upload-in-form.txt (ASCII) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-.txt (x-user-defined) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-☺😂.txt (windows-1252) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-★星★.txt (JIS X 0201 and JIS X 0208) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-☺😂.txt (Unicode) in fetch with FormData]
expected: FAIL
[Upload file-for-upload-in-form-ABC~‾¥≈¤・・•∙·☼★星🌟星★☼·∙•・・¤≈¥‾~XYZ.txt (Unicode) in fetch with FormData]
expected: FAIL

View file

@ -1,8 +0,0 @@
[send-file-formdata.any.html]
[Upload file-for-upload-in-form.txt (ASCII) in fetch with FormData]
expected: FAIL
[send-file-formdata.any.worker.html]
[Upload file-for-upload-in-form.txt (ASCII) in fetch with FormData]
expected: FAIL

View file

@ -1,5 +0,0 @@
[historical.https.html]
[Historical features]
expected: FAIL
[Service worker test setup]
expected: FAIL

View file

@ -1,20 +0,0 @@
[idlharness.any.worker.html]
[FileReader interface: operation readAsBinaryString(Blob)]
expected: FAIL
[FileReader interface: new FileReader() must inherit property "readAsBinaryString(Blob)" with the proper type]
expected: FAIL
[FileReader interface: calling readAsBinaryString(Blob) on new FileReader() with too few arguments must throw TypeError]
expected: FAIL
[idlharness.any.html]
[FileReader interface: operation readAsBinaryString(Blob)]
expected: FAIL
[FileReader interface: new FileReader() must inherit property "readAsBinaryString(Blob)" with the proper type]
expected: FAIL
[FileReader interface: calling readAsBinaryString(Blob) on new FileReader() with too few arguments must throw TypeError]
expected: FAIL

View file

@ -1,19 +0,0 @@
[idlharness.html]
type: testharness
[URL interface: operation createFor(Blob)]
expected: FAIL
[FileReader interface: operation readAsBinaryString(Blob)]
expected: FAIL
[FileReader interface: new FileReader() must inherit property "readAsBinaryString" with the proper type (1)]
expected: FAIL
[FileReader interface: calling readAsBinaryString(Blob) on new FileReader() with too few arguments must throw TypeError]
expected: FAIL
[FileReader interface: new FileReader() must inherit property "readAsBinaryString(Blob)" with the proper type]
expected: FAIL
[File API automated IDL tests]
expected: FAIL

View file

@ -1,31 +0,0 @@
[idlharness.worker.html]
type: testharness
[ArrayBuffer interface: existence and properties of interface object]
expected: FAIL
[URL interface: operation createFor(Blob)]
expected: FAIL
[EventTarget interface: existence and properties of interface object]
expected: FAIL
[Event interface: existence and properties of interface object]
expected: FAIL
[FileReader interface: operation readAsBinaryString(Blob)]
expected: FAIL
[FileReader interface: new FileReader() must inherit property "readAsBinaryString" with the proper type (1)]
expected: FAIL
[FileReader interface: calling readAsBinaryString(Blob) on new FileReader() with too few arguments must throw TypeError]
expected: FAIL
[FileReader interface: new FileReader() must inherit property "readAsBinaryString(Blob)" with the proper type]
expected: FAIL
[Untitled]
expected: FAIL
[idlharness]
expected: FAIL

View file

@ -1,8 +0,0 @@
[filereader_readAsBinaryString.any.worker.html]
[FileAPI Test: filereader_readAsBinaryString]
expected: FAIL
[filereader_readAsBinaryString.any.html]
[FileAPI Test: filereader_readAsBinaryString]
expected: FAIL

View file

@ -1,14 +0,0 @@
[filereader_readAsDataURL.any.worker.html]
[readAsDataURL result for Blob with unspecified MIME type]
expected: FAIL
[readAsDataURL result for empty Blob]
expected: FAIL
[filereader_readAsDataURL.any.html]
[readAsDataURL result for Blob with unspecified MIME type]
expected: FAIL
[readAsDataURL result for empty Blob]
expected: FAIL

View file

@ -1,20 +0,0 @@
[filereader_result.any.html]
[readAsBinaryString]
expected: FAIL
[result is null during "loadstart" event for readAsBinaryString]
expected: FAIL
[result is null during "progress" event for readAsBinaryString]
expected: FAIL
[filereader_result.any.worker.html]
[readAsBinaryString]
expected: FAIL
[result is null during "loadstart" event for readAsBinaryString]
expected: FAIL
[result is null during "progress" event for readAsBinaryString]
expected: FAIL

View file

@ -1,4 +0,0 @@
[unicode.html]
[Blob/Unicode interaction: normalization and encoding]
expected: FAIL

View file

@ -1,43 +0,0 @@
[sandboxed-iframe.html]
expected: TIMEOUT
[Blob URLs can be used in <script> tags]
expected: TIMEOUT
[Blob URLs can be used in iframes, and are treated same origin]
expected: FAIL
[Blob URL fragment is implemented.]
expected: TIMEOUT
[Blob URLs can be used in XHR]
expected: FAIL
[XHR with a fragment should succeed]
expected: FAIL
[Only exact matches should revoke URLs, using XHR]
expected: FAIL
[Revoke blob URL after open(), will fetch]
expected: FAIL
[Blob URLs can be used in fetch]
expected: FAIL
[fetch with a fragment should succeed]
expected: FAIL
[Only exact matches should revoke URLs, using fetch]
expected: FAIL
[fetch should return Content-Type from Blob]
expected: FAIL
[Revoke blob URL after creating Request, will fetch]
expected: FAIL
[Revoke blob URL after calling fetch, fetch should succeed]
expected: FAIL
[XHR should return Content-Type from Blob]
expected: FAIL

View file

@ -1,8 +0,0 @@
[url-format.any.worker.html]
[Generated Blob URLs are unique]
expected: FAIL
[url-format.any.html]
[Generated Blob URLs are unique]
expected: FAIL

View file

@ -1,16 +0,0 @@
[url-in-tags-revoke.window.html]
expected: TIMEOUT
[Fetching a blob URL immediately before revoking it works in an iframe.]
expected: FAIL
[Fetching a blob URL immediately before revoking it works in an iframe navigation.]
expected: FAIL
[Opening a blob URL in a new window immediately before revoking it works.]
expected: FAIL
[Opening a blob URL in a noopener about:blank window immediately before revoking it works.]
expected: TIMEOUT
[Opening a blob URL in a new window by clicking an <a> tag works immediately before revoking the URL.]
expected: TIMEOUT

View file

@ -1,5 +0,0 @@
[url-in-tags.window.html]
expected: TIMEOUT
[Blob URL fragment is implemented.]
expected: TIMEOUT

View file

@ -1,7 +0,0 @@
[url-lifetime.html]
[Terminating worker revokes its URLs]
expected: FAIL
[Removing an iframe revokes its URLs]
expected: FAIL

View file

@ -1,3 +0,0 @@
[url-reload.window.html]
[Reloading a blob URL succeeds even if the URL was revoked.]
expected: FAIL

View file

@ -1,14 +0,0 @@
[url-with-fetch.any.worker.html]
[Revoke blob URL after creating Request, will fetch]
expected: FAIL
[Revoke blob URL after creating Request, then clone Request, will fetch]
expected: FAIL
[url-with-fetch.any.html]
[Revoke blob URL after creating Request, will fetch]
expected: FAIL
[Revoke blob URL after creating Request, then clone Request, will fetch]
expected: FAIL

View file

@ -1,10 +0,0 @@
[url-with-xhr.any.html]
[Revoke blob URL after open(), will fetch]
expected: FAIL
[url-with-xhr.any.worker.html]
[Revoke blob URL after open(), will fetch]
expected: FAIL

File diff suppressed because it is too large Load diff

View file

@ -1 +0,0 @@
prefs: [dom_crypto_subtle_enabled: true]

View file

@ -1,7 +0,0 @@
[algorithm-discards-context.https.window.html]
expected: TIMEOUT
[Context is discarded in sign]
expected: TIMEOUT
[Context is discarded in verify]
expected: TIMEOUT

View file

@ -1,8 +0,0 @@
[crypto_key_cached_slots.https.any.html]
[CryptoKey.usages getter returns cached object]
expected: FAIL
[crypto_key_cached_slots.https.any.worker.html]
[CryptoKey.usages getter returns cached object]
expected: FAIL

View file

@ -1,99 +0,0 @@
[cfrg_curves_bits_curve25519.https.any.worker.html]
[X25519 key derivation checks for all-zero value result with a key of order 0]
expected: FAIL
[X25519 key derivation checks for all-zero value result with a key of order 1]
expected: FAIL
[X25519 key derivation checks for all-zero value result with a key of order 8]
expected: FAIL
[X25519 key derivation checks for all-zero value result with a key of order p-1 (order 2)]
expected: FAIL
[X25519 key derivation checks for all-zero value result with a key of order p (=0, order 4)]
expected: FAIL
[X25519 key derivation checks for all-zero value result with a key of order p+1 (=1, order 1)]
expected: FAIL
[X25519 good parameters]
expected: FAIL
[X25519 mixed case parameters]
expected: FAIL
[X25519 short result]
expected: FAIL
[X25519 non-multiple of 8 bits]
expected: FAIL
[X25519 mismatched algorithms]
expected: FAIL
[X25519 no deriveBits usage for base key]
expected: FAIL
[X25519 base key is not a private key]
expected: FAIL
[X25519 public property value is a private key]
expected: FAIL
[X25519 public property value is a secret key]
expected: FAIL
[X25519 asking for too many bits]
expected: FAIL
[cfrg_curves_bits_curve25519.https.any.html]
expected: ERROR
[X25519 key derivation checks for all-zero value result with a key of order 0]
expected: FAIL
[X25519 key derivation checks for all-zero value result with a key of order 1]
expected: FAIL
[X25519 key derivation checks for all-zero value result with a key of order 8]
expected: FAIL
[X25519 key derivation checks for all-zero value result with a key of order p-1 (order 2)]
expected: FAIL
[X25519 key derivation checks for all-zero value result with a key of order p (=0, order 4)]
expected: FAIL
[X25519 key derivation checks for all-zero value result with a key of order p+1 (=1, order 1)]
expected: FAIL
[X25519 good parameters]
expected: FAIL
[X25519 mixed case parameters]
expected: FAIL
[X25519 short result]
expected: FAIL
[X25519 non-multiple of 8 bits]
expected: FAIL
[X25519 mismatched algorithms]
expected: FAIL
[X25519 no deriveBits usage for base key]
expected: FAIL
[X25519 base key is not a private key]
expected: FAIL
[X25519 public property value is a private key]
expected: FAIL
[X25519 public property value is a secret key]
expected: FAIL
[X25519 asking for too many bits]
expected: FAIL

View file

@ -1,93 +0,0 @@
[cfrg_curves_bits_curve448.https.any.html]
expected: ERROR
[X448 key derivation checks for all-zero value result with a key of order 0]
expected: FAIL
[X448 key derivation checks for all-zero value result with a key of order 1]
expected: FAIL
[X448 key derivation checks for all-zero value result with a key of order p-1 (order 2)]
expected: FAIL
[X448 key derivation checks for all-zero value result with a key of order p (=0, order 4)]
expected: FAIL
[X448 key derivation checks for all-zero value result with a key of order p+1 (=1, order 1)]
expected: FAIL
[X448 good parameters]
expected: FAIL
[X448 mixed case parameters]
expected: FAIL
[X448 short result]
expected: FAIL
[X448 non-multiple of 8 bits]
expected: FAIL
[X448 mismatched algorithms]
expected: FAIL
[X448 no deriveBits usage for base key]
expected: FAIL
[X448 base key is not a private key]
expected: FAIL
[X448 public property value is a private key]
expected: FAIL
[X448 public property value is a secret key]
expected: FAIL
[X448 asking for too many bits]
expected: FAIL
[cfrg_curves_bits_curve448.https.any.worker.html]
[X448 key derivation checks for all-zero value result with a key of order 0]
expected: FAIL
[X448 key derivation checks for all-zero value result with a key of order 1]
expected: FAIL
[X448 key derivation checks for all-zero value result with a key of order p-1 (order 2)]
expected: FAIL
[X448 key derivation checks for all-zero value result with a key of order p (=0, order 4)]
expected: FAIL
[X448 key derivation checks for all-zero value result with a key of order p+1 (=1, order 1)]
expected: FAIL
[X448 good parameters]
expected: FAIL
[X448 mixed case parameters]
expected: FAIL
[X448 short result]
expected: FAIL
[X448 non-multiple of 8 bits]
expected: FAIL
[X448 mismatched algorithms]
expected: FAIL
[X448 no deriveBits usage for base key]
expected: FAIL
[X448 base key is not a private key]
expected: FAIL
[X448 public property value is a private key]
expected: FAIL
[X448 public property value is a secret key]
expected: FAIL
[X448 asking for too many bits]
expected: FAIL

Some files were not shown because too many files have changed in this diff Show more