aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/CODEOWNERS1
-rw-r--r--Cargo.lock4
-rw-r--r--components/config/opts.rs2
-rw-r--r--components/layout/flexbox/layout.rs42
-rw-r--r--components/layout/flow/mod.rs66
-rw-r--r--components/layout/formatting_contexts.rs5
-rw-r--r--components/layout/geom.rs86
-rw-r--r--components/layout/positioned.rs61
-rw-r--r--components/layout/taffy/layout.rs15
-rw-r--r--components/net/hsts.rs134
-rw-r--r--components/net/resource_thread.rs16
-rw-r--r--components/net/tests/hsts.rs98
-rw-r--r--ports/servoshell/prefs.rs2
-rw-r--r--servobuild.example3
14 files changed, 393 insertions, 142 deletions
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 3668ac41f48..0bac7800467 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -11,7 +11,6 @@
/components/compositing @mrobinson
# Reviewers for layout-related code
-/components/layout_2020 @mrobinson @Loirooriol @nicoburns
/components/layout @mrobinson @Loirooriol @nicoburns
# Reviewers for Minibrowser related code
diff --git a/Cargo.lock b/Cargo.lock
index 363ca82cfb7..36e579d2921 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -7993,9 +7993,9 @@ checksum = "ce607aae8ab0ab3abf3a2723a9ab6f09bb8639ed83fdd888d857b8e556c868d8"
[[package]]
name = "truetype"
-version = "0.47.8"
+version = "0.47.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d9ffbd4cf26797938aa36b2d03ec051800e6886fbed4bf70333d96b230a575d"
+checksum = "05ce9543b570c6e8a392274b67e1001816bce953aa89724e52a4639db02a10e0"
dependencies = [
"typeface",
]
diff --git a/components/config/opts.rs b/components/config/opts.rs
index 785b43b0acd..3db866a7443 100644
--- a/components/config/opts.rs
+++ b/components/config/opts.rs
@@ -104,7 +104,7 @@ pub struct DebugOptions {
/// Dumps the rule tree.
pub dump_rule_tree: bool,
- /// Print the flow tree (Layout 2013) or fragment tree (Layout 2020) after each layout.
+ /// Print the fragment tree after each layout.
pub dump_flow_tree: bool,
/// Print the stacking context tree after each layout.
diff --git a/components/layout/flexbox/layout.rs b/components/layout/flexbox/layout.rs
index 3ddbb71ba89..c83ce4b3067 100644
--- a/components/layout/flexbox/layout.rs
+++ b/components/layout/flexbox/layout.rs
@@ -32,7 +32,7 @@ use crate::formatting_contexts::{Baselines, IndependentFormattingContextContents
use crate::fragment_tree::{
BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags, SpecificLayoutInfo,
};
-use crate::geom::{AuOrAuto, LogicalRect, LogicalSides, LogicalVec2, Size, Sizes};
+use crate::geom::{AuOrAuto, LazySize, LogicalRect, LogicalSides, LogicalVec2, Size, Sizes};
use crate::layout_box_base::CacheableLayoutResult;
use crate::positioned::{
AbsolutelyPositionedBox, PositioningContext, PositioningContextLength, relative_adjustement,
@@ -1936,6 +1936,27 @@ impl FlexItem<'_> {
}
}
+ let lazy_block_size = if cross_axis_is_item_block_axis {
+ // This means that an auto size with stretch alignment will behave different than
+ // a stretch size. That's not what the spec says, but matches other browsers.
+ // To be discussed in https://github.com/w3c/csswg-drafts/issues/11784.
+ let stretch_size = containing_block
+ .size
+ .block
+ .to_definite()
+ .map(|size| Au::zero().max(size - self.pbm_auto_is_zero.cross));
+ LazySize::new(
+ &self.content_cross_sizes,
+ Direction::Block,
+ Size::FitContent,
+ Au::zero,
+ stretch_size,
+ self.is_table(),
+ )
+ } else {
+ used_main_size.into()
+ };
+
let layout = non_replaced.layout(
flex_context.layout_context,
&mut positioning_context,
@@ -1945,6 +1966,7 @@ impl FlexItem<'_> {
flex_axis == FlexAxis::Column ||
self.cross_size_stretches_to_line ||
self.depends_on_block_constraints,
+ &lazy_block_size,
);
let CacheableLayoutResult {
fragments,
@@ -1962,22 +1984,7 @@ impl FlexItem<'_> {
});
let hypothetical_cross_size = if cross_axis_is_item_block_axis {
- // This means that an auto size with stretch alignment will behave different than
- // a stretch size. That's not what the spec says, but matches other browsers.
- // To be discussed in https://github.com/w3c/csswg-drafts/issues/11784.
- let stretch_size = containing_block
- .size
- .block
- .to_definite()
- .map(|size| Au::zero().max(size - self.pbm_auto_is_zero.cross));
- self.content_cross_sizes.resolve(
- Direction::Block,
- Size::FitContent,
- Au::zero,
- stretch_size,
- || content_block_size.into(),
- self.is_table(),
- )
+ lazy_block_size.resolve(|| content_block_size)
} else {
inline_size
};
@@ -2687,6 +2694,7 @@ impl FlexItemBox {
flex_context.containing_block,
&self.independent_formatting_context.base,
false, /* depends_on_block_constraints */
+ &LazySize::intrinsic(),
)
.content_block_size
};
diff --git a/components/layout/flow/mod.rs b/components/layout/flow/mod.rs
index 6adb63153d6..99b84d088e5 100644
--- a/components/layout/flow/mod.rs
+++ b/components/layout/flow/mod.rs
@@ -36,8 +36,8 @@ use crate::fragment_tree::{
BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, CollapsedMargin, Fragment, FragmentFlags,
};
use crate::geom::{
- AuOrAuto, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint, PhysicalRect,
- PhysicalSides, Size, Sizes, ToLogical, ToLogicalWithContainingBlock,
+ AuOrAuto, LazySize, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint,
+ PhysicalRect, PhysicalSides, Size, Sizes, ToLogical, ToLogicalWithContainingBlock,
};
use crate::layout_box_base::{CacheableLayoutResult, LayoutBoxBase};
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength};
@@ -1217,6 +1217,15 @@ impl IndependentNonReplacedContents {
ignore_block_margins_for_stretch,
);
+ let lazy_block_size = LazySize::new(
+ &block_sizes,
+ Direction::Block,
+ Size::FitContent,
+ Au::zero,
+ available_block_size,
+ layout_style.is_table(),
+ );
+
let layout = self.layout(
layout_context,
positioning_context,
@@ -1224,19 +1233,13 @@ impl IndependentNonReplacedContents {
containing_block,
base,
false, /* depends_on_block_constraints */
+ &lazy_block_size,
);
let inline_size = layout
.content_inline_size_for_table
.unwrap_or(containing_block_for_children.size.inline);
- let block_size = block_sizes.resolve(
- Direction::Block,
- Size::FitContent,
- Au::zero,
- available_block_size,
- || layout.content_block_size.into(),
- layout_style.is_table(),
- );
+ let block_size = lazy_block_size.resolve(|| layout.content_block_size);
let ResolvedMargins {
margin,
@@ -1369,16 +1372,14 @@ impl IndependentNonReplacedContents {
)
};
- let compute_block_size = |layout: &CacheableLayoutResult| {
- content_box_sizes.block.resolve(
- Direction::Block,
- Size::FitContent,
- Au::zero,
- available_block_size,
- || layout.content_block_size.into(),
- is_table,
- )
- };
+ let lazy_block_size = LazySize::new(
+ &content_box_sizes.block,
+ Direction::Block,
+ Size::FitContent,
+ Au::zero,
+ available_block_size,
+ is_table,
+ );
// The final inline size can depend on the available space, which depends on where
// we are placing the box, since floats reduce the available space.
@@ -1407,10 +1408,11 @@ impl IndependentNonReplacedContents {
containing_block,
base,
false, /* depends_on_block_constraints */
+ &lazy_block_size,
);
content_size = LogicalVec2 {
- block: compute_block_size(&layout),
+ block: lazy_block_size.resolve(|| layout.content_block_size),
inline: layout.content_inline_size_for_table.unwrap_or(inline_size),
};
@@ -1472,6 +1474,7 @@ impl IndependentNonReplacedContents {
containing_block,
base,
false, /* depends_on_block_constraints */
+ &lazy_block_size,
);
let inline_size = if let Some(inline_size) = layout.content_inline_size_for_table {
@@ -1485,7 +1488,7 @@ impl IndependentNonReplacedContents {
proposed_inline_size
};
content_size = LogicalVec2 {
- block: compute_block_size(&layout),
+ block: lazy_block_size.resolve(|| layout.content_block_size),
inline: inline_size,
};
@@ -2419,6 +2422,15 @@ impl IndependentFormattingContext {
"Mixed horizontal and vertical writing modes are not supported yet"
);
+ let lazy_block_size = LazySize::new(
+ &content_box_sizes_and_pbm.content_box_sizes.block,
+ Direction::Block,
+ Size::FitContent,
+ Au::zero,
+ available_block_size,
+ is_table,
+ );
+
let independent_layout = non_replaced.layout(
layout_context,
child_positioning_context,
@@ -2426,18 +2438,12 @@ impl IndependentFormattingContext {
containing_block,
&self.base,
false, /* depends_on_block_constraints */
+ &lazy_block_size,
);
let inline_size = independent_layout
.content_inline_size_for_table
.unwrap_or(inline_size);
- let block_size = content_box_sizes_and_pbm.content_box_sizes.block.resolve(
- Direction::Block,
- Size::FitContent,
- Au::zero,
- available_block_size,
- || independent_layout.content_block_size.into(),
- is_table,
- );
+ let block_size = lazy_block_size.resolve(|| independent_layout.content_block_size);
let content_size = LogicalVec2 {
block: block_size,
diff --git a/components/layout/formatting_contexts.rs b/components/layout/formatting_contexts.rs
index d704011d0e7..3942af63312 100644
--- a/components/layout/formatting_contexts.rs
+++ b/components/layout/formatting_contexts.rs
@@ -15,6 +15,7 @@ use crate::dom_traversal::{Contents, NodeAndStyleInfo};
use crate::flexbox::FlexContainer;
use crate::flow::BlockFormattingContext;
use crate::fragment_tree::{BaseFragmentInfo, FragmentFlags};
+use crate::geom::LazySize;
use crate::layout_box_base::{
CacheableLayoutResult, CacheableLayoutResultAndInputs, LayoutBoxBase,
};
@@ -242,6 +243,7 @@ impl IndependentNonReplacedContents {
containing_block_for_children: &ContainingBlock,
containing_block: &ContainingBlock,
depends_on_block_constraints: bool,
+ _lazy_block_size: &LazySize,
) -> CacheableLayoutResult {
match self {
IndependentNonReplacedContents::Flow(bfc) => bfc.layout(
@@ -281,6 +283,7 @@ impl IndependentNonReplacedContents {
level = "trace",
)
)]
+ #[allow(clippy::too_many_arguments)]
pub fn layout(
&self,
layout_context: &LayoutContext,
@@ -289,6 +292,7 @@ impl IndependentNonReplacedContents {
containing_block: &ContainingBlock,
base: &LayoutBoxBase,
depends_on_block_constraints: bool,
+ lazy_block_size: &LazySize,
) -> CacheableLayoutResult {
if let Some(cache) = base.cached_layout_result.borrow().as_ref() {
let cache = &**cache;
@@ -317,6 +321,7 @@ impl IndependentNonReplacedContents {
containing_block_for_children,
containing_block,
depends_on_block_constraints,
+ lazy_block_size,
);
*base.cached_layout_result.borrow_mut() = Some(Box::new(CacheableLayoutResultAndInputs {
diff --git a/components/layout/geom.rs b/components/layout/geom.rs
index 6a09519b7ed..4065b785832 100644
--- a/components/layout/geom.rs
+++ b/components/layout/geom.rs
@@ -2,7 +2,7 @@
* 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::LazyCell;
+use std::cell::{LazyCell, OnceCell};
use std::convert::From;
use std::fmt;
use std::ops::{Add, AddAssign, Neg, Sub, SubAssign};
@@ -1102,3 +1102,87 @@ impl Sizes {
)
}
}
+
+struct LazySizeData<'a> {
+ sizes: &'a Sizes,
+ axis: Direction,
+ automatic_size: Size<Au>,
+ get_automatic_minimum_size: fn() -> Au,
+ stretch_size: Option<Au>,
+ is_table: bool,
+}
+
+/// Represents a size that can't be fully resolved until the intrinsic size
+/// is known. This is useful in the block axis, since the intrinsic size
+/// depends on layout, but the other inputs are known beforehand.
+pub(crate) struct LazySize<'a> {
+ result: OnceCell<Au>,
+ data: Option<LazySizeData<'a>>,
+}
+
+impl<'a> LazySize<'a> {
+ pub(crate) fn new(
+ sizes: &'a Sizes,
+ axis: Direction,
+ automatic_size: Size<Au>,
+ get_automatic_minimum_size: fn() -> Au,
+ stretch_size: Option<Au>,
+ is_table: bool,
+ ) -> Self {
+ Self {
+ result: OnceCell::new(),
+ data: Some(LazySizeData {
+ sizes,
+ axis,
+ automatic_size,
+ get_automatic_minimum_size,
+ stretch_size,
+ is_table,
+ }),
+ }
+ }
+
+ /// Creates a [`LazySize`] that will resolve to the intrinsic size.
+ /// Should be equivalent to [`LazySize::new()`] with default parameters,
+ /// but avoiding the trouble of getting a reference to a [`Sizes::default()`]
+ /// which lives long enough.
+ ///
+ /// TODO: It's not clear what this should do if/when [`LazySize::resolve()`]
+ /// is changed to accept a [`ContentSizes`] as the intrinsic size.
+ pub(crate) fn intrinsic() -> Self {
+ Self {
+ result: OnceCell::new(),
+ data: None,
+ }
+ }
+
+ /// Resolves the [`LazySize`] into [`Au`], caching the result.
+ /// The argument is a callback that computes the intrinsic size lazily.
+ ///
+ /// TODO: The intrinsic size should probably be a [`ContentSizes`] instead of [`Au`].
+ pub(crate) fn resolve(&self, get_content_size: impl FnOnce() -> Au) -> Au {
+ *self.result.get_or_init(|| {
+ let Some(ref data) = self.data else {
+ return get_content_size();
+ };
+ data.sizes.resolve(
+ data.axis,
+ data.automatic_size,
+ data.get_automatic_minimum_size,
+ data.stretch_size,
+ || get_content_size().into(),
+ data.is_table,
+ )
+ })
+ }
+}
+
+impl From<Au> for LazySize<'_> {
+ /// Creates a [`LazySize`] that will resolve to the given [`Au`],
+ /// ignoring the intrinsic size.
+ fn from(value: Au) -> Self {
+ let result = OnceCell::new();
+ result.set(value).unwrap();
+ LazySize { result, data: None }
+ }
+}
diff --git a/components/layout/positioned.rs b/components/layout/positioned.rs
index b607462d6eb..6280864d533 100644
--- a/components/layout/positioned.rs
+++ b/components/layout/positioned.rs
@@ -24,12 +24,11 @@ use crate::fragment_tree::{
BoxFragment, Fragment, FragmentFlags, HoistedSharedFragment, SpecificLayoutInfo,
};
use crate::geom::{
- AuOrAuto, LengthPercentageOrAuto, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2,
- PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, PhysicalVec, Size, Sizes, ToLogical,
- ToLogicalWithContainingBlock,
+ AuOrAuto, LazySize, LengthPercentageOrAuto, LogicalRect, LogicalSides, LogicalSides1D,
+ LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, PhysicalVec, Size,
+ Sizes, ToLogical, ToLogicalWithContainingBlock,
};
use crate::layout_box_base::LayoutBoxBase;
-use crate::sizing::ContentSizes;
use crate::style_ext::{Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, DisplayInside};
use crate::{
ConstraintSpace, ContainingBlock, ContainingBlockSize, DefiniteContainingBlock,
@@ -560,18 +559,32 @@ impl HoistedAbsolutelyPositionedBox {
// The block size can depend on layout results, so we only solve it extrinsically,
// we may have to resolve it properly later on.
- let extrinsic_block_size = block_axis_solver.solve_size_extrinsically();
+ let block_automatic_size = block_axis_solver.automatic_size();
+ let block_stretch_size = Some(block_axis_solver.stretch_size());
+ let extrinsic_block_size = block_axis_solver.computed_sizes.resolve_extrinsic(
+ block_automatic_size,
+ Au::zero(),
+ block_stretch_size,
+ );
// The inline axis can be fully resolved, computing intrinsic sizes using the
// extrinsic block size.
- let inline_size = inline_axis_solver.solve_size(|| {
+ let get_inline_content_size = || {
let ratio = context.preferred_aspect_ratio(&pbm.padding_border_sums);
let constraint_space =
ConstraintSpace::new(extrinsic_block_size, style.writing_mode, ratio);
context
.inline_content_sizes(layout_context, &constraint_space)
.sizes
- });
+ };
+ let inline_size = inline_axis_solver.computed_sizes.resolve(
+ Direction::Inline,
+ inline_axis_solver.automatic_size(),
+ Au::zero,
+ Some(inline_axis_solver.stretch_size()),
+ get_inline_content_size,
+ is_table,
+ );
let containing_block_for_children = ContainingBlock {
size: ContainingBlockSize {
@@ -587,6 +600,14 @@ impl HoistedAbsolutelyPositionedBox {
"Mixed horizontal and vertical writing modes are not supported yet"
);
+ let lazy_block_size = LazySize::new(
+ &block_axis_solver.computed_sizes,
+ Direction::Block,
+ block_automatic_size,
+ Au::zero,
+ block_stretch_size,
+ is_table,
+ );
let independent_layout = non_replaced.layout(
layout_context,
&mut positioning_context,
@@ -594,6 +615,7 @@ impl HoistedAbsolutelyPositionedBox {
containing_block,
&context.base,
false, /* depends_on_block_constraints */
+ &lazy_block_size,
);
// Tables can become narrower than predicted due to collapsed columns
@@ -602,8 +624,8 @@ impl HoistedAbsolutelyPositionedBox {
.unwrap_or(inline_size);
// Now we can properly solve the block size.
- let block_size = block_axis_solver
- .solve_size(|| independent_layout.content_block_size.into());
+ let block_size =
+ lazy_block_size.resolve(|| independent_layout.content_block_size);
content_size = LogicalVec2 {
inline: inline_size,
@@ -765,27 +787,6 @@ impl AbsoluteAxisSolver<'_> {
)
}
- #[inline]
- fn solve_size_extrinsically(&self) -> SizeConstraint {
- self.computed_sizes.resolve_extrinsic(
- self.automatic_size(),
- Au::zero(),
- Some(self.stretch_size()),
- )
- }
-
- #[inline]
- fn solve_size(&self, get_content_size: impl FnOnce() -> ContentSizes) -> Au {
- self.computed_sizes.resolve(
- self.axis,
- self.automatic_size(),
- Au::zero,
- Some(self.stretch_size()),
- get_content_size,
- self.is_table,
- )
- }
-
fn solve_margins(&self, size: Au) -> LogicalSides1D<Au> {
if self.box_offsets.either_auto() {
LogicalSides1D::new(
diff --git a/components/layout/taffy/layout.rs b/components/layout/taffy/layout.rs
index a5838c1bd65..61c4a0508e9 100644
--- a/components/layout/taffy/layout.rs
+++ b/components/layout/taffy/layout.rs
@@ -23,8 +23,8 @@ use crate::fragment_tree::{
BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags, SpecificLayoutInfo,
};
use crate::geom::{
- LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, Size, SizeConstraint,
- Sizes,
+ LazySize, LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, Size,
+ SizeConstraint, Sizes,
};
use crate::layout_box_base::CacheableLayoutResult;
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength};
@@ -251,6 +251,12 @@ impl taffy::LayoutPartialTree for TaffyContainerContext<'_> {
style,
};
+ let lazy_block_size = match content_box_known_dimensions.height {
+ // FIXME: use the correct min/max sizes.
+ None => LazySize::intrinsic(),
+ Some(height) => Au::from_f32_px(height).into(),
+ };
+
child.positioning_context = PositioningContext::default();
let layout = non_replaced.layout_without_caching(
self.layout_context,
@@ -258,13 +264,16 @@ impl taffy::LayoutPartialTree for TaffyContainerContext<'_> {
&content_box_size_override,
containing_block,
false, /* depends_on_block_constraints */
+ &lazy_block_size,
);
child.child_fragments = layout.fragments;
self.child_specific_layout_infos[usize::from(node_id)] =
layout.specific_layout_info;
- let block_size = layout.content_block_size.to_f32_px();
+ let block_size = lazy_block_size
+ .resolve(|| layout.content_block_size)
+ .to_f32_px();
let computed_size = taffy::Size {
width: inline_size + pb_sum.inline,
diff --git a/components/net/hsts.rs b/components/net/hsts.rs
index d74794ce60a..be955980d2b 100644
--- a/components/net/hsts.rs
+++ b/components/net/hsts.rs
@@ -4,26 +4,37 @@
use std::collections::HashMap;
use std::net::{Ipv4Addr, Ipv6Addr};
+use std::num::NonZeroU64;
+use std::sync::LazyLock;
use std::time::Duration;
-use base::cross_process_instant::CrossProcessInstant;
use embedder_traits::resources::{self, Resource};
use headers::{HeaderMapExt, StrictTransportSecurity};
use http::HeaderMap;
-use log::{error, info};
+use log::{debug, error, info};
use malloc_size_of_derive::MallocSizeOf;
use net_traits::IncludeSubdomains;
use net_traits::pub_domains::reg_suffix;
use serde::{Deserialize, Serialize};
use servo_config::pref;
use servo_url::{Host, ServoUrl};
+use time::UtcDateTime;
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct HstsEntry {
pub host: String,
pub include_subdomains: bool,
- pub max_age: Option<Duration>,
- pub timestamp: Option<CrossProcessInstant>,
+ // Nonzero to allow for memory optimization
+ pub expires_at: Option<NonZeroU64>,
+}
+
+// Zero and negative times are all expired
+fn unix_timestamp_to_nonzerou64(timestamp: i64) -> NonZeroU64 {
+ if timestamp <= 0 {
+ NonZeroU64::new(1).unwrap()
+ } else {
+ NonZeroU64::new(timestamp.try_into().unwrap()).unwrap()
+ }
}
impl HstsEntry {
@@ -32,43 +43,59 @@ impl HstsEntry {
subdomains: IncludeSubdomains,
max_age: Option<Duration>,
) -> Option<HstsEntry> {
+ let expires_at = max_age.map(|duration| {
+ unix_timestamp_to_nonzerou64((UtcDateTime::now() + duration).unix_timestamp())
+ });
if host.parse::<Ipv4Addr>().is_ok() || host.parse::<Ipv6Addr>().is_ok() {
None
} else {
Some(HstsEntry {
host,
include_subdomains: (subdomains == IncludeSubdomains::Included),
- max_age,
- timestamp: Some(CrossProcessInstant::now()),
+ expires_at,
})
}
}
pub fn is_expired(&self) -> bool {
- match (self.max_age, self.timestamp) {
- (Some(max_age), Some(timestamp)) => CrossProcessInstant::now() - timestamp >= max_age,
-
+ match self.expires_at {
+ Some(timestamp) => {
+ unix_timestamp_to_nonzerou64(UtcDateTime::now().unix_timestamp()) >= timestamp
+ },
_ => false,
}
}
fn matches_domain(&self, host: &str) -> bool {
- !self.is_expired() && self.host == host
+ self.host == host
}
fn matches_subdomain(&self, host: &str) -> bool {
- !self.is_expired() && host.ends_with(&format!(".{}", self.host))
+ host.ends_with(&format!(".{}", self.host))
}
}
#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, Serialize)]
pub struct HstsList {
+ // Map from base domains to a list of entries that are subdomains of base domain
pub entries_map: HashMap<String, Vec<HstsEntry>>,
}
-impl HstsList {
+/// Represents the portion of the HSTS list that comes from the preload list
+/// it is split out to allow sharing between the private and public http state
+/// as well as potentially swpaping out the underlying type to something immutable
+/// and more efficient like FSTs or DAFSA/DAWGs.
+#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, Serialize)]
+pub struct HstsPreloadList {
+ pub entries_map: HashMap<String, Vec<HstsEntry>>,
+}
+
+pub static PRELOAD_LIST_ENTRIES: LazyLock<HstsPreloadList> =
+ LazyLock::new(HstsPreloadList::from_servo_preload);
+
+impl HstsPreloadList {
/// Create an `HstsList` from the bytes of a JSON preload file.
- pub fn from_preload(preload_content: &str) -> Option<HstsList> {
+ pub fn from_preload(preload_content: &str) -> Option<HstsPreloadList> {
#[derive(Deserialize)]
struct HstsEntries {
entries: Vec<HstsEntry>,
@@ -77,7 +104,7 @@ impl HstsList {
let hsts_entries: Option<HstsEntries> = serde_json::from_str(preload_content).ok();
hsts_entries.map(|hsts_entries| {
- let mut hsts_list: HstsList = HstsList::default();
+ let mut hsts_list: HstsPreloadList = HstsPreloadList::default();
for hsts_entry in hsts_entries.entries {
hsts_list.push(hsts_entry);
@@ -87,17 +114,21 @@ impl HstsList {
})
}
- pub fn from_servo_preload() -> HstsList {
+ pub fn from_servo_preload() -> HstsPreloadList {
+ debug!("Intializing HSTS Preload list");
let list = resources::read_string(Resource::HstsPreloadList);
- HstsList::from_preload(&list).unwrap_or_else(|| {
+ HstsPreloadList::from_preload(&list).unwrap_or_else(|| {
error!("HSTS preload file is invalid. Setting HSTS list to default values");
- HstsList::default()
+ HstsPreloadList {
+ entries_map: Default::default(),
+ }
})
}
pub fn is_host_secure(&self, host: &str) -> bool {
let base_domain = reg_suffix(host);
self.entries_map.get(base_domain).is_some_and(|entries| {
+ // No need to check for expiration in the preload list
entries.iter().any(|e| {
if e.include_subdomains {
e.matches_subdomain(host) || e.matches_domain(host)
@@ -108,16 +139,74 @@ impl HstsList {
})
}
- fn has_domain(&self, host: &str, base_domain: &str) -> bool {
+ pub fn has_domain(&self, host: &str, base_domain: &str) -> bool {
self.entries_map
.get(base_domain)
.is_some_and(|entries| entries.iter().any(|e| e.matches_domain(host)))
}
- fn has_subdomain(&self, host: &str, base_domain: &str) -> bool {
+ pub fn has_subdomain(&self, host: &str, base_domain: &str) -> bool {
+ self.entries_map.get(base_domain).is_some_and(|entries| {
+ entries
+ .iter()
+ .any(|e| e.include_subdomains && e.matches_subdomain(host))
+ })
+ }
+
+ pub fn push(&mut self, entry: HstsEntry) {
+ let host = entry.host.clone();
+ let base_domain = reg_suffix(&host);
+ let have_domain = self.has_domain(&entry.host, base_domain);
+ let have_subdomain = self.has_subdomain(&entry.host, base_domain);
+
+ let entries = self.entries_map.entry(base_domain.to_owned()).or_default();
+ if !have_domain && !have_subdomain {
+ entries.push(entry);
+ } else if !have_subdomain {
+ for e in entries {
+ if e.matches_domain(&entry.host) {
+ e.include_subdomains = entry.include_subdomains;
+ // TODO(sebsebmc): We could shrink the the HSTS preload memory use further by using a type
+ // that doesn't store an expiry since all preload entries should be "forever"
+ e.expires_at = entry.expires_at;
+ }
+ }
+ }
+ }
+}
+
+impl HstsList {
+ pub fn is_host_secure(&self, host: &str) -> bool {
+ debug!("HSTS: is {host} secure?");
+ if PRELOAD_LIST_ENTRIES.is_host_secure(host) {
+ info!("{host} is in the preload list");
+ return true;
+ }
+
+ let base_domain = reg_suffix(host);
+ self.entries_map.get(base_domain).is_some_and(|entries| {
+ entries.iter().filter(|e| !e.is_expired()).any(|e| {
+ if e.include_subdomains {
+ e.matches_subdomain(host) || e.matches_domain(host)
+ } else {
+ e.matches_domain(host)
+ }
+ })
+ })
+ }
+
+ fn has_domain(&self, host: &str, base_domain: &str) -> bool {
self.entries_map
.get(base_domain)
- .is_some_and(|entries| entries.iter().any(|e| e.matches_subdomain(host)))
+ .is_some_and(|entries| entries.iter().any(|e| e.matches_domain(host)))
+ }
+
+ fn has_subdomain(&self, host: &str, base_domain: &str) -> bool {
+ self.entries_map.get(base_domain).is_some_and(|entries| {
+ entries
+ .iter()
+ .any(|e| e.include_subdomains && e.matches_subdomain(host))
+ })
}
pub fn push(&mut self, entry: HstsEntry) {
@@ -130,13 +219,14 @@ impl HstsList {
if !have_domain && !have_subdomain {
entries.push(entry);
} else if !have_subdomain {
- for e in entries {
+ for e in entries.iter_mut() {
if e.matches_domain(&entry.host) {
e.include_subdomains = entry.include_subdomains;
- e.max_age = entry.max_age;
+ e.expires_at = entry.expires_at;
}
}
}
+ entries.retain(|e| !e.is_expired());
}
/// Step 2.9 of <https://fetch.spec.whatwg.org/#concept-main-fetch>.
diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs
index 5d1ede28c32..d361d63f44a 100644
--- a/components/net/resource_thread.rs
+++ b/components/net/resource_thread.rs
@@ -21,6 +21,7 @@ use embedder_traits::EmbedderProxy;
use hyper_serde::Serde;
use ipc_channel::ipc::{self, IpcReceiver, IpcReceiverSet, IpcSender};
use log::{debug, trace, warn};
+use malloc_size_of::MallocSizeOf;
use net_traits::blob_url_store::parse_blob_url;
use net_traits::filemanager_thread::FileTokenCheck;
use net_traits::request::{Destination, RequestBuilder, RequestId};
@@ -32,8 +33,10 @@ use net_traits::{
WebSocketDomAction, WebSocketNetworkEvent,
};
use profile_traits::mem::{
- ProcessReports, ProfilerChan as MemProfilerChan, ReportsChan, perform_memory_report,
+ ProcessReports, ProfilerChan as MemProfilerChan, Report, ReportKind, ReportsChan,
+ perform_memory_report,
};
+use profile_traits::path;
use profile_traits::time::ProfilerChan;
use rustls::RootCertStore;
use serde::{Deserialize, Serialize};
@@ -50,7 +53,7 @@ use crate::fetch::cors_cache::CorsCache;
use crate::fetch::fetch_params::FetchParams;
use crate::fetch::methods::{CancellationListener, FetchContext, fetch};
use crate::filemanager_thread::FileManager;
-use crate::hsts::HstsList;
+use crate::hsts::{self, HstsList};
use crate::http_cache::HttpCache;
use crate::http_loader::{HttpState, http_redirect_fetch};
use crate::protocols::ProtocolRegistry;
@@ -176,7 +179,7 @@ fn create_http_states(
ignore_certificate_errors: bool,
embedder_proxy: EmbedderProxy,
) -> (Arc<HttpState>, Arc<HttpState>) {
- let mut hsts_list = HstsList::from_servo_preload();
+ let mut hsts_list = HstsList::default();
let mut auth_cache = AuthCache::default();
let http_cache = HttpCache::default();
let mut cookie_jar = CookieStorage::new(150);
@@ -205,7 +208,7 @@ fn create_http_states(
let override_manager = CertificateErrorOverrideManager::new();
let private_http_state = HttpState {
- hsts_list: RwLock::new(HstsList::from_servo_preload()),
+ hsts_list: RwLock::new(HstsList::default()),
cookie_jar: RwLock::new(CookieStorage::new(150)),
auth_cache: RwLock::new(AuthCache::default()),
history_states: RwLock::new(HashMap::new()),
@@ -284,6 +287,11 @@ impl ResourceChannelManager {
perform_memory_report(|ops| {
let mut reports = public_http_state.memory_reports("public", ops);
reports.extend(private_http_state.memory_reports("private", ops));
+ reports.push(Report {
+ path: path!["hsts-preload-list"],
+ kind: ReportKind::ExplicitJemallocHeapSize,
+ size: hsts::PRELOAD_LIST_ENTRIES.size_of(ops),
+ });
msg.send(ProcessReports::new(reports));
})
}
diff --git a/components/net/tests/hsts.rs b/components/net/tests/hsts.rs
index 863cbc56fe1..e1e754beb3c 100644
--- a/components/net/tests/hsts.rs
+++ b/components/net/tests/hsts.rs
@@ -3,32 +3,18 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::collections::HashMap;
+use std::num::NonZeroU64;
use std::time::Duration as StdDuration;
-use base::cross_process_instant::CrossProcessInstant;
-use net::hsts::{HstsEntry, HstsList};
+use net::hsts::{HstsEntry, HstsList, HstsPreloadList};
use net_traits::IncludeSubdomains;
-use time::Duration;
#[test]
-fn test_hsts_entry_is_not_expired_when_it_has_no_timestamp() {
+fn test_hsts_entry_is_not_expired_when_it_has_no_expires_at() {
let entry = HstsEntry {
host: "mozilla.org".to_owned(),
include_subdomains: false,
- max_age: Some(StdDuration::from_secs(20)),
- timestamp: None,
- };
-
- assert!(!entry.is_expired());
-}
-
-#[test]
-fn test_hsts_entry_is_not_expired_when_it_has_no_max_age() {
- let entry = HstsEntry {
- host: "mozilla.org".to_owned(),
- include_subdomains: false,
- max_age: None,
- timestamp: Some(CrossProcessInstant::now()),
+ expires_at: None,
};
assert!(!entry.is_expired());
@@ -39,8 +25,7 @@ fn test_hsts_entry_is_expired_when_it_has_reached_its_max_age() {
let entry = HstsEntry {
host: "mozilla.org".to_owned(),
include_subdomains: false,
- max_age: Some(StdDuration::from_secs(10)),
- timestamp: Some(CrossProcessInstant::now() - Duration::seconds(20)),
+ expires_at: Some(NonZeroU64::new(1).unwrap()),
};
assert!(entry.is_expired());
@@ -102,7 +87,7 @@ fn test_base_domain_in_entries_map() {
}
#[test]
-fn test_push_entry_with_0_max_age_evicts_entry_from_list() {
+fn test_push_entry_with_0_max_age_is_not_secure() {
let mut entries_map = HashMap::new();
entries_map.insert(
"mozilla.org".to_owned(),
@@ -131,6 +116,36 @@ fn test_push_entry_with_0_max_age_evicts_entry_from_list() {
assert_eq!(list.is_host_secure("mozilla.org"), false)
}
+fn test_push_entry_with_0_max_age_evicts_entry_from_list() {
+ let mut entries_map = HashMap::new();
+ entries_map.insert(
+ "mozilla.org".to_owned(),
+ vec![
+ HstsEntry::new(
+ "mozilla.org".to_owned(),
+ IncludeSubdomains::NotIncluded,
+ Some(StdDuration::from_secs(500000)),
+ )
+ .unwrap(),
+ ],
+ );
+ let mut list = HstsList {
+ entries_map: entries_map,
+ };
+
+ assert_eq!(list.entries_map.get("mozilla.org").unwrap().len(), 1);
+
+ list.push(
+ HstsEntry::new(
+ "mozilla.org".to_owned(),
+ IncludeSubdomains::NotIncluded,
+ Some(StdDuration::ZERO),
+ )
+ .unwrap(),
+ );
+ assert_eq!(list.entries_map.get("mozilla.org").unwrap().len(), 0);
+}
+
#[test]
fn test_push_entry_to_hsts_list_should_not_add_subdomains_whose_superdomain_is_already_matched() {
let mut entries_map = HashMap::new();
@@ -155,6 +170,36 @@ fn test_push_entry_to_hsts_list_should_not_add_subdomains_whose_superdomain_is_a
}
#[test]
+fn test_push_entry_to_hsts_list_should_add_subdomains_whose_superdomain_doesnt_include() {
+ let mut entries_map = HashMap::new();
+ entries_map.insert(
+ "mozilla.org".to_owned(),
+ vec![
+ HstsEntry::new(
+ "mozilla.org".to_owned(),
+ IncludeSubdomains::NotIncluded,
+ None,
+ )
+ .unwrap(),
+ ],
+ );
+ let mut list = HstsList {
+ entries_map: entries_map,
+ };
+
+ list.push(
+ HstsEntry::new(
+ "servo.mozilla.org".to_owned(),
+ IncludeSubdomains::NotIncluded,
+ None,
+ )
+ .unwrap(),
+ );
+
+ assert_eq!(list.entries_map.get("mozilla.org").unwrap().len(), 2)
+}
+
+#[test]
fn test_push_entry_to_hsts_list_should_update_existing_domain_entrys_include_subdomains() {
let mut entries_map = HashMap::new();
entries_map.insert(
@@ -244,7 +289,7 @@ fn test_push_entry_to_hsts_list_should_add_an_entry() {
fn test_parse_hsts_preload_should_return_none_when_json_invalid() {
let mock_preload_content = "derp";
assert!(
- HstsList::from_preload(mock_preload_content).is_none(),
+ HstsPreloadList::from_preload(mock_preload_content).is_none(),
"invalid preload list should not have parsed"
)
}
@@ -253,7 +298,7 @@ fn test_parse_hsts_preload_should_return_none_when_json_invalid() {
fn test_parse_hsts_preload_should_return_none_when_json_contains_no_entries_map_key() {
let mock_preload_content = "{\"nothing\": \"to see here\"}";
assert!(
- HstsList::from_preload(mock_preload_content).is_none(),
+ HstsPreloadList::from_preload(mock_preload_content).is_none(),
"invalid preload list should not have parsed"
)
}
@@ -266,7 +311,7 @@ fn test_parse_hsts_preload_should_decode_host_and_includes_subdomains() {
\"include_subdomains\": false}\
]\
}";
- let hsts_list = HstsList::from_preload(mock_preload_content);
+ let hsts_list = HstsPreloadList::from_preload(mock_preload_content);
let entries_map = hsts_list.unwrap().entries_map;
assert_eq!(
@@ -378,8 +423,7 @@ fn test_hsts_list_with_expired_entry_is_not_is_host_secure() {
vec![HstsEntry {
host: "mozilla.org".to_owned(),
include_subdomains: false,
- max_age: Some(StdDuration::from_secs(20)),
- timestamp: Some(CrossProcessInstant::now() - Duration::seconds(100)),
+ expires_at: Some(NonZeroU64::new(1).unwrap()),
}],
);
let hsts_list = HstsList {
@@ -391,6 +435,6 @@ fn test_hsts_list_with_expired_entry_is_not_is_host_secure() {
#[test]
fn test_preload_hsts_domains_well_formed() {
- let hsts_list = HstsList::from_servo_preload();
+ let hsts_list = HstsPreloadList::from_servo_preload();
assert!(!hsts_list.entries_map.is_empty());
}
diff --git a/ports/servoshell/prefs.rs b/ports/servoshell/prefs.rs
index a3ebda231d0..a602939cb65 100644
--- a/ports/servoshell/prefs.rs
+++ b/ports/servoshell/prefs.rs
@@ -736,7 +736,7 @@ fn print_debug_options_usage(app: &str) {
);
print_option(
"dump-flow-tree",
- "Print the flow tree (Layout 2013) or fragment tree (Layout 2020) after each layout.",
+ "Print the fragment tree after each layout.",
);
print_option(
"dump-rule-tree",
diff --git a/servobuild.example b/servobuild.example
index 40dd62e2421..8efc1e2907a 100644
--- a/servobuild.example
+++ b/servobuild.example
@@ -36,9 +36,6 @@ webgl-backtrace = false
# that triggered it.
dom-backtrace = false
-# Default to the “2020” implementation of CSS layout instead of the “2013” one.
-layout-2020 = true
-
# Pick a media stack based on the target. Other values are "gstreamer" and "dummy"
media-stack = "auto"