1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 //! Layout for CSS block-level elements.
6 //!
7 //! As a terminology note, the term *absolute positioning* here refers to elements with position
8 //! `absolute` or `fixed`. The term *positioned element* refers to elements with position
9 //! `relative`, `absolute`, and `fixed`. The term *containing block* (occasionally abbreviated as
10 //! *CB*) is the containing block for the current flow, which differs from the static containing
11 //! block if the flow is absolutely-positioned.
12 //!
13 //! "CSS 2.1" or "CSS 2.2" refers to the editor's draft of the W3C "Cascading Style Sheets Level 2
14 //! Revision 2 (CSS 2.2) Specification" available here:
15 //!
16 //! http://dev.w3.org/csswg/css2/
17 //!
18 //! "INTRINSIC" refers to L. David Baron's "More Precise Definitions of Inline Layout and Table
19 //! Layout" available here:
20 //!
21 //! http://dbaron.org/css/intrinsic/
22 //!
23 //! "CSS-SIZING" refers to the W3C "CSS Intrinsic & Extrinsic Sizing Module Level 3" document
24 //! available here:
25 //!
26 //! http://dev.w3.org/csswg/css-sizing/
27
28 #![deny(unsafe_code)]
29
30 use app_units::{Au, MAX_AU};
31 use context::LayoutContext;
32 use display_list::{BlockFlowDisplayListBuilding, BorderPaintingMode};
33 use display_list::{DisplayListBuildState, StackingContextCollectionFlags};
34 use display_list::StackingContextCollectionState;
35 use euclid::{Point2D, Rect, SideOffsets2D, Size2D};
36 use floats::{ClearType, FloatKind, Floats, PlacementInfo};
37 use flow::{BaseFlow, EarlyAbsolutePositionInfo, Flow, FlowClass, ForceNonfloatedFlag, GetBaseFlow};
38 use flow::{ImmutableFlowUtils, LateAbsolutePositionInfo, OpaqueFlow, FragmentationContext, FlowFlags};
39 use flow_list::FlowList;
40 use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, Overflow, FragmentFlags};
41 use gfx::display_list::DisplayListSection;
42 use gfx_traits::print_tree::PrintTree;
43 use incremental::RelayoutMode;
44 use layout_debug;
45 use model::{AdjoiningMargins, CollapsibleMargins, IntrinsicISizes, MarginCollapseInfo, MaybeAuto};
46 use sequential;
47 use serde::{Serialize, Serializer};
48 use servo_geometry::MaxRect;
49 use std::cmp::{max, min};
50 use std::fmt;
51 use std::sync::Arc;
52 use style::computed_values::box_sizing::T as BoxSizing;
53 use style::computed_values::display::T as Display;
54 use style::computed_values::float::T as Float;
55 use style::computed_values::overflow_x::T as StyleOverflow;
56 use style::computed_values::position::T as Position;
57 use style::computed_values::text_align::T as TextAlign;
58 use style::context::SharedStyleContext;
59 use style::logical_geometry::{LogicalMargin, LogicalPoint, LogicalRect, LogicalSize, WritingMode};
60 use style::properties::ComputedValues;
61 use style::servo::restyle_damage::ServoRestyleDamage;
62 use style::values::computed::{LengthOrPercentageOrNone, LengthOrPercentage};
63 use style::values::computed::LengthOrPercentageOrAuto;
64 use traversal::PreorderFlowTraversal;
65
66 /// Information specific to floated blocks.
67 #[derive(Clone, Serialize)]
68 pub struct FloatedBlockInfo {
69 /// The amount of inline size that is available for the float.
70 pub containing_inline_size: Au,
71
72 /// The float ceiling, relative to `BaseFlow::position::cur_b` (i.e. the top part of the border
73 /// box).
74 pub float_ceiling: Au,
75
76 /// Left or right?
77 pub float_kind: FloatKind,
78 }
79
80 impl FloatedBlockInfo {
new(float_kind: FloatKind) -> FloatedBlockInfo81 pub fn new(float_kind: FloatKind) -> FloatedBlockInfo {
82 FloatedBlockInfo {
83 containing_inline_size: Au(0),
84 float_ceiling: Au(0),
85 float_kind: float_kind,
86 }
87 }
88 }
89
90 /// The solutions for the block-size-and-margins constraint equation.
91 #[derive(Clone, Copy)]
92 struct BSizeConstraintSolution {
93 block_start: Au,
94 block_size: Au,
95 margin_block_start: Au,
96 margin_block_end: Au
97 }
98
99 impl BSizeConstraintSolution {
new(block_start: Au, block_size: Au, margin_block_start: Au, margin_block_end: Au) -> BSizeConstraintSolution100 fn new(block_start: Au,
101 block_size: Au,
102 margin_block_start: Au,
103 margin_block_end: Au)
104 -> BSizeConstraintSolution {
105 BSizeConstraintSolution {
106 block_start: block_start,
107 block_size: block_size,
108 margin_block_start: margin_block_start,
109 margin_block_end: margin_block_end,
110 }
111 }
112
113 /// Solve the vertical constraint equation for absolute non-replaced elements.
114 ///
115 /// CSS Section 10.6.4
116 /// Constraint equation:
117 /// block-start + block-end + block-size + margin-block-start + margin-block-end
118 /// = absolute containing block block-size - (vertical padding and border)
119 /// [aka available_block-size]
120 ///
121 /// Return the solution for the equation.
solve_vertical_constraints_abs_nonreplaced(block_size: MaybeAuto, block_start_margin: MaybeAuto, block_end_margin: MaybeAuto, block_start: MaybeAuto, block_end: MaybeAuto, content_block_size: Au, available_block_size: Au) -> BSizeConstraintSolution122 fn solve_vertical_constraints_abs_nonreplaced(block_size: MaybeAuto,
123 block_start_margin: MaybeAuto,
124 block_end_margin: MaybeAuto,
125 block_start: MaybeAuto,
126 block_end: MaybeAuto,
127 content_block_size: Au,
128 available_block_size: Au)
129 -> BSizeConstraintSolution {
130 let (block_start, block_size, margin_block_start, margin_block_end) =
131 match (block_start, block_end, block_size) {
132 (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Auto) => {
133 let margin_block_start = block_start_margin.specified_or_zero();
134 let margin_block_end = block_end_margin.specified_or_zero();
135 // Now it is the same situation as block-start Specified and block-end
136 // and block-size Auto.
137 let block_size = content_block_size;
138 // Use a dummy value for `block_start`, since it has the static position.
139 (Au(0), block_size, margin_block_start, margin_block_end)
140 }
141 (MaybeAuto::Specified(block_start),
142 MaybeAuto::Specified(block_end),
143 MaybeAuto::Specified(block_size)) => {
144 match (block_start_margin, block_end_margin) {
145 (MaybeAuto::Auto, MaybeAuto::Auto) => {
146 let total_margin_val =
147 available_block_size - block_start - block_end - block_size;
148 (block_start,
149 block_size,
150 total_margin_val.scale_by(0.5),
151 total_margin_val.scale_by(0.5))
152 }
153 (MaybeAuto::Specified(margin_block_start), MaybeAuto::Auto) => {
154 let sum = block_start + block_end + block_size + margin_block_start;
155 (block_start,
156 block_size,
157 margin_block_start,
158 available_block_size - sum)
159 }
160 (MaybeAuto::Auto, MaybeAuto::Specified(margin_block_end)) => {
161 let sum = block_start + block_end + block_size + margin_block_end;
162 (block_start, block_size, available_block_size - sum, margin_block_end)
163 }
164 (MaybeAuto::Specified(margin_block_start),
165 MaybeAuto::Specified(margin_block_end)) => {
166 // Values are over-constrained. Ignore value for 'block-end'.
167 (block_start, block_size, margin_block_start, margin_block_end)
168 }
169 }
170 }
171
172 // For the rest of the cases, auto values for margin are set to 0
173
174 // If only one is Auto, solve for it
175 (MaybeAuto::Auto,
176 MaybeAuto::Specified(block_end),
177 MaybeAuto::Specified(block_size)) => {
178 let margin_block_start = block_start_margin.specified_or_zero();
179 let margin_block_end = block_end_margin.specified_or_zero();
180 let sum = block_end + block_size + margin_block_start + margin_block_end;
181 (available_block_size - sum, block_size, margin_block_start, margin_block_end)
182 }
183 (MaybeAuto::Specified(block_start),
184 MaybeAuto::Auto,
185 MaybeAuto::Specified(block_size)) => {
186 let margin_block_start = block_start_margin.specified_or_zero();
187 let margin_block_end = block_end_margin.specified_or_zero();
188 (block_start, block_size, margin_block_start, margin_block_end)
189 }
190 (MaybeAuto::Specified(block_start),
191 MaybeAuto::Specified(block_end),
192 MaybeAuto::Auto) => {
193 let margin_block_start = block_start_margin.specified_or_zero();
194 let margin_block_end = block_end_margin.specified_or_zero();
195 let sum = block_start + block_end + margin_block_start + margin_block_end;
196 (block_start, available_block_size - sum, margin_block_start, margin_block_end)
197 }
198
199 // If block-size is auto, then block-size is content block-size. Solve for the
200 // non-auto value.
201 (MaybeAuto::Specified(block_start), MaybeAuto::Auto, MaybeAuto::Auto) => {
202 let margin_block_start = block_start_margin.specified_or_zero();
203 let margin_block_end = block_end_margin.specified_or_zero();
204 let block_size = content_block_size;
205 (block_start, block_size, margin_block_start, margin_block_end)
206 }
207 (MaybeAuto::Auto, MaybeAuto::Specified(block_end), MaybeAuto::Auto) => {
208 let margin_block_start = block_start_margin.specified_or_zero();
209 let margin_block_end = block_end_margin.specified_or_zero();
210 let block_size = content_block_size;
211 let sum = block_end + block_size + margin_block_start + margin_block_end;
212 (available_block_size - sum, block_size, margin_block_start, margin_block_end)
213 }
214
215 (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Specified(block_size)) => {
216 let margin_block_start = block_start_margin.specified_or_zero();
217 let margin_block_end = block_end_margin.specified_or_zero();
218 // Use a dummy value for `block_start`, since it has the static position.
219 (Au(0), block_size, margin_block_start, margin_block_end)
220 }
221 };
222
223 BSizeConstraintSolution::new(block_start, block_size, margin_block_start, margin_block_end)
224 }
225
226 /// Solve the vertical constraint equation for absolute replaced elements.
227 ///
228 /// Assumption: The used value for block-size has already been calculated.
229 ///
230 /// CSS Section 10.6.5
231 /// Constraint equation:
232 /// block-start + block-end + block-size + margin-block-start + margin-block-end
233 /// = absolute containing block block-size - (vertical padding and border)
234 /// [aka available block-size]
235 ///
236 /// Return the solution for the equation.
solve_vertical_constraints_abs_replaced(block_size: Au, block_start_margin: MaybeAuto, block_end_margin: MaybeAuto, block_start: MaybeAuto, block_end: MaybeAuto, _: Au, available_block_size: Au) -> BSizeConstraintSolution237 fn solve_vertical_constraints_abs_replaced(block_size: Au,
238 block_start_margin: MaybeAuto,
239 block_end_margin: MaybeAuto,
240 block_start: MaybeAuto,
241 block_end: MaybeAuto,
242 _: Au,
243 available_block_size: Au)
244 -> BSizeConstraintSolution {
245 let (block_start, block_size, margin_block_start, margin_block_end) =
246 match (block_start, block_end) {
247 (MaybeAuto::Auto, MaybeAuto::Auto) => {
248 let margin_block_start = block_start_margin.specified_or_zero();
249 let margin_block_end = block_end_margin.specified_or_zero();
250 // Use a dummy value for `block_start`, since it has the static position.
251 (Au(0), block_size, margin_block_start, margin_block_end)
252 }
253 (MaybeAuto::Specified(block_start), MaybeAuto::Specified(block_end)) => {
254 match (block_start_margin, block_end_margin) {
255 (MaybeAuto::Auto, MaybeAuto::Auto) => {
256 let total_margin_val = available_block_size - block_start - block_end -
257 block_size;
258 (block_start,
259 block_size,
260 total_margin_val.scale_by(0.5),
261 total_margin_val.scale_by(0.5))
262 }
263 (MaybeAuto::Specified(margin_block_start), MaybeAuto::Auto) => {
264 let sum = block_start + block_end + block_size + margin_block_start;
265 (block_start,
266 block_size,
267 margin_block_start,
268 available_block_size - sum)
269 }
270 (MaybeAuto::Auto, MaybeAuto::Specified(margin_block_end)) => {
271 let sum = block_start + block_end + block_size + margin_block_end;
272 (block_start, block_size, available_block_size - sum, margin_block_end)
273 }
274 (MaybeAuto::Specified(margin_block_start),
275 MaybeAuto::Specified(margin_block_end)) => {
276 // Values are over-constrained. Ignore value for 'block-end'.
277 (block_start, block_size, margin_block_start, margin_block_end)
278 }
279 }
280 }
281
282 // If only one is Auto, solve for it
283 (MaybeAuto::Auto, MaybeAuto::Specified(block_end)) => {
284 let margin_block_start = block_start_margin.specified_or_zero();
285 let margin_block_end = block_end_margin.specified_or_zero();
286 let sum = block_end + block_size + margin_block_start + margin_block_end;
287 (available_block_size - sum, block_size, margin_block_start, margin_block_end)
288 }
289 (MaybeAuto::Specified(block_start), MaybeAuto::Auto) => {
290 let margin_block_start = block_start_margin.specified_or_zero();
291 let margin_block_end = block_end_margin.specified_or_zero();
292 (block_start, block_size, margin_block_start, margin_block_end)
293 }
294 };
295 BSizeConstraintSolution::new(block_start, block_size, margin_block_start, margin_block_end)
296 }
297 }
298
299 /// Performs block-size calculations potentially multiple times, taking
300 /// (assuming an horizontal writing mode) `height`, `min-height`, and `max-height`
301 /// into account. After each call to `next()`, the caller must call `.try()` with the
302 /// current calculated value of `height`.
303 ///
304 /// See CSS 2.1 § 10.7.
305 pub struct CandidateBSizeIterator {
306 block_size: MaybeAuto,
307 max_block_size: Option<Au>,
308 min_block_size: Au,
309 pub candidate_value: Au,
310 status: CandidateBSizeIteratorStatus,
311 }
312
313 impl CandidateBSizeIterator {
314 /// Creates a new candidate block-size iterator. `block_container_block-size` is `None` if the block-size
315 /// of the block container has not been determined yet. It will always be `Some` in the case of
316 /// absolutely-positioned containing blocks.
new(fragment: &Fragment, block_container_block_size: Option<Au>) -> CandidateBSizeIterator317 pub fn new(fragment: &Fragment, block_container_block_size: Option<Au>)
318 -> CandidateBSizeIterator {
319 // Per CSS 2.1 § 10.7, (assuming an horizontal writing mode,)
320 // percentages in `min-height` and `max-height` refer to the height of
321 // the containing block.
322 // If that is not determined yet by the time we need to resolve
323 // `min-height` and `max-height`, percentage values are ignored.
324
325 let block_size = match (fragment.style.content_block_size(), block_container_block_size) {
326 (LengthOrPercentageOrAuto::Percentage(percent), Some(block_container_block_size)) => {
327 MaybeAuto::Specified(block_container_block_size.scale_by(percent.0))
328 }
329 (LengthOrPercentageOrAuto::Calc(calc), _) => {
330 MaybeAuto::from_option(calc.to_used_value(block_container_block_size))
331 }
332 (LengthOrPercentageOrAuto::Percentage(_), None) |
333 (LengthOrPercentageOrAuto::Auto, _) => MaybeAuto::Auto,
334 (LengthOrPercentageOrAuto::Length(length), _) => MaybeAuto::Specified(Au::from(length)),
335 };
336 let max_block_size = match (fragment.style.max_block_size(), block_container_block_size) {
337 (LengthOrPercentageOrNone::Percentage(percent), Some(block_container_block_size)) => {
338 Some(block_container_block_size.scale_by(percent.0))
339 }
340 (LengthOrPercentageOrNone::Calc(calc), _) => {
341 calc.to_used_value(block_container_block_size)
342 }
343 (LengthOrPercentageOrNone::Percentage(_), None) |
344 (LengthOrPercentageOrNone::None, _) => None,
345 (LengthOrPercentageOrNone::Length(length), _) => Some(Au::from(length)),
346 };
347 let min_block_size = match (fragment.style.min_block_size(), block_container_block_size) {
348 (LengthOrPercentage::Percentage(percent), Some(block_container_block_size)) => {
349 block_container_block_size.scale_by(percent.0)
350 }
351 (LengthOrPercentage::Calc(calc), _) => {
352 calc.to_used_value(block_container_block_size).unwrap_or(Au(0))
353 }
354 (LengthOrPercentage::Percentage(_), None) => Au(0),
355 (LengthOrPercentage::Length(length), _) => Au::from(length),
356 };
357
358 // If the style includes `box-sizing: border-box`, subtract the border and padding.
359 let adjustment_for_box_sizing = match fragment.style.get_position().box_sizing {
360 BoxSizing::BorderBox => fragment.border_padding.block_start_end(),
361 BoxSizing::ContentBox => Au(0),
362 };
363
364 return CandidateBSizeIterator {
365 block_size: block_size.map(|size| adjust(size, adjustment_for_box_sizing)),
366 max_block_size: max_block_size.map(|size| adjust(size, adjustment_for_box_sizing)),
367 min_block_size: adjust(min_block_size, adjustment_for_box_sizing),
368 candidate_value: Au(0),
369 status: CandidateBSizeIteratorStatus::Initial,
370 };
371
372 fn adjust(size: Au, delta: Au) -> Au {
373 max(size - delta, Au(0))
374 }
375 }
376 }
377
378 impl Iterator for CandidateBSizeIterator {
379 type Item = MaybeAuto;
next(&mut self) -> Option<MaybeAuto>380 fn next(&mut self) -> Option<MaybeAuto> {
381 self.status = match self.status {
382 CandidateBSizeIteratorStatus::Initial => CandidateBSizeIteratorStatus::Trying,
383 CandidateBSizeIteratorStatus::Trying => {
384 match self.max_block_size {
385 Some(max_block_size) if self.candidate_value > max_block_size => {
386 CandidateBSizeIteratorStatus::TryingMax
387 }
388 _ if self.candidate_value < self.min_block_size => {
389 CandidateBSizeIteratorStatus::TryingMin
390 }
391 _ => CandidateBSizeIteratorStatus::Found,
392 }
393 }
394 CandidateBSizeIteratorStatus::TryingMax => {
395 if self.candidate_value < self.min_block_size {
396 CandidateBSizeIteratorStatus::TryingMin
397 } else {
398 CandidateBSizeIteratorStatus::Found
399 }
400 }
401 CandidateBSizeIteratorStatus::TryingMin | CandidateBSizeIteratorStatus::Found => {
402 CandidateBSizeIteratorStatus::Found
403 }
404 };
405
406 match self.status {
407 CandidateBSizeIteratorStatus::Trying => Some(self.block_size),
408 CandidateBSizeIteratorStatus::TryingMax => {
409 Some(MaybeAuto::Specified(self.max_block_size.unwrap()))
410 }
411 CandidateBSizeIteratorStatus::TryingMin => {
412 Some(MaybeAuto::Specified(self.min_block_size))
413 }
414 CandidateBSizeIteratorStatus::Found => None,
415 CandidateBSizeIteratorStatus::Initial => panic!(),
416 }
417 }
418 }
419
420 enum CandidateBSizeIteratorStatus {
421 Initial,
422 Trying,
423 TryingMax,
424 TryingMin,
425 Found,
426 }
427
428 // A helper function used in block-size calculation.
translate_including_floats(cur_b: &mut Au, delta: Au, floats: &mut Floats)429 fn translate_including_floats(cur_b: &mut Au, delta: Au, floats: &mut Floats) {
430 *cur_b = *cur_b + delta;
431 let writing_mode = floats.writing_mode;
432 floats.translate(LogicalSize::new(writing_mode, Au(0), -delta));
433 }
434
435 /// The real assign-block-sizes traversal for flows with position 'absolute'.
436 ///
437 /// This is a traversal of an Absolute Flow tree.
438 /// - Relatively positioned flows and the Root flow start new Absolute flow trees.
439 /// - The kids of a flow in this tree will be the flows for which it is the
440 /// absolute Containing Block.
441 /// - Thus, leaf nodes and inner non-root nodes are all Absolute Flows.
442 ///
443 /// A Flow tree can have several Absolute Flow trees (depending on the number
444 /// of relatively positioned flows it has).
445 ///
446 /// Note that flows with position 'fixed' just form a flat list as they all
447 /// have the Root flow as their CB.
448 pub struct AbsoluteAssignBSizesTraversal<'a>(pub &'a SharedStyleContext<'a>);
449
450 impl<'a> PreorderFlowTraversal for AbsoluteAssignBSizesTraversal<'a> {
451 #[inline]
process(&self, flow: &mut Flow)452 fn process(&self, flow: &mut Flow) {
453 if !flow.is_block_like() {
454 return
455 }
456
457 // This flow might not be an absolutely positioned flow if it is the root of the tree.
458 let block = flow.as_mut_block();
459 if !block.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) {
460 return;
461 }
462
463 if !block.base.restyle_damage.intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW) {
464 return
465 }
466
467 block.calculate_absolute_block_size_and_margins(self.0);
468 }
469 }
470
471 pub enum BlockType {
472 Replaced,
473 NonReplaced,
474 AbsoluteReplaced,
475 AbsoluteNonReplaced,
476 FloatReplaced,
477 FloatNonReplaced,
478 InlineBlockReplaced,
479 InlineBlockNonReplaced,
480 InlineFlexItem,
481 }
482
483 #[derive(Clone, PartialEq)]
484 pub enum MarginsMayCollapseFlag {
485 MarginsMayCollapse,
486 MarginsMayNotCollapse,
487 }
488
489 #[derive(Debug, PartialEq)]
490 pub enum FormattingContextType {
491 None,
492 Block,
493 Other,
494 }
495
496 #[allow(unsafe_code)]
497 unsafe impl ::flow::HasBaseFlow for BlockFlow {}
498
499 // A block formatting context.
500 #[derive(Serialize)]
501 #[repr(C)]
502 pub struct BlockFlow {
503 /// Data common to all flows.
504 pub base: BaseFlow,
505
506 /// The associated fragment.
507 pub fragment: Fragment,
508
509 /// Additional floating flow members.
510 pub float: Option<Box<FloatedBlockInfo>>,
511
512 /// Various flags.
513 flags: BlockFlowFlags,
514 }
515
516 bitflags! {
517 struct BlockFlowFlags: u8 {
518 #[doc = "If this is set, then this block flow is the root flow."]
519 const IS_ROOT = 0b0000_0001;
520 #[doc = "If this is set, then this block flow has overflow and it will scroll."]
521 const HAS_SCROLLING_OVERFLOW = 0b0000_0010;
522 }
523 }
524
525 impl Serialize for BlockFlowFlags {
serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>526 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
527 self.bits().serialize(serializer)
528 }
529 }
530
531 impl BlockFlow {
from_fragment(fragment: Fragment) -> BlockFlow532 pub fn from_fragment(fragment: Fragment) -> BlockFlow {
533 BlockFlow::from_fragment_and_float_kind(fragment, None)
534 }
535
from_fragment_and_float_kind(fragment: Fragment, float_kind: Option<FloatKind>) -> BlockFlow536 pub fn from_fragment_and_float_kind(fragment: Fragment, float_kind: Option<FloatKind>)
537 -> BlockFlow {
538 let writing_mode = fragment.style().writing_mode;
539 BlockFlow {
540 base: BaseFlow::new(Some(fragment.style()), writing_mode, match float_kind {
541 Some(_) => ForceNonfloatedFlag::FloatIfNecessary,
542 None => ForceNonfloatedFlag::ForceNonfloated,
543 }),
544 fragment: fragment,
545 float: float_kind.map(|kind| Box::new(FloatedBlockInfo::new(kind))),
546 flags: BlockFlowFlags::empty(),
547 }
548 }
549
550 /// Return the type of this block.
551 ///
552 /// This determines the algorithm used to calculate inline-size, block-size, and the
553 /// relevant margins for this Block.
block_type(&self) -> BlockType554 pub fn block_type(&self) -> BlockType {
555 if self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) {
556 if self.fragment.is_replaced() {
557 BlockType::AbsoluteReplaced
558 } else {
559 BlockType::AbsoluteNonReplaced
560 }
561 } else if self.is_inline_flex_item() {
562 BlockType::InlineFlexItem
563 } else if self.base.flags.is_float() {
564 if self.fragment.is_replaced() {
565 BlockType::FloatReplaced
566 } else {
567 BlockType::FloatNonReplaced
568 }
569 } else if self.is_inline_block_or_inline_flex() {
570 if self.fragment.is_replaced() {
571 BlockType::InlineBlockReplaced
572 } else {
573 BlockType::InlineBlockNonReplaced
574 }
575 } else {
576 if self.fragment.is_replaced() {
577 BlockType::Replaced
578 } else {
579 BlockType::NonReplaced
580 }
581 }
582 }
583
584 /// Compute the actual inline size and position for this block.
compute_used_inline_size(&mut self, shared_context: &SharedStyleContext, containing_block_inline_size: Au)585 pub fn compute_used_inline_size(&mut self,
586 shared_context: &SharedStyleContext,
587 containing_block_inline_size: Au) {
588 let block_type = self.block_type();
589 match block_type {
590 BlockType::AbsoluteReplaced => {
591 let inline_size_computer = AbsoluteReplaced;
592 inline_size_computer.compute_used_inline_size(self,
593 shared_context,
594 containing_block_inline_size);
595 }
596 BlockType::AbsoluteNonReplaced => {
597 let inline_size_computer = AbsoluteNonReplaced;
598 inline_size_computer.compute_used_inline_size(self,
599 shared_context,
600 containing_block_inline_size);
601 }
602 BlockType::FloatReplaced => {
603 let inline_size_computer = FloatReplaced;
604 inline_size_computer.compute_used_inline_size(self,
605 shared_context,
606 containing_block_inline_size);
607 }
608 BlockType::FloatNonReplaced => {
609 let inline_size_computer = FloatNonReplaced;
610 inline_size_computer.compute_used_inline_size(self,
611 shared_context,
612 containing_block_inline_size);
613 }
614 BlockType::InlineBlockReplaced => {
615 let inline_size_computer = InlineBlockReplaced;
616 inline_size_computer.compute_used_inline_size(self,
617 shared_context,
618 containing_block_inline_size);
619 }
620 BlockType::InlineBlockNonReplaced => {
621 let inline_size_computer = InlineBlockNonReplaced;
622 inline_size_computer.compute_used_inline_size(self,
623 shared_context,
624 containing_block_inline_size);
625 }
626 BlockType::Replaced => {
627 let inline_size_computer = BlockReplaced;
628 inline_size_computer.compute_used_inline_size(self,
629 shared_context,
630 containing_block_inline_size);
631 }
632 BlockType::NonReplaced => {
633 let inline_size_computer = BlockNonReplaced;
634 inline_size_computer.compute_used_inline_size(self,
635 shared_context,
636 containing_block_inline_size);
637 }
638 BlockType::InlineFlexItem => {
639 let inline_size_computer = InlineFlexItem;
640 inline_size_computer.compute_used_inline_size(self,
641 shared_context,
642 containing_block_inline_size);
643 }
644 }
645 }
646
647 /// Return this flow's fragment.
fragment(&mut self) -> &mut Fragment648 pub fn fragment(&mut self) -> &mut Fragment {
649 &mut self.fragment
650 }
651
stacking_relative_border_box(&self, coor: CoordinateSystem) -> Rect<Au>652 pub fn stacking_relative_border_box(&self, coor: CoordinateSystem) -> Rect<Au> {
653 return self.fragment.stacking_relative_border_box(
654 &self.base.stacking_relative_position,
655 &self.base.early_absolute_position_info.relative_containing_block_size,
656 self.base.early_absolute_position_info.relative_containing_block_mode,
657 coor);
658 }
659
660 /// Return the size of the containing block for the given immediate absolute descendant of this
661 /// flow.
662 ///
663 /// Right now, this only gets the containing block size for absolutely positioned elements.
664 /// Note: We assume this is called in a top-down traversal, so it is ok to reference the CB.
665 #[inline]
containing_block_size(&self, viewport_size: &Size2D<Au>, descendant: OpaqueFlow) -> LogicalSize<Au>666 pub fn containing_block_size(&self, viewport_size: &Size2D<Au>, descendant: OpaqueFlow)
667 -> LogicalSize<Au> {
668 debug_assert!(self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED));
669 if self.is_fixed() || self.is_root() {
670 // Initial containing block is the CB for the root
671 LogicalSize::from_physical(self.base.writing_mode, *viewport_size)
672 } else {
673 self.base.absolute_cb.generated_containing_block_size(descendant)
674 }
675 }
676
677 /// Return shrink-to-fit inline-size.
678 ///
679 /// This is where we use the preferred inline-sizes and minimum inline-sizes
680 /// calculated in the bubble-inline-sizes traversal.
get_shrink_to_fit_inline_size(&self, available_inline_size: Au) -> Au681 pub fn get_shrink_to_fit_inline_size(&self, available_inline_size: Au) -> Au {
682 let content_intrinsic_inline_sizes = self.content_intrinsic_inline_sizes();
683 min(content_intrinsic_inline_sizes.preferred_inline_size,
684 max(content_intrinsic_inline_sizes.minimum_inline_size, available_inline_size))
685 }
686
687 /// If this is the root flow, shifts all kids down and adjusts our size to account for
688 /// root flow margins, which should never be collapsed according to CSS § 8.3.1.
689 ///
690 /// TODO(#2017, pcwalton): This is somewhat inefficient (traverses kids twice); can we do
691 /// better?
adjust_fragments_for_collapsed_margins_if_root(&mut self, shared_context: &SharedStyleContext)692 fn adjust_fragments_for_collapsed_margins_if_root(&mut self,
693 shared_context: &SharedStyleContext) {
694 if !self.is_root() {
695 return
696 }
697
698 let (block_start_margin_value, block_end_margin_value) =
699 match self.base.collapsible_margins {
700 CollapsibleMargins::CollapseThrough(_) => {
701 panic!("Margins unexpectedly collapsed through root flow.")
702 }
703 CollapsibleMargins::Collapse(block_start_margin, block_end_margin) => {
704 (block_start_margin.collapse(), block_end_margin.collapse())
705 }
706 CollapsibleMargins::None(block_start, block_end) => (block_start, block_end),
707 };
708
709 // Shift all kids down (or up, if margins are negative) if necessary.
710 if block_start_margin_value != Au(0) {
711 for kid in self.base.child_iter_mut() {
712 let kid_base = kid.mut_base();
713 kid_base.position.start.b = kid_base.position.start.b + block_start_margin_value
714 }
715 }
716
717 // FIXME(#2003, pcwalton): The max is taken here so that you can scroll the page, but this
718 // is not correct behavior according to CSS 2.1 § 10.5. Instead I think we should treat the
719 // root element as having `overflow: scroll` and use the layers-based scrolling
720 // infrastructure to make it scrollable.
721 let viewport_size =
722 LogicalSize::from_physical(self.fragment.style.writing_mode,
723 shared_context.viewport_size());
724 let block_size = max(viewport_size.block,
725 self.fragment.border_box.size.block + block_start_margin_value +
726 block_end_margin_value);
727
728 self.base.position.size.block = block_size;
729 self.fragment.border_box.size.block = block_size;
730 }
731
732 // FIXME: Record enough info to deal with fragmented decorations.
733 // See https://drafts.csswg.org/css-break/#break-decoration
734 // For borders, this might be `enum FragmentPosition { First, Middle, Last }`
clone_with_children(&self, new_children: FlowList) -> BlockFlow735 fn clone_with_children(&self, new_children: FlowList) -> BlockFlow {
736 BlockFlow {
737 base: self.base.clone_with_children(new_children),
738 fragment: self.fragment.clone(),
739 float: self.float.clone(),
740 ..*self
741 }
742 }
743
744 /// Writes in the size of the relative containing block for children. (This information
745 /// is also needed to handle RTL.)
propagate_early_absolute_position_info_to_children(&mut self)746 fn propagate_early_absolute_position_info_to_children(&mut self) {
747 for kid in self.base.child_iter_mut() {
748 kid.mut_base().early_absolute_position_info = EarlyAbsolutePositionInfo {
749 relative_containing_block_size: self.fragment.content_box().size,
750 relative_containing_block_mode: self.fragment.style().writing_mode,
751 }
752 }
753 }
754
755 /// Assign block-size for current flow.
756 ///
757 /// * Collapse margins for flow's children and set in-flow child flows' block offsets now that
758 /// we know their block-sizes.
759 /// * Calculate and set the block-size of the current flow.
760 /// * Calculate block-size, vertical margins, and block offset for the flow's box using CSS §
761 /// 10.6.7.
762 ///
763 /// For absolute flows, we store the calculated content block-size for the flow. We defer the
764 /// calculation of the other values until a later traversal.
765 ///
766 /// When `fragmentation_context` is given (not `None`), this should fit as much of the content
767 /// as possible within the available block size.
768 /// If there is more content (that doesn’t fit), this flow is *fragmented*
769 /// with the extra content moved to another fragment (a flow like this one) which is returned.
770 /// See `Flow::fragment`.
771 ///
772 /// The return value is always `None` when `fragmentation_context` is `None`.
773 ///
774 /// `inline(always)` because this is only ever called by in-order or non-in-order top-level
775 /// methods.
776 #[inline(always)]
assign_block_size_block_base(&mut self, layout_context: &LayoutContext, mut fragmentation_context: Option<FragmentationContext>, margins_may_collapse: MarginsMayCollapseFlag) -> Option<Arc<Flow>>777 pub fn assign_block_size_block_base(&mut self,
778 layout_context: &LayoutContext,
779 mut fragmentation_context: Option<FragmentationContext>,
780 margins_may_collapse: MarginsMayCollapseFlag)
781 -> Option<Arc<Flow>> {
782 let _scope = layout_debug_scope!("assign_block_size_block_base {:x}",
783 self.base.debug_id());
784
785 let mut break_at = None;
786 let content_box = self.fragment.content_box();
787 if self.base.restyle_damage.contains(ServoRestyleDamage::REFLOW) {
788 // Our current border-box position.
789 let mut cur_b = Au(0);
790
791 // Absolute positioning establishes a block formatting context. Don't propagate floats
792 // in or out. (But do propagate them between kids.)
793 if self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) ||
794 margins_may_collapse != MarginsMayCollapseFlag::MarginsMayCollapse {
795 self.base.floats = Floats::new(self.fragment.style.writing_mode);
796 }
797
798 let writing_mode = self.base.floats.writing_mode;
799 self.base.floats.translate(LogicalSize::new(
800 writing_mode, -self.fragment.inline_start_offset(), Au(0)));
801
802 // The sum of our block-start border and block-start padding.
803 let block_start_offset = self.fragment.border_padding.block_start;
804 translate_including_floats(&mut cur_b, block_start_offset, &mut self.base.floats);
805
806 let can_collapse_block_start_margin_with_kids =
807 margins_may_collapse == MarginsMayCollapseFlag::MarginsMayCollapse &&
808 !self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) &&
809 self.fragment.border_padding.block_start == Au(0);
810 let mut margin_collapse_info = MarginCollapseInfo::initialize_block_start_margin(
811 &self.fragment,
812 can_collapse_block_start_margin_with_kids);
813
814 // At this point, `cur_b` is at the content edge of our box. Now iterate over children.
815 let mut floats = self.base.floats.clone();
816 let thread_id = self.base.thread_id;
817 let (mut had_floated_children, mut had_children_with_clearance) = (false, false);
818 for (child_index, kid) in self.base.child_iter_mut().enumerate() {
819 if kid.base().flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) {
820 // Assume that the *hypothetical box* for an absolute flow starts immediately
821 // after the margin-end border edge of the previous flow.
822 if kid.base().flags.contains(FlowFlags::BLOCK_POSITION_IS_STATIC) {
823 let previous_bottom_margin = margin_collapse_info.current_float_ceiling();
824
825 kid.mut_base().position.start.b = cur_b +
826 kid.base().collapsible_margins
827 .block_start_margin_for_noncollapsible_context() +
828 previous_bottom_margin
829 }
830 kid.place_float_if_applicable();
831 if !kid.base().flags.is_float() {
832 kid.assign_block_size_for_inorder_child_if_necessary(layout_context,
833 thread_id,
834 content_box);
835 }
836
837 // Skip the collapsing and float processing for absolute flow kids and continue
838 // with the next flow.
839 continue
840 }
841
842 let previous_b = cur_b;
843 if let Some(ctx) = fragmentation_context {
844 let child_ctx = FragmentationContext {
845 available_block_size: ctx.available_block_size - cur_b,
846 this_fragment_is_empty: ctx.this_fragment_is_empty,
847 };
848 if let Some(remaining) = kid.fragment(layout_context, Some(child_ctx)) {
849 break_at = Some((child_index + 1, Some(remaining)));
850 }
851 }
852
853 // Assign block-size now for the child if it might have floats in and we couldn't
854 // before.
855 kid.mut_base().floats = floats.clone();
856 if kid.base().flags.is_float() {
857 had_floated_children = true;
858 kid.mut_base().position.start.b = cur_b;
859 {
860 let kid_block = kid.as_mut_block();
861 let float_ceiling = margin_collapse_info.current_float_ceiling();
862 kid_block.float.as_mut().unwrap().float_ceiling = float_ceiling
863 }
864 kid.place_float_if_applicable();
865
866 let kid_base = kid.mut_base();
867 floats = kid_base.floats.clone();
868 continue
869 }
870
871 // If we have clearance, assume there are no floats in.
872 //
873 // FIXME(#2008, pcwalton): This could be wrong if we have `clear: left` or `clear:
874 // right` and there are still floats to impact, of course. But this gets
875 // complicated with margin collapse. Possibly the right thing to do is to lay out
876 // the block again in this rare case. (Note that WebKit can lay blocks out twice;
877 // this may be related, although I haven't looked into it closely.)
878 if kid.base().flags.clears_floats() {
879 kid.mut_base().floats = Floats::new(self.fragment.style.writing_mode)
880 }
881
882 // Lay the child out if this was an in-order traversal.
883 let need_to_process_child_floats =
884 kid.assign_block_size_for_inorder_child_if_necessary(layout_context,
885 thread_id,
886 content_box);
887
888 if !had_children_with_clearance &&
889 floats.is_present() &&
890 (kid.base().flags.contains(FlowFlags::CLEARS_LEFT) ||
891 kid.base().flags.contains(FlowFlags::CLEARS_RIGHT)) {
892 had_children_with_clearance = true
893 }
894
895 // Handle any (possibly collapsed) top margin.
896 let delta = margin_collapse_info.advance_block_start_margin(
897 &kid.base().collapsible_margins,
898 !had_children_with_clearance);
899 translate_including_floats(&mut cur_b, delta, &mut floats);
900
901 // Collapse-through margins should be placed at the top edge,
902 // so we'll handle the delta after the bottom margin is processed
903 if let CollapsibleMargins::CollapseThrough(_) = kid.base().collapsible_margins {
904 cur_b = cur_b - delta;
905 }
906
907 // Clear past the floats that came in, if necessary.
908 let clearance = match (kid.base().flags.contains(FlowFlags::CLEARS_LEFT),
909 kid.base().flags.contains(FlowFlags::CLEARS_RIGHT)) {
910 (false, false) => Au(0),
911 (true, false) => floats.clearance(ClearType::Left),
912 (false, true) => floats.clearance(ClearType::Right),
913 (true, true) => floats.clearance(ClearType::Both),
914 };
915 translate_including_floats(&mut cur_b, clearance, &mut floats);
916
917 // At this point, `cur_b` is at the border edge of the child.
918 kid.mut_base().position.start.b = cur_b;
919
920 // Now pull out the child's outgoing floats. We didn't do this immediately after
921 // the `assign_block_size_for_inorder_child_if_necessary` call because clearance on
922 // a block operates on the floats that come *in*, not the floats that go *out*.
923 if need_to_process_child_floats {
924 floats = kid.mut_base().floats.clone()
925 }
926
927 // Move past the child's border box. Do not use the `translate_including_floats`
928 // function here because the child has already translated floats past its border
929 // box.
930 let kid_base = kid.mut_base();
931 cur_b = cur_b + kid_base.position.size.block;
932
933 // Handle any (possibly collapsed) block-end margin.
934 let delta =
935 margin_collapse_info.advance_block_end_margin(&kid_base.collapsible_margins);
936 translate_including_floats(&mut cur_b, delta, &mut floats);
937
938 // Collapse-through margin should be placed at the top edge of the flow.
939 let collapse_delta = match kid_base.collapsible_margins {
940 CollapsibleMargins::CollapseThrough(_) => {
941 let delta = margin_collapse_info.current_float_ceiling();
942 cur_b = cur_b + delta;
943 kid_base.position.start.b = kid_base.position.start.b + delta;
944 delta
945 }
946 _ => Au(0)
947 };
948
949 if break_at.is_some() {
950 break
951 }
952
953 if let Some(ref mut ctx) = fragmentation_context {
954 if cur_b > ctx.available_block_size && !ctx.this_fragment_is_empty {
955 break_at = Some((child_index, None));
956 cur_b = previous_b;
957 break
958 }
959 ctx.this_fragment_is_empty = false
960 }
961
962 // For consecutive collapse-through flows, their top margin should be calculated
963 // from the same baseline.
964 cur_b = cur_b - collapse_delta;
965 }
966
967 // Add in our block-end margin and compute our collapsible margins.
968 let can_collapse_block_end_margin_with_kids =
969 margins_may_collapse == MarginsMayCollapseFlag::MarginsMayCollapse &&
970 !self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) &&
971 self.fragment.border_padding.block_end == Au(0);
972 let (collapsible_margins, delta) =
973 margin_collapse_info.finish_and_compute_collapsible_margins(
974 &self.fragment,
975 self.base.block_container_explicit_block_size,
976 can_collapse_block_end_margin_with_kids,
977 !had_floated_children);
978 self.base.collapsible_margins = collapsible_margins;
979 translate_including_floats(&mut cur_b, delta, &mut floats);
980
981 let mut block_size = cur_b - block_start_offset;
982 let is_root = self.is_root();
983
984 if is_root || self.formatting_context_type() != FormattingContextType::None ||
985 self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) {
986 // The content block-size includes all the floats per CSS 2.1 § 10.6.7. The easiest
987 // way to handle this is to just treat it as clearance.
988 block_size = block_size + floats.clearance(ClearType::Both);
989 }
990
991 if self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) {
992 // FIXME(#2003, pcwalton): The max is taken here so that you can scroll the page,
993 // but this is not correct behavior according to CSS 2.1 § 10.5. Instead I think we
994 // should treat the root element as having `overflow: scroll` and use the layers-
995 // based scrolling infrastructure to make it scrollable.
996 if is_root {
997 let viewport_size =
998 LogicalSize::from_physical(self.fragment.style.writing_mode,
999 layout_context.shared_context().viewport_size());
1000 block_size = max(viewport_size.block, block_size)
1001 }
1002
1003 // Store the content block-size for use in calculating the absolute flow's
1004 // dimensions later.
1005 //
1006 // FIXME(pcwalton): This looks not idempotent. Is it?
1007 self.fragment.border_box.size.block = block_size;
1008 }
1009
1010
1011 if self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) {
1012 self.propagate_early_absolute_position_info_to_children();
1013 return None
1014 }
1015
1016 // Compute any explicitly-specified block size.
1017 // Can't use `for` because we assign to `candidate_block_size_iterator.candidate_value`.
1018 let mut candidate_block_size_iterator = CandidateBSizeIterator::new(
1019 &self.fragment,
1020 self.base.block_container_explicit_block_size);
1021 while let Some(candidate_block_size) = candidate_block_size_iterator.next() {
1022 candidate_block_size_iterator.candidate_value =
1023 match candidate_block_size {
1024 MaybeAuto::Auto => block_size,
1025 MaybeAuto::Specified(value) => value
1026 }
1027 }
1028
1029 // Adjust `cur_b` as necessary to account for the explicitly-specified block-size.
1030 block_size = candidate_block_size_iterator.candidate_value;
1031 let delta = block_size - (cur_b - block_start_offset);
1032 translate_including_floats(&mut cur_b, delta, &mut floats);
1033
1034 // Take border and padding into account.
1035 let block_end_offset = self.fragment.border_padding.block_end;
1036 translate_including_floats(&mut cur_b, block_end_offset, &mut floats);
1037
1038 // Now that `cur_b` is at the block-end of the border box, compute the final border box
1039 // position.
1040 self.fragment.border_box.size.block = cur_b;
1041 self.fragment.border_box.start.b = Au(0);
1042 self.base.position.size.block = cur_b;
1043
1044 self.propagate_early_absolute_position_info_to_children();
1045
1046 // Translate the current set of floats back into the parent coordinate system in the
1047 // inline direction, and store them in the flow so that flows that come later in the
1048 // document can access them.
1049 floats.translate(LogicalSize::new(writing_mode,
1050 self.fragment.inline_start_offset(),
1051 Au(0)));
1052 self.base.floats = floats.clone();
1053 self.adjust_fragments_for_collapsed_margins_if_root(layout_context.shared_context());
1054 } else {
1055 // We don't need to reflow, but we still need to perform in-order traversals if
1056 // necessary.
1057 let thread_id = self.base.thread_id;
1058 for kid in self.base.child_iter_mut() {
1059 kid.assign_block_size_for_inorder_child_if_necessary(layout_context,
1060 thread_id,
1061 content_box);
1062 }
1063 }
1064
1065 if (&*self as &Flow).contains_roots_of_absolute_flow_tree() {
1066 // Assign block-sizes for all flows in this absolute flow tree.
1067 // This is preorder because the block-size of an absolute flow may depend on
1068 // the block-size of its containing block, which may also be an absolute flow.
1069 let assign_abs_b_sizes = AbsoluteAssignBSizesTraversal(layout_context.shared_context());
1070 assign_abs_b_sizes.traverse_absolute_flows(&mut *self);
1071 }
1072
1073 // Don't remove the dirty bits yet if we're absolutely-positioned, since our final size
1074 // has not been calculated yet. (See `calculate_absolute_block_size_and_margins` for that.)
1075 // Also don't remove the dirty bits if we're a block formatting context since our inline
1076 // size has not yet been computed. (See `assign_inline_position_for_formatting_context()`.)
1077 if (self.base.flags.is_float() ||
1078 self.formatting_context_type() == FormattingContextType::None) &&
1079 !self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) {
1080 self.base.restyle_damage.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
1081 self.fragment.restyle_damage.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
1082 }
1083
1084 break_at.and_then(|(i, child_remaining)| {
1085 if i == self.base.children.len() && child_remaining.is_none() {
1086 None
1087 } else {
1088 let mut children = self.base.children.split_off(i);
1089 if let Some(child) = child_remaining {
1090 children.push_front_arc(child);
1091 }
1092 Some(Arc::new(self.clone_with_children(children)) as Arc<Flow>)
1093 }
1094 })
1095 }
1096
1097 /// Add placement information about current float flow for use by the parent.
1098 ///
1099 /// Also, use information given by parent about other floats to find out our relative position.
1100 ///
1101 /// This does not give any information about any float descendants because they do not affect
1102 /// elements outside of the subtree rooted at this float.
1103 ///
1104 /// This function is called on a kid flow by a parent. Therefore, `assign_block_size_float` was
1105 /// already called on this kid flow by the traversal function. So, the values used are
1106 /// well-defined.
place_float(&mut self)1107 pub fn place_float(&mut self) {
1108 let block_size = self.fragment.border_box.size.block;
1109 let clearance = match self.fragment.clear() {
1110 None => Au(0),
1111 Some(clear) => self.base.floats.clearance(clear),
1112 };
1113
1114 let float_info: FloatedBlockInfo = (**self.float.as_ref().unwrap()).clone();
1115
1116 // Our `position` field accounts for positive margins, but not negative margins. (See
1117 // calculation of `extra_inline_size_from_margin` below.) Negative margins must be taken
1118 // into account for float placement, however. So we add them in here.
1119 let inline_size_for_float_placement = self.base.position.size.inline +
1120 min(Au(0), self.fragment.margin.inline_start_end());
1121
1122 let info = PlacementInfo {
1123 size: LogicalSize::new(
1124 self.fragment.style.writing_mode,
1125 inline_size_for_float_placement,
1126 block_size + self.fragment.margin.block_start_end())
1127 .convert(self.fragment.style.writing_mode, self.base.floats.writing_mode),
1128 ceiling: clearance + float_info.float_ceiling,
1129 max_inline_size: float_info.containing_inline_size,
1130 kind: float_info.float_kind,
1131 };
1132
1133 // Place the float and return the `Floats` back to the parent flow.
1134 // After, grab the position and use that to set our position.
1135 self.base.floats.add_float(&info);
1136
1137 // FIXME (mbrubeck) Get the correct container size for self.base.floats;
1138 let container_size = Size2D::new(self.base.block_container_inline_size, Au(0));
1139
1140 // Move in from the margin edge, as per CSS 2.1 § 9.5, floats may not overlap anything on
1141 // their margin edges.
1142 let float_offset = self.base.floats.last_float_pos().unwrap()
1143 .convert(self.base.floats.writing_mode,
1144 self.base.writing_mode,
1145 container_size)
1146 .start;
1147 let margin_offset = LogicalPoint::new(self.base.writing_mode,
1148 Au(0),
1149 self.fragment.margin.block_start);
1150
1151 let mut origin = LogicalPoint::new(self.base.writing_mode,
1152 self.base.position.start.i,
1153 self.base.position.start.b);
1154 origin = origin.add_point(&float_offset).add_point(&margin_offset);
1155 self.base.position = LogicalRect::from_point_size(self.base.writing_mode,
1156 origin,
1157 self.base.position.size);
1158 }
1159
explicit_block_containing_size(&self, shared_context: &SharedStyleContext) -> Option<Au>1160 pub fn explicit_block_containing_size(&self, shared_context: &SharedStyleContext) -> Option<Au> {
1161 if self.is_root() || self.is_fixed() {
1162 let viewport_size = LogicalSize::from_physical(self.fragment.style.writing_mode,
1163 shared_context.viewport_size());
1164 Some(viewport_size.block)
1165 } else if self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) &&
1166 self.base.block_container_explicit_block_size.is_none() {
1167 self.base.absolute_cb.explicit_block_containing_size(shared_context)
1168 } else {
1169 self.base.block_container_explicit_block_size
1170 }
1171 }
1172
explicit_block_size(&self, containing_block_size: Option<Au>) -> Option<Au>1173 pub fn explicit_block_size(&self, containing_block_size: Option<Au>) -> Option<Au> {
1174 let content_block_size = self.fragment.style().content_block_size();
1175
1176 match (content_block_size, containing_block_size) {
1177 (LengthOrPercentageOrAuto::Calc(calc), _) => {
1178 calc.to_used_value(containing_block_size)
1179 }
1180 (LengthOrPercentageOrAuto::Length(length), _) => Some(Au::from(length)),
1181 (LengthOrPercentageOrAuto::Percentage(percent), Some(container_size)) => {
1182 Some(container_size.scale_by(percent.0))
1183 }
1184 (LengthOrPercentageOrAuto::Percentage(_), None) |
1185 (LengthOrPercentageOrAuto::Auto, None) => {
1186 None
1187 }
1188 (LengthOrPercentageOrAuto::Auto, Some(container_size)) => {
1189 let (block_start, block_end) = {
1190 let position = self.fragment.style().logical_position();
1191 (MaybeAuto::from_style(position.block_start, container_size),
1192 MaybeAuto::from_style(position.block_end, container_size))
1193 };
1194
1195 match (block_start, block_end) {
1196 (MaybeAuto::Specified(block_start), MaybeAuto::Specified(block_end)) => {
1197 let available_block_size = container_size - self.fragment.border_padding.block_start_end();
1198
1199 // Non-auto margin-block-start and margin-block-end values have already been
1200 // calculated during assign-inline-size.
1201 let margin = self.fragment.style().logical_margin();
1202 let margin_block_start = match margin.block_start {
1203 LengthOrPercentageOrAuto::Auto => MaybeAuto::Auto,
1204 _ => MaybeAuto::Specified(self.fragment.margin.block_start)
1205 };
1206 let margin_block_end = match margin.block_end {
1207 LengthOrPercentageOrAuto::Auto => MaybeAuto::Auto,
1208 _ => MaybeAuto::Specified(self.fragment.margin.block_end)
1209 };
1210
1211 let margin_block_start = margin_block_start.specified_or_zero();
1212 let margin_block_end = margin_block_end.specified_or_zero();
1213 let sum = block_start + block_end + margin_block_start + margin_block_end;
1214 Some(available_block_size - sum)
1215 }
1216
1217 (_, _) => {
1218 None
1219 }
1220 }
1221 }
1222 }
1223 }
1224
calculate_absolute_block_size_and_margins(&mut self, shared_context: &SharedStyleContext)1225 fn calculate_absolute_block_size_and_margins(&mut self, shared_context: &SharedStyleContext) {
1226 let opaque_self = OpaqueFlow::from_flow(self);
1227 let containing_block_block_size =
1228 self.containing_block_size(&shared_context.viewport_size(), opaque_self).block;
1229
1230 // This is the stored content block-size value from assign-block-size
1231 let content_block_size = self.fragment.border_box.size.block;
1232
1233 let mut solution = None;
1234 {
1235 // Non-auto margin-block-start and margin-block-end values have already been
1236 // calculated during assign-inline-size.
1237 let margin = self.fragment.style().logical_margin();
1238 let margin_block_start = match margin.block_start {
1239 LengthOrPercentageOrAuto::Auto => MaybeAuto::Auto,
1240 _ => MaybeAuto::Specified(self.fragment.margin.block_start)
1241 };
1242 let margin_block_end = match margin.block_end {
1243 LengthOrPercentageOrAuto::Auto => MaybeAuto::Auto,
1244 _ => MaybeAuto::Specified(self.fragment.margin.block_end)
1245 };
1246
1247 let block_start;
1248 let block_end;
1249 {
1250 let position = self.fragment.style().logical_position();
1251 block_start = MaybeAuto::from_style(position.block_start,
1252 containing_block_block_size);
1253 block_end = MaybeAuto::from_style(position.block_end, containing_block_block_size);
1254 }
1255
1256 let available_block_size = containing_block_block_size -
1257 self.fragment.border_padding.block_start_end();
1258 if self.fragment.is_replaced() {
1259 // Calculate used value of block-size just like we do for inline replaced elements.
1260 // TODO: Pass in the containing block block-size when Fragment's
1261 // assign-block-size can handle it correctly.
1262 self.fragment.assign_replaced_block_size_if_necessary();
1263 // TODO: Right now, this content block-size value includes the
1264 // margin because of erroneous block-size calculation in fragment.
1265 // Check this when that has been fixed.
1266 let block_size_used_val = self.fragment.border_box.size.block;
1267 solution = Some(BSizeConstraintSolution::solve_vertical_constraints_abs_replaced(
1268 block_size_used_val,
1269 margin_block_start,
1270 margin_block_end,
1271 block_start,
1272 block_end,
1273 content_block_size,
1274 available_block_size))
1275 } else {
1276 let mut candidate_block_size_iterator =
1277 CandidateBSizeIterator::new(&self.fragment, Some(containing_block_block_size));
1278
1279 // Can't use `for` because we assign to
1280 // `candidate_block_size_iterator.candidate_value`.
1281 while let Some(block_size_used_val) = candidate_block_size_iterator.next() {
1282 solution = Some(
1283 BSizeConstraintSolution::solve_vertical_constraints_abs_nonreplaced(
1284 block_size_used_val,
1285 margin_block_start,
1286 margin_block_end,
1287 block_start,
1288 block_end,
1289 content_block_size,
1290 available_block_size));
1291
1292 candidate_block_size_iterator.candidate_value =
1293 solution.unwrap().block_size;
1294 }
1295 }
1296 }
1297
1298 let solution = solution.unwrap();
1299 self.fragment.margin.block_start = solution.margin_block_start;
1300 self.fragment.margin.block_end = solution.margin_block_end;
1301 self.fragment.border_box.start.b = Au(0);
1302
1303 if !self.base.flags.contains(FlowFlags::BLOCK_POSITION_IS_STATIC) {
1304 self.base.position.start.b = solution.block_start + self.fragment.margin.block_start
1305 }
1306
1307 let block_size = solution.block_size + self.fragment.border_padding.block_start_end();
1308 self.fragment.border_box.size.block = block_size;
1309 self.base.position.size.block = block_size;
1310
1311 self.base.restyle_damage.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
1312 self.fragment.restyle_damage.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
1313 }
1314
1315 /// Compute inline size based using the `block_container_inline_size` set by the parent flow.
1316 ///
1317 /// This is run in the `AssignISizes` traversal.
propagate_and_compute_used_inline_size(&mut self, shared_context: &SharedStyleContext)1318 fn propagate_and_compute_used_inline_size(&mut self, shared_context: &SharedStyleContext) {
1319 let containing_block_inline_size = self.base.block_container_inline_size;
1320 self.compute_used_inline_size(shared_context, containing_block_inline_size);
1321 if self.base.flags.is_float() {
1322 self.float.as_mut().unwrap().containing_inline_size = containing_block_inline_size
1323 }
1324 }
1325
1326 /// Assigns the computed inline-start content edge and inline-size to all the children of this
1327 /// block flow. The given `callback`, if supplied, will be called once per child; it is
1328 /// currently used to push down column sizes for tables.
1329 ///
1330 /// `#[inline(always)]` because this is called only from block or table inline-size assignment
1331 /// and the code for block layout is significantly simpler.
1332 #[inline(always)]
propagate_assigned_inline_size_to_children<F>(&mut self, shared_context: &SharedStyleContext, inline_start_content_edge: Au, inline_end_content_edge: Au, content_inline_size: Au, mut callback: F) where F: FnMut(&mut Flow, usize, Au, WritingMode, &mut Au, &mut Au)1333 pub fn propagate_assigned_inline_size_to_children<F>(&mut self,
1334 shared_context: &SharedStyleContext,
1335 inline_start_content_edge: Au,
1336 inline_end_content_edge: Au,
1337 content_inline_size: Au,
1338 mut callback: F)
1339 where F: FnMut(&mut Flow,
1340 usize,
1341 Au,
1342 WritingMode,
1343 &mut Au,
1344 &mut Au) {
1345 let flags = self.base.flags.clone();
1346
1347 let opaque_self = OpaqueFlow::from_flow(self);
1348
1349 // Calculate non-auto block size to pass to children.
1350 let box_border = match self.fragment.style().get_position().box_sizing {
1351 BoxSizing::BorderBox => self.fragment.border_padding.block_start_end(),
1352 BoxSizing::ContentBox => Au(0),
1353 };
1354 let parent_container_size = self.explicit_block_containing_size(shared_context);
1355 // https://drafts.csswg.org/css-ui-3/#box-sizing
1356 let mut explicit_content_size = self
1357 .explicit_block_size(parent_container_size)
1358 .map(|x| if x < box_border { Au(0) } else { x - box_border });
1359 if self.is_root() { explicit_content_size = max(parent_container_size, explicit_content_size); }
1360 // Calculate containing block inline size.
1361 let containing_block_size = if flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) {
1362 self.containing_block_size(&shared_context.viewport_size(), opaque_self).inline
1363 } else {
1364 content_inline_size
1365 };
1366 // FIXME (mbrubeck): Get correct mode for absolute containing block
1367 let containing_block_mode = self.base.writing_mode;
1368
1369 let mut inline_start_margin_edge = inline_start_content_edge;
1370 let mut inline_end_margin_edge = inline_end_content_edge;
1371
1372 let mut iterator = self.base.child_iter_mut().enumerate().peekable();
1373 while let Some((i, kid)) = iterator.next() {
1374 kid.mut_base().block_container_explicit_block_size = explicit_content_size;
1375
1376 // The inline-start margin edge of the child flow is at our inline-start content edge,
1377 // and its inline-size is our content inline-size.
1378 let kid_mode = kid.base().writing_mode;
1379 {
1380 // Don't assign positions to children unless they're going to be reflowed.
1381 // Otherwise, the position we assign might be incorrect and never fixed up. (Issue
1382 // #13704.)
1383 //
1384 // For instance, floats have their true inline position calculated in
1385 // `assign_block_size()`, which won't do anything unless `REFLOW` is set. So, if a
1386 // float child does not have `REFLOW` set, we must be careful to avoid touching its
1387 // inline position, as no logic will run afterward to set its true value.
1388 let kid_base = kid.mut_base();
1389 let reflow_damage = if kid_base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) {
1390 ServoRestyleDamage::REFLOW_OUT_OF_FLOW
1391 } else {
1392 ServoRestyleDamage::REFLOW
1393 };
1394 if kid_base.flags.contains(FlowFlags::INLINE_POSITION_IS_STATIC) &&
1395 kid_base.restyle_damage.contains(reflow_damage) {
1396 kid_base.position.start.i =
1397 if kid_mode.is_bidi_ltr() == containing_block_mode.is_bidi_ltr() {
1398 inline_start_content_edge
1399 } else {
1400 // The kid's inline 'start' is at the parent's 'end'
1401 inline_end_content_edge
1402 };
1403 }
1404 kid_base.block_container_inline_size = content_inline_size;
1405 kid_base.block_container_writing_mode = containing_block_mode;
1406 }
1407
1408 // Call the callback to propagate extra inline size information down to the child. This
1409 // is currently used for tables.
1410 callback(kid,
1411 i,
1412 content_inline_size,
1413 containing_block_mode,
1414 &mut inline_start_margin_edge,
1415 &mut inline_end_margin_edge);
1416
1417 // Per CSS 2.1 § 16.3.1, text alignment propagates to all children in flow.
1418 //
1419 // TODO(#2265, pcwalton): Do this in the cascade instead.
1420 let containing_block_text_align = self.fragment.style().get_inheritedtext().text_align;
1421 kid.mut_base().flags.set_text_align(containing_block_text_align);
1422
1423 // Handle `text-indent` on behalf of any inline children that we have. This is
1424 // necessary because any percentages are relative to the containing block, which only
1425 // we know.
1426 if kid.is_inline_flow() {
1427 kid.as_mut_inline().first_line_indentation =
1428 self.fragment.style().get_inheritedtext().text_indent
1429 .to_used_value(containing_block_size);
1430 }
1431 }
1432 }
1433
1434 /// Determines the type of formatting context this is. See the definition of
1435 /// `FormattingContextType`.
formatting_context_type(&self) -> FormattingContextType1436 pub fn formatting_context_type(&self) -> FormattingContextType {
1437 if self.is_inline_flex_item() || self.is_block_flex_item() {
1438 return FormattingContextType::Other
1439 }
1440 let style = self.fragment.style();
1441 if style.get_box().float != Float::None {
1442 return FormattingContextType::Other
1443 }
1444 match style.get_box().display {
1445 Display::TableCell |
1446 Display::TableCaption |
1447 Display::TableRowGroup |
1448 Display::Table |
1449 Display::InlineBlock |
1450 Display::Flex => {
1451 FormattingContextType::Other
1452 }
1453 _ if style.get_box().overflow_x != StyleOverflow::Visible ||
1454 style.get_box().overflow_y != StyleOverflow::Visible ||
1455 style.is_multicol() => {
1456 FormattingContextType::Block
1457 }
1458 _ => FormattingContextType::None,
1459 }
1460 }
1461
1462 /// Per CSS 2.1 § 9.5, block formatting contexts' inline widths and positions are affected by
1463 /// the presence of floats. This is the part of the assign-heights traversal that computes
1464 /// the final inline position and width for such flows.
1465 ///
1466 /// Note that this is part of the assign-block-sizes traversal, not the assign-inline-sizes
1467 /// traversal as one might expect. That is because, in general, float placement cannot occur
1468 /// until heights are assigned. To work around this unfortunate circular dependency, by the
1469 /// time we get here we have already estimated the width of the block formatting context based
1470 /// on the floats we could see at the time of inline-size assignment. The job of this function,
1471 /// therefore, is not only to assign the final size but also to perform the layout again for
1472 /// this block formatting context if our speculation was wrong.
assign_inline_position_for_formatting_context(&mut self, layout_context: &LayoutContext, content_box: LogicalRect<Au>)1473 fn assign_inline_position_for_formatting_context(&mut self,
1474 layout_context: &LayoutContext,
1475 content_box: LogicalRect<Au>) {
1476 debug_assert_ne!(self.formatting_context_type(), FormattingContextType::None);
1477
1478 if !self.base.restyle_damage.intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW) {
1479 return
1480 }
1481
1482 // We do this first to avoid recomputing our inline size when we propagate it.
1483 self.base.restyle_damage.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
1484 self.fragment.restyle_damage.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
1485
1486 // The code below would completely wreck the layout if run on a flex item, however:
1487 // * Flex items are always the children of flex containers.
1488 // * Flex containers only contain flex items.
1489 // * Floats cannot intrude into flex containers.
1490 // * Floats cannot escape flex items.
1491 // * Flex items cannot also be floats.
1492 // Therefore, a flex item cannot be impacted by a float.
1493 // See also: https://www.w3.org/TR/css-flexbox-1/#flex-containers
1494 if !self.base.might_have_floats_in() {
1495 return
1496 }
1497
1498 // If you remove the might_have_floats_in conditional, this will go off.
1499 debug_assert!(!self.is_inline_flex_item());
1500
1501 // Compute the available space for us, based on the actual floats.
1502 let rect = self.base.floats.available_rect(Au(0),
1503 self.fragment.border_box.size.block,
1504 content_box.size.inline);
1505 let available_inline_size = if let Some(rect) = rect {
1506 // Offset our position by whatever displacement is needed to not impact the floats.
1507 // Also, account for margins sliding behind floats.
1508 let inline_offset = if self.fragment.margin.inline_start < rect.start.i {
1509 // Do not do anything for negative margins; those are handled separately.
1510 rect.start.i - max(Au(0), self.fragment.margin.inline_start)
1511 } else {
1512 Au(0)
1513 };
1514 self.base.position.start.i = content_box.start.i + inline_offset;
1515 // Handle the end margin sliding behind the float.
1516 let end = content_box.size.inline - rect.start.i - rect.size.inline;
1517 let inline_end_offset = if self.fragment.margin.inline_end < end {
1518 end - max(Au(0), self.fragment.margin.inline_end)
1519 } else {
1520 Au(0)
1521 };
1522 content_box.size.inline - inline_offset - inline_end_offset
1523 } else {
1524 content_box.size.inline
1525 } - self.fragment.margin.inline_start_end();
1526 let max_inline_size =
1527 self.fragment.style().max_inline_size()
1528 .to_used_value(self.base.block_container_inline_size)
1529 .unwrap_or(MAX_AU);
1530 let min_inline_size =
1531 self.fragment.style().min_inline_size().to_used_value(self.base.block_container_inline_size);
1532 let specified_inline_size = self.fragment.style().content_inline_size();
1533 let container_size = self.base.block_container_inline_size;
1534 let inline_size =
1535 if let MaybeAuto::Specified(size) = MaybeAuto::from_style(specified_inline_size,
1536 container_size) {
1537 match self.fragment.style().get_position().box_sizing {
1538 BoxSizing::BorderBox => size,
1539 BoxSizing::ContentBox =>
1540 size + self.fragment.border_padding.inline_start_end(),
1541 }
1542 } else {
1543 max(min_inline_size, min(available_inline_size, max_inline_size))
1544 };
1545 self.base.position.size.inline = inline_size + self.fragment.margin.inline_start_end();
1546
1547 // If float speculation failed, fixup our layout, and re-layout all the children.
1548 if self.fragment.margin_box_inline_size() != self.base.position.size.inline {
1549 debug!("assign_inline_position_for_formatting_context: float speculation failed");
1550 // Fix-up our own layout.
1551 // We can't just traverse_flow_tree_preorder ourself, because that would re-run
1552 // float speculation, instead of acting on the actual results.
1553 self.fragment.border_box.size.inline = inline_size;
1554 // Assign final-final inline sizes on all our children.
1555 self.assign_inline_sizes(layout_context);
1556 // Re-run layout on our children.
1557 for child in self.base.child_iter_mut() {
1558 sequential::reflow(child, layout_context, RelayoutMode::Force);
1559 }
1560 // Assign our final-final block size.
1561 self.assign_block_size(layout_context);
1562 }
1563
1564 debug_assert_eq!(self.fragment.margin_box_inline_size(), self.base.position.size.inline);
1565 }
1566
is_inline_block_or_inline_flex(&self) -> bool1567 fn is_inline_block_or_inline_flex(&self) -> bool {
1568 self.fragment.style().get_box().display == Display::InlineBlock ||
1569 self.fragment.style().get_box().display == Display::InlineFlex
1570 }
1571
1572 /// Computes the content portion (only) of the intrinsic inline sizes of this flow. This is
1573 /// used for calculating shrink-to-fit width. Assumes that intrinsic sizes have already been
1574 /// computed for this flow.
content_intrinsic_inline_sizes(&self) -> IntrinsicISizes1575 fn content_intrinsic_inline_sizes(&self) -> IntrinsicISizes {
1576 let (border_padding, margin) = self.fragment.surrounding_intrinsic_inline_size();
1577 IntrinsicISizes {
1578 minimum_inline_size: self.base.intrinsic_inline_sizes.minimum_inline_size -
1579 border_padding - margin,
1580 preferred_inline_size: self.base.intrinsic_inline_sizes.preferred_inline_size -
1581 border_padding - margin,
1582 }
1583 }
1584
1585 /// Computes intrinsic inline sizes for a block.
bubble_inline_sizes_for_block(&mut self, consult_children: bool)1586 pub fn bubble_inline_sizes_for_block(&mut self, consult_children: bool) {
1587 let _scope = layout_debug_scope!("block::bubble_inline_sizes {:x}", self.base.debug_id());
1588
1589 let mut flags = self.base.flags;
1590 if self.definitely_has_zero_block_size() {
1591 // This is kind of a hack for Acid2. But it's a harmless one, because (a) this behavior
1592 // is unspecified; (b) it matches the behavior one would intuitively expect, since
1593 // floats don't flow around blocks that take up no space in the block direction.
1594 flags.remove(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS);
1595 } else if self.fragment.is_text_or_replaced() {
1596 flags.insert(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS);
1597 } else {
1598 flags.remove(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS);
1599 for kid in self.base.children.iter() {
1600 if kid.base().flags.contains(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS) {
1601 flags.insert(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS);
1602 break
1603 }
1604 }
1605 }
1606
1607 // Find the maximum inline-size from children.
1608 //
1609 // See: https://lists.w3.org/Archives/Public/www-style/2014Nov/0085.html
1610 //
1611 // FIXME(pcwalton): This doesn't exactly follow that algorithm at the moment.
1612 // FIXME(pcwalton): This should consider all float descendants, not just children.
1613 let mut computation = self.fragment.compute_intrinsic_inline_sizes();
1614 let (mut left_float_width, mut right_float_width) = (Au(0), Au(0));
1615 let (mut left_float_width_accumulator, mut right_float_width_accumulator) = (Au(0), Au(0));
1616 let mut preferred_inline_size_of_children_without_text_or_replaced_fragments = Au(0);
1617 for kid in self.base.child_iter_mut() {
1618 if kid.base().flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) || !consult_children {
1619 continue
1620 }
1621
1622 let child_base = kid.mut_base();
1623 let float_kind = child_base.flags.float_kind();
1624 computation.content_intrinsic_sizes.minimum_inline_size =
1625 max(computation.content_intrinsic_sizes.minimum_inline_size,
1626 child_base.intrinsic_inline_sizes.minimum_inline_size);
1627
1628 if child_base.flags.contains(FlowFlags::CLEARS_LEFT) {
1629 left_float_width = max(left_float_width, left_float_width_accumulator);
1630 left_float_width_accumulator = Au(0)
1631 }
1632 if child_base.flags.contains(FlowFlags::CLEARS_RIGHT) {
1633 right_float_width = max(right_float_width, right_float_width_accumulator);
1634 right_float_width_accumulator = Au(0)
1635 }
1636
1637 match (float_kind, child_base.flags.contains(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS)) {
1638 (Float::None, true) => {
1639 computation.content_intrinsic_sizes.preferred_inline_size =
1640 max(computation.content_intrinsic_sizes.preferred_inline_size,
1641 child_base.intrinsic_inline_sizes.preferred_inline_size);
1642 }
1643 (Float::None, false) => {
1644 preferred_inline_size_of_children_without_text_or_replaced_fragments = max(
1645 preferred_inline_size_of_children_without_text_or_replaced_fragments,
1646 child_base.intrinsic_inline_sizes.preferred_inline_size)
1647 }
1648 (Float::Left, _) => {
1649 left_float_width_accumulator = left_float_width_accumulator +
1650 child_base.intrinsic_inline_sizes.preferred_inline_size;
1651 }
1652 (Float::Right, _) => {
1653 right_float_width_accumulator = right_float_width_accumulator +
1654 child_base.intrinsic_inline_sizes.preferred_inline_size;
1655 }
1656 }
1657 }
1658
1659 left_float_width = max(left_float_width, left_float_width_accumulator);
1660 right_float_width = max(right_float_width, right_float_width_accumulator);
1661
1662 computation.content_intrinsic_sizes.preferred_inline_size =
1663 computation.content_intrinsic_sizes.preferred_inline_size + left_float_width +
1664 right_float_width;
1665 computation.content_intrinsic_sizes.preferred_inline_size =
1666 max(computation.content_intrinsic_sizes.preferred_inline_size,
1667 preferred_inline_size_of_children_without_text_or_replaced_fragments);
1668
1669 self.base.intrinsic_inline_sizes = computation.finish();
1670 self.base.flags = flags
1671 }
1672
overflow_style_may_require_clip_scroll_node(&self) -> bool1673 pub fn overflow_style_may_require_clip_scroll_node(&self) -> bool {
1674 match (self.fragment.style().get_box().overflow_x,
1675 self.fragment.style().get_box().overflow_y) {
1676 (StyleOverflow::Auto, _) | (StyleOverflow::Scroll, _) | (StyleOverflow::Hidden, _) |
1677 (_, StyleOverflow::Auto) | (_, StyleOverflow::Scroll) | (_, StyleOverflow::Hidden) =>
1678 true,
1679 (_, _) => false,
1680 }
1681 }
1682
compute_inline_sizes(&mut self, shared_context: &SharedStyleContext)1683 pub fn compute_inline_sizes(&mut self, shared_context: &SharedStyleContext) {
1684 if !self.base.restyle_damage.intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW) {
1685 return
1686 }
1687
1688 debug!("assign_inline_sizes({}): assigning inline_size for flow",
1689 if self.base.flags.is_float() {
1690 "float"
1691 } else {
1692 "block"
1693 });
1694
1695 self.base.floats = Floats::new(self.base.writing_mode);
1696
1697 self.initialize_container_size_for_root(shared_context);
1698
1699 // Our inline-size was set to the inline-size of the containing block by the flow's parent.
1700 // Now compute the real value.
1701 self.propagate_and_compute_used_inline_size(shared_context);
1702
1703 self.guess_inline_size_for_block_formatting_context_if_necessary()
1704 }
1705
1706 /// If this is the root flow, initialize values that would normally be set by the parent.
1707 ///
1708 /// Should be called during `assign_inline_sizes` for flows that may be the root.
initialize_container_size_for_root(&mut self, shared_context: &SharedStyleContext)1709 pub fn initialize_container_size_for_root(&mut self, shared_context: &SharedStyleContext) {
1710 if self.is_root() {
1711 debug!("Setting root position");
1712 self.base.position.start = LogicalPoint::zero(self.base.writing_mode);
1713 self.base.block_container_inline_size = LogicalSize::from_physical(
1714 self.base.writing_mode, shared_context.viewport_size()).inline;
1715 self.base.block_container_writing_mode = self.base.writing_mode;
1716 }
1717 }
1718
guess_inline_size_for_block_formatting_context_if_necessary(&mut self)1719 fn guess_inline_size_for_block_formatting_context_if_necessary(&mut self) {
1720 // We don't need to guess anything unless this is a block formatting context.
1721 if self.formatting_context_type() != FormattingContextType::Block {
1722 return
1723 }
1724
1725 // If `max-width` is set, then don't perform this speculation. We guess that the
1726 // page set `max-width` in order to avoid hitting floats. The search box on Google
1727 // SERPs falls into this category.
1728 if self.fragment.style.max_inline_size() != LengthOrPercentageOrNone::None {
1729 return
1730 }
1731
1732 // At this point, we know we can't precisely compute the inline-size of this block now,
1733 // because floats might affect it. Speculate that its inline-size is equal to the
1734 // inline-size computed above minus the inline-size of the previous left and/or right
1735 // floats.
1736 let speculated_left_float_size = if self.fragment.margin.inline_start >= Au(0) &&
1737 self.base.speculated_float_placement_in.left > self.fragment.margin.inline_start {
1738 self.base.speculated_float_placement_in.left - self.fragment.margin.inline_start
1739 } else {
1740 Au(0)
1741 };
1742 let speculated_right_float_size = if self.fragment.margin.inline_end >= Au(0) &&
1743 self.base.speculated_float_placement_in.right > self.fragment.margin.inline_end {
1744 self.base.speculated_float_placement_in.right - self.fragment.margin.inline_end
1745 } else {
1746 Au(0)
1747 };
1748 self.fragment.border_box.size.inline = self.fragment.border_box.size.inline -
1749 speculated_left_float_size - speculated_right_float_size
1750 }
1751
definitely_has_zero_block_size(&self) -> bool1752 fn definitely_has_zero_block_size(&self) -> bool {
1753 if !self.fragment.style.content_block_size().is_definitely_zero() {
1754 return false
1755 }
1756 let border_width = self.fragment.border_width();
1757 if border_width.block_start != Au(0) || border_width.block_end != Au(0) {
1758 return false
1759 }
1760 let padding = self.fragment.style.logical_padding();
1761 padding.block_start.is_definitely_zero() && padding.block_end.is_definitely_zero()
1762 }
1763
is_inline_flex_item(&self) -> bool1764 pub fn is_inline_flex_item(&self) -> bool {
1765 self.fragment.flags.contains(FragmentFlags::IS_INLINE_FLEX_ITEM)
1766 }
1767
is_block_flex_item(&self) -> bool1768 pub fn is_block_flex_item(&self) -> bool {
1769 self.fragment.flags.contains(FragmentFlags::IS_BLOCK_FLEX_ITEM)
1770 }
1771
mark_scrolling_overflow(&mut self, has_scrolling_overflow: bool)1772 pub fn mark_scrolling_overflow(&mut self, has_scrolling_overflow: bool) {
1773 if has_scrolling_overflow {
1774 self.flags.insert(BlockFlowFlags::HAS_SCROLLING_OVERFLOW);
1775 } else {
1776 self.flags.remove(BlockFlowFlags::HAS_SCROLLING_OVERFLOW);
1777 }
1778 }
1779
has_scrolling_overflow(&self) -> bool1780 pub fn has_scrolling_overflow(&self) -> bool {
1781 self.flags.contains(BlockFlowFlags::HAS_SCROLLING_OVERFLOW)
1782 }
1783
1784 // Return offset from original position because of `position: sticky`.
sticky_position(&self) -> SideOffsets2D<MaybeAuto>1785 pub fn sticky_position(&self) -> SideOffsets2D<MaybeAuto> {
1786 let containing_block_size = &self.base.early_absolute_position_info
1787 .relative_containing_block_size;
1788 let writing_mode = self.base.early_absolute_position_info.relative_containing_block_mode;
1789 let offsets = self.fragment.style().logical_position();
1790 let as_margins = LogicalMargin::new(writing_mode,
1791 MaybeAuto::from_style(offsets.block_start, containing_block_size.inline),
1792 MaybeAuto::from_style(offsets.inline_end, containing_block_size.inline),
1793 MaybeAuto::from_style(offsets.block_end, containing_block_size.inline),
1794 MaybeAuto::from_style(offsets.inline_start, containing_block_size.inline));
1795 as_margins.to_physical(writing_mode)
1796 }
1797
background_border_section(&self) -> DisplayListSection1798 pub fn background_border_section(&self) -> DisplayListSection {
1799 if self.base.flags.is_float() {
1800 DisplayListSection::BackgroundAndBorders
1801 } else if self.base
1802 .flags
1803 .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
1804 {
1805 if self.fragment.establishes_stacking_context() {
1806 DisplayListSection::BackgroundAndBorders
1807 } else {
1808 DisplayListSection::BlockBackgroundsAndBorders
1809 }
1810 } else {
1811 DisplayListSection::BlockBackgroundsAndBorders
1812 }
1813 }
1814 }
1815
1816 impl Flow for BlockFlow {
class(&self) -> FlowClass1817 fn class(&self) -> FlowClass {
1818 FlowClass::Block
1819 }
1820
as_mut_block(&mut self) -> &mut BlockFlow1821 fn as_mut_block(&mut self) -> &mut BlockFlow {
1822 self
1823 }
1824
as_block(&self) -> &BlockFlow1825 fn as_block(&self) -> &BlockFlow {
1826 self
1827 }
1828
1829 /// Pass 1 of reflow: computes minimum and preferred inline-sizes.
1830 ///
1831 /// Recursively (bottom-up) determine the flow's minimum and preferred inline-sizes. When
1832 /// called on this flow, all child flows have had their minimum and preferred inline-sizes set.
1833 /// This function must decide minimum/preferred inline-sizes based on its children's
1834 /// inline-sizes and the dimensions of any fragments it is responsible for flowing.
bubble_inline_sizes(&mut self)1835 fn bubble_inline_sizes(&mut self) {
1836 // If this block has a fixed width, just use that for the minimum and preferred width,
1837 // rather than bubbling up children inline width.
1838 let consult_children = match self.fragment.style().get_position().width {
1839 LengthOrPercentageOrAuto::Length(_) => false,
1840 _ => true,
1841 };
1842 self.bubble_inline_sizes_for_block(consult_children);
1843 self.fragment.restyle_damage.remove(ServoRestyleDamage::BUBBLE_ISIZES);
1844 }
1845
1846 /// Recursively (top-down) determines the actual inline-size of child contexts and fragments.
1847 /// When called on this context, the context has had its inline-size set by the parent context.
1848 ///
1849 /// Dual fragments consume some inline-size first, and the remainder is assigned to all child
1850 /// (block) contexts.
assign_inline_sizes(&mut self, layout_context: &LayoutContext)1851 fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
1852 let _scope = layout_debug_scope!("block::assign_inline_sizes {:x}", self.base.debug_id());
1853
1854 let shared_context = layout_context.shared_context();
1855 self.compute_inline_sizes(shared_context);
1856
1857 // Move in from the inline-start border edge.
1858 let inline_start_content_edge = self.fragment.border_box.start.i +
1859 self.fragment.border_padding.inline_start;
1860
1861 let padding_and_borders = self.fragment.border_padding.inline_start_end();
1862
1863 // Distance from the inline-end margin edge to the inline-end content edge.
1864 let inline_end_content_edge =
1865 self.fragment.margin.inline_end +
1866 self.fragment.border_padding.inline_end;
1867
1868 let content_inline_size = self.fragment.border_box.size.inline - padding_and_borders;
1869
1870 self.propagate_assigned_inline_size_to_children(shared_context,
1871 inline_start_content_edge,
1872 inline_end_content_edge,
1873 content_inline_size,
1874 |_, _, _, _, _, _| {});
1875 }
1876
place_float_if_applicable<'a>(&mut self)1877 fn place_float_if_applicable<'a>(&mut self) {
1878 if self.base.flags.is_float() {
1879 self.place_float();
1880 }
1881 }
1882
assign_block_size_for_inorder_child_if_necessary(&mut self, layout_context: &LayoutContext, parent_thread_id: u8, content_box: LogicalRect<Au>) -> bool1883 fn assign_block_size_for_inorder_child_if_necessary(&mut self,
1884 layout_context: &LayoutContext,
1885 parent_thread_id: u8,
1886 content_box: LogicalRect<Au>)
1887 -> bool {
1888 if self.base.flags.is_float() {
1889 return false
1890 }
1891
1892 let is_formatting_context = self.formatting_context_type() != FormattingContextType::None;
1893 if !self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) && is_formatting_context {
1894 self.assign_inline_position_for_formatting_context(layout_context, content_box);
1895 }
1896
1897 if (self as &Flow).floats_might_flow_through() {
1898 self.base.thread_id = parent_thread_id;
1899 if self.base.restyle_damage.intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW |
1900 ServoRestyleDamage::REFLOW) {
1901 self.assign_block_size(layout_context);
1902 // Don't remove the restyle damage; `assign_block_size` decides whether that is
1903 // appropriate (which in the case of e.g. absolutely-positioned flows, it is not).
1904 }
1905 return true
1906 }
1907
1908 if is_formatting_context {
1909 // If this is a formatting context and definitely did not have floats in, then we must
1910 // translate the floats past us.
1911 let writing_mode = self.base.floats.writing_mode;
1912 let delta = self.base.position.size.block;
1913 self.base.floats.translate(LogicalSize::new(writing_mode, Au(0), -delta));
1914 return true
1915 }
1916
1917 false
1918 }
1919
assign_block_size(&mut self, ctx: &LayoutContext)1920 fn assign_block_size(&mut self, ctx: &LayoutContext) {
1921 let remaining = Flow::fragment(self, ctx, None);
1922 debug_assert!(remaining.is_none());
1923 }
1924
fragment(&mut self, layout_context: &LayoutContext, fragmentation_context: Option<FragmentationContext>) -> Option<Arc<Flow>>1925 fn fragment(&mut self, layout_context: &LayoutContext,
1926 fragmentation_context: Option<FragmentationContext>)
1927 -> Option<Arc<Flow>> {
1928 if self.fragment.is_replaced() {
1929 let _scope = layout_debug_scope!("assign_replaced_block_size_if_necessary {:x}",
1930 self.base.debug_id());
1931
1932 // Assign block-size for fragment if it is an image fragment.
1933 self.fragment.assign_replaced_block_size_if_necessary();
1934 if !self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) {
1935 self.base.position.size.block = self.fragment.border_box.size.block;
1936 let mut block_start = AdjoiningMargins::from_margin(self.fragment.margin.block_start);
1937 let block_end = AdjoiningMargins::from_margin(self.fragment.margin.block_end);
1938 if self.fragment.border_box.size.block == Au(0) {
1939 block_start.union(block_end);
1940 self.base.collapsible_margins = CollapsibleMargins::CollapseThrough(block_start);
1941 } else {
1942 self.base.collapsible_margins = CollapsibleMargins::Collapse(block_start, block_end);
1943 }
1944 self.base.restyle_damage.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
1945 self.fragment.restyle_damage.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW |
1946 ServoRestyleDamage::REFLOW);
1947 }
1948 None
1949 } else if self.is_root() ||
1950 self.formatting_context_type() != FormattingContextType::None ||
1951 self.base.flags.contains(FlowFlags::MARGINS_CANNOT_COLLAPSE) {
1952 // Root element margins should never be collapsed according to CSS § 8.3.1.
1953 debug!("assign_block_size: assigning block_size for root flow {:?}",
1954 self.base().debug_id());
1955 self.assign_block_size_block_base(
1956 layout_context,
1957 fragmentation_context,
1958 MarginsMayCollapseFlag::MarginsMayNotCollapse)
1959 } else {
1960 debug!("assign_block_size: assigning block_size for block {:?}",
1961 self.base().debug_id());
1962 self.assign_block_size_block_base(
1963 layout_context,
1964 fragmentation_context,
1965 MarginsMayCollapseFlag::MarginsMayCollapse)
1966 }
1967 }
1968
compute_stacking_relative_position(&mut self, _layout_context: &LayoutContext)1969 fn compute_stacking_relative_position(&mut self, _layout_context: &LayoutContext) {
1970 // FIXME (mbrubeck): Get the real container size, taking the container writing mode into
1971 // account. Must handle vertical writing modes.
1972 let container_size = Size2D::new(self.base.block_container_inline_size, Au(0));
1973
1974 if self.is_root() {
1975 self.base.clip = Rect::max_rect();
1976 }
1977
1978 if self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) {
1979 let position_start = self.base.position.start.to_physical(self.base.writing_mode,
1980 container_size);
1981
1982 // Compute our position relative to the nearest ancestor stacking context. This will be
1983 // passed down later as part of containing block details for absolute descendants.
1984 let absolute_stacking_relative_position = if self.is_fixed() {
1985 // The viewport is initially at (0, 0).
1986 position_start
1987 } else {
1988 // Absolute position of the containing block + position of absolute
1989 // flow w.r.t. the containing block.
1990 self.base
1991 .late_absolute_position_info
1992 .stacking_relative_position_of_absolute_containing_block + position_start.to_vector()
1993 };
1994
1995 if !self.base.writing_mode.is_vertical() {
1996 if !self.base.flags.contains(FlowFlags::INLINE_POSITION_IS_STATIC) {
1997 self.base.stacking_relative_position.x = absolute_stacking_relative_position.x
1998 }
1999 if !self.base.flags.contains(FlowFlags::BLOCK_POSITION_IS_STATIC) {
2000 self.base.stacking_relative_position.y = absolute_stacking_relative_position.y
2001 }
2002 } else {
2003 if !self.base.flags.contains(FlowFlags::INLINE_POSITION_IS_STATIC) {
2004 self.base.stacking_relative_position.y = absolute_stacking_relative_position.y
2005 }
2006 if !self.base.flags.contains(FlowFlags::BLOCK_POSITION_IS_STATIC) {
2007 self.base.stacking_relative_position.x = absolute_stacking_relative_position.x
2008 }
2009 }
2010 }
2011
2012 // For relatively-positioned descendants, the containing block formed by a block is just
2013 // the content box. The containing block for absolutely-positioned descendants, on the
2014 // other hand, is established in other circumstances (see `is_absolute_containing_block').
2015 let relative_offset =
2016 self.fragment.relative_position(&self.base
2017 .early_absolute_position_info
2018 .relative_containing_block_size);
2019 if self.is_absolute_containing_block() {
2020 let border_box_origin = (self.fragment.border_box -
2021 self.fragment.style.logical_border_width()).start;
2022 self.base
2023 .late_absolute_position_info
2024 .stacking_relative_position_of_absolute_containing_block =
2025 self.base.stacking_relative_position.to_point() +
2026 (border_box_origin + relative_offset).to_physical(self.base.writing_mode,
2027 container_size).to_vector()
2028 }
2029
2030 // Compute absolute position info for children.
2031 let stacking_relative_position_of_absolute_containing_block_for_children =
2032 if self.fragment.establishes_stacking_context() {
2033 let logical_border_width = self.fragment.style().logical_border_width();
2034 let position = LogicalPoint::new(self.base.writing_mode,
2035 logical_border_width.inline_start,
2036 logical_border_width.block_start);
2037 let position = position.to_physical(self.base.writing_mode, container_size);
2038
2039 // Some blocks establish a stacking context, but not a containing block for
2040 // absolutely positioned elements. An example of this might be a block that has
2041 // `position: static` and `opacity` set. In these cases, absolutely-positioned
2042 // children will not be positioned relative to us but will instead be positioned
2043 // relative to our containing block.
2044 if self.is_absolute_containing_block() {
2045 position
2046 } else {
2047 position - self.base.stacking_relative_position
2048 }
2049 } else {
2050 self.base
2051 .late_absolute_position_info
2052 .stacking_relative_position_of_absolute_containing_block
2053 };
2054 let late_absolute_position_info_for_children = LateAbsolutePositionInfo {
2055 stacking_relative_position_of_absolute_containing_block:
2056 stacking_relative_position_of_absolute_containing_block_for_children,
2057 };
2058 let container_size_for_children =
2059 self.base.position.size.to_physical(self.base.writing_mode);
2060
2061 // Compute the origin and clipping rectangle for children.
2062 let relative_offset = relative_offset.to_physical(self.base.writing_mode).to_vector();
2063 let is_stacking_context = self.fragment.establishes_stacking_context();
2064 let origin_for_children = if is_stacking_context {
2065 // We establish a stacking context, so the position of our children is vertically
2066 // correct, but has to be adjusted to accommodate horizontal margins. (Note the
2067 // calculation involving `position` below and recall that inline-direction flow
2068 // positions are relative to the edges of the margin box.)
2069 //
2070 // FIXME(pcwalton): Is this vertical-writing-direction-safe?
2071 let margin = self.fragment.margin.to_physical(self.base.writing_mode);
2072 Point2D::new(-margin.left, Au(0))
2073 } else {
2074 self.base.stacking_relative_position.to_point() + relative_offset
2075 };
2076
2077 // Process children.
2078 for kid in self.base.child_iter_mut() {
2079 if kid.base().flags.contains(FlowFlags::INLINE_POSITION_IS_STATIC) ||
2080 kid.base().flags.contains(FlowFlags::BLOCK_POSITION_IS_STATIC) {
2081 let kid_base = kid.mut_base();
2082 let physical_position = kid_base.position.to_physical(kid_base.writing_mode,
2083 container_size_for_children);
2084
2085 // Set the inline and block positions as necessary.
2086 if !kid_base.writing_mode.is_vertical() {
2087 if kid_base.flags.contains(FlowFlags::INLINE_POSITION_IS_STATIC) {
2088 kid_base.stacking_relative_position.x = origin_for_children.x +
2089 physical_position.origin.x
2090 }
2091 if kid_base.flags.contains(FlowFlags::BLOCK_POSITION_IS_STATIC) {
2092 kid_base.stacking_relative_position.y = origin_for_children.y +
2093 physical_position.origin.y
2094 }
2095 } else {
2096 if kid_base.flags.contains(FlowFlags::INLINE_POSITION_IS_STATIC) {
2097 kid_base.stacking_relative_position.y = origin_for_children.y +
2098 physical_position.origin.y
2099 }
2100 if kid_base.flags.contains(FlowFlags::BLOCK_POSITION_IS_STATIC) {
2101 kid_base.stacking_relative_position.x = origin_for_children.x +
2102 physical_position.origin.x
2103 }
2104 }
2105 }
2106
2107 kid.mut_base().late_absolute_position_info =
2108 late_absolute_position_info_for_children;
2109 }
2110 }
2111
mark_as_root(&mut self)2112 fn mark_as_root(&mut self) {
2113 self.flags.insert(BlockFlowFlags::IS_ROOT)
2114 }
2115
is_root(&self) -> bool2116 fn is_root(&self) -> bool {
2117 self.flags.contains(BlockFlowFlags::IS_ROOT)
2118 }
2119
2120 /// The 'position' property of this flow.
positioning(&self) -> Position2121 fn positioning(&self) -> Position {
2122 self.fragment.style.get_box().position
2123 }
2124
2125 /// Return the dimensions of the containing block generated by this flow for absolutely-
2126 /// positioned descendants. For block flows, this is the padding box.
generated_containing_block_size(&self, _: OpaqueFlow) -> LogicalSize<Au>2127 fn generated_containing_block_size(&self, _: OpaqueFlow) -> LogicalSize<Au> {
2128 (self.fragment.border_box - self.fragment.style().logical_border_width()).size
2129 }
2130
2131 /// Returns true if this flow contains fragments that are roots of an absolute flow tree.
contains_roots_of_absolute_flow_tree(&self) -> bool2132 fn contains_roots_of_absolute_flow_tree(&self) -> bool {
2133 self.contains_relatively_positioned_fragments() || self.is_root() ||
2134 self.fragment.has_filter_transform_or_perspective()
2135 }
2136
2137 /// Returns true if this is an absolute containing block.
is_absolute_containing_block(&self) -> bool2138 fn is_absolute_containing_block(&self) -> bool {
2139 self.contains_positioned_fragments() || self.fragment.has_filter_transform_or_perspective()
2140 }
2141
update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au)2142 fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
2143 if self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) &&
2144 self.fragment.style().logical_position().inline_start ==
2145 LengthOrPercentageOrAuto::Auto &&
2146 self.fragment.style().logical_position().inline_end ==
2147 LengthOrPercentageOrAuto::Auto {
2148 self.base.position.start.i = inline_position
2149 }
2150 }
2151
update_late_computed_block_position_if_necessary(&mut self, block_position: Au)2152 fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
2153 if self.base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) &&
2154 self.fragment.style().logical_position().block_start ==
2155 LengthOrPercentageOrAuto::Auto &&
2156 self.fragment.style().logical_position().block_end ==
2157 LengthOrPercentageOrAuto::Auto {
2158 self.base.position.start.b = block_position
2159 }
2160 }
2161
collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState)2162 fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
2163 self.collect_stacking_contexts_for_block(state, StackingContextCollectionFlags::empty());
2164 }
2165
build_display_list(&mut self, state: &mut DisplayListBuildState)2166 fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
2167 self.build_display_list_for_block(state, BorderPaintingMode::Separate);
2168 }
2169
repair_style(&mut self, new_style: &::ServoArc<ComputedValues>)2170 fn repair_style(&mut self, new_style: &::ServoArc<ComputedValues>) {
2171 self.fragment.repair_style(new_style)
2172 }
2173
compute_overflow(&self) -> Overflow2174 fn compute_overflow(&self) -> Overflow {
2175 let flow_size = self.base.position.size.to_physical(self.base.writing_mode);
2176 let overflow = self.fragment.compute_overflow(&flow_size,
2177 &self.base
2178 .early_absolute_position_info
2179 .relative_containing_block_size);
2180 overflow
2181 }
2182
iterate_through_fragment_border_boxes(&self, iterator: &mut FragmentBorderBoxIterator, level: i32, stacking_context_position: &Point2D<Au>)2183 fn iterate_through_fragment_border_boxes(&self,
2184 iterator: &mut FragmentBorderBoxIterator,
2185 level: i32,
2186 stacking_context_position: &Point2D<Au>) {
2187 if !iterator.should_process(&self.fragment) {
2188 return
2189 }
2190
2191 iterator.process(&self.fragment,
2192 level,
2193 &self.fragment
2194 .stacking_relative_border_box(&self.base.stacking_relative_position,
2195 &self.base
2196 .early_absolute_position_info
2197 .relative_containing_block_size,
2198 self.base
2199 .early_absolute_position_info
2200 .relative_containing_block_mode,
2201 CoordinateSystem::Own)
2202 .translate(&stacking_context_position.to_vector()));
2203 }
2204
mutate_fragments(&mut self, mutator: &mut FnMut(&mut Fragment))2205 fn mutate_fragments(&mut self, mutator: &mut FnMut(&mut Fragment)) {
2206 (*mutator)(&mut self.fragment)
2207 }
2208
print_extra_flow_children(&self, print_tree: &mut PrintTree)2209 fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
2210 print_tree.add_item(format!("↑↑ Fragment for block:{:?}", self.fragment));
2211 }
2212 }
2213
2214 impl fmt::Debug for BlockFlow {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result2215 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2216 write!(f,
2217 "{:?}({:x}) {:?}",
2218 self.class(),
2219 self.base.debug_id(),
2220 self.base)
2221 }
2222 }
2223
2224 /// The inputs for the inline-sizes-and-margins constraint equation.
2225 #[derive(Clone, Copy, Debug)]
2226 pub struct ISizeConstraintInput {
2227 pub computed_inline_size: MaybeAuto,
2228 pub inline_start_margin: MaybeAuto,
2229 pub inline_end_margin: MaybeAuto,
2230 pub inline_start: MaybeAuto,
2231 pub inline_end: MaybeAuto,
2232 pub text_align: TextAlign,
2233 pub available_inline_size: Au,
2234 }
2235
2236 impl ISizeConstraintInput {
new(computed_inline_size: MaybeAuto, inline_start_margin: MaybeAuto, inline_end_margin: MaybeAuto, inline_start: MaybeAuto, inline_end: MaybeAuto, text_align: TextAlign, available_inline_size: Au) -> ISizeConstraintInput2237 pub fn new(computed_inline_size: MaybeAuto,
2238 inline_start_margin: MaybeAuto,
2239 inline_end_margin: MaybeAuto,
2240 inline_start: MaybeAuto,
2241 inline_end: MaybeAuto,
2242 text_align: TextAlign,
2243 available_inline_size: Au)
2244 -> ISizeConstraintInput {
2245 ISizeConstraintInput {
2246 computed_inline_size: computed_inline_size,
2247 inline_start_margin: inline_start_margin,
2248 inline_end_margin: inline_end_margin,
2249 inline_start: inline_start,
2250 inline_end: inline_end,
2251 text_align: text_align,
2252 available_inline_size: available_inline_size,
2253 }
2254 }
2255 }
2256
2257 /// The solutions for the inline-size-and-margins constraint equation.
2258 #[derive(Clone, Copy, Debug)]
2259 pub struct ISizeConstraintSolution {
2260 pub inline_start: Au,
2261 pub inline_size: Au,
2262 pub margin_inline_start: Au,
2263 pub margin_inline_end: Au
2264 }
2265
2266 impl ISizeConstraintSolution {
new(inline_size: Au, margin_inline_start: Au, margin_inline_end: Au) -> ISizeConstraintSolution2267 pub fn new(inline_size: Au, margin_inline_start: Au, margin_inline_end: Au)
2268 -> ISizeConstraintSolution {
2269 ISizeConstraintSolution {
2270 inline_start: Au(0),
2271 inline_size: inline_size,
2272 margin_inline_start: margin_inline_start,
2273 margin_inline_end: margin_inline_end,
2274 }
2275 }
2276
for_absolute_flow(inline_start: Au, inline_size: Au, margin_inline_start: Au, margin_inline_end: Au) -> ISizeConstraintSolution2277 fn for_absolute_flow(inline_start: Au,
2278 inline_size: Au,
2279 margin_inline_start: Au,
2280 margin_inline_end: Au)
2281 -> ISizeConstraintSolution {
2282 ISizeConstraintSolution {
2283 inline_start: inline_start,
2284 inline_size: inline_size,
2285 margin_inline_start: margin_inline_start,
2286 margin_inline_end: margin_inline_end,
2287 }
2288 }
2289 }
2290
2291 // Trait to encapsulate the ISize and Margin calculation.
2292 //
2293 // CSS Section 10.3
2294 pub trait ISizeAndMarginsComputer {
2295 /// Instructs the fragment to compute its border and padding.
compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au)2296 fn compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au) {
2297 block.fragment.compute_border_and_padding(containing_block_inline_size);
2298 }
2299
2300 /// Compute the inputs for the ISize constraint equation.
2301 ///
2302 /// This is called only once to compute the initial inputs. For calculations involving
2303 /// minimum and maximum inline-size, we don't need to recompute these.
compute_inline_size_constraint_inputs(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, shared_context: &SharedStyleContext) -> ISizeConstraintInput2304 fn compute_inline_size_constraint_inputs(&self,
2305 block: &mut BlockFlow,
2306 parent_flow_inline_size: Au,
2307 shared_context: &SharedStyleContext)
2308 -> ISizeConstraintInput {
2309 let containing_block_inline_size =
2310 self.containing_block_inline_size(block, parent_flow_inline_size, shared_context);
2311
2312 block.fragment.compute_block_direction_margins(containing_block_inline_size);
2313 block.fragment.compute_inline_direction_margins(containing_block_inline_size);
2314 self.compute_border_and_padding(block, containing_block_inline_size);
2315
2316 let mut computed_inline_size = self.initial_computed_inline_size(block,
2317 parent_flow_inline_size,
2318 shared_context);
2319 let style = block.fragment.style();
2320 match (computed_inline_size, style.get_position().box_sizing) {
2321 (MaybeAuto::Specified(size), BoxSizing::BorderBox) => {
2322 computed_inline_size =
2323 MaybeAuto::Specified(size - block.fragment.border_padding.inline_start_end())
2324 }
2325 (MaybeAuto::Auto, BoxSizing::BorderBox) |
2326 (_, BoxSizing::ContentBox) => {}
2327 }
2328
2329 let margin = style.logical_margin();
2330 let position = style.logical_position();
2331
2332 let available_inline_size = containing_block_inline_size -
2333 block.fragment.border_padding.inline_start_end();
2334 ISizeConstraintInput::new(computed_inline_size,
2335 MaybeAuto::from_style(margin.inline_start,
2336 containing_block_inline_size),
2337 MaybeAuto::from_style(margin.inline_end,
2338 containing_block_inline_size),
2339 MaybeAuto::from_style(position.inline_start,
2340 containing_block_inline_size),
2341 MaybeAuto::from_style(position.inline_end,
2342 containing_block_inline_size),
2343 style.get_inheritedtext().text_align,
2344 available_inline_size)
2345 }
2346
2347 /// Set the used values for inline-size and margins from the relevant constraint equation.
2348 /// This is called only once.
2349 ///
2350 /// Set:
2351 /// * Used values for content inline-size, inline-start margin, and inline-end margin for this
2352 /// flow's box;
2353 /// * Inline-start coordinate of this flow's box;
2354 /// * Inline-start coordinate of the flow with respect to its containing block (if this is an
2355 /// absolute flow).
set_inline_size_constraint_solutions(&self, block: &mut BlockFlow, solution: ISizeConstraintSolution)2356 fn set_inline_size_constraint_solutions(&self,
2357 block: &mut BlockFlow,
2358 solution: ISizeConstraintSolution) {
2359 let inline_size;
2360 let extra_inline_size_from_margin;
2361 {
2362 let block_mode = block.base.writing_mode;
2363
2364 // FIXME (mbrubeck): Get correct containing block for positioned blocks?
2365 let container_mode = block.base.block_container_writing_mode;
2366 let container_size = block.base.block_container_inline_size;
2367
2368 let fragment = block.fragment();
2369 fragment.margin.inline_start = solution.margin_inline_start;
2370 fragment.margin.inline_end = solution.margin_inline_end;
2371
2372 // The associated fragment has the border box of this flow.
2373 inline_size = solution.inline_size + fragment.border_padding.inline_start_end();
2374 fragment.border_box.size.inline = inline_size;
2375
2376 // Start border edge.
2377 // FIXME (mbrubeck): Handle vertical writing modes.
2378 fragment.border_box.start.i =
2379 if container_mode.is_bidi_ltr() == block_mode.is_bidi_ltr() {
2380 fragment.margin.inline_start
2381 } else {
2382 // The parent's "start" direction is the child's "end" direction.
2383 container_size - inline_size - fragment.margin.inline_end
2384 };
2385
2386 // To calculate the total size of this block, we also need to account for any
2387 // additional size contribution from positive margins. Negative margins means the block
2388 // isn't made larger at all by the margin.
2389 extra_inline_size_from_margin = max(Au(0), fragment.margin.inline_start) +
2390 max(Au(0), fragment.margin.inline_end);
2391 }
2392
2393 // We also resize the block itself, to ensure that overflow is not calculated
2394 // as the inline-size of our parent. We might be smaller and we might be larger if we
2395 // overflow.
2396 block.mut_base().position.size.inline = inline_size + extra_inline_size_from_margin;
2397 }
2398
2399 /// Set the inline coordinate of the given flow if it is absolutely positioned.
set_inline_position_of_flow_if_necessary(&self, _: &mut BlockFlow, _: ISizeConstraintSolution)2400 fn set_inline_position_of_flow_if_necessary(&self,
2401 _: &mut BlockFlow,
2402 _: ISizeConstraintSolution) {}
2403
2404 /// Solve the inline-size and margins constraints for this block flow.
solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution2405 fn solve_inline_size_constraints(&self,
2406 block: &mut BlockFlow,
2407 input: &ISizeConstraintInput)
2408 -> ISizeConstraintSolution;
2409
initial_computed_inline_size(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, shared_context: &SharedStyleContext) -> MaybeAuto2410 fn initial_computed_inline_size(&self,
2411 block: &mut BlockFlow,
2412 parent_flow_inline_size: Au,
2413 shared_context: &SharedStyleContext)
2414 -> MaybeAuto {
2415 MaybeAuto::from_style(block.fragment().style().content_inline_size(),
2416 self.containing_block_inline_size(block,
2417 parent_flow_inline_size,
2418 shared_context))
2419 }
2420
containing_block_inline_size(&self, _: &mut BlockFlow, parent_flow_inline_size: Au, _: &SharedStyleContext) -> Au2421 fn containing_block_inline_size(&self,
2422 _: &mut BlockFlow,
2423 parent_flow_inline_size: Au,
2424 _: &SharedStyleContext)
2425 -> Au {
2426 parent_flow_inline_size
2427 }
2428
2429 /// Compute the used value of inline-size, taking care of min-inline-size and max-inline-size.
2430 ///
2431 /// CSS Section 10.4: Minimum and Maximum inline-sizes
compute_used_inline_size(&self, block: &mut BlockFlow, shared_context: &SharedStyleContext, parent_flow_inline_size: Au)2432 fn compute_used_inline_size(&self,
2433 block: &mut BlockFlow,
2434 shared_context: &SharedStyleContext,
2435 parent_flow_inline_size: Au) {
2436 let mut input = self.compute_inline_size_constraint_inputs(block,
2437 parent_flow_inline_size,
2438 shared_context);
2439
2440 let containing_block_inline_size =
2441 self.containing_block_inline_size(block, parent_flow_inline_size, shared_context);
2442
2443 let mut solution = self.solve_inline_size_constraints(block, &input);
2444
2445 // If the tentative used inline-size is greater than 'max-inline-size', inline-size should
2446 // be recalculated, but this time using the computed value of 'max-inline-size' as the
2447 // computed value for 'inline-size'.
2448 match block.fragment().style().max_inline_size().to_used_value(containing_block_inline_size) {
2449 Some(max_inline_size) if max_inline_size < solution.inline_size => {
2450 input.computed_inline_size = MaybeAuto::Specified(max_inline_size);
2451 solution = self.solve_inline_size_constraints(block, &input);
2452 }
2453 _ => {}
2454 }
2455
2456 // If the resulting inline-size is smaller than 'min-inline-size', inline-size should be
2457 // recalculated, but this time using the value of 'min-inline-size' as the computed value
2458 // for 'inline-size'.
2459 let computed_min_inline_size =
2460 block.fragment().style().min_inline_size().to_used_value(containing_block_inline_size);
2461 if computed_min_inline_size > solution.inline_size {
2462 input.computed_inline_size = MaybeAuto::Specified(computed_min_inline_size);
2463 solution = self.solve_inline_size_constraints(block, &input);
2464 }
2465
2466 self.set_inline_size_constraint_solutions(block, solution);
2467 self.set_inline_position_of_flow_if_necessary(block, solution);
2468 }
2469
2470 /// Computes inline-start and inline-end margins and inline-size.
2471 ///
2472 /// This is used by both replaced and non-replaced Blocks.
2473 ///
2474 /// CSS 2.1 Section 10.3.3.
2475 /// Constraint Equation: margin-inline-start + margin-inline-end + inline-size =
2476 /// available_inline-size
2477 /// where available_inline-size = CB inline-size - (horizontal border + padding)
solve_block_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution2478 fn solve_block_inline_size_constraints(&self,
2479 block: &mut BlockFlow,
2480 input: &ISizeConstraintInput)
2481 -> ISizeConstraintSolution {
2482 let (computed_inline_size, inline_start_margin, inline_end_margin, available_inline_size) =
2483 (input.computed_inline_size,
2484 input.inline_start_margin,
2485 input.inline_end_margin,
2486 input.available_inline_size);
2487
2488 // Check for direction of parent flow (NOT Containing Block)
2489 let block_mode = block.base.writing_mode;
2490 let container_mode = block.base.block_container_writing_mode;
2491 let block_align = block.base.flags.text_align();
2492
2493 // FIXME (mbrubeck): Handle vertical writing modes.
2494 let parent_has_same_direction = container_mode.is_bidi_ltr() == block_mode.is_bidi_ltr();
2495
2496 // If inline-size is not 'auto', and inline-size + margins > available_inline-size, all
2497 // 'auto' margins are treated as 0.
2498 let (inline_start_margin, inline_end_margin) = match computed_inline_size {
2499 MaybeAuto::Auto => (inline_start_margin, inline_end_margin),
2500 MaybeAuto::Specified(inline_size) => {
2501 let inline_start = inline_start_margin.specified_or_zero();
2502 let inline_end = inline_end_margin.specified_or_zero();
2503
2504 if (inline_start + inline_end + inline_size) > available_inline_size {
2505 (MaybeAuto::Specified(inline_start), MaybeAuto::Specified(inline_end))
2506 } else {
2507 (inline_start_margin, inline_end_margin)
2508 }
2509 }
2510 };
2511
2512 // Invariant: inline-start_margin + inline-size + inline-end_margin ==
2513 // available_inline-size
2514 let (inline_start_margin, inline_size, inline_end_margin) =
2515 match (inline_start_margin, computed_inline_size, inline_end_margin) {
2516 // If all have a computed value other than 'auto', the system is over-constrained.
2517 (MaybeAuto::Specified(margin_start),
2518 MaybeAuto::Specified(inline_size),
2519 MaybeAuto::Specified(margin_end)) => {
2520 // servo_left, servo_right, and servo_center are used to implement
2521 // the "align descendants" rule in HTML5 § 14.2.
2522 if block_align == TextAlign::ServoCenter {
2523 // Ignore any existing margins, and make the inline-start and
2524 // inline-end margins equal.
2525 let margin = (available_inline_size - inline_size).scale_by(0.5);
2526 (margin, inline_size, margin)
2527 } else {
2528 let ignore_end_margin = match block_align {
2529 TextAlign::ServoLeft => block_mode.is_bidi_ltr(),
2530 TextAlign::ServoRight => !block_mode.is_bidi_ltr(),
2531 _ => parent_has_same_direction,
2532 };
2533 if ignore_end_margin {
2534 (margin_start, inline_size, available_inline_size -
2535 (margin_start + inline_size))
2536 } else {
2537 (available_inline_size - (margin_end + inline_size),
2538 inline_size,
2539 margin_end)
2540 }
2541 }
2542 }
2543 // If exactly one value is 'auto', solve for it
2544 (MaybeAuto::Auto,
2545 MaybeAuto::Specified(inline_size),
2546 MaybeAuto::Specified(margin_end)) =>
2547 (available_inline_size - (inline_size + margin_end), inline_size, margin_end),
2548 (MaybeAuto::Specified(margin_start),
2549 MaybeAuto::Auto,
2550 MaybeAuto::Specified(margin_end)) => {
2551 (margin_start,
2552 available_inline_size - (margin_start + margin_end),
2553 margin_end)
2554 }
2555 (MaybeAuto::Specified(margin_start),
2556 MaybeAuto::Specified(inline_size),
2557 MaybeAuto::Auto) => {
2558 (margin_start,
2559 inline_size,
2560 available_inline_size - (margin_start + inline_size))
2561 }
2562
2563 // If inline-size is set to 'auto', any other 'auto' value becomes '0',
2564 // and inline-size is solved for
2565 (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Specified(margin_end)) => {
2566 (Au(0), available_inline_size - margin_end, margin_end)
2567 }
2568 (MaybeAuto::Specified(margin_start), MaybeAuto::Auto, MaybeAuto::Auto) => {
2569 (margin_start, available_inline_size - margin_start, Au(0))
2570 }
2571 (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Auto) => {
2572 (Au(0), available_inline_size, Au(0))
2573 }
2574
2575 // If inline-start and inline-end margins are auto, they become equal
2576 (MaybeAuto::Auto, MaybeAuto::Specified(inline_size), MaybeAuto::Auto) => {
2577 let margin = (available_inline_size - inline_size).scale_by(0.5);
2578 (margin, inline_size, margin)
2579 }
2580 };
2581
2582 ISizeConstraintSolution::new(inline_size, inline_start_margin, inline_end_margin)
2583 }
2584 }
2585
2586 /// The different types of Blocks.
2587 ///
2588 /// They mainly differ in the way inline-size and block-sizes and margins are calculated
2589 /// for them.
2590 pub struct AbsoluteNonReplaced;
2591 pub struct AbsoluteReplaced;
2592 pub struct BlockNonReplaced;
2593 pub struct BlockReplaced;
2594 pub struct FloatNonReplaced;
2595 pub struct FloatReplaced;
2596 pub struct InlineBlockNonReplaced;
2597 pub struct InlineBlockReplaced;
2598 pub struct InlineFlexItem;
2599
2600 impl ISizeAndMarginsComputer for AbsoluteNonReplaced {
2601 /// Solve the horizontal constraint equation for absolute non-replaced elements.
2602 ///
2603 /// CSS Section 10.3.7
2604 /// Constraint equation:
2605 /// inline-start + inline-end + inline-size + margin-inline-start + margin-inline-end
2606 /// = absolute containing block inline-size - (horizontal padding and border)
2607 /// [aka available inline-size]
2608 ///
2609 /// Return the solution for the equation.
solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution2610 fn solve_inline_size_constraints(&self,
2611 block: &mut BlockFlow,
2612 input: &ISizeConstraintInput)
2613 -> ISizeConstraintSolution {
2614 let &ISizeConstraintInput {
2615 computed_inline_size,
2616 inline_start_margin,
2617 inline_end_margin,
2618 inline_start,
2619 inline_end,
2620 available_inline_size,
2621 ..
2622 } = input;
2623
2624 // Check for direction of parent flow (NOT Containing Block)
2625 let block_mode = block.base.writing_mode;
2626 let container_mode = block.base.block_container_writing_mode;
2627
2628 // FIXME (mbrubeck): Handle vertical writing modes.
2629 let parent_has_same_direction = container_mode.is_bidi_ltr() == block_mode.is_bidi_ltr();
2630
2631 let (inline_start, inline_size, margin_inline_start, margin_inline_end) =
2632 match (inline_start, inline_end, computed_inline_size) {
2633 (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Auto) => {
2634 let margin_start = inline_start_margin.specified_or_zero();
2635 let margin_end = inline_end_margin.specified_or_zero();
2636 // Now it is the same situation as inline-start Specified and inline-end
2637 // and inline-size Auto.
2638
2639 // Set inline-end to zero to calculate inline-size.
2640 let inline_size =
2641 block.get_shrink_to_fit_inline_size(available_inline_size -
2642 (margin_start + margin_end));
2643 (Au(0), inline_size, margin_start, margin_end)
2644 }
2645 (MaybeAuto::Specified(inline_start),
2646 MaybeAuto::Specified(inline_end),
2647 MaybeAuto::Specified(inline_size)) => {
2648 match (inline_start_margin, inline_end_margin) {
2649 (MaybeAuto::Auto, MaybeAuto::Auto) => {
2650 let total_margin_val =
2651 available_inline_size - inline_start - inline_end - inline_size;
2652 if total_margin_val < Au(0) {
2653 if parent_has_same_direction {
2654 // margin-inline-start becomes 0
2655 (inline_start, inline_size, Au(0), total_margin_val)
2656 } else {
2657 // margin-inline-end becomes 0, because it's toward the parent's
2658 // inline-start edge.
2659 (inline_start, inline_size, total_margin_val, Au(0))
2660 }
2661 } else {
2662 // Equal margins
2663 (inline_start,
2664 inline_size,
2665 total_margin_val.scale_by(0.5),
2666 total_margin_val.scale_by(0.5))
2667 }
2668 }
2669 (MaybeAuto::Specified(margin_start), MaybeAuto::Auto) => {
2670 let sum = inline_start + inline_end + inline_size + margin_start;
2671 (inline_start, inline_size, margin_start, available_inline_size - sum)
2672 }
2673 (MaybeAuto::Auto, MaybeAuto::Specified(margin_end)) => {
2674 let sum = inline_start + inline_end + inline_size + margin_end;
2675 (inline_start, inline_size, available_inline_size - sum, margin_end)
2676 }
2677 (MaybeAuto::Specified(margin_start), MaybeAuto::Specified(margin_end)) => {
2678 // Values are over-constrained.
2679 let sum = inline_start + inline_size + margin_start + margin_end;
2680 if parent_has_same_direction {
2681 // Ignore value for 'inline-end'
2682 (inline_start, inline_size, margin_start, margin_end)
2683 } else {
2684 // Ignore value for 'inline-start'
2685 (available_inline_size - sum,
2686 inline_size,
2687 margin_start,
2688 margin_end)
2689 }
2690 }
2691 }
2692 }
2693 // For the rest of the cases, auto values for margin are set to 0
2694
2695 // If only one is Auto, solve for it
2696 (MaybeAuto::Auto,
2697 MaybeAuto::Specified(inline_end),
2698 MaybeAuto::Specified(inline_size)) => {
2699 let margin_start = inline_start_margin.specified_or_zero();
2700 let margin_end = inline_end_margin.specified_or_zero();
2701 let sum = inline_end + inline_size + margin_start + margin_end;
2702 (available_inline_size - sum, inline_size, margin_start, margin_end)
2703 }
2704 (MaybeAuto::Specified(inline_start),
2705 MaybeAuto::Auto,
2706 MaybeAuto::Specified(inline_size)) => {
2707 let margin_start = inline_start_margin.specified_or_zero();
2708 let margin_end = inline_end_margin.specified_or_zero();
2709 (inline_start, inline_size, margin_start, margin_end)
2710 }
2711 (MaybeAuto::Specified(inline_start),
2712 MaybeAuto::Specified(inline_end),
2713 MaybeAuto::Auto) => {
2714 let margin_start = inline_start_margin.specified_or_zero();
2715 let margin_end = inline_end_margin.specified_or_zero();
2716 let sum = inline_start + inline_end + margin_start + margin_end;
2717 (inline_start, available_inline_size - sum, margin_start, margin_end)
2718 }
2719
2720 // If inline-size is auto, then inline-size is shrink-to-fit. Solve for the
2721 // non-auto value.
2722 (MaybeAuto::Specified(inline_start), MaybeAuto::Auto, MaybeAuto::Auto) => {
2723 let margin_start = inline_start_margin.specified_or_zero();
2724 let margin_end = inline_end_margin.specified_or_zero();
2725 // Set inline-end to zero to calculate inline-size
2726 let inline_size =
2727 block.get_shrink_to_fit_inline_size(available_inline_size -
2728 (margin_start + margin_end));
2729 (inline_start, inline_size, margin_start, margin_end)
2730 }
2731 (MaybeAuto::Auto, MaybeAuto::Specified(inline_end), MaybeAuto::Auto) => {
2732 let margin_start = inline_start_margin.specified_or_zero();
2733 let margin_end = inline_end_margin.specified_or_zero();
2734 // Set inline-start to zero to calculate inline-size
2735 let inline_size =
2736 block.get_shrink_to_fit_inline_size(available_inline_size -
2737 (margin_start + margin_end));
2738 let sum = inline_end + inline_size + margin_start + margin_end;
2739 (available_inline_size - sum, inline_size, margin_start, margin_end)
2740 }
2741
2742 (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Specified(inline_size)) => {
2743 let margin_start = inline_start_margin.specified_or_zero();
2744 let margin_end = inline_end_margin.specified_or_zero();
2745 // Setting 'inline-start' to static position because direction is 'ltr'.
2746 // TODO: Handle 'rtl' when it is implemented.
2747 (Au(0), inline_size, margin_start, margin_end)
2748 }
2749 };
2750 ISizeConstraintSolution::for_absolute_flow(inline_start,
2751 inline_size,
2752 margin_inline_start,
2753 margin_inline_end)
2754 }
2755
containing_block_inline_size(&self, block: &mut BlockFlow, _: Au, shared_context: &SharedStyleContext) -> Au2756 fn containing_block_inline_size(&self,
2757 block: &mut BlockFlow,
2758 _: Au,
2759 shared_context: &SharedStyleContext)
2760 -> Au {
2761 let opaque_block = OpaqueFlow::from_flow(block);
2762 block.containing_block_size(&shared_context.viewport_size(), opaque_block).inline
2763 }
2764
set_inline_position_of_flow_if_necessary(&self, block: &mut BlockFlow, solution: ISizeConstraintSolution)2765 fn set_inline_position_of_flow_if_necessary(&self,
2766 block: &mut BlockFlow,
2767 solution: ISizeConstraintSolution) {
2768 // Set the inline position of the absolute flow wrt to its containing block.
2769 if !block.base.flags.contains(FlowFlags::INLINE_POSITION_IS_STATIC) {
2770 block.base.position.start.i = solution.inline_start;
2771 }
2772 }
2773 }
2774
2775 impl ISizeAndMarginsComputer for AbsoluteReplaced {
2776 /// Solve the horizontal constraint equation for absolute replaced elements.
2777 ///
2778 /// CSS Section 10.3.8
2779 /// Constraint equation:
2780 /// inline-start + inline-end + inline-size + margin-inline-start + margin-inline-end
2781 /// = absolute containing block inline-size - (horizontal padding and border)
2782 /// [aka available_inline-size]
2783 ///
2784 /// Return the solution for the equation.
solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution2785 fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput)
2786 -> ISizeConstraintSolution {
2787 let &ISizeConstraintInput {
2788 computed_inline_size,
2789 inline_start_margin,
2790 inline_end_margin,
2791 inline_start,
2792 inline_end,
2793 available_inline_size,
2794 ..
2795 } = input;
2796 // TODO: Check for direction of static-position Containing Block (aka
2797 // parent flow, _not_ the actual Containing Block) when right-to-left
2798 // is implemented
2799 // Assume direction is 'ltr' for now
2800 // TODO: Handle all the cases for 'rtl' direction.
2801
2802 let inline_size = match computed_inline_size {
2803 MaybeAuto::Specified(w) => w,
2804 _ => panic!("{} {}",
2805 "The used value for inline_size for absolute replaced flow",
2806 "should have already been calculated by now.")
2807 };
2808
2809 let (inline_start, inline_size, margin_inline_start, margin_inline_end) =
2810 match (inline_start, inline_end) {
2811 (MaybeAuto::Auto, MaybeAuto::Auto) => {
2812 let margin_start = inline_start_margin.specified_or_zero();
2813 let margin_end = inline_end_margin.specified_or_zero();
2814 (Au(0), inline_size, margin_start, margin_end)
2815 }
2816 // If only one is Auto, solve for it
2817 (MaybeAuto::Auto, MaybeAuto::Specified(inline_end)) => {
2818 let margin_start = inline_start_margin.specified_or_zero();
2819 let margin_end = inline_end_margin.specified_or_zero();
2820 let sum = inline_end + inline_size + margin_start + margin_end;
2821 (available_inline_size - sum, inline_size, margin_start, margin_end)
2822 }
2823 (MaybeAuto::Specified(inline_start), MaybeAuto::Auto) => {
2824 let margin_start = inline_start_margin.specified_or_zero();
2825 let margin_end = inline_end_margin.specified_or_zero();
2826 (inline_start, inline_size, margin_start, margin_end)
2827 }
2828 (MaybeAuto::Specified(inline_start), MaybeAuto::Specified(inline_end)) => {
2829 match (inline_start_margin, inline_end_margin) {
2830 (MaybeAuto::Auto, MaybeAuto::Auto) => {
2831 let total_margin_val = available_inline_size - inline_start -
2832 inline_end - inline_size;
2833 if total_margin_val < Au(0) {
2834 // margin-inline-start becomes 0 because direction is 'ltr'.
2835 (inline_start, inline_size, Au(0), total_margin_val)
2836 } else {
2837 // Equal margins
2838 (inline_start,
2839 inline_size,
2840 total_margin_val.scale_by(0.5),
2841 total_margin_val.scale_by(0.5))
2842 }
2843 }
2844 (MaybeAuto::Specified(margin_start), MaybeAuto::Auto) => {
2845 let sum = inline_start + inline_end + inline_size + margin_start;
2846 (inline_start, inline_size, margin_start, available_inline_size - sum)
2847 }
2848 (MaybeAuto::Auto, MaybeAuto::Specified(margin_end)) => {
2849 let sum = inline_start + inline_end + inline_size + margin_end;
2850 (inline_start, inline_size, available_inline_size - sum, margin_end)
2851 }
2852 (MaybeAuto::Specified(margin_start), MaybeAuto::Specified(margin_end)) => {
2853 // Values are over-constrained.
2854 // Ignore value for 'inline-end' cos direction is 'ltr'.
2855 (inline_start, inline_size, margin_start, margin_end)
2856 }
2857 }
2858 }
2859 };
2860 ISizeConstraintSolution::for_absolute_flow(inline_start,
2861 inline_size,
2862 margin_inline_start,
2863 margin_inline_end)
2864 }
2865
2866 /// Calculate used value of inline-size just like we do for inline replaced elements.
initial_computed_inline_size(&self, block: &mut BlockFlow, _: Au, shared_context: &SharedStyleContext) -> MaybeAuto2867 fn initial_computed_inline_size(&self,
2868 block: &mut BlockFlow,
2869 _: Au,
2870 shared_context: &SharedStyleContext)
2871 -> MaybeAuto {
2872 let opaque_block = OpaqueFlow::from_flow(block);
2873 let containing_block_inline_size =
2874 block.containing_block_size(&shared_context.viewport_size(), opaque_block).inline;
2875 let container_block_size = block.explicit_block_containing_size(shared_context);
2876 let fragment = block.fragment();
2877 fragment.assign_replaced_inline_size_if_necessary(containing_block_inline_size, container_block_size);
2878 // For replaced absolute flow, the rest of the constraint solving will
2879 // take inline-size to be specified as the value computed here.
2880 MaybeAuto::Specified(fragment.content_box().size.inline)
2881 }
2882
containing_block_inline_size(&self, block: &mut BlockFlow, _: Au, shared_context: &SharedStyleContext) -> Au2883 fn containing_block_inline_size(&self,
2884 block: &mut BlockFlow,
2885 _: Au,
2886 shared_context: &SharedStyleContext)
2887 -> Au {
2888 let opaque_block = OpaqueFlow::from_flow(block);
2889 block.containing_block_size(&shared_context.viewport_size(), opaque_block).inline
2890 }
2891
set_inline_position_of_flow_if_necessary(&self, block: &mut BlockFlow, solution: ISizeConstraintSolution)2892 fn set_inline_position_of_flow_if_necessary(&self,
2893 block: &mut BlockFlow,
2894 solution: ISizeConstraintSolution) {
2895 // Set the x-coordinate of the absolute flow wrt to its containing block.
2896 block.base.position.start.i = solution.inline_start;
2897 }
2898 }
2899
2900 impl ISizeAndMarginsComputer for BlockNonReplaced {
2901 /// Compute inline-start and inline-end margins and inline-size.
solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution2902 fn solve_inline_size_constraints(&self,
2903 block: &mut BlockFlow,
2904 input: &ISizeConstraintInput)
2905 -> ISizeConstraintSolution {
2906 self.solve_block_inline_size_constraints(block, input)
2907 }
2908 }
2909
2910 impl ISizeAndMarginsComputer for BlockReplaced {
2911 /// Compute inline-start and inline-end margins and inline-size.
2912 ///
2913 /// ISize has already been calculated. We now calculate the margins just
2914 /// like for non-replaced blocks.
solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution2915 fn solve_inline_size_constraints(&self,
2916 block: &mut BlockFlow,
2917 input: &ISizeConstraintInput)
2918 -> ISizeConstraintSolution {
2919 match input.computed_inline_size {
2920 MaybeAuto::Specified(_) => {},
2921 MaybeAuto::Auto => {
2922 panic!("BlockReplaced: inline_size should have been computed by now")
2923 }
2924 };
2925 self.solve_block_inline_size_constraints(block, input)
2926 }
2927
2928 /// Calculate used value of inline-size just like we do for inline replaced elements.
initial_computed_inline_size(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, shared_context: &SharedStyleContext) -> MaybeAuto2929 fn initial_computed_inline_size(&self,
2930 block: &mut BlockFlow,
2931 parent_flow_inline_size: Au,
2932 shared_context: &SharedStyleContext)
2933 -> MaybeAuto {
2934 let container_block_size = block.explicit_block_containing_size(shared_context);
2935 let fragment = block.fragment();
2936 fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size, container_block_size);
2937 // For replaced block flow, the rest of the constraint solving will
2938 // take inline-size to be specified as the value computed here.
2939 MaybeAuto::Specified(fragment.content_box().size.inline)
2940 }
2941
2942 }
2943
2944 impl ISizeAndMarginsComputer for FloatNonReplaced {
2945 /// CSS Section 10.3.5
2946 ///
2947 /// If inline-size is computed as 'auto', the used value is the 'shrink-to-fit' inline-size.
solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution2948 fn solve_inline_size_constraints(&self,
2949 block: &mut BlockFlow,
2950 input: &ISizeConstraintInput)
2951 -> ISizeConstraintSolution {
2952 let (computed_inline_size, inline_start_margin, inline_end_margin, available_inline_size) =
2953 (input.computed_inline_size,
2954 input.inline_start_margin,
2955 input.inline_end_margin,
2956 input.available_inline_size);
2957 let margin_inline_start = inline_start_margin.specified_or_zero();
2958 let margin_inline_end = inline_end_margin.specified_or_zero();
2959 let available_inline_size_float = available_inline_size - margin_inline_start -
2960 margin_inline_end;
2961 let shrink_to_fit = block.get_shrink_to_fit_inline_size(available_inline_size_float);
2962 let inline_size = computed_inline_size.specified_or_default(shrink_to_fit);
2963 debug!("assign_inline_sizes_float -- inline_size: {:?}", inline_size);
2964 ISizeConstraintSolution::new(inline_size, margin_inline_start, margin_inline_end)
2965 }
2966 }
2967
2968 impl ISizeAndMarginsComputer for FloatReplaced {
2969 /// CSS Section 10.3.5
2970 ///
2971 /// If inline-size is computed as 'auto', the used value is the 'shrink-to-fit' inline-size.
solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution2972 fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput)
2973 -> ISizeConstraintSolution {
2974 let (computed_inline_size, inline_start_margin, inline_end_margin) =
2975 (input.computed_inline_size, input.inline_start_margin, input.inline_end_margin);
2976 let margin_inline_start = inline_start_margin.specified_or_zero();
2977 let margin_inline_end = inline_end_margin.specified_or_zero();
2978 let inline_size = match computed_inline_size {
2979 MaybeAuto::Specified(w) => w,
2980 MaybeAuto::Auto => panic!("FloatReplaced: inline_size should have been computed by now")
2981 };
2982 debug!("assign_inline_sizes_float -- inline_size: {:?}", inline_size);
2983 ISizeConstraintSolution::new(inline_size, margin_inline_start, margin_inline_end)
2984 }
2985
2986 /// Calculate used value of inline-size just like we do for inline replaced elements.
initial_computed_inline_size(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, shared_context: &SharedStyleContext) -> MaybeAuto2987 fn initial_computed_inline_size(&self,
2988 block: &mut BlockFlow,
2989 parent_flow_inline_size: Au,
2990 shared_context: &SharedStyleContext)
2991 -> MaybeAuto {
2992 let container_block_size = block.explicit_block_containing_size(shared_context);
2993 let fragment = block.fragment();
2994 fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size, container_block_size);
2995 // For replaced block flow, the rest of the constraint solving will
2996 // take inline-size to be specified as the value computed here.
2997 MaybeAuto::Specified(fragment.content_box().size.inline)
2998 }
2999 }
3000
3001 impl ISizeAndMarginsComputer for InlineBlockNonReplaced {
3002 /// Compute inline-start and inline-end margins and inline-size.
solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution3003 fn solve_inline_size_constraints(&self,
3004 block: &mut BlockFlow,
3005 input: &ISizeConstraintInput)
3006 -> ISizeConstraintSolution {
3007 let (computed_inline_size,
3008 inline_start_margin,
3009 inline_end_margin,
3010 available_inline_size) =
3011 (input.computed_inline_size,
3012 input.inline_start_margin,
3013 input.inline_end_margin,
3014 input.available_inline_size);
3015
3016 // For inline-blocks, `auto` margins compute to 0.
3017 let inline_start_margin = inline_start_margin.specified_or_zero();
3018 let inline_end_margin = inline_end_margin.specified_or_zero();
3019
3020 // If inline-size is set to 'auto', and this is an inline block, use the
3021 // shrink to fit algorithm (see CSS 2.1 § 10.3.9)
3022 let inline_size = match computed_inline_size {
3023 MaybeAuto::Auto => {
3024 block.get_shrink_to_fit_inline_size(available_inline_size - (inline_start_margin +
3025 inline_end_margin))
3026 }
3027 MaybeAuto::Specified(inline_size) => inline_size,
3028 };
3029
3030 ISizeConstraintSolution::new(inline_size, inline_start_margin, inline_end_margin)
3031 }
3032 }
3033
3034 impl ISizeAndMarginsComputer for InlineBlockReplaced {
3035 /// Compute inline-start and inline-end margins and inline-size.
3036 ///
3037 /// ISize has already been calculated. We now calculate the margins just
3038 /// like for non-replaced blocks.
solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution3039 fn solve_inline_size_constraints(&self,
3040 block: &mut BlockFlow,
3041 input: &ISizeConstraintInput)
3042 -> ISizeConstraintSolution {
3043 debug_assert!(match input.computed_inline_size {
3044 MaybeAuto::Specified(_) => true,
3045 MaybeAuto::Auto => false,
3046 });
3047
3048 let (computed_inline_size,
3049 inline_start_margin,
3050 inline_end_margin,
3051 available_inline_size) =
3052 (input.computed_inline_size,
3053 input.inline_start_margin,
3054 input.inline_end_margin,
3055 input.available_inline_size);
3056
3057 // For inline-blocks, `auto` margins compute to 0.
3058 let inline_start_margin = inline_start_margin.specified_or_zero();
3059 let inline_end_margin = inline_end_margin.specified_or_zero();
3060
3061 // If inline-size is set to 'auto', and this is an inline block, use the
3062 // shrink to fit algorithm (see CSS 2.1 § 10.3.9)
3063 let inline_size = match computed_inline_size {
3064 MaybeAuto::Auto => {
3065 block.get_shrink_to_fit_inline_size(available_inline_size - (inline_start_margin +
3066 inline_end_margin))
3067 }
3068 MaybeAuto::Specified(inline_size) => inline_size,
3069 };
3070
3071 ISizeConstraintSolution::new(inline_size, inline_start_margin, inline_end_margin)
3072 }
3073
3074 /// Calculate used value of inline-size just like we do for inline replaced elements.
initial_computed_inline_size(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, shared_context: &SharedStyleContext) -> MaybeAuto3075 fn initial_computed_inline_size(&self,
3076 block: &mut BlockFlow,
3077 parent_flow_inline_size: Au,
3078 shared_context: &SharedStyleContext)
3079 -> MaybeAuto {
3080 let container_block_size = block.explicit_block_containing_size(shared_context);
3081 let fragment = block.fragment();
3082 fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size, container_block_size);
3083 // For replaced block flow, the rest of the constraint solving will
3084 // take inline-size to be specified as the value computed here.
3085 MaybeAuto::Specified(fragment.content_box().size.inline)
3086 }
3087 }
3088
3089 impl ISizeAndMarginsComputer for InlineFlexItem {
3090 // Replace the default method directly to prevent recalculating and setting margins again
3091 // which has already been set by its parent.
compute_used_inline_size(&self, block: &mut BlockFlow, shared_context: &SharedStyleContext, parent_flow_inline_size: Au)3092 fn compute_used_inline_size(&self,
3093 block: &mut BlockFlow,
3094 shared_context: &SharedStyleContext,
3095 parent_flow_inline_size: Au) {
3096 let container_block_size = block.explicit_block_containing_size(shared_context);
3097 block.fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size,
3098 container_block_size);
3099 }
3100
3101 // The used inline size and margins are set by parent flex flow, do nothing here.
solve_inline_size_constraints(&self, block: &mut BlockFlow, _: &ISizeConstraintInput) -> ISizeConstraintSolution3102 fn solve_inline_size_constraints(&self,
3103 block: &mut BlockFlow,
3104 _: &ISizeConstraintInput)
3105 -> ISizeConstraintSolution {
3106 let fragment = block.fragment();
3107 ISizeConstraintSolution::new(fragment.border_box.size.inline,
3108 fragment.margin.inline_start,
3109 fragment.margin.inline_end)
3110 }
3111 }
3112