[LayoutReloaded] Remove left/right width/height setters from Layout.Box
[WebKit-https.git] / Tools / LayoutReloaded / FormattingContext / FloatingContext.js
1 /*
2  * Copyright (C) 2018 Apple Inc. All Rights Reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
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.
12  *
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.
24  */
25
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;
32     }
33
34     computePosition(layoutBox) {
35         if (layoutBox.isOutOfFlowPositioned())
36             return;
37         let displayBox = this._formattingContext().toDisplayBox(layoutBox);
38         if (layoutBox.isFloatingPositioned()) {
39             let position = this._positionForFloating(layoutBox);
40             this._addFloating(layoutBox);
41             return displayBox.setTopLeft(position);
42         }
43         if (Utils.hasClear(layoutBox))
44             return displayBox.setTopLeft(this._positionForClear(layoutBox));
45         // Intruding floats might force this box move.
46         displayBox.setTopLeft(this._computePositionToAvoidIntrudingFloats(layoutBox));
47     }
48
49     bottom() {
50         let leftBottom = this._bottom(this.m_leftFloatingBoxStack);
51         let rightBottom = this._bottom(this.m_rightFloatingBoxStack);
52         if (Number.isNaN(leftBottom) && Number.isNaN(rightBottom))
53             return Number.NaN;
54         if (!Number.isNaN(leftBottom) && !Number.isNaN(rightBottom))
55             return Math.max(leftBottom, rightBottom);
56         if (!Number.isNaN(leftBottom))
57             return leftBottom;
58         return rightBottom;
59     }
60
61     _positionForFloating(floatingBox) {
62         if (this._isEmpty())
63             return this._adjustedFloatingPosition(floatingBox, this._formattingContext().absoluteMarginBox(floatingBox).top());
64         let verticalPosition = Math.max(this._formattingContext().absoluteMarginBox(floatingBox).top(), this._formattingContext().absoluteMarginBox(this.m_lastFloating).top());
65         let spaceNeeded = this._formattingContext().absoluteMarginBox(floatingBox).width();
66         while (true) {
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);
71         }
72         return Math.Nan;
73     }
74
75     _positionForClear(layoutBox) {
76         ASSERT(Utils.hasClear(layoutBox));
77         if (this._isEmpty())
78             return layoutBox.topLeft();
79
80         let leftBottom = Number.NaN;
81         let rightBottom = Number.NaN;
82         if (Utils.hasClearLeft(layoutBox) || Utils.hasClearBoth(layoutBox))
83             leftBottom = this._bottom(this.m_leftFloatingBoxStack);
84         if (Utils.hasClearRight(layoutBox) || Utils.hasClearBoth(layoutBox))
85             rightBottom = this._bottom(this.m_rightFloatingBoxStack);
86
87         if (!Number.isNaN(leftBottom) && !Number.isNaN(rightBottom))
88             return new LayoutPoint(Math.max(leftBottom, rightBottom), layoutBox.rect().left());
89         if (!Number.isNaN(leftBottom))
90             return new LayoutPoint(leftBottom, layoutBox.rect().left());
91         if (!Number.isNaN(rightBottom))
92             return new LayoutPoint(rightBottom, layoutBox.rect().left());
93         return layoutBox.topLeft();
94     }
95
96     _computePositionToAvoidIntrudingFloats(layoutBox) {
97         if (!layoutBox.establishesBlockFormattingContext() || this._isEmpty())
98             return layoutBox.topLeft();
99         // The border box of a table, a block-level replaced element, or an element in the normal flow that establishes
100         // a new block formatting context (such as an element with 'overflow' other than 'visible') must not overlap the
101         // margin box of any floats in the same block formatting context as the element itself.
102         // For some reason, we position this as if it was floating left.
103         return this._positionForFloating(layoutBox);
104     }
105
106     _addFloating(floatingBox) {
107         this.m_lastFloating = floatingBox;
108         if (Utils.isFloatingLeft(floatingBox)) {
109             this.m_leftFloatingBoxStack.push(floatingBox);
110             return;
111         }
112         this.m_rightFloatingBoxStack.push(floatingBox);
113     }
114
115     _findInnerMostLeftAndRight(verticalPosition) {
116         let leftFloating = this._findFloatingAtVerticalPosition(verticalPosition, this.m_leftFloatingBoxStack);
117         let rightFloating = this._findFloatingAtVerticalPosition(verticalPosition, this.m_rightFloatingBoxStack);
118         return { left: leftFloating, right: rightFloating };
119     }
120
121     _moveToNextVerticalPosition(floatingPair) {
122         if (!floatingPair.left && !floatingPair.right)
123             return Math.NaN;
124         let leftBottom = Number.POSITIVE_INFINITY;
125         let rightBottom = Number.POSITIVE_INFINITY;
126         if (floatingPair.left)
127             leftBottom = this._formattingContext().absoluteMarginBox(floatingPair.left).bottom();
128         if (floatingPair.right)
129             rightBottom = this._formattingContext().absoluteMarginBox(floatingPair.right).bottom();
130         return Math.min(leftBottom, rightBottom);
131     }
132
133     _availableSpace(containingBlock, floatingPair) {
134         if (floatingPair.left && floatingPair.right)
135             return this._formattingContext().absoluteMarginBox(floatingPair.right).left() - this._formattingContext().absoluteMarginBox(floatingPair.left).right();
136         if (floatingPair.left)
137             return containingBlock.contentBox().width() - (this._formattingContext().absoluteMarginBox(floatingPair.left).right() - this._formattingContext().absoluteBorderBox(containingBlock).left());
138         if (floatingPair.right)
139             return this._formattingContext().absoluteMarginBox(floatingPair.right).left();
140         return containingBlock.contentBox().width();
141     }
142
143     _findFloatingAtVerticalPosition(verticalPosition, floatingStack) {
144         let index = floatingStack.length;
145         while (--index >= 0 && this._formattingContext().absoluteMarginBox(floatingStack[index]).bottom() <= verticalPosition);
146         return index >= 0 ? floatingStack[index] : null;
147     }
148
149     _isEmpty() {
150         return !this.m_leftFloatingBoxStack.length && !this.m_rightFloatingBoxStack.length;
151     }
152
153     _adjustedFloatingPosition(floatingBox, verticalPosition, leftRightFloatings) {
154         let containingBlock = floatingBox.containingBlock();
155         // Convert all coordinates relative to formatting context's root.
156         let left = this._formattingContext().absoluteContentBox(containingBlock).left();
157         let right = this._formattingContext().absoluteContentBox(containingBlock).right();
158         if (leftRightFloatings) {
159             if (leftRightFloatings.left) {
160                 let floatingBoxRight = this._formattingContext().absoluteMarginBox(leftRightFloatings.left).right();
161                 if (floatingBoxRight > left)
162                     left = floatingBoxRight;
163             }
164
165             if (leftRightFloatings.right) {
166                 let floatingBoxLeft = this._formattingContext().absoluteMarginBox(leftRightFloatings.right).left();
167                 if (floatingBoxLeft < right)
168                     right = floatingBoxLeft;
169             }
170         }
171         left += this._formattingContext().marginLeft(floatingBox);
172         right -= this._formattingContext().marginRight(floatingBox);
173         verticalPosition += this._formattingContext().marginTop(floatingBox);
174         // No convert them back relative to the floatingBox's containing block.
175         let containingBlockLeft = this._formattingContext().absoluteBorderBox(containingBlock).left();
176         let containingBlockTop = this._formattingContext().absoluteBorderBox(containingBlock).top();
177         left -= containingBlockLeft;
178         right -= containingBlockLeft;
179         verticalPosition -= containingBlockTop;
180
181         if (Utils.isFloatingLeft(floatingBox) || !Utils.isFloatingPositioned(floatingBox))
182             return new LayoutPoint(verticalPosition, left);
183         return new LayoutPoint(verticalPosition, right - floatingBox.rect().width());
184     }
185
186     _bottom(floatingStack) {
187         if (!floatingStack || !floatingStack.length)
188             return Number.NaN;
189         let max = Number.NEGATIVE_INFINITY;
190         for (let i = 0; i < floatingStack.length; ++i)
191             max = Math.max(this._formattingContext().absoluteMarginBox(floatingStack[i]).bottom(), max);
192         return max;
193     }
194
195     _formattingContext() {
196         return this.m_formattingContext;
197     }
198 }