mirror of
https://github.com/servo/servo.git
synced 2025-06-06 00:25:37 +00:00
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:
parent
f93006af95
commit
7594dc6991
17224 changed files with 23 additions and 1584835 deletions
110
Cargo.lock
generated
110
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
@ -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>;
|
||||
}
|
|
@ -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 {}
|
|
@ -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);
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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, ¢er),
|
||||
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, ¢er)
|
||||
},
|
||||
};
|
||||
|
||||
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(),
|
||||
)
|
||||
}
|
|
@ -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>>;
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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
|
@ -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])
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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 {
|
||||
"es[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
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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};
|
|
@ -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);
|
||||
}
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 doesn’t
|
||||
/// 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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
|
@ -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
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()?;
|
||||
|
|
|
@ -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
|
@ -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"],
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -164,7 +164,6 @@ class MachCommands(CommandBase):
|
|||
"constellation",
|
||||
"fonts",
|
||||
"hyper_serde",
|
||||
"layout_2013",
|
||||
"layout_2020",
|
||||
"net",
|
||||
"net_traits",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
[cross-partition-navigation.tentative.https.html]
|
||||
expected: TIMEOUT
|
|
@ -1,2 +0,0 @@
|
|||
[cross-partition-worker-creation.tentative.https.html]
|
||||
expected: TIMEOUT
|
|
@ -1,2 +0,0 @@
|
|||
[cross-partition.tentative.https.html]
|
||||
expected: TIMEOUT
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[send-file-form.html]
|
||||
[Upload file-for-upload-in-form.txt (ASCII) in UTF-8 form]
|
||||
expected: FAIL
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,5 +0,0 @@
|
|||
[historical.https.html]
|
||||
[Historical features]
|
||||
expected: FAIL
|
||||
[Service worker test setup]
|
||||
expected: FAIL
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,4 +0,0 @@
|
|||
[unicode.html]
|
||||
[Blob/Unicode interaction: normalization and encoding]
|
||||
expected: FAIL
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,5 +0,0 @@
|
|||
[url-in-tags.window.html]
|
||||
expected: TIMEOUT
|
||||
[Blob URL fragment is implemented.]
|
||||
expected: TIMEOUT
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
[url-lifetime.html]
|
||||
[Terminating worker revokes its URLs]
|
||||
expected: FAIL
|
||||
|
||||
[Removing an iframe revokes its URLs]
|
||||
expected: FAIL
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[url-reload.window.html]
|
||||
[Reloading a blob URL succeeds even if the URL was revoked.]
|
||||
expected: FAIL
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
896030
tests/wpt/meta-legacy-layout/MANIFEST.json
vendored
896030
tests/wpt/meta-legacy-layout/MANIFEST.json
vendored
File diff suppressed because it is too large
Load diff
|
@ -1 +0,0 @@
|
|||
prefs: [dom_crypto_subtle_enabled: true]
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue