will-change: transform should affect nested position:fixed
[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 /*
27 class FloatingContext {
28 public:
29     void computePosition(Layout::Box&);
30     LayoutUnit left(LayoutUnit verticalPosition);
31     LayoutUnit right(LayoutUnit verticalPosition);
32     LayoutUnit bottom();
33
34 private:
35     LayoutPoint positionForFloating(const Layout::Box&);
36     LayoutPoint positionForClear(const Layout::Box&);
37     LayoutPoint computePositionToAvoidIntrudingFloats(const Layout::Box&);
38 };
39 */
40 // All geometry here is absolute to the formatting context's root.
41 class FloatingContext {
42     constructor(floatingState) {
43         this.m_floatingState = floatingState;
44     }
45
46     computePosition(layoutBox) {
47         if (layoutBox.isOutOfFlowPositioned())
48             return;
49         let displayBox = this._formattingState().displayBox(layoutBox);
50         if (layoutBox.isFloatingPositioned()) {
51             displayBox.setTopLeft(this._positionForFloating(layoutBox));
52             this._addFloatingBox(layoutBox);
53             return;
54         }
55         if (Utils.hasClear(layoutBox))
56             return displayBox.setTopLeft(this._positionForClear(layoutBox));
57         // Intruding floats might force this box move.
58         displayBox.setTopLeft(this._computePositionToAvoidIntrudingFloats(layoutBox));
59     }
60
61     left(verticalPosition) {
62         // Relative to the formatting context's root.
63         let leftFloating = this._findFloatingAtVerticalPosition(verticalPosition, this._leftFloatings());
64         if (!leftFloating)
65             return Number.NaN;
66         return this._mapDisplayMarginBoxToFormattingRoot(leftFloating).right();
67     }
68
69     right(verticalPosition) {
70         // Relative to the formatting context's root.
71         let rightFloating = this._findFloatingAtVerticalPosition(verticalPosition, this._rightFloatings());
72         if (!rightFloating)
73             return Number.NaN;
74         return this._mapDisplayMarginBoxToFormattingRoot(rightFloating).left();
75     }
76
77     bottom() {
78         let leftBottom = this._bottom(this._leftFloatings());
79         let rightBottom = this._bottom(this._rightFloatings());
80         if (Number.isNaN(leftBottom) && Number.isNaN(rightBottom))
81             return Number.NaN;
82         if (!Number.isNaN(leftBottom) && !Number.isNaN(rightBottom))
83             return Math.max(leftBottom, rightBottom);
84         if (!Number.isNaN(leftBottom))
85             return leftBottom;
86         return rightBottom;
87     }
88
89     _positionForFloating(floatingBox) {
90         let absoluteFloatingBox = this._mapMarginBoxToFormattingRoot(floatingBox);
91         if (this._isEmpty())
92             return this._adjustedFloatingPosition(floatingBox, absoluteFloatingBox.top());
93         let verticalPosition = Math.max(absoluteFloatingBox.top(), this._mapDisplayMarginBoxToFormattingRoot(this._lastFloating()).top());
94         let spaceNeeded = absoluteFloatingBox.width();
95         while (true) {
96             let floatingPair = this._findInnerMostLeftAndRight(verticalPosition);
97             if (this._availableSpace(floatingBox.containingBlock(), floatingPair) >= spaceNeeded)
98                 return this._adjustedFloatingPosition(floatingBox, verticalPosition, floatingPair);
99             verticalPosition = this._moveToNextVerticalPosition(floatingPair);
100         }
101         return Math.Nan;
102     }
103
104     _positionForClear(layoutBox) {
105         ASSERT(Utils.hasClear(layoutBox));
106         let displayBox = this._formattingState().displayBox(layoutBox);
107         if (this._isEmpty())
108             return displayBox.topLeft();
109
110         let leftBottom = Number.NaN;
111         let rightBottom = Number.NaN;
112         if (Utils.hasClearLeft(layoutBox) || Utils.hasClearBoth(layoutBox))
113             leftBottom = this._bottom(this._leftFloatings());
114         if (Utils.hasClearRight(layoutBox) || Utils.hasClearBoth(layoutBox))
115             rightBottom = this._bottom(this._rightFloatings());
116
117         if (!Number.isNaN(leftBottom) && !Number.isNaN(rightBottom))
118             return new LayoutPoint(Math.max(leftBottom, rightBottom), displayBox.left());
119         if (!Number.isNaN(leftBottom))
120             return new LayoutPoint(leftBottom, displayBox.left());
121         if (!Number.isNaN(rightBottom))
122             return new LayoutPoint(rightBottom, displayBox.left());
123         return displayBox.topLeft();
124     }
125
126     _computePositionToAvoidIntrudingFloats(layoutBox) {
127         if (!layoutBox.establishesBlockFormattingContext() || this._isEmpty())
128             return this._formattingState().displayBox(layoutBox).topLeft();
129         // The border box of a table, a block-level replaced element, or an element in the normal flow that establishes
130         // a new block formatting context (such as an element with 'overflow' other than 'visible') must not overlap the
131         // margin box of any floats in the same block formatting context as the element itself.
132         // For some reason, we position this as if it was floating left.
133         return this._positionForFloating(layoutBox);
134     }
135
136     _findInnerMostLeftAndRight(verticalPosition) {
137         let leftFloating = this._findFloatingAtVerticalPosition(verticalPosition, this._leftFloatings());
138         let rightFloating = this._findFloatingAtVerticalPosition(verticalPosition, this._rightFloatings());
139         return { left: leftFloating, right: rightFloating };
140     }
141
142     _moveToNextVerticalPosition(floatingPair) {
143         if (!floatingPair.left && !floatingPair.right)
144             return Math.NaN;
145         let leftBottom = Number.POSITIVE_INFINITY;
146         let rightBottom = Number.POSITIVE_INFINITY;
147         if (floatingPair.left)
148             leftBottom = this._mapDisplayMarginBoxToFormattingRoot(floatingPair.left).bottom();
149         if (floatingPair.right)
150             rightBottom = this._mapDisplayMarginBoxToFormattingRoot(floatingPair.right).bottom();
151         return Math.min(leftBottom, rightBottom);
152     }
153
154     _availableSpace(containingBlock, floatingPair) {
155         let containingBlockContentBox = this._formattingState().displayBox(containingBlock);
156         if (floatingPair.left && floatingPair.right)
157             return floatingPair.right.left() - floatingPair.left.right();
158         if (floatingPair.left) {
159             return containingBlockContentBox.width() - (this._mapDisplayMarginBoxToFormattingRoot(floatingPair.left).right() - this._mapBorderBoxToFormattingRoot(containingBlock).left());
160         }
161         if (floatingPair.right)
162             return this._mapDisplayMarginBoxToFormattingRoot(floatingPair.right).left();
163         return containingBlockContentBox.width();
164     }
165
166     _findFloatingAtVerticalPosition(verticalPosition, floatingStack) {
167         let index = floatingStack.length;
168         while (--index >= 0 && this._mapDisplayMarginBoxToFormattingRoot(floatingStack[index]).bottom() <= verticalPosition);
169         return index >= 0 ? floatingStack[index] : null;
170     }
171
172     _isEmpty() {
173         return !this._leftFloatings().length && !this._rightFloatings().length;
174     }
175
176     _adjustedFloatingPosition(floatingBox, verticalPosition, leftRightFloatings) {
177         let containingBlock = floatingBox.containingBlock();
178         // Convert all coordinates relative to formatting context's root.
179         let left = this._mapContentBoxToFormattingRoot(containingBlock).left();
180         let right = this._mapContentBoxToFormattingRoot(containingBlock).right();
181         if (leftRightFloatings) {
182             if (leftRightFloatings.left) {
183                 let floatingBoxRight = this._mapDisplayMarginBoxToFormattingRoot(leftRightFloatings.left).right();
184                 if (floatingBoxRight > left)
185                     left = floatingBoxRight;
186             }
187
188             if (leftRightFloatings.right) {
189                 let floatingBoxLeft = this._mapDisplayMarginBoxToFormattingRoot(leftRightFloatings.right).left();
190                 if (floatingBoxLeft < right)
191                     right = floatingBoxLeft;
192             }
193         }
194         let floatingDisplayBox = this._formattingState().displayBox(floatingBox);
195         left += floatingDisplayBox.marginLeft();
196         right -= floatingDisplayBox.marginRight();
197         verticalPosition += floatingDisplayBox.marginTop();
198         // No convert them back relative to the floatingBox's containing block.
199         let containingBlockLeft = this._mapBorderBoxToFormattingRoot(containingBlock).left();
200         let containingBlockTop = this._mapBorderBoxToFormattingRoot(containingBlock).top();
201         left -= containingBlockLeft;
202         right -= containingBlockLeft;
203         verticalPosition -= containingBlockTop;
204
205         if (Utils.isFloatingLeft(floatingBox) || !Utils.isFloatingPositioned(floatingBox))
206             return new LayoutPoint(verticalPosition, left);
207         return new LayoutPoint(verticalPosition, right - floatingDisplayBox.rect().width());
208     }
209
210     _bottom(floatingStack) {
211         if (!floatingStack || !floatingStack.length)
212             return Number.NaN;
213         let max = Number.NEGATIVE_INFINITY;
214         for (let i = 0; i < floatingStack.length; ++i)
215             max = Math.max(this._mapDisplayMarginBoxToFormattingRoot(floatingStack[i]).bottom(), max);
216         return max;
217     }
218
219     _addFloatingBox(layoutBox) {
220         this._floatingState().addFloating(this._formattingState().displayBox(layoutBox), Utils.isFloatingLeft(layoutBox));
221     }
222
223     _mapMarginBoxToFormattingRoot(layoutBox) {
224         ASSERT(layoutBox instanceof Layout.Box);
225         return this._mapDisplayMarginBoxToFormattingRoot(this._formattingState().displayBox(layoutBox));
226     }
227
228     _mapDisplayMarginBoxToFormattingRoot(displayBox) {
229         ASSERT(displayBox instanceof Display.Box);
230         return Utils.marginBox(displayBox, this._formattingState().displayBox(this.formattingRoot()));
231     }
232
233     _mapBorderBoxToFormattingRoot(layoutBox) {
234         let displayBox = this._formattingState().displayBox(layoutBox);
235         let rootDisplayBox = this._formattingState().displayBox(this.formattingRoot());
236         return Utils.borderBox(displayBox, rootDisplayBox);
237     }
238
239     _mapContentBoxToFormattingRoot(layoutBox) {
240         let displayBox = this._formattingState().displayBox(layoutBox);
241         let rootDisplayBox = this._formattingState().displayBox(this.formattingRoot());
242         return Utils.contentBox(displayBox, rootDisplayBox);
243     }
244
245     formattingRoot() {
246         return this._formattingState().formattingRoot();
247     }
248
249     _floatingState() {
250         return this.m_floatingState;
251     }
252
253     _formattingState() {
254         return this._floatingState().formattingState();
255     }
256
257     _lastFloating() {
258         return this._floatingState().lastFloating();
259     }
260
261     _leftFloatings() {
262         return this._floatingState().leftFloatingStack();
263     }
264
265     _rightFloatings() {
266         return this._floatingState().rightFloatingStack();
267     }
268 }