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 class FloatingContext {
27 constructor(formattingContext) {
28 this.m_leftFloatingBoxStack = new Array();
29 this.m_rightFloatingBoxStack = new Array();
30 this.m_lastFloating = null;
31 this.m_formattingContext = formattingContext;
34 computePosition(box) {
35 if (box.isOutOfFlowPositioned())
37 if (box.isFloatingPositioned()) {
38 let position = this._positionForFloating(box);
39 this._addFloating(box);
42 if (Utils.hasClear(box))
43 return this._positionForClear(box);
44 // Intruding floats might force this box move.
45 return this._computePositionToAvoidIntrudingFloats(box);
49 let leftBottom = this._bottom(this.m_leftFloatingBoxStack);
50 let rightBottom = this._bottom(this.m_rightFloatingBoxStack);
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) {
62 return this._adjustedFloatingPosition(floatingBox, this._formattingContext().absoluteMarginBox(floatingBox).top());
63 let verticalPosition = Math.max(this._formattingContext().absoluteMarginBox(floatingBox).top(), this._formattingContext().absoluteMarginBox(this.m_lastFloating).top());
64 let spaceNeeded = this._formattingContext().absoluteMarginBox(floatingBox).width();
66 let floatingPair = this._findInnerMostLeftAndRight(verticalPosition);
67 if (this._availableSpace(floatingBox.containingBlock(), floatingPair) >= spaceNeeded)
68 return this._adjustedFloatingPosition(floatingBox, verticalPosition, floatingPair);
69 verticalPosition = this._moveToNextVerticalPosition(floatingPair);
74 _positionForClear(box) {
75 ASSERT(Utils.hasClear(box));
79 let leftBottom = Number.NaN;
80 let rightBottom = Number.NaN;
81 if (Utils.hasClearLeft(box) || Utils.hasClearBoth(box))
82 leftBottom = this._bottom(this.m_leftFloatingBoxStack);
83 if (Utils.hasClearRight(box) || Utils.hasClearBoth(box))
84 rightBottom = this._bottom(this.m_rightFloatingBoxStack);
86 if (!Number.isNaN(leftBottom) && !Number.isNaN(rightBottom))
87 return new LayoutPoint(Math.max(leftBottom, rightBottom), box.rect().left());
88 if (!Number.isNaN(leftBottom))
89 return new LayoutPoint(leftBottom, box.rect().left());
90 if (!Number.isNaN(rightBottom))
91 return new LayoutPoint(rightBottom, box.rect().left());
95 _computePositionToAvoidIntrudingFloats(box) {
96 if (!box.establishesBlockFormattingContext() || this._isEmpty())
98 // The border box of a table, a block-level replaced element, or an element in the normal flow that establishes
99 // a new block formatting context (such as an element with 'overflow' other than 'visible') must not overlap the
100 // margin box of any floats in the same block formatting context as the element itself.
101 // For some reason, we position this as if it was floating left.
102 return this._positionForFloating(box);
105 _addFloating(floatingBox) {
106 this.m_lastFloating = floatingBox;
107 if (Utils.isFloatingLeft(floatingBox)) {
108 this.m_leftFloatingBoxStack.push(floatingBox);
111 this.m_rightFloatingBoxStack.push(floatingBox);
114 _findInnerMostLeftAndRight(verticalPosition) {
115 let leftFloating = this._findFloatingAtVerticalPosition(verticalPosition, this.m_leftFloatingBoxStack);
116 let rightFloating = this._findFloatingAtVerticalPosition(verticalPosition, this.m_rightFloatingBoxStack);
117 return { left: leftFloating, right: rightFloating };
120 _moveToNextVerticalPosition(floatingPair) {
121 if (!floatingPair.left && !floatingPair.right)
123 let leftBottom = Number.POSITIVE_INFINITY;
124 let rightBottom = Number.POSITIVE_INFINITY;
125 if (floatingPair.left)
126 leftBottom = this._formattingContext().absoluteMarginBox(floatingPair.left).bottom();
127 if (floatingPair.right)
128 rightBottom = this._formattingContext().absoluteMarginBox(floatingPair.right).bottom();
129 return Math.min(leftBottom, rightBottom);
132 _availableSpace(containingBlock, floatingPair) {
133 if (floatingPair.left && floatingPair.right)
134 return this._formattingContext().absoluteMarginBox(floatingPair.right).left() - this._formattingContext().absoluteMarginBox(floatingPair.left).right();
135 if (floatingPair.left)
136 return containingBlock.contentBox().width() - (this._formattingContext().absoluteMarginBox(floatingPair.left).right() - this._formattingContext().absoluteBorderBox(containingBlock).left());
137 if (floatingPair.right)
138 return this._formattingContext().absoluteMarginBox(floatingPair.right).left();
139 return containingBlock.contentBox().width();
142 _findFloatingAtVerticalPosition(verticalPosition, floatingStack) {
143 let index = floatingStack.length;
144 while (--index >= 0 && this._formattingContext().absoluteMarginBox(floatingStack[index]).bottom() <= verticalPosition);
145 return index >= 0 ? floatingStack[index] : null;
149 return !this.m_leftFloatingBoxStack.length && !this.m_rightFloatingBoxStack.length;
152 _adjustedFloatingPosition(floatingBox, verticalPosition, leftRightFloatings) {
153 let containingBlock = floatingBox.containingBlock();
154 // Convert all coordinates relative to formatting context's root.
155 let left = this._formattingContext().absoluteContentBox(containingBlock).left();
156 let right = this._formattingContext().absoluteContentBox(containingBlock).right();
157 if (leftRightFloatings) {
158 if (leftRightFloatings.left) {
159 let floatingBoxRight = this._formattingContext().absoluteMarginBox(leftRightFloatings.left).right();
160 if (floatingBoxRight > left)
161 left = floatingBoxRight;
164 if (leftRightFloatings.right) {
165 let floatingBoxLeft = this._formattingContext().absoluteMarginBox(leftRightFloatings.right).left();
166 if (floatingBoxLeft < right)
167 right = floatingBoxLeft;
170 left += this._formattingContext().marginLeft(floatingBox);
171 right -= this._formattingContext().marginRight(floatingBox);
172 verticalPosition += this._formattingContext().marginTop(floatingBox);
173 // No convert them back relative to the floatingBox's containing block.
174 let containingBlockLeft = this._formattingContext().absoluteBorderBox(containingBlock).left();
175 let containingBlockTop = this._formattingContext().absoluteBorderBox(containingBlock).top();
176 left -= containingBlockLeft;
177 right -= containingBlockLeft;
178 verticalPosition -= containingBlockTop;
180 if (Utils.isFloatingLeft(floatingBox) || !Utils.isFloatingPositioned(floatingBox))
181 return new LayoutPoint(verticalPosition, left);
182 return new LayoutPoint(verticalPosition, right - floatingBox.rect().width());
185 _bottom(floatingStack) {
186 if (!floatingStack || !floatingStack.length)
188 let max = Number.NEGATIVE_INFINITY;
189 for (let i = 0; i < floatingStack.length; ++i)
190 max = Math.max(this._formattingContext().absoluteMarginBox(floatingStack[i]).bottom(), max);
194 _formattingContext() {
195 return this.m_formattingContext;