mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
* style: Simplify selector flag setting now that flag setting is atomic These bits are write-only, actually, and we don't even need to read them. Differential Revision: https://phabricator.services.mozilla.com/D141888 * Further changes required by Servo * style: Support media queries for dynamic-range and video-dynamic-range This is a stub that only matches "standard" for all platforms. Differential Revision: https://phabricator.services.mozilla.com/D141053 * style: Remove :-moz-lwtheme-{brighttext,darktext} They are just convenience for :root[lwthemetextcolor="light"] (and dark, respectively), but they generally shouldn't be used for dark mode theming. In the past it was the only way to do it but now we have prefers-color-scheme. While at it, change lwthemetextcolor to be "lwtheme-brighttext" for consistency with similar code we have for popups etc, and move it to _setDarkModeAttributes. While at it, remove layout.css.moz-lwtheme.content.enabled (which is false always, we unshipped these from content successfully). Differential Revision: https://phabricator.services.mozilla.com/D141593 * style: layout.css.moz-locale-dir.content.enabled We successfully removed these from content in bug 1740230 (Firefox 96). Differential Revision: https://phabricator.services.mozilla.com/D141727 * style: Really honor background-color: transparent in HCM even for form controls I forgot we were doing this "revert-or-initial" shenanigans (which is needed for stuff like link colors to be honored), so we need to early-return. Use a more explicit test rather than a reftest for this. Differential Revision: https://phabricator.services.mozilla.com/D142063 * style: Ignore unchanged property for scroll-linked effect detector I think this is cleaner. Differential Revision: https://phabricator.services.mozilla.com/D141737 * style: Allow to derive Parse/ToCss/SpecifiedValueInfo on bitflags We keep getting this pattern of properties that have a set of joint and disjoint flags, and copy-pasting or writing the same parsing and serialization code in slightly different ways. container-type is one such type, and I think we should have a single way of dealing with this, thus implement deriving for various traits for bitflags, with an attribute that says which flags are single vs mixed. See docs and properties I ported. The remaining ones I left TODOs with, they are a bit trickier but can be ported with some care. Differential Revision: https://phabricator.services.mozilla.com/D142418 * Further changes required by Servo * style: Implement parsing / serialization for container{,-type,-name} CSS properties Two noteworthy details that may seem random otherwise: * Moving values around in nsStyleDisplay is needed so that the struct remains under the size limit that we have to avoid jumping allocator buckets. * All the test expectation churn is because tests depend on `container-type: size` parsing to run, and now they run. Tests for the relevant bits I implemented are passing, with the only exception of some `container-name-computed.html` failures which are https://github.com/w3c/csswg-drafts/issues/7181. Safari agrees with us there. Other notes when looking at the spec and seeing how it matches the implementation: * `container` syntax doesn't match spec, but matches tests and sanity: https://github.com/w3c/csswg-drafts/issues/7180 * `container-type` syntax doesn't _quite_ match spec, but matches tests and I think it's a spec bug since the definition for the missing keyword is gone: https://github.com/w3c/csswg-drafts/issues/7179 Differential Revision: https://phabricator.services.mozilla.com/D142419 * style: Remove assert that doesn't hold for text-decorations because of presentation hints MANUAL PUSH: Orange fix CLOSED TREE * style: Migrate `<th>` `text-align` behaviour from presentation hint to UA CSS Differential Revision: https://phabricator.services.mozilla.com/D142494 * style: Deduplicate TokenList values faster Remember whether we have already de-duplicated them once and avoid doing that again. This is an alternative approach that doesn't add overhead to attribute setting in the general case. Differential Revision: https://phabricator.services.mozilla.com/D142813 * style: Inherit used color-scheme from embedder <browser> elements This allows popups and sidebars to use the chrome preferred color-scheme. This moves the responsibility of setting the content-preferred color scheme to the appropriate browsers to the front-end (via tabs.css). We still return the PreferredColorSchemeForContent() when there's no pres context (e.g., for display:none in-process iframes). We could potentially move a bunch of the pres-context data to the document instead, but that should be acceptable IMO as for general web content there's no behavior change in any case. Differential Revision: https://phabricator.services.mozilla.com/D142578 * Further changes required by Servo * style: Add basic @container rule parsing and boilerplate For now parse a MediaFeatureCondition. That needs being made more specific, but that is probably worth its own patch. Differential Revision: https://phabricator.services.mozilla.com/D143192 * Further changes required by Servo * style: Tweak cascade priority to split writing-mode and font properties This makes the worst case for cascade performance slightly more expensive (4 rather than three declaration walks), but my hope is that it will make the average case faster, since the best case is now just two walks instead of three, and writing mode properties are somewhat rare. This needs a test, but needs to wait until the writing-mode dependent viewport units land (will wait to land with a test). Differential Revision: https://phabricator.services.mozilla.com/D143261 * style: Implement new {small,large,dynamic} viewport units Differential Revision: https://phabricator.services.mozilla.com/D143252 * Further changes required by Servo * style: Implement new *vi and *vb units Differential Revision: https://phabricator.services.mozilla.com/D143253 * style: Implement prefers-contrast: custom and let prefers-contrast ride the trains Differential Revision: https://phabricator.services.mozilla.com/D143198 * style: Join servo style threads during shutdown I was unable to change the BLOOM_KEY field to no longer be leaked, as the TLS is also accessed on the main thread, which is not exited before the leak checker shuts down. Differential Revision: https://phabricator.services.mozilla.com/D143529 * Further changes required by Servo * style: Fix visited handling after bug 1763750 Before bug 1763750, we unconditionally called compute_writing_mode, which got the writing mode from the cascade mode for visited styles. However after that bug we only do that if we apply any writing-mode-related property. We could just call compute_writing_mode unconditionally, but instead it seems better to skip all that work for visited cascade and reuse the mechanism introduced in that bug to only apply the visited-dependent longhands. We assert that all visited-dependent longhands are "late" longhands, so as to also avoid applying the font group and such. Differential Revision: https://phabricator.services.mozilla.com/D143490 * style: Clean-up viewport unit resolution a bit I should've caught this when reviewing the new viewport units but alas :-) Differential Revision: https://phabricator.services.mozilla.com/D143856 * style: Implement `contain: inline-size` Differential Revision: https://phabricator.services.mozilla.com/D143501 * style: Simplify media query evaluation code a bit This patch: * Removes generic <ident> support for media features. These were used for some privileged media features but are no longer used. * Simplifies media feature getters by shifting the responsibility of dealing with RangeOrOperator to the caller. This makes it easier to implement container-query / mediaqueries-4 syntax, and also cleans up the code a bunch. There should be no change in behavior. Differential Revision: https://phabricator.services.mozilla.com/D144051 * Further changes required by Servo * style: Move transitions and animations to nsStyleUIReset This mostly just moves code around, to minimize potential behavior changes. There are some cleanups that we should try to do long term (this "have an array with n different counts" is pretty weird). But for now this should unblock people. The destination struct (nsStyleUIReset) was chosen mainly because it's small and non-inherited, and it doesn't seem like a worse place than nsStyleDisplay. Differential Revision: https://phabricator.services.mozilla.com/D144183 * Further changes required by Servo * style: Make media feature evaluation take a computed::Context This has no behavior change right now, but will simplify sharing code with container queries. Container queries will have container information in the computed::Context (this is necessary anyways for container-based units), so this avoids having to have different code for media and container queries. Differential Revision: https://phabricator.services.mozilla.com/D144152 * Further changes required by Servo * style: Add scroll() to animation-timeline for style system scroll() is defined in the spec proposal, and there is a temporary spec: https://drafts.csswg.org/scroll-animations-1/rewrite#scroll-notation. The spec is still under development, so we don't drop the orignal scroll-timeline at rule. Instead, we add a new scroll() notation to animation-timeline, and support both syntax for now. Differential Revision: https://phabricator.services.mozilla.com/D143417 * style: Tweak contain bitflag definition order to avoid static constructors This has no behavior change otherwise. The STRICT definition depended on SIZE, which was defined later. That's fine in Rust, but in C++ it causes the initialization to be dynamic because it doesn't have the definition of SIZE yet (ugh). This is the fix for the regression, though the following patch turns on constexpr support in cbindgen, which would've caught this at build-time, and guarantees that we don't have extra static constructors. Differential Revision: https://phabricator.services.mozilla.com/D144316 * style: Move some of the media query code to a more generic queries module No behavior change, just moving and renaming files. The code in the "queries" module will be shared between @media and @container. @media has some other code that container queries doesn't need like MediaList / MediaType / etc. That remains in the media_queries module. Differential Revision: https://phabricator.services.mozilla.com/D144435 * Further changes required by Servo * style: cleanup animation-name Make the representation the same between Gecko and Servo code. This will enable further clean-ups in the future. Make serialization be correct, serializing as identifier unless it's an invalid one (in which case we serialize as a string). This changes our stringification behavior in the specified style, but now it will match the computed style and be more correct over-all. Differential Revision: https://phabricator.services.mozilla.com/D144473 * Further changes required by Servo * style: Add support for parsing container-query-specific features There are some mediaqueries-5 features that we still don't support and explain the remaining failures in at-container-{parsing,serialization}. Differential Revision: https://phabricator.services.mozilla.com/D144446 * Further changes required by Servo * style: Introduce Optional<T> to represent optional values in the style system cross-fade() was kinda doing this in its own way with PercentOrNone, but since now we have more use-cases for this we should probably make this a slightly more general solution. I added some convenience APIs, but they're unused as of this patch so let me know if you want them gone. Differential Revision: https://phabricator.services.mozilla.com/D144831 * style: Fix insertRule with layer statements before imports We need to do a bit more nuanced check because @layer statements might go before imports. Differential Revision: https://phabricator.services.mozilla.com/D144996 * style: Factor out parsing the query feature name No behavior change. Differential Revision: https://phabricator.services.mozilla.com/D145229 * style: Refactor media feature expression representation in preparation to support multi-range syntax No behavior change. Depends on D145229 Differential Revision: https://phabricator.services.mozilla.com/D145230 * style: Implement media feature expression multi-range syntax Differential Revision: https://phabricator.services.mozilla.com/D145231 * style: Remove proton places tooltip code There's nobody working on it, and tooltips should hopefully be nice enough after recent changes (bug 1765423). Having it enabled causes artifacts like bug 1767815 comment 3. We can always rescue this from hg history if needed. Differential Revision: https://phabricator.services.mozilla.com/D145621 * style: Simplify selector flags setup even more In my investigation for bug 1766439, I am digging into why selector matching regressed. It doesn't help that the selector-matching code is instantiated a gazillion times (so there's a ton of copies of the relevant functions). This was needed in the past because we had different ways of setting the selector flags on elements, but I unified that recently and now we only need to either set them or not. That is the kind of thing that MatchingContext is really good for, so pass that instead on MatchingContext creation. Differential Revision: https://phabricator.services.mozilla.com/D145428 * Further changes required by Servo * style: Track @container condition id in style rules Much like we track layer rules. Consolidate that "containing rule state we pass down while building the cascade data" in a single struct that we can easily restore. For now, do nothing with it. I want to land this patch separately because it touches the Rule struct and CascadeData rebuilds, which both are performance sensitive. Its layout shouldn't change because I also changed LayerId to be a u16 (this shouldn't matter in practice, since LayerOrder is already a u16). Differential Revision: https://phabricator.services.mozilla.com/D145243 * Further changes required by Servo * style: Fix layer statement rules with multiple layer names MANUAL PUSH: Trivial orange fix CLOSED TREE. * style: Implement piecewise linear function Differential Revision: https://phabricator.services.mozilla.com/D145256 * style: Convert specified value tests to compile-time tests These were written at a time where std::mem::size_of wasn't a `const fn` in Rust. Now that it is, we can make these tests live in the style crate, and the build not to compile if they fail. Differential Revision: https://phabricator.services.mozilla.com/D146103 * Further changes required by Servo * style: Move size of tests to compile-time tests in the style crate Same reasoning as the previous commit. Differential Revision: https://phabricator.services.mozilla.com/D146104 * Further changes required by Servo * style: Lint and 32-bit build fix. MANUAL PUSH: Bustage fix CLOSED TREE * style: Implement 'update' media feature Differential Revision: https://phabricator.services.mozilla.com/D146338 * style: Clean up unused -moz-window-shadow values After bug 1768278 and bug 1767815 there's no more uses of the cliprounded value in the tree (also it causes artifacts on HiDPI screens so we probably don't want new usages). The "sheet" value is unused, and the other values other than "default" and "none" are only derived from "default", so they don't need to be exposed in the style system. Differential Revision: https://phabricator.services.mozilla.com/D145821 * style: More container queries plumbing Provide container information in computed::Context and use it to resolve the container queries. This still fails a lot of tests because we are not ensuring that layout is up-to-date when we style the container descendants, but that's expected. Differential Revision: https://phabricator.services.mozilla.com/D146478 * Further changes required by Servo * style: Ensure options in listbox selects are not stacking contexts by default We could have a different property or something but this seems reasonable as well probably. Differential Revision: https://phabricator.services.mozilla.com/D146994 * style: Implement overflow-clip-margin: <length> Differential Revision: https://phabricator.services.mozilla.com/D146432 * style: Change order of container shorthand Since the initial value of container-type is an open issue [1], I'm leaving that as-is for now. [1] https://github.com/w3c/csswg-drafts/issues/7202 Differential Revision: https://phabricator.services.mozilla.com/D147338 * style: Make modal dialog code more generic, and make it apply to fullscreen too behind a pref For now, don't turn it on by default yet, because I want to wait for more discussion in https://github.com/w3c/csswg-drafts/issues/6965 and so on. But I think the code is simple enough to land this. Differential Revision: https://phabricator.services.mozilla.com/D147295 * style: Cache computed styles objects display: none subtrees This reuses our existing undisplayed style generation, but in a per-document rather than per-nsComputedDOMStyle object, which means that we can avoid re-resolving styles of elements in display: none subtrees much more often. This brings the test-case in the bug to par with other browsers or better, and is much simpler than the initial approach I tried back in the day. Differential Revision: https://phabricator.services.mozilla.com/D147547 * Further changes required by Servo * style: Parse scroll-snap-stop style and propagate it to APZ side Depends on D146147 Differential Revision: https://phabricator.services.mozilla.com/D145850 * style: Update color-mix() syntax to match the current spec Test expectation updates for this in the latest patch of the bug. Differential Revision: https://phabricator.services.mozilla.com/D147002 * Further changes required by Servo * style: Make the color interpolation code more generic It's really piece-wise premultiplied interpolation, with a special-case for hue, so centralize the implementation. Differential Revision: https://phabricator.services.mozilla.com/D147003 * style: Implement more color-mix() color-spaces We had code to convert between these and the latest draft supports them so... Differential Revision: https://phabricator.services.mozilla.com/D147004 * style: Fix color-mix() percentage normalization Differential Revision: https://phabricator.services.mozilla.com/D147005 * style: Fix hue adjustment to match the spec The value to sum is tau, not pi. This was caught by some tests, see https://drafts.csswg.org/css-color/#shorter Differential Revision: https://phabricator.services.mozilla.com/D147006 * style: Do hue interpolations in degrees rather than radians. r=barret This gives us a bit more precision. Differential Revision: https://phabricator.services.mozilla.com/D147007 * style: Improve Percentage -> LengthPercentage conversion This doesn't change behavior because we only use them for images that have no clamping. Depends on D147008 Differential Revision: https://phabricator.services.mozilla.com/D147511 * style: Remove some dead vibrancy code Drive-by cleanup. Differential Revision: https://phabricator.services.mozilla.com/D147698 * style: Fix warnings about whitelist/blocklist functions being deprecated in bindgen 0.59 Differential Revision: https://phabricator.services.mozilla.com/D147695 * style: Update style to arrayvec 0.7 Differential Revision: https://phabricator.services.mozilla.com/D147476 * style: Update style to uluru 3.0 Differential Revision: https://phabricator.services.mozilla.com/D147477 * style: Remove -moz-scrollbar-thumb-proportional It unconditionally matches on all platforms, so it's not returning any useful information. Depends on D147689 Differential Revision: https://phabricator.services.mozilla.com/D147690 * style: Use ColorMix for interpolated colors in the computed style rather than ComplexColorRatios This among other things preserves the right color-space when interpolating currentColor. Differential Revision: https://phabricator.services.mozilla.com/D147512 * Further changes required by Servo * style: Add an input-region-margin to widgets, and implement it on Linux Recompute the input region when resizing the widget and so on, and use it to check for rollups. Depends on D148211 Differential Revision: https://phabricator.services.mozilla.com/D148222 * Avoid complaints from ./mach test-tidy * Update test expectations --------- Co-authored-by: Emilio Cobos Álvarez <emilio@crisal.io> Co-authored-by: Brad Werth <bwerth@mozilla.com> Co-authored-by: David Shin <dshin@mozilla.com> Co-authored-by: Hiroyuki Ikezoe <hikezoe.birchill@mozilla.com> Co-authored-by: Nika Layzell <nika@thelayzells.com> Co-authored-by: Boris Chiou <boris.chiou@gmail.com> Co-authored-by: Autumn on Tape <autumn@cyfox.net> Co-authored-by: Mike Hommey <mh+mozilla@glandium.org>
1385 lines
54 KiB
Rust
1385 lines
54 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
//! CSS table formatting contexts.
|
|
|
|
use crate::block::{BlockFlow, CandidateBSizeIterator, ISizeAndMarginsComputer};
|
|
use crate::block::{ISizeConstraintInput, ISizeConstraintSolution};
|
|
use crate::context::LayoutContext;
|
|
use crate::display_list::{
|
|
BorderPaintingMode, DisplayListBuildState, StackingContextCollectionFlags,
|
|
StackingContextCollectionState,
|
|
};
|
|
use crate::flow::{
|
|
BaseFlow, EarlyAbsolutePositionInfo, Flow, FlowClass, GetBaseFlow, ImmutableFlowUtils,
|
|
OpaqueFlow,
|
|
};
|
|
use crate::flow_list::{FlowListIterator, MutFlowListIterator};
|
|
use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
|
|
use crate::layout_debug;
|
|
use crate::model::{IntrinsicISizes, IntrinsicISizesContribution, MaybeAuto};
|
|
use crate::table_cell::TableCellFlow;
|
|
use crate::table_row::{self, CellIntrinsicInlineSize, CollapsedBorder, CollapsedBorderProvenance};
|
|
use crate::table_row::{TableRowFlow, TableRowSizeData};
|
|
use crate::table_wrapper::TableLayout;
|
|
use app_units::Au;
|
|
use euclid::default::Point2D;
|
|
use gfx_traits::print_tree::PrintTree;
|
|
use std::{cmp, fmt};
|
|
use style::computed_values::{border_collapse, border_spacing, table_layout};
|
|
use style::context::SharedStyleContext;
|
|
use style::logical_geometry::LogicalSize;
|
|
use style::properties::style_structs::Background;
|
|
use style::properties::ComputedValues;
|
|
use style::servo::restyle_damage::ServoRestyleDamage;
|
|
use style::values::computed::Size;
|
|
use style::values::CSSFloat;
|
|
|
|
#[allow(unsafe_code)]
|
|
unsafe impl crate::flow::HasBaseFlow for TableFlow {}
|
|
|
|
/// A table flow corresponded to the table's internal table fragment under a table wrapper flow.
|
|
/// The properties `position`, `float`, and `margin-*` are used on the table wrapper fragment,
|
|
/// not table fragment per CSS 2.1 § 10.5.
|
|
#[derive(Serialize)]
|
|
#[repr(C)]
|
|
pub struct TableFlow {
|
|
pub block_flow: BlockFlow,
|
|
|
|
/// Information about the intrinsic inline-sizes of each column, computed bottom-up during
|
|
/// intrinsic inline-size bubbling.
|
|
pub column_intrinsic_inline_sizes: Vec<ColumnIntrinsicInlineSize>,
|
|
|
|
/// Information about the actual inline sizes of each column, computed top-down during actual
|
|
/// inline-size bubbling.
|
|
pub column_computed_inline_sizes: Vec<ColumnComputedInlineSize>,
|
|
|
|
/// 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>,
|
|
|
|
/// Table-layout property
|
|
pub table_layout: TableLayout,
|
|
}
|
|
|
|
impl TableFlow {
|
|
pub fn from_fragment(fragment: Fragment) -> TableFlow {
|
|
let mut block_flow = BlockFlow::from_fragment(fragment);
|
|
let table_layout =
|
|
if block_flow.fragment().style().get_table().table_layout == table_layout::T::Fixed {
|
|
TableLayout::Fixed
|
|
} else {
|
|
TableLayout::Auto
|
|
};
|
|
TableFlow {
|
|
block_flow: block_flow,
|
|
column_intrinsic_inline_sizes: Vec::new(),
|
|
column_computed_inline_sizes: Vec::new(),
|
|
collapsed_inline_direction_border_widths_for_table: Vec::new(),
|
|
collapsed_block_direction_border_widths_for_table: Vec::new(),
|
|
table_layout: table_layout,
|
|
}
|
|
}
|
|
|
|
/// Update the corresponding value of `self_inline_sizes` if a value of `kid_inline_sizes` has
|
|
/// a larger value than one of `self_inline_sizes`. Returns the minimum and preferred inline
|
|
/// sizes.
|
|
fn update_automatic_column_inline_sizes(
|
|
parent_inline_sizes: &mut Vec<ColumnIntrinsicInlineSize>,
|
|
child_cell_inline_sizes: &[CellIntrinsicInlineSize],
|
|
surrounding_size: Au,
|
|
) -> IntrinsicISizes {
|
|
let mut total_inline_sizes = IntrinsicISizes {
|
|
minimum_inline_size: surrounding_size,
|
|
preferred_inline_size: surrounding_size,
|
|
};
|
|
let mut column_index = 0;
|
|
let mut incoming_rowspan = vec![];
|
|
|
|
for child_cell_inline_size in child_cell_inline_sizes {
|
|
// Skip any column occupied by a cell from a previous row.
|
|
while column_index < incoming_rowspan.len() && incoming_rowspan[column_index] != 1 {
|
|
if incoming_rowspan[column_index] > 1 {
|
|
incoming_rowspan[column_index] -= 1;
|
|
}
|
|
column_index += 1;
|
|
}
|
|
for _ in 0..child_cell_inline_size.column_span {
|
|
if column_index < parent_inline_sizes.len() {
|
|
// We already have some intrinsic size information for this column. Merge it in
|
|
// according to the rules specified in INTRINSIC § 4.
|
|
let parent_sizes = &mut parent_inline_sizes[column_index];
|
|
if child_cell_inline_size.column_span > 1 {
|
|
// TODO(pcwalton): Perform the recursive algorithm specified in INTRINSIC §
|
|
// 4. For now we make this column contribute no width.
|
|
} else {
|
|
let column_size = &child_cell_inline_size.column_size;
|
|
*parent_sizes = ColumnIntrinsicInlineSize {
|
|
minimum_length: cmp::max(
|
|
parent_sizes.minimum_length,
|
|
column_size.minimum_length,
|
|
),
|
|
percentage: parent_sizes.greatest_percentage(column_size),
|
|
preferred: cmp::max(parent_sizes.preferred, column_size.preferred),
|
|
constrained: parent_sizes.constrained || column_size.constrained,
|
|
}
|
|
}
|
|
} else {
|
|
// We discovered a new column. Initialize its data.
|
|
debug_assert_eq!(column_index, parent_inline_sizes.len());
|
|
if child_cell_inline_size.column_span > 1 {
|
|
// TODO(pcwalton): Perform the recursive algorithm specified in INTRINSIC §
|
|
// 4. For now we make this column contribute no width.
|
|
parent_inline_sizes.push(ColumnIntrinsicInlineSize::new())
|
|
} else {
|
|
parent_inline_sizes.push(child_cell_inline_size.column_size)
|
|
}
|
|
}
|
|
|
|
total_inline_sizes.minimum_inline_size +=
|
|
parent_inline_sizes[column_index].minimum_length;
|
|
total_inline_sizes.preferred_inline_size +=
|
|
parent_inline_sizes[column_index].preferred;
|
|
|
|
// If this cell spans later rows, record its rowspan.
|
|
if child_cell_inline_size.row_span > 1 {
|
|
if incoming_rowspan.len() < column_index + 1 {
|
|
incoming_rowspan.resize(column_index + 1, 0);
|
|
}
|
|
incoming_rowspan[column_index] = child_cell_inline_size.row_span;
|
|
}
|
|
|
|
column_index += 1
|
|
}
|
|
}
|
|
|
|
total_inline_sizes
|
|
}
|
|
|
|
/// Updates the minimum and preferred inline-size calculation for a single row. This is
|
|
/// factored out into a separate function because we process children of rowgroups too.
|
|
fn update_column_inline_sizes_for_row(
|
|
row: &TableRowFlow,
|
|
column_inline_sizes: &mut Vec<ColumnIntrinsicInlineSize>,
|
|
computation: &mut IntrinsicISizesContribution,
|
|
first_row: bool,
|
|
table_layout: TableLayout,
|
|
surrounding_inline_size: Au,
|
|
) {
|
|
// Read column inline-sizes from the table-row, and assign inline-size=0 for the columns
|
|
// not defined in the column group.
|
|
//
|
|
// FIXME: Need to read inline-sizes from either table-header-group OR the first table-row.
|
|
match table_layout {
|
|
TableLayout::Fixed => {
|
|
// Fixed table layout only looks at the first row.
|
|
//
|
|
// FIXME(pcwalton): This is really inefficient. We should stop after the first row!
|
|
if first_row {
|
|
for cell_inline_size in &row.cell_intrinsic_inline_sizes {
|
|
column_inline_sizes.push(cell_inline_size.column_size);
|
|
}
|
|
}
|
|
},
|
|
TableLayout::Auto => {
|
|
computation.union_block(&TableFlow::update_automatic_column_inline_sizes(
|
|
column_inline_sizes,
|
|
&row.cell_intrinsic_inline_sizes,
|
|
surrounding_inline_size,
|
|
))
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Returns the effective spacing per cell, taking the value of `border-collapse` into account.
|
|
pub fn spacing(&self) -> border_spacing::T {
|
|
let style = self.block_flow.fragment.style();
|
|
match style.get_inherited_table().border_collapse {
|
|
border_collapse::T::Separate => style.get_inherited_table().border_spacing,
|
|
border_collapse::T::Collapse => border_spacing::T::zero(),
|
|
}
|
|
}
|
|
|
|
pub fn total_horizontal_spacing(&self) -> Au {
|
|
let num_columns = self.column_intrinsic_inline_sizes.len();
|
|
if num_columns == 0 {
|
|
return Au(0);
|
|
}
|
|
self.spacing().horizontal() * (num_columns as i32 + 1)
|
|
}
|
|
|
|
fn column_styles(&self) -> Vec<ColumnStyle> {
|
|
let mut styles = vec![];
|
|
for group in self
|
|
.block_flow
|
|
.base
|
|
.child_iter()
|
|
.filter(|kid| kid.is_table_colgroup())
|
|
{
|
|
// XXXManishearth these as_foo methods should return options
|
|
// so that we can filter_map
|
|
let group = group.as_table_colgroup();
|
|
let colgroup_style = group.fragment.as_ref().map(|f| f.style());
|
|
|
|
// The colgroup's span attribute is only relevant when
|
|
// it has no children
|
|
// https://html.spec.whatwg.org/multipage/#forming-a-table
|
|
if group.cols.is_empty() {
|
|
let span = group
|
|
.fragment
|
|
.as_ref()
|
|
.map(|f| f.column_span())
|
|
.unwrap_or(1);
|
|
styles.push(ColumnStyle {
|
|
span,
|
|
colgroup_style,
|
|
col_style: None,
|
|
});
|
|
} else {
|
|
for col in &group.cols {
|
|
// XXXManishearth Arc-cloning colgroup_style is suboptimal
|
|
styles.push(ColumnStyle {
|
|
span: col.column_span(),
|
|
colgroup_style: colgroup_style,
|
|
col_style: Some(col.style()),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
styles
|
|
}
|
|
}
|
|
|
|
impl Flow for TableFlow {
|
|
fn class(&self) -> FlowClass {
|
|
FlowClass::Table
|
|
}
|
|
|
|
fn as_mut_table(&mut self) -> &mut TableFlow {
|
|
self
|
|
}
|
|
|
|
fn as_table(&self) -> &TableFlow {
|
|
self
|
|
}
|
|
|
|
fn as_mut_block(&mut self) -> &mut BlockFlow {
|
|
&mut self.block_flow
|
|
}
|
|
|
|
fn as_block(&self) -> &BlockFlow {
|
|
&self.block_flow
|
|
}
|
|
|
|
fn mark_as_root(&mut self) {
|
|
self.block_flow.mark_as_root();
|
|
}
|
|
|
|
/// The specified column inline-sizes are set from column group and the first row for the fixed
|
|
/// table layout calculation.
|
|
/// The maximum min/pref inline-sizes of each column are set from the rows for the automatic
|
|
/// table layout calculation.
|
|
fn bubble_inline_sizes(&mut self) {
|
|
let _scope = layout_debug_scope!(
|
|
"table::bubble_inline_sizes {:x}",
|
|
self.block_flow.base.debug_id()
|
|
);
|
|
|
|
// Get column inline sizes from colgroups
|
|
for kid in self
|
|
.block_flow
|
|
.base
|
|
.child_iter_mut()
|
|
.filter(|kid| kid.is_table_colgroup())
|
|
{
|
|
for specified_inline_size in &kid.as_mut_table_colgroup().inline_sizes {
|
|
self.column_intrinsic_inline_sizes
|
|
.push(ColumnIntrinsicInlineSize {
|
|
minimum_length: match *specified_inline_size {
|
|
Size::Auto => Au(0),
|
|
Size::LengthPercentage(ref lp) => {
|
|
lp.maybe_to_used_value(None).unwrap_or(Au(0))
|
|
},
|
|
},
|
|
percentage: match *specified_inline_size {
|
|
Size::Auto => 0.0,
|
|
Size::LengthPercentage(ref lp) => {
|
|
lp.0.to_percentage().map_or(0.0, |p| p.0)
|
|
},
|
|
},
|
|
preferred: Au(0),
|
|
constrained: false,
|
|
})
|
|
}
|
|
}
|
|
|
|
self.collapsed_inline_direction_border_widths_for_table = Vec::new();
|
|
self.collapsed_block_direction_border_widths_for_table = vec![Au(0)];
|
|
|
|
let collapsing_borders = self
|
|
.block_flow
|
|
.fragment
|
|
.style
|
|
.get_inherited_table()
|
|
.border_collapse ==
|
|
border_collapse::T::Collapse;
|
|
let table_inline_collapsed_borders = if collapsing_borders {
|
|
Some(TableInlineCollapsedBorders {
|
|
start: CollapsedBorder::inline_start(
|
|
&*self.block_flow.fragment.style,
|
|
CollapsedBorderProvenance::FromTable,
|
|
),
|
|
end: CollapsedBorder::inline_end(
|
|
&*self.block_flow.fragment.style,
|
|
CollapsedBorderProvenance::FromTable,
|
|
),
|
|
})
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let mut computation = IntrinsicISizesContribution::new();
|
|
let mut previous_collapsed_block_end_borders =
|
|
PreviousBlockCollapsedBorders::FromTable(CollapsedBorder::block_start(
|
|
&*self.block_flow.fragment.style,
|
|
CollapsedBorderProvenance::FromTable,
|
|
));
|
|
let mut first_row = true;
|
|
let (border_padding, _) = self.block_flow.fragment.surrounding_intrinsic_inline_size();
|
|
|
|
{
|
|
let mut iterator = TableRowIterator::new(&mut self.block_flow.base).peekable();
|
|
while let Some(row) = iterator.next() {
|
|
TableFlow::update_column_inline_sizes_for_row(
|
|
row,
|
|
&mut self.column_intrinsic_inline_sizes,
|
|
&mut computation,
|
|
first_row,
|
|
self.table_layout,
|
|
border_padding,
|
|
);
|
|
if collapsing_borders {
|
|
let next_index_and_sibling = iterator.peek();
|
|
let next_collapsed_borders_in_block_direction = match next_index_and_sibling {
|
|
Some(next_sibling) => NextBlockCollapsedBorders::FromNextRow(
|
|
&next_sibling
|
|
.as_table_row()
|
|
.preliminary_collapsed_borders
|
|
.block_start,
|
|
),
|
|
None => NextBlockCollapsedBorders::FromTable(CollapsedBorder::block_end(
|
|
&*self.block_flow.fragment.style,
|
|
CollapsedBorderProvenance::FromTable,
|
|
)),
|
|
};
|
|
perform_border_collapse_for_row(
|
|
row,
|
|
table_inline_collapsed_borders.as_ref().unwrap(),
|
|
previous_collapsed_block_end_borders,
|
|
next_collapsed_borders_in_block_direction,
|
|
&mut self.collapsed_inline_direction_border_widths_for_table,
|
|
&mut self.collapsed_block_direction_border_widths_for_table,
|
|
);
|
|
previous_collapsed_block_end_borders =
|
|
PreviousBlockCollapsedBorders::FromPreviousRow(
|
|
row.final_collapsed_borders.block_end.clone(),
|
|
);
|
|
}
|
|
first_row = false
|
|
}
|
|
}
|
|
|
|
let total_horizontal_spacing = self.total_horizontal_spacing();
|
|
let mut style_specified_intrinsic_inline_size = self
|
|
.block_flow
|
|
.fragment
|
|
.style_specified_intrinsic_inline_size()
|
|
.finish();
|
|
style_specified_intrinsic_inline_size.minimum_inline_size -= total_horizontal_spacing;
|
|
style_specified_intrinsic_inline_size.preferred_inline_size -= total_horizontal_spacing;
|
|
computation.union_block(&style_specified_intrinsic_inline_size);
|
|
computation.surrounding_size += total_horizontal_spacing;
|
|
|
|
self.block_flow.base.intrinsic_inline_sizes = computation.finish()
|
|
}
|
|
|
|
/// 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) {
|
|
let _scope = layout_debug_scope!(
|
|
"table::assign_inline_sizes {:x}",
|
|
self.block_flow.base.debug_id()
|
|
);
|
|
debug!(
|
|
"assign_inline_sizes({}): assigning inline_size for flow",
|
|
"table"
|
|
);
|
|
trace!("TableFlow before assigning: {:?}", &self);
|
|
|
|
let shared_context = layout_context.shared_context();
|
|
// The position was set to the containing block by the flow's parent.
|
|
// FIXME: The code for distributing column widths should really be placed under table_wrapper.rs.
|
|
let containing_block_inline_size = self.block_flow.base.block_container_inline_size;
|
|
|
|
let mut constrained_column_inline_sizes_indices = vec![];
|
|
let mut unspecified_inline_sizes_indices = vec![];
|
|
for (idx, column_inline_size) in self.column_intrinsic_inline_sizes.iter().enumerate() {
|
|
if column_inline_size.constrained {
|
|
constrained_column_inline_sizes_indices.push(idx);
|
|
} else if column_inline_size.percentage == 0.0 {
|
|
unspecified_inline_sizes_indices.push(idx);
|
|
}
|
|
}
|
|
|
|
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_padding.inline_start;
|
|
let inline_end_content_edge = self.block_flow.fragment.border_padding.inline_end;
|
|
let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end();
|
|
let spacing_per_cell = self.spacing();
|
|
let total_horizontal_spacing = self.total_horizontal_spacing();
|
|
let content_inline_size = self.block_flow.fragment.border_box.size.inline -
|
|
padding_and_borders -
|
|
total_horizontal_spacing;
|
|
let mut remaining_inline_size = content_inline_size;
|
|
|
|
match self.table_layout {
|
|
TableLayout::Fixed => {
|
|
self.column_computed_inline_sizes.clear();
|
|
|
|
// https://drafts.csswg.org/css2/tables.html#fixed-table-layout
|
|
for column_inline_size in &self.column_intrinsic_inline_sizes {
|
|
if column_inline_size.constrained {
|
|
self.column_computed_inline_sizes
|
|
.push(ColumnComputedInlineSize {
|
|
size: column_inline_size.minimum_length,
|
|
});
|
|
remaining_inline_size -= column_inline_size.minimum_length;
|
|
} else if column_inline_size.percentage != 0.0 {
|
|
let size = remaining_inline_size.scale_by(column_inline_size.percentage);
|
|
self.column_computed_inline_sizes
|
|
.push(ColumnComputedInlineSize { size: size });
|
|
remaining_inline_size -= size;
|
|
} else {
|
|
// Set the size to 0 now, distribute the remaining widths later
|
|
self.column_computed_inline_sizes
|
|
.push(ColumnComputedInlineSize { size: Au(0) });
|
|
}
|
|
}
|
|
|
|
// Distribute remaining content inline size
|
|
if unspecified_inline_sizes_indices.len() > 0 {
|
|
for &index in &unspecified_inline_sizes_indices {
|
|
self.column_computed_inline_sizes[index].size = remaining_inline_size
|
|
.scale_by(1.0 / unspecified_inline_sizes_indices.len() as f32);
|
|
}
|
|
} else {
|
|
let total_minimum_size = self
|
|
.column_intrinsic_inline_sizes
|
|
.iter()
|
|
.filter(|size| size.constrained)
|
|
.map(|size| size.minimum_length.0 as f32)
|
|
.sum::<f32>();
|
|
|
|
for &index in &constrained_column_inline_sizes_indices {
|
|
let inline_size = self.column_computed_inline_sizes[index].size.0;
|
|
self.column_computed_inline_sizes[index].size +=
|
|
remaining_inline_size.scale_by(inline_size as f32 / total_minimum_size);
|
|
}
|
|
}
|
|
},
|
|
_ => {
|
|
// The table wrapper already computed the inline-sizes and propagated them down
|
|
// to us.
|
|
},
|
|
}
|
|
|
|
let column_computed_inline_sizes = &self.column_computed_inline_sizes;
|
|
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();
|
|
let mut incoming_rowspan = vec![];
|
|
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| {
|
|
table_row::propagate_column_inline_sizes_to_child(
|
|
child_flow,
|
|
writing_mode,
|
|
column_computed_inline_sizes,
|
|
&spacing_per_cell,
|
|
&mut incoming_rowspan,
|
|
);
|
|
if child_flow.is_table_row() {
|
|
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,
|
|
);
|
|
} else if child_flow.is_table_rowgroup() {
|
|
let child_table_rowgroup = child_flow.as_mut_table_rowgroup();
|
|
child_table_rowgroup.populate_collapsed_border_spacing(
|
|
collapsed_inline_direction_border_widths_for_table,
|
|
&mut collapsed_block_direction_border_widths_for_table,
|
|
);
|
|
}
|
|
},
|
|
);
|
|
|
|
trace!("TableFlow after assigning: {:?}", &self);
|
|
}
|
|
|
|
fn assign_block_size(&mut self, lc: &LayoutContext) {
|
|
debug!("assign_block_size: assigning block_size for table");
|
|
trace!("TableFlow before assigning: {:?}", &self);
|
|
|
|
let vertical_spacing = self.spacing().vertical();
|
|
self.block_flow
|
|
.assign_block_size_for_table_like_flow(vertical_spacing, lc);
|
|
|
|
trace!("TableFlow after assigning: {:?}", &self);
|
|
}
|
|
|
|
fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
|
|
self.block_flow
|
|
.compute_stacking_relative_position(layout_context)
|
|
}
|
|
|
|
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
|
|
self.block_flow.generated_containing_block_size(flow)
|
|
}
|
|
|
|
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) {
|
|
let border_painting_mode = match self
|
|
.block_flow
|
|
.fragment
|
|
.style
|
|
.get_inherited_table()
|
|
.border_collapse
|
|
{
|
|
border_collapse::T::Separate => BorderPaintingMode::Separate,
|
|
border_collapse::T::Collapse => BorderPaintingMode::Hidden,
|
|
};
|
|
|
|
self.block_flow
|
|
.build_display_list_for_block(state, border_painting_mode);
|
|
|
|
let iter = TableCellStyleIterator::new(&self);
|
|
for style in iter {
|
|
style.build_display_list(state)
|
|
}
|
|
}
|
|
|
|
fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
|
|
// Stacking contexts are collected by the table wrapper.
|
|
self.block_flow.collect_stacking_contexts_for_block(
|
|
state,
|
|
StackingContextCollectionFlags::NEVER_CREATES_STACKING_CONTEXT,
|
|
);
|
|
}
|
|
|
|
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 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);
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct ColumnStyle<'table> {
|
|
span: u32,
|
|
colgroup_style: Option<&'table ComputedValues>,
|
|
col_style: Option<&'table ComputedValues>,
|
|
}
|
|
|
|
impl fmt::Debug for TableFlow {
|
|
/// Outputs a debugging string describing this table flow.
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "TableFlow: {:?}", self.block_flow)
|
|
}
|
|
}
|
|
|
|
/// Table, TableRowGroup, TableRow, TableCell types.
|
|
/// Their inline-sizes are calculated in the same way and do not have margins.
|
|
pub struct InternalTable;
|
|
|
|
impl ISizeAndMarginsComputer for InternalTable {
|
|
/// Compute the used value of inline-size, taking care of min-inline-size and max-inline-size.
|
|
///
|
|
/// CSS Section 10.4: Minimum and Maximum inline-sizes
|
|
fn compute_used_inline_size(
|
|
&self,
|
|
block: &mut BlockFlow,
|
|
shared_context: &SharedStyleContext,
|
|
parent_flow_inline_size: Au,
|
|
) {
|
|
let mut input = self.compute_inline_size_constraint_inputs(
|
|
block,
|
|
parent_flow_inline_size,
|
|
shared_context,
|
|
);
|
|
|
|
// Tables are always at least as wide as their minimum inline size.
|
|
let minimum_inline_size = block.base.intrinsic_inline_sizes.minimum_inline_size -
|
|
block.fragment.border_padding.inline_start_end();
|
|
input.available_inline_size = cmp::max(input.available_inline_size, minimum_inline_size);
|
|
|
|
let solution = self.solve_inline_size_constraints(block, &input);
|
|
self.set_inline_size_constraint_solutions(block, solution);
|
|
}
|
|
|
|
/// Solve the inline-size and margins constraints for this block flow.
|
|
fn solve_inline_size_constraints(
|
|
&self,
|
|
_: &mut BlockFlow,
|
|
input: &ISizeConstraintInput,
|
|
) -> ISizeConstraintSolution {
|
|
ISizeConstraintSolution::new(input.available_inline_size, Au(0), Au(0))
|
|
}
|
|
}
|
|
|
|
/// Information about the intrinsic inline sizes of columns within a table.
|
|
///
|
|
/// During table inline-size bubbling, we might need to store both a percentage constraint and a
|
|
/// specific width constraint. For instance, one cell might say that it wants to be 100 pixels wide
|
|
/// in the inline direction and another cell might say that it wants to take up 20% of the inline-
|
|
/// size of the table. Now because we bubble up these constraints during the bubble-inline-sizes
|
|
/// phase of layout, we don't know yet how wide the table is ultimately going to be in the inline
|
|
/// direction. As we need to pick the maximum width of all cells for a column (in this case, the
|
|
/// maximum of 100 pixels and 20% of the table), the preceding constraint means that we must
|
|
/// potentially store both a specified width *and* a specified percentage, so that the inline-size
|
|
/// assignment phase of layout will know which one to pick.
|
|
#[derive(Clone, Copy, Debug, Serialize)]
|
|
pub struct ColumnIntrinsicInlineSize {
|
|
/// The preferred intrinsic inline size.
|
|
pub preferred: Au,
|
|
/// The largest specified size of this column as a length.
|
|
pub minimum_length: Au,
|
|
/// The largest specified size of this column as a percentage (`width` property).
|
|
pub percentage: CSSFloat,
|
|
/// Whether the column inline size is *constrained* per INTRINSIC § 4.1.
|
|
pub constrained: bool,
|
|
}
|
|
|
|
impl ColumnIntrinsicInlineSize {
|
|
/// Returns a newly-initialized `ColumnIntrinsicInlineSize` with all fields blank.
|
|
pub fn new() -> ColumnIntrinsicInlineSize {
|
|
ColumnIntrinsicInlineSize {
|
|
preferred: Au(0),
|
|
minimum_length: Au(0),
|
|
percentage: 0.0,
|
|
constrained: false,
|
|
}
|
|
}
|
|
|
|
/// Returns the higher of the two percentages specified in `self` and `other`.
|
|
pub fn greatest_percentage(&self, other: &ColumnIntrinsicInlineSize) -> CSSFloat {
|
|
if self.percentage > other.percentage {
|
|
self.percentage
|
|
} else {
|
|
other.percentage
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The actual inline size for each column.
|
|
///
|
|
/// TODO(pcwalton): There will probably be some `border-collapse`-related info in here too
|
|
/// eventually.
|
|
#[derive(Clone, Copy, Debug, Serialize)]
|
|
pub struct ColumnComputedInlineSize {
|
|
/// The computed size of this inline column.
|
|
pub size: Au,
|
|
}
|
|
|
|
pub trait VecExt<T> {
|
|
fn push_or_set(&mut self, index: usize, value: T) -> &mut T;
|
|
fn get_mut_or_push(&mut self, index: usize, zero: T) -> &mut T;
|
|
}
|
|
|
|
impl<T> VecExt<T> for Vec<T> {
|
|
fn push_or_set(&mut self, index: usize, value: T) -> &mut T {
|
|
if index < self.len() {
|
|
self[index] = value
|
|
} else {
|
|
debug_assert_eq!(index, self.len());
|
|
self.push(value)
|
|
}
|
|
&mut self[index]
|
|
}
|
|
|
|
fn get_mut_or_push(&mut self, index: usize, zero: T) -> &mut T {
|
|
if index >= self.len() {
|
|
debug_assert_eq!(index, self.len());
|
|
self.push(zero)
|
|
}
|
|
&mut self[index]
|
|
}
|
|
}
|
|
|
|
/// Updates the border styles in the block direction for a single row. This function should
|
|
/// only be called if border collapsing is on. It is factored out into a separate function
|
|
/// because we process children of rowgroups too.
|
|
fn perform_border_collapse_for_row(
|
|
child_table_row: &mut TableRowFlow,
|
|
table_inline_borders: &TableInlineCollapsedBorders,
|
|
previous_block_borders: PreviousBlockCollapsedBorders,
|
|
next_block_borders: NextBlockCollapsedBorders,
|
|
inline_spacing: &mut Vec<Au>,
|
|
block_spacing: &mut Vec<Au>,
|
|
) {
|
|
// TODO mbrubeck: Take rowspan and colspan into account.
|
|
let number_of_borders_inline_direction =
|
|
child_table_row.preliminary_collapsed_borders.inline.len();
|
|
// Compute interior inline borders.
|
|
for (i, this_inline_border) in child_table_row
|
|
.preliminary_collapsed_borders
|
|
.inline
|
|
.iter_mut()
|
|
.enumerate()
|
|
{
|
|
child_table_row
|
|
.final_collapsed_borders
|
|
.inline
|
|
.push_or_set(i, this_inline_border.clone());
|
|
if i == 0 {
|
|
child_table_row.final_collapsed_borders.inline[i].combine(&table_inline_borders.start);
|
|
} else if i + 1 == number_of_borders_inline_direction {
|
|
child_table_row.final_collapsed_borders.inline[i].combine(&table_inline_borders.end);
|
|
}
|
|
|
|
let inline_spacing = inline_spacing.get_mut_or_push(i, Au(0));
|
|
*inline_spacing = cmp::max(
|
|
*inline_spacing,
|
|
child_table_row.final_collapsed_borders.inline[i].width,
|
|
)
|
|
}
|
|
|
|
// Compute block-start borders.
|
|
let block_start_borders = &mut child_table_row.final_collapsed_borders.block_start;
|
|
*block_start_borders = child_table_row
|
|
.preliminary_collapsed_borders
|
|
.block_start
|
|
.clone();
|
|
for (i, this_border) in block_start_borders.iter_mut().enumerate() {
|
|
match previous_block_borders {
|
|
PreviousBlockCollapsedBorders::FromPreviousRow(ref previous_block_borders) => {
|
|
if previous_block_borders.len() > i {
|
|
this_border.combine(&previous_block_borders[i]);
|
|
}
|
|
},
|
|
PreviousBlockCollapsedBorders::FromTable(ref table_border) => {
|
|
this_border.combine(&table_border);
|
|
},
|
|
}
|
|
}
|
|
|
|
// Compute block-end borders.
|
|
let next_block = &mut child_table_row.final_collapsed_borders.block_end;
|
|
block_spacing.push(Au(0));
|
|
let block_spacing = block_spacing.last_mut().unwrap();
|
|
for (i, this_block_border) in child_table_row
|
|
.preliminary_collapsed_borders
|
|
.block_end
|
|
.iter()
|
|
.enumerate()
|
|
{
|
|
let next_block = next_block.push_or_set(i, this_block_border.clone());
|
|
match next_block_borders {
|
|
NextBlockCollapsedBorders::FromNextRow(next_block_borders) => {
|
|
if next_block_borders.len() > i {
|
|
next_block.combine(&next_block_borders[i])
|
|
}
|
|
},
|
|
NextBlockCollapsedBorders::FromTable(ref next_block_borders) => {
|
|
next_block.combine(next_block_borders);
|
|
},
|
|
}
|
|
*block_spacing = cmp::max(*block_spacing, next_block.width)
|
|
}
|
|
}
|
|
|
|
/// Encapsulates functionality shared among all table-like flows: for now, tables and table
|
|
/// rowgroups.
|
|
pub trait TableLikeFlow {
|
|
/// Lays out the rows of a table.
|
|
fn assign_block_size_for_table_like_flow(
|
|
&mut self,
|
|
block_direction_spacing: Au,
|
|
layout_context: &LayoutContext,
|
|
);
|
|
}
|
|
|
|
impl TableLikeFlow for BlockFlow {
|
|
fn assign_block_size_for_table_like_flow(
|
|
&mut self,
|
|
block_direction_spacing: Au,
|
|
layout_context: &LayoutContext,
|
|
) {
|
|
debug_assert!(
|
|
self.fragment.style.get_inherited_table().border_collapse ==
|
|
border_collapse::T::Separate ||
|
|
block_direction_spacing == Au(0)
|
|
);
|
|
|
|
fn border_spacing_for_row(
|
|
fragment: &Fragment,
|
|
row: &TableRowFlow,
|
|
block_direction_spacing: Au,
|
|
) -> Au {
|
|
match fragment.style.get_inherited_table().border_collapse {
|
|
border_collapse::T::Separate => block_direction_spacing,
|
|
border_collapse::T::Collapse => row.collapsed_border_spacing.block_start,
|
|
}
|
|
}
|
|
|
|
if self
|
|
.base
|
|
.restyle_damage
|
|
.contains(ServoRestyleDamage::REFLOW)
|
|
{
|
|
let mut sizes = vec![Default::default()];
|
|
// The amount of border spacing up to and including this row,
|
|
// but not including the spacing beneath it
|
|
let mut cumulative_border_spacing = Au(0);
|
|
let mut incoming_rowspan_data = vec![];
|
|
let mut rowgroup_id = 0;
|
|
let mut first = true;
|
|
|
|
// First pass: Compute block-direction border spacings
|
|
// XXXManishearth this can be done in tandem with the second pass,
|
|
// provided we never hit any rowspan cases
|
|
for kid in self.base.child_iter_mut() {
|
|
if kid.is_table_row() {
|
|
// skip the first row, it is accounted for
|
|
if first {
|
|
first = false;
|
|
continue;
|
|
}
|
|
cumulative_border_spacing += border_spacing_for_row(
|
|
&self.fragment,
|
|
kid.as_table_row(),
|
|
block_direction_spacing,
|
|
);
|
|
sizes.push(TableRowSizeData {
|
|
// we haven't calculated sizes yet
|
|
size: Au(0),
|
|
cumulative_border_spacing,
|
|
rowgroup_id,
|
|
});
|
|
} else if kid.is_table_rowgroup() && !first {
|
|
rowgroup_id += 1;
|
|
}
|
|
}
|
|
|
|
// Second pass: Compute row block sizes
|
|
// [expensive: iterates over cells]
|
|
let mut i = 0;
|
|
for kid in self.base.child_iter_mut() {
|
|
if kid.is_table_row() {
|
|
let size = kid.as_mut_table_row().compute_block_size_table_row_base(
|
|
layout_context,
|
|
&mut incoming_rowspan_data,
|
|
&sizes,
|
|
i,
|
|
);
|
|
sizes[i].size = size;
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
// Our current border-box position.
|
|
let block_start_border_padding = self.fragment.border_padding.block_start;
|
|
let mut current_block_offset = block_start_border_padding;
|
|
let mut has_rows = false;
|
|
|
|
// Third pass: Assign block sizes and positions to rows, cells, and other children
|
|
// [expensive: iterates over cells]
|
|
// At this point, `current_block_offset` is at the content edge of our box. Now iterate
|
|
// over children.
|
|
let mut i = 0;
|
|
for kid in self.base.child_iter_mut() {
|
|
if kid.is_table_row() {
|
|
has_rows = true;
|
|
let row = kid.as_mut_table_row();
|
|
row.assign_block_size_to_self_and_children(&sizes, i);
|
|
row.mut_base().restyle_damage.remove(
|
|
ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW,
|
|
);
|
|
current_block_offset = current_block_offset +
|
|
border_spacing_for_row(&self.fragment, row, block_direction_spacing);
|
|
i += 1;
|
|
}
|
|
|
|
// At this point, `current_block_offset` is at the border edge of the child.
|
|
kid.mut_base().position.start.b = current_block_offset;
|
|
|
|
// Move past the child's border box. Do not use the `translate_including_floats`
|
|
// function here because the child has already translated floats past its border
|
|
// box.
|
|
let kid_base = kid.mut_base();
|
|
current_block_offset = current_block_offset + kid_base.position.size.block;
|
|
}
|
|
|
|
// Compute any explicitly-specified block size.
|
|
// Can't use `for` because we assign to
|
|
// `candidate_block_size_iterator.candidate_value`.
|
|
let mut block_size = current_block_offset - block_start_border_padding;
|
|
let mut candidate_block_size_iterator = CandidateBSizeIterator::new(
|
|
&self.fragment,
|
|
self.base.block_container_explicit_block_size,
|
|
);
|
|
while let Some(candidate_block_size) = candidate_block_size_iterator.next() {
|
|
candidate_block_size_iterator.candidate_value = match candidate_block_size {
|
|
MaybeAuto::Auto => block_size,
|
|
MaybeAuto::Specified(value) => value,
|
|
};
|
|
}
|
|
|
|
// Adjust `current_block_offset` as necessary to account for the explicitly-specified
|
|
// block-size.
|
|
block_size = candidate_block_size_iterator.candidate_value;
|
|
let delta = block_size - (current_block_offset - block_start_border_padding);
|
|
current_block_offset = current_block_offset + delta;
|
|
|
|
// Take border, padding, and spacing into account.
|
|
let block_end_offset = self.fragment.border_padding.block_end +
|
|
if has_rows {
|
|
block_direction_spacing
|
|
} else {
|
|
Au(0)
|
|
};
|
|
current_block_offset = current_block_offset + block_end_offset;
|
|
|
|
// Now that `current_block_offset` is at the block-end of the border box, compute the
|
|
// final border box position.
|
|
self.fragment.border_box.size.block = current_block_offset;
|
|
self.fragment.border_box.start.b = Au(0);
|
|
self.base.position.size.block = current_block_offset;
|
|
|
|
// Fourth pass: Assign absolute position info
|
|
// Write in the size of the relative containing block for children. (This information
|
|
// is also needed to handle RTL.)
|
|
for kid in self.base.child_iter_mut() {
|
|
kid.mut_base().early_absolute_position_info = EarlyAbsolutePositionInfo {
|
|
relative_containing_block_size: self.fragment.content_box().size,
|
|
relative_containing_block_mode: self.fragment.style().writing_mode,
|
|
};
|
|
}
|
|
}
|
|
|
|
self.base
|
|
.restyle_damage
|
|
.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
|
|
}
|
|
}
|
|
|
|
/// Inline collapsed borders for the table itself.
|
|
#[derive(Debug)]
|
|
struct TableInlineCollapsedBorders {
|
|
/// The table border at the start of the inline direction.
|
|
start: CollapsedBorder,
|
|
/// The table border at the end of the inline direction.
|
|
end: CollapsedBorder,
|
|
}
|
|
|
|
enum PreviousBlockCollapsedBorders {
|
|
FromPreviousRow(Vec<CollapsedBorder>),
|
|
FromTable(CollapsedBorder),
|
|
}
|
|
|
|
enum NextBlockCollapsedBorders<'a> {
|
|
FromNextRow(&'a [CollapsedBorder]),
|
|
FromTable(CollapsedBorder),
|
|
}
|
|
|
|
/// Iterator over all the rows of a table, which also
|
|
/// provides the Fragment for rowgroups if any
|
|
struct TableRowAndGroupIterator<'a> {
|
|
kids: FlowListIterator<'a>,
|
|
group: Option<(&'a Fragment, FlowListIterator<'a>)>,
|
|
}
|
|
|
|
impl<'a> TableRowAndGroupIterator<'a> {
|
|
fn new(base: &'a BaseFlow) -> Self {
|
|
TableRowAndGroupIterator {
|
|
kids: base.child_iter(),
|
|
group: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Iterator for TableRowAndGroupIterator<'a> {
|
|
type Item = (Option<&'a Fragment>, &'a TableRowFlow);
|
|
#[inline]
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
// If we're inside a rowgroup, iterate through the rowgroup's children.
|
|
if let Some(ref mut group) = self.group {
|
|
if let Some(grandkid) = group.1.next() {
|
|
return Some((Some(group.0), grandkid.as_table_row()));
|
|
}
|
|
}
|
|
// Otherwise, iterate through the table's children.
|
|
self.group = None;
|
|
match self.kids.next() {
|
|
Some(kid) => {
|
|
if kid.is_table_rowgroup() {
|
|
let rowgroup = kid.as_table_rowgroup();
|
|
let iter = rowgroup.block_flow.base.child_iter();
|
|
self.group = Some((&rowgroup.block_flow.fragment, iter));
|
|
self.next()
|
|
} else if kid.is_table_row() {
|
|
Some((None, kid.as_table_row()))
|
|
} else {
|
|
self.next() // Skip children that are not rows or rowgroups
|
|
}
|
|
},
|
|
None => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Iterator over all the rows of a table, which also
|
|
/// provides the Fragment for rowgroups if any
|
|
struct MutTableRowAndGroupIterator<'a> {
|
|
kids: MutFlowListIterator<'a>,
|
|
group: Option<(&'a Fragment, MutFlowListIterator<'a>)>,
|
|
}
|
|
|
|
impl<'a> MutTableRowAndGroupIterator<'a> {
|
|
fn new(base: &'a mut BaseFlow) -> Self {
|
|
MutTableRowAndGroupIterator {
|
|
kids: base.child_iter_mut(),
|
|
group: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Iterator for MutTableRowAndGroupIterator<'a> {
|
|
type Item = (Option<&'a Fragment>, &'a mut TableRowFlow);
|
|
#[inline]
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
// If we're inside a rowgroup, iterate through the rowgroup's children.
|
|
if let Some(ref mut group) = self.group {
|
|
if let Some(grandkid) = group.1.next() {
|
|
return Some((Some(group.0), grandkid.as_mut_table_row()));
|
|
}
|
|
}
|
|
// Otherwise, iterate through the table's children.
|
|
self.group = None;
|
|
match self.kids.next() {
|
|
Some(kid) => {
|
|
if kid.is_table_rowgroup() {
|
|
let rowgroup = kid.as_mut_table_rowgroup();
|
|
let iter = rowgroup.block_flow.base.child_iter_mut();
|
|
self.group = Some((&rowgroup.block_flow.fragment, iter));
|
|
self.next()
|
|
} else if kid.is_table_row() {
|
|
Some((None, kid.as_mut_table_row()))
|
|
} else {
|
|
self.next() // Skip children that are not rows or rowgroups
|
|
}
|
|
},
|
|
None => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Iterator over all the rows of a table
|
|
struct TableRowIterator<'a>(MutTableRowAndGroupIterator<'a>);
|
|
|
|
impl<'a> TableRowIterator<'a> {
|
|
fn new(base: &'a mut BaseFlow) -> Self {
|
|
TableRowIterator(MutTableRowAndGroupIterator::new(base))
|
|
}
|
|
}
|
|
|
|
impl<'a> Iterator for TableRowIterator<'a> {
|
|
type Item = &'a mut TableRowFlow;
|
|
#[inline]
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
self.0.next().map(|n| n.1)
|
|
}
|
|
}
|
|
|
|
/// An iterator over table cells, yielding all relevant style objects
|
|
/// for each cell
|
|
///
|
|
/// Used for correctly handling table layers from
|
|
/// https://drafts.csswg.org/css2/tables.html#table-layers
|
|
struct TableCellStyleIterator<'table> {
|
|
column_styles: Vec<ColumnStyle<'table>>,
|
|
row_iterator: TableRowAndGroupIterator<'table>,
|
|
row_info: Option<TableCellStyleIteratorRowInfo<'table>>,
|
|
column_index: TableCellColumnIndexData,
|
|
}
|
|
|
|
struct TableCellStyleIteratorRowInfo<'table> {
|
|
row: &'table TableRowFlow,
|
|
rowgroup: Option<&'table Fragment>,
|
|
cell_iterator: FlowListIterator<'table>,
|
|
}
|
|
|
|
impl<'table> TableCellStyleIterator<'table> {
|
|
fn new(table: &'table TableFlow) -> Self {
|
|
let column_styles = table.column_styles();
|
|
let mut row_iterator = TableRowAndGroupIterator::new(&table.block_flow.base);
|
|
let row_info = if let Some((group, row)) = row_iterator.next() {
|
|
Some(TableCellStyleIteratorRowInfo {
|
|
row: &row,
|
|
rowgroup: group,
|
|
cell_iterator: row.block_flow.base.child_iter(),
|
|
})
|
|
} else {
|
|
None
|
|
};
|
|
TableCellStyleIterator {
|
|
column_styles,
|
|
row_iterator,
|
|
row_info,
|
|
column_index: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
struct TableCellStyleInfo<'table> {
|
|
cell: &'table TableCellFlow,
|
|
colgroup_style: Option<&'table ComputedValues>,
|
|
col_style: Option<&'table ComputedValues>,
|
|
rowgroup_style: Option<&'table ComputedValues>,
|
|
row_style: &'table ComputedValues,
|
|
}
|
|
|
|
struct TableCellColumnIndexData {
|
|
/// Which column this is in the table
|
|
pub absolute: u32,
|
|
/// The index of the current column in column_styles
|
|
/// (i.e. which <col> element it is)
|
|
pub relative: u32,
|
|
/// In case of multispan <col>s, where we are in the
|
|
/// span of the current <col> element
|
|
pub relative_offset: u32,
|
|
}
|
|
|
|
impl Default for TableCellColumnIndexData {
|
|
fn default() -> Self {
|
|
TableCellColumnIndexData {
|
|
absolute: 0,
|
|
relative: 0,
|
|
relative_offset: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TableCellColumnIndexData {
|
|
/// Moves forward by `amount` columns, updating the various indices used
|
|
///
|
|
/// This totally ignores rowspan -- if colspan and rowspan clash,
|
|
/// they just overlap, so we ignore it.
|
|
fn advance(&mut self, amount: u32, column_styles: &[ColumnStyle]) {
|
|
self.absolute += amount;
|
|
self.relative_offset += amount;
|
|
if let Some(mut current_col) = column_styles.get(self.relative as usize) {
|
|
while self.relative_offset >= current_col.span {
|
|
// move to the next column
|
|
self.relative += 1;
|
|
self.relative_offset -= current_col.span;
|
|
if let Some(column_style) = column_styles.get(self.relative as usize) {
|
|
current_col = column_style;
|
|
} else {
|
|
// we ran out of column_styles,
|
|
// so we don't need to update the indices
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'table> Iterator for TableCellStyleIterator<'table> {
|
|
type Item = TableCellStyleInfo<'table>;
|
|
#[inline]
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
// FIXME We do this awkward .take() followed by shoving it back in
|
|
// because without NLL the row_info borrow lasts too long
|
|
if let Some(mut row_info) = self.row_info.take() {
|
|
if let Some(rowspan) = row_info
|
|
.row
|
|
.incoming_rowspan
|
|
.get(self.column_index.absolute as usize)
|
|
{
|
|
// we are not allowed to use this column as a starting point. Try the next one.
|
|
if *rowspan > 1 {
|
|
self.column_index.advance(1, &self.column_styles);
|
|
// put row_info back in
|
|
self.row_info = Some(row_info);
|
|
// try again
|
|
return self.next();
|
|
}
|
|
}
|
|
if let Some(cell) = row_info.cell_iterator.next() {
|
|
let rowgroup_style = row_info.rowgroup.map(|r| r.style());
|
|
let row_style = row_info.row.block_flow.fragment.style();
|
|
let cell = cell.as_table_cell();
|
|
let (col_style, colgroup_style) = if let Some(column_style) =
|
|
self.column_styles.get(self.column_index.relative as usize)
|
|
{
|
|
let styles = (
|
|
column_style.col_style.clone(),
|
|
column_style.colgroup_style.clone(),
|
|
);
|
|
self.column_index
|
|
.advance(cell.column_span, &self.column_styles);
|
|
|
|
styles
|
|
} else {
|
|
(None, None)
|
|
};
|
|
// put row_info back in
|
|
self.row_info = Some(row_info);
|
|
return Some(TableCellStyleInfo {
|
|
cell,
|
|
colgroup_style,
|
|
col_style,
|
|
rowgroup_style,
|
|
row_style,
|
|
});
|
|
} else {
|
|
// next row
|
|
if let Some((group, row)) = self.row_iterator.next() {
|
|
self.row_info = Some(TableCellStyleIteratorRowInfo {
|
|
row: &row,
|
|
rowgroup: group,
|
|
cell_iterator: row.block_flow.base.child_iter(),
|
|
});
|
|
self.column_index = Default::default();
|
|
self.next()
|
|
} else {
|
|
// out of rows
|
|
// row_info stays None
|
|
None
|
|
}
|
|
}
|
|
} else {
|
|
// empty table
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'table> TableCellStyleInfo<'table> {
|
|
fn build_display_list(&self, mut state: &mut DisplayListBuildState) {
|
|
use style::computed_values::visibility::T as Visibility;
|
|
|
|
if !self.cell.visible ||
|
|
self.cell
|
|
.block_flow
|
|
.fragment
|
|
.style()
|
|
.get_inherited_box()
|
|
.visibility !=
|
|
Visibility::Visible
|
|
{
|
|
return;
|
|
}
|
|
let border_painting_mode = match self
|
|
.cell
|
|
.block_flow
|
|
.fragment
|
|
.style
|
|
.get_inherited_table()
|
|
.border_collapse
|
|
{
|
|
border_collapse::T::Separate => BorderPaintingMode::Separate,
|
|
border_collapse::T::Collapse => {
|
|
BorderPaintingMode::Collapse(&self.cell.collapsed_borders)
|
|
},
|
|
};
|
|
{
|
|
let cell_flow = &self.cell.block_flow;
|
|
let initial = ComputedValues::initial_values();
|
|
|
|
let build_dl = |sty: &ComputedValues, state: &mut &mut DisplayListBuildState| {
|
|
let background = sty.get_background();
|
|
// Don't redraw backgrounds that we've already drawn
|
|
if background as *const Background == initial.get_background() as *const _ {
|
|
return;
|
|
}
|
|
let background_color = sty.resolve_color(background.background_color.clone());
|
|
cell_flow.build_display_list_for_background_if_applicable_with_background(
|
|
state,
|
|
background,
|
|
background_color,
|
|
);
|
|
};
|
|
|
|
if let Some(ref sty) = self.colgroup_style {
|
|
build_dl(&sty, &mut state);
|
|
}
|
|
if let Some(ref sty) = self.col_style {
|
|
build_dl(&sty, &mut state);
|
|
}
|
|
if let Some(ref sty) = self.rowgroup_style {
|
|
build_dl(sty, &mut state);
|
|
}
|
|
build_dl(self.row_style, &mut state);
|
|
}
|
|
// the restyle damage will be set in TableCellFlow::build_display_list()
|
|
self.cell
|
|
.block_flow
|
|
.build_display_list_for_block_no_damage(state, border_painting_mode)
|
|
}
|
|
}
|