diff options
-rw-r--r-- | .github/CODEOWNERS | 1 | ||||
-rw-r--r-- | Cargo.lock | 4 | ||||
-rw-r--r-- | components/config/opts.rs | 2 | ||||
-rw-r--r-- | components/layout/flexbox/layout.rs | 42 | ||||
-rw-r--r-- | components/layout/flow/mod.rs | 66 | ||||
-rw-r--r-- | components/layout/formatting_contexts.rs | 5 | ||||
-rw-r--r-- | components/layout/geom.rs | 86 | ||||
-rw-r--r-- | components/layout/positioned.rs | 61 | ||||
-rw-r--r-- | components/layout/taffy/layout.rs | 15 | ||||
-rw-r--r-- | components/net/hsts.rs | 134 | ||||
-rw-r--r-- | components/net/resource_thread.rs | 16 | ||||
-rw-r--r-- | components/net/tests/hsts.rs | 98 | ||||
-rw-r--r-- | ports/servoshell/prefs.rs | 2 | ||||
-rw-r--r-- | servobuild.example | 3 |
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" |