2 * Copyright (C) 2018 Apple Inc. All Rights Reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 // All geometry here is absolute to the formatting context's root.
27 class FloatingContext {
28 constructor(floatingState, parentFormattingContext) {
29 this.m_floatingState = floatingState;
30 this.m_parentFormattingContext = parentFormattingContext;
33 computePosition(layoutBox) {
34 if (layoutBox.isOutOfFlowPositioned())
36 let displayBox = this._formattingState().displayBox(layoutBox);
37 if (layoutBox.isFloatingPositioned()) {
38 displayBox.setTopLeft(this._positionForFloating(layoutBox));
39 this._addFloatingBox(layoutBox);
42 if (Utils.hasClear(layoutBox))
43 return displayBox.setTopLeft(this._positionForClear(layoutBox));
44 // Intruding floats might force this box move.
45 displayBox.setTopLeft(this._computePositionToAvoidIntrudingFloats(layoutBox));
49 let leftBottom = this._bottom(this._leftFloatingStack());
50 let rightBottom = this._bottom(this._rightFloatingStack());
51 if (Number.isNaN(leftBottom) && Number.isNaN(rightBottom))
53 if (!Number.isNaN(leftBottom) && !Number.isNaN(rightBottom))
54 return Math.max(leftBottom, rightBottom);
55 if (!Number.isNaN(leftBottom))
60 _positionForFloating(floatingBox) {
61 let absoluteFloatingBox = this._mapMarginBoxToFormattingRoot(floatingBox);
63 return this._adjustedFloatingPosition(floatingBox, absoluteFloatingBox.top());
64 let verticalPosition = Math.max(absoluteFloatingBox.top(), this._mapDisplayMarginBoxToFormattingRoot(this._lastFloating()).top());
65 let spaceNeeded = absoluteFloatingBox.width();
67 let floatingPair = this._findInnerMostLeftAndRight(verticalPosition);
68 if (this._availableSpace(floatingBox.containingBlock(), floatingPair) >= spaceNeeded)
69 return this._adjustedFloatingPosition(floatingBox, verticalPosition, floatingPair);
70 verticalPosition = this._moveToNextVerticalPosition(floatingPair);
75 _positionForClear(layoutBox) {
76 ASSERT(Utils.hasClear(layoutBox));
77 let displayBox = this._formattingState().displayBox(layoutBox);
79 return displayBox.topLeft();
81 let leftBottom = Number.NaN;
82 let rightBottom = Number.NaN;
83 if (Utils.hasClearLeft(layoutBox) || Utils.hasClearBoth(layoutBox))
84 leftBottom = this._bottom(this._leftFloatingStack());
85 if (Utils.hasClearRight(layoutBox) || Utils.hasClearBoth(layoutBox))
86 rightBottom = this._bottom(this._rightFloatingStack());
88 if (!Number.isNaN(leftBottom) && !Number.isNaN(rightBottom))
89 return new LayoutPoint(Math.max(leftBottom, rightBottom), displayBox.left());
90 if (!Number.isNaN(leftBottom))
91 return new LayoutPoint(leftBottom, displayBox.left());
92 if (!Number.isNaN(rightBottom))
93 return new LayoutPoint(rightBottom, displayBox.left());
94 return displayBox.topLeft();
97 _computePositionToAvoidIntrudingFloats(layoutBox) {
98 if (!layoutBox.establishesBlockFormattingContext() || this._isEmpty())
99 return this._formattingState().displayBox(layoutBox).topLeft();
100 // The border box of a table, a block-level replaced element, or an element in the normal flow that establishes
101 // a new block formatting context (such as an element with 'overflow' other than 'visible') must not overlap the
102 // margin box of any floats in the same block formatting context as the element itself.
103 // For some reason, we position this as if it was floating left.
104 return this._positionForFloating(layoutBox);
107 _findInnerMostLeftAndRight(verticalPosition) {
108 let leftFloating = this._findFloatingAtVerticalPosition(verticalPosition, this._leftFloatingStack());
109 let rightFloating = this._findFloatingAtVerticalPosition(verticalPosition, this._rightFloatingStack());
110 return { left: leftFloating, right: rightFloating };
113 _moveToNextVerticalPosition(floatingPair) {
114 if (!floatingPair.left && !floatingPair.right)
116 let leftBottom = Number.POSITIVE_INFINITY;
117 let rightBottom = Number.POSITIVE_INFINITY;
118 if (floatingPair.left)
119 leftBottom = this._mapDisplayMarginBoxToFormattingRoot(floatingPair.left).bottom();
120 if (floatingPair.right)
121 rightBottom = this._mapDisplayMarginBoxToFormattingRoot(floatingPair.right).bottom();
122 return Math.min(leftBottom, rightBottom);
125 _availableSpace(containingBlock, floatingPair) {
126 let containingBlockContentBox = this._formattingContext().displayBox(containingBlock);
127 if (floatingPair.left && floatingPair.right)
128 return floatingPair.right.left() - floatingPair.left.right();
129 if (floatingPair.left) {
130 return containingBlockContentBox.width() - (this._mapDisplayMarginBoxToFormattingRoot(floatingPair.left).right() - this._mapBorderBoxToFormattingRoot(containingBlock).left());
132 if (floatingPair.right)
133 return this._mapDisplayMarginBoxToFormattingRoot(floatingPair.right).left();
134 return containingBlockContentBox.width();
137 _findFloatingAtVerticalPosition(verticalPosition, floatingStack) {
138 let index = floatingStack.length;
139 while (--index >= 0 && this._mapDisplayMarginBoxToFormattingRoot(floatingStack[index]).bottom() <= verticalPosition);
140 return index >= 0 ? floatingStack[index] : null;
144 return !this._leftFloatingStack().length && !this._rightFloatingStack().length;
147 _adjustedFloatingPosition(floatingBox, verticalPosition, leftRightFloatings) {
148 let containingBlock = floatingBox.containingBlock();
149 // Convert all coordinates relative to formatting context's root.
150 let left = this._mapContentBoxToFormattingRoot(containingBlock).left();
151 let right = this._mapContentBoxToFormattingRoot(containingBlock).right();
152 if (leftRightFloatings) {
153 if (leftRightFloatings.left) {
154 let floatingBoxRight = this._mapDisplayMarginBoxToFormattingRoot(leftRightFloatings.left).right();
155 if (floatingBoxRight > left)
156 left = floatingBoxRight;
159 if (leftRightFloatings.right) {
160 let floatingBoxLeft = this._mapDisplayMarginBoxToFormattingRoot(leftRightFloatings.right).left();
161 if (floatingBoxLeft < right)
162 right = floatingBoxLeft;
165 left += this._formattingContext().marginLeft(floatingBox);
166 right -= this._formattingContext().marginRight(floatingBox);
167 verticalPosition += this._formattingContext().marginTop(floatingBox);
168 // No convert them back relative to the floatingBox's containing block.
169 let containingBlockLeft = this._mapBorderBoxToFormattingRoot(containingBlock).left();
170 let containingBlockTop = this._mapBorderBoxToFormattingRoot(containingBlock).top();
171 left -= containingBlockLeft;
172 right -= containingBlockLeft;
173 verticalPosition -= containingBlockTop;
175 if (Utils.isFloatingLeft(floatingBox) || !Utils.isFloatingPositioned(floatingBox))
176 return new LayoutPoint(verticalPosition, left);
177 return new LayoutPoint(verticalPosition, right - this._formattingContext().displayBox(floatingBox).rect().width());
180 _bottom(floatingStack) {
181 if (!floatingStack || !floatingStack.length)
183 let max = Number.NEGATIVE_INFINITY;
184 for (let i = 0; i < floatingStack.length; ++i)
185 max = Math.max(this._mapDisplayMarginBoxToFormattingRoot(floatingStack[i]).bottom(), max);
189 _addFloatingBox(layoutBox) {
190 this._floatingState().addFloating(this._formattingContext().displayBox(layoutBox), Utils.isFloatingLeft(layoutBox));
193 _mapMarginBoxToFormattingRoot(layoutBox) {
194 ASSERT(layoutBox instanceof Layout.Box);
195 return this._mapDisplayMarginBoxToFormattingRoot(this._formattingState().displayBox(layoutBox));
198 _mapDisplayMarginBoxToFormattingRoot(displayBox) {
199 ASSERT(displayBox instanceof Display.Box);
200 return Utils.marginBox(displayBox, this._formattingState().displayBox(this._formattingRoot()));
203 _mapBorderBoxToFormattingRoot(layoutBox) {
204 let displayBox = this._formattingState().displayBox(layoutBox);
205 let rootDisplayBox = this._formattingState().displayBox(this._formattingRoot());
206 return Utils.borderBox(displayBox, rootDisplayBox);
209 _mapContentBoxToFormattingRoot(layoutBox) {
210 let displayBox = this._formattingState().displayBox(layoutBox);
211 let rootDisplayBox = this._formattingState().displayBox(this._formattingRoot());
212 return Utils.contentBox(displayBox, rootDisplayBox);
216 return this.m_floatingState;
219 _formattingContext() {
220 return this.m_parentFormattingContext;
224 return this._formattingState().formattingRoot();
228 return this._floatingState().formattingState();
232 return this._floatingState().lastFloating();
235 _leftFloatingStack() {
236 return this._floatingState().leftFloatingStack();
239 _rightFloatingStack() {
240 return this._floatingState().rightFloatingStack();