[LFC][IFC] Add support for out-of-flow positioned boxes
[WebKit-https.git] / Tools / LayoutReloaded / Utils.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 LayoutPoint {
27     constructor(top, left) {
28         this.m_top = top;
29         this.m_left = left;
30     }
31
32     setLeft(left) {
33         this.m_left = left;
34     }
35
36     setTop(top) {
37         this.m_top = top;
38     }
39
40     left() {
41         return this.m_left;
42     }
43
44     top() {
45         return this.m_top;
46     }
47
48     shiftLeft(distance) {
49         this.m_left += distance;
50     }
51
52     shiftTop(distance) {
53         this.m_top += distance;
54     }
55
56     moveBy(distance) {
57         if (distance.top && distance.left) {
58             this.m_top += distance.top();
59             this.m_left += distance.left();
60         }
61         else if (distance.width && distance.height) {
62             this.m_top += distance.height();
63             this.m_left += distance.width();
64         }
65     }
66
67     equal(other) {
68         return this.top() == other.top() && this.left() == other.left();
69     }
70
71     clone() {
72         return new LayoutPoint(this.top(), this.left());
73     }
74 }
75
76 class LayoutSize {
77     constructor(width, height) {
78         this.m_width = width;
79         this.m_height = height;
80     }
81
82     setWidth(width) {
83         this.m_width = width;
84     }
85
86     setHeight(height) {
87         this.m_height = height;
88     }
89
90     width() {
91         return this.m_width;
92     }
93
94     height() {
95         return this.m_height;
96     }
97
98     growBy(distance) {
99         this.m_width += distance.width();
100         this.m_height += distance.height();
101     }
102
103     shrinkBy(distance) {
104         this.m_width -= distance.width();
105         this.m_height -= distance.height();
106     }
107
108     isEmpty() {
109         return this.m_width <= 0 || this.m_height <= 0;
110     }
111
112     equal(other) {
113         return this.width() == other.width() && this.height() == other.height();
114     }
115
116     clone() {
117         return new LayoutSize(this.width(), this.height());
118     }
119 }
120
121 class LayoutRect {
122     constructor(topLeft, size) {
123         this.m_topLeft = topLeft.clone();
124         this.m_size = size.clone();
125     }
126
127     setTop(top) {
128         this.m_topLeft.setTop(top);
129     }
130
131     setLeft(left) {
132         this.m_topLeft.setLeft(left);
133     }
134
135     setBottom(bottom) {
136         this.m_size.setHeight(bottom - this.m_topLeft.top());
137     }
138
139     setRight(right) {
140         this.m_size.setWidth(right - this.m_topLeft.left());
141     }
142
143     left() {
144         return this.m_topLeft.left();
145     }
146
147     top() {
148         return this.m_topLeft.top();
149     }
150
151     bottom() {
152         return this.m_topLeft.top() + this.m_size.height();
153     }
154
155     right() {
156         return this.m_topLeft.left() + this.m_size.width();
157     }
158
159     setTopLeft(topLeft) {
160         this.m_topLeft = topLeft.clone();
161     }
162
163     topLeft() {
164         return this.m_topLeft.clone();
165     }
166
167     topRight() {
168         return new LayoutPoint(this.top(), this.right());
169     }
170
171     bottomRight() {
172         return new LayoutPoint(this.bottom(), this.right());
173     }
174
175     setWidth(width) {
176         this.m_size.setWidth(width);
177     }
178
179     setHeight(height) {
180         this.m_size.setHeight(height);
181     }
182
183     setSize(newSize) {
184         this.m_size = newSize.clone();
185     }
186
187     size() {
188         return this.m_size.clone();
189     }
190
191     width() {
192         return this.m_size.width();
193     }
194
195     height() {
196         return this.m_size.height();
197     }
198
199     growBy(distance) {
200         this.m_size.growBy(distance);
201     }
202
203     shrinkBy(distance) {
204         this.m_size.shrinkBy(distance);
205     }
206
207     moveBy(distance) {
208         this.m_topLeft.moveBy(distance);
209     }
210
211     growHorizontally(distance) {
212         this.m_size.setWidth(this.m_size.width() + distance);
213     }
214
215     moveHorizontally(distance) {
216         this.m_topLeft.shiftLeft(distance);
217     }
218
219     moveVertically(distance) {
220         this.m_topLeft.shiftTop(distance);
221     }
222
223     isEmpty() {
224         return this.m_size.isEmpty();
225     }
226
227     equal(other) {
228         return this.m_topLeft.equal(other.topLeft()) && this.m_size.equal(other.size());
229     }
230
231     intersects(other) {
232         return !this.isEmpty() && !other.isEmpty()
233         && this.left() < other.right() && other.left() < this.right()
234         && this.top() < other.bottom() && other.top() < this.bottom();
235     }
236
237     contains(other) {
238         return this.left() <= other.left() && this.right() >= other.right()
239         && this.top() <= other.top() && this.bottom() >= other.bottom();
240     }
241
242     clone() {
243         return new LayoutRect(this.topLeft().clone(), this.size().clone());
244     }
245 }
246
247 function ASSERT_NOT_REACHED() {
248     throw Error("Should not reach!");
249 }
250
251 function ASSERT(statement) {
252     if (statement)
253         return;
254     throw Error("Assertion failure");
255 }
256
257 class Utils {
258     static computedValue(strValue, baseValue) {
259         if (strValue.indexOf("px") > -1)
260             return parseFloat(strValue);
261         if (strValue.indexOf("%") > -1)
262             return parseFloat(strValue) * baseValue / 100;
263         return Number.NaN;
264     }
265
266     static propertyIsAuto(propertyName, box) {
267         if (box.isAnonymous())
268             return true;
269         return window.getComputedStyle(box.node()).isPropertyValueInitial(propertyName);
270     }
271
272     static isWidthAuto(box) {
273         return Utils.propertyIsAuto("width", box);
274     }
275
276     static isHeightAuto(box) {
277         return Utils.propertyIsAuto("height", box);
278     }
279
280     static isTopAuto(box) {
281         return Utils.propertyIsAuto("top", box);
282     }
283
284     static isLeftAuto(box) {
285         return Utils.propertyIsAuto("left", box);
286     }
287
288     static isBottomAuto(box) {
289         return Utils.propertyIsAuto("bottom", box);
290     }
291
292     static isRightAuto(box) {
293         return Utils.propertyIsAuto("right", box);
294     }
295
296     static width(box) {
297         ASSERT(!Utils.isWidthAuto(box));
298         return parseFloat(window.getComputedStyle(box.node()).width);
299     }
300
301     static height(box) {
302         ASSERT(!Utils.isHeightAuto(box));
303         return parseFloat(window.getComputedStyle(box.node()).height);
304     }
305
306     static top(box) {
307         return parseFloat(box.node().style.top);
308     }
309
310     static bottom(box) {
311         return parseFloat(box.node().style.bottom);
312     }
313
314     static left(box) {
315         return parseFloat(box.node().style.left);
316     }
317
318     static right(box) {
319         return parseFloat(box.node().style.right);
320     }
321
322     static hasBorderTop(box) {
323         return window.getComputedStyle(box.node()).borderTopWidth != "0px";
324     }
325
326     static hasBorderBottom(box) {
327         return window.getComputedStyle(box.node()).borderBottomWidth != "0px";
328     }
329
330     static hasPaddingTop(box) {
331         return window.getComputedStyle(box.node()).paddingTop != "0px";
332     }
333
334     static hasPaddingBottom(box) {
335         return window.getComputedStyle(box.node()).paddingBottom != "0px";
336     }
337
338     static computedMarginTop(node) {
339         return Utils.computedValue(window.getComputedStyle(node).marginTop);
340     }
341
342     static computedMarginLeft(node) {
343         return Utils.computedValue(window.getComputedStyle(node).marginLeft);
344     }
345
346     static computedMarginBottom(node) {
347         return Utils.computedValue(window.getComputedStyle(node).marginBottom);
348     }
349
350     static computedMarginRight(node) {
351         return Utils.computedValue(window.getComputedStyle(node).marginRight);
352     }
353
354     static computedBorderTopLeft(node) {
355         return new LayoutSize(Utils.computedValue(window.getComputedStyle(node).borderLeftWidth), Utils.computedValue(window.getComputedStyle(node).borderTopWidth));
356     }
357
358     static computedBorderBottomRight(node) {
359         return new LayoutSize(Utils.computedValue(window.getComputedStyle(node).borderRightWidth), Utils.computedValue(window.getComputedStyle(node).borderBottomWidth));
360     }
361
362     static computedPaddingTopLeft(node) {
363         return new LayoutSize(Utils.computedValue(window.getComputedStyle(node).paddingLeft), Utils.computedValue(window.getComputedStyle(node).paddingTop));
364     }
365
366     static computedPaddingBottomRight(node) {
367         return new LayoutSize(Utils.computedValue(window.getComputedStyle(node).paddingRight), Utils.computedValue(window.getComputedStyle(node).paddingBottom));
368     }
369
370     static computedBorderAndPaddingTop(node) {
371         return Utils.computedBorderTopLeft(node).height() + Utils.computedPaddingTopLeft(node).height();
372     }
373
374     static computedBorderAndPaddingLeft(node) {
375         return Utils.computedBorderTopLeft(node).width() + Utils.computedPaddingTopLeft(node).width();
376     }
377
378     static computedBorderAndPaddingTop(node) {
379         return Utils.computedBorderTopLeft(node).height() + Utils.computedPaddingTopLeft(node).height();
380     }
381
382     static computedBorderAndPaddingLeft(node) {
383         return Utils.computedBorderTopLeft(node).width() + Utils.computedPaddingTopLeft(node).width();
384     }
385
386     static computedBorderAndPaddingBottom(node) {
387         return Utils.computedBorderBottomRight(node).height() + Utils.computedPaddingBottomRight(node).height();
388     }
389
390     static computedBorderAndPaddingRight(node) {
391         return Utils.computedBorderBottomRight(node).width() + Utils.computedPaddingBottomRight(node).width();
392     }
393
394     static computedHorizontalBorderAndPadding(node) {
395         return this.computedBorderAndPaddingLeft(node) + this.computedBorderAndPaddingRight(node);
396     }
397
398     static computedVerticalBorderAndPadding(node) {
399         return this.computedBorderAndPaddingTop(node) + this.computedBorderAndPaddingBottom(node);
400     }
401
402     static computedLineHeight(node) {
403         return Utils.computedValue(window.getComputedStyle(node).lineHeight);
404     }
405
406     static hasClear(box) {
407         return Utils.hasClearLeft(box) || Utils.hasClearRight(box) || Utils.hasClearBoth(box);
408     }
409
410     static hasClearLeft(box) {
411         return window.getComputedStyle(box.node()).clear == "left";
412     }
413
414     static hasClearRight(box) {
415         return window.getComputedStyle(box.node()).clear == "right";
416     }
417
418     static hasClearBoth(box) {
419         return window.getComputedStyle(box.node()).clear == "both";
420     }
421
422     static isBlockLevelElement(node) {
423         if (!node)
424             return false;
425         let display = window.getComputedStyle(node).display;
426         return  display == "block" || display == "list-item" || display == "table";
427     }
428
429     static isBlockContainerElement(node) {
430         if (!node || node.nodeType != Node.ELEMENT_NODE)
431             return false;
432         let display = window.getComputedStyle(node).display;
433         return  display == "block" || display == "list-item" || display == "inline-block" || display == "table-cell" || display == "table-caption"; //TODO && !replaced element
434     }
435
436     static isInlineLevelElement(node) {
437         let display = window.getComputedStyle(node).display;
438         return  display == "inline" || display == "inline-block" || display == "inline-table";
439     }
440
441     static isTableElement(node) {
442         let display = window.getComputedStyle(node).display;
443         return  display == "table" || display == "inline-table";
444     }
445
446     static isInlineBlockElement(node) {
447         if (!node || node.nodeType != Node.ELEMENT_NODE)
448             return false;
449         let display = window.getComputedStyle(node).display;
450         return  display == "inline-block";
451     }
452
453     static isRelativelyPositioned(box) {
454         if (box.isAnonymous())
455             return false;
456         let node = box.node();
457         return window.getComputedStyle(node).position == "relative";
458     }
459
460     static isAbsolutelyPositioned(box) {
461         if (box.isAnonymous())
462             return false;
463         let node = box.node();
464         return window.getComputedStyle(node).position == "absolute";
465     }
466
467     static isFixedPositioned(box) {
468         if (box.isAnonymous())
469             return false;
470         let node = box.node();
471         return window.getComputedStyle(node).position == "fixed";
472     }
473
474     static isStaticallyPositioned(box) {
475         if (box.isAnonymous())
476             return true;
477         let node = box.node();
478         return (Utils.propertyIsAuto("top", box) && Utils.propertyIsAuto("bottom", box)) || (Utils.propertyIsAuto("left", box) && Utils.propertyIsAuto("right", box));
479     }
480
481     static isOverflowVisible(box) {
482         return window.getComputedStyle(box.node()).overflow == "visible";
483     }
484
485     static isFloatingPositioned(box) {
486         if (box.isAnonymous())
487             return false;
488         let node = box.node();
489         return window.getComputedStyle(node).float != "none";
490     }
491
492     static isFloatingLeft(box) {
493         let node = box.node();
494         return window.getComputedStyle(node).float == "left";
495     }
496
497     static mapPosition(position, box, container) {
498         ASSERT(box instanceof Display.Box);
499         ASSERT(container instanceof Display.Box);
500
501         if (box == container)
502             return position;
503         for (let ascendant = box.parent(); ascendant && ascendant != container; ascendant = ascendant.parent())
504             position.moveBy(ascendant.topLeft());
505         return position;
506     }
507
508     static marginBox(box, container) {
509         let marginBox = box.marginBox();
510         let mappedPosition = Utils.mapPosition(marginBox.topLeft(), box, container);
511         return new LayoutRect(mappedPosition, marginBox.size());
512     }
513
514     static borderBox(box, container) {
515         let borderBox = box.borderBox();
516         let mappedPosition = Utils.mapPosition(box.topLeft(), box, container);
517         mappedPosition.moveBy(borderBox.topLeft());
518         return new LayoutRect(mappedPosition, borderBox.size());
519     }
520
521     static contentBox(box, container) {
522         let contentBox = box.contentBox();
523         let mappedPosition = Utils.mapPosition(box.topLeft(), box, container);
524         mappedPosition.moveBy(contentBox.topLeft());
525         return new LayoutRect(mappedPosition, contentBox.size());
526     }
527
528     static textRuns(text, container) {
529         return window.collectTextRuns(text, container.node());
530     }
531
532     static textRunsForLine(text, availableSpace, container) {
533         return window.collectTextRuns(text, container.node(), availableSpace);
534     }
535
536     static nextBreakingOpportunity(textBox, currentPosition)
537     {
538         return window.nextBreakingOpportunity(textBox.content(), currentPosition);
539     }
540
541     static measureText(texBox, start, end)
542     {
543         return texBox.node().textWidth(start, end);
544     }
545
546     static textHeight(textBox)
547     {
548         return textBox.text().node().textHeight();
549     }
550
551     static layoutBoxById(layoutBoxId, box) {
552         if (box.id() == layoutBoxId)
553             return box;
554         if (!box.isContainer())
555             return null;
556         // Super inefficient but this is all temporary anyway.
557         for (let child = box.firstChild(); child; child = child.nextSibling()) {
558             if (child.id() == layoutBoxId)
559                 return child;
560             let foundIt = Utils.layoutBoxById(layoutBoxId, child);
561             if (foundIt)
562                 return foundIt;
563         }
564         return null;
565     }
566     // "RenderView at (0,0) size 1317x366\n HTML RenderBlock at (0,0) size 1317x116\n  BODY RenderBody at (8,8) size 1301x100\n   DIV RenderBlock at (0,0) size 100x100\n";
567     static layoutTreeDump(layoutState) {
568         return this._dumpBox(layoutState, layoutState.rootContainer(), 1) + this._dumpTree(layoutState, layoutState.rootContainer(), 2);
569     }
570
571     static _dumpBox(layoutState, box, level) {
572         // Skip anonymous boxes for now -This is the case where WebKit does not generate an anon inline container for text content where the text is a direct child
573         // of a block container.
574         let indentation = " ".repeat(level);
575         if (box.isInlineBox()) {
576             if (box.text())
577                 return indentation + "#text RenderText\n";
578         }
579         if (box.name() == "RenderInline") {
580             if (box.isInFlowPositioned()) {
581                 let displayBox = layoutState.displayBox(box);
582                 let boxRect = displayBox.rect();
583                 return indentation + box.node().tagName + " " + box.name() + "  (" + Utils.precisionRoundWithDecimals(boxRect.left()) + ", " + Utils.precisionRoundWithDecimals(boxRect.top()) + ")\n";
584             }
585             return indentation + box.node().tagName + " " + box.name() + "\n";
586         }
587         if (box.isAnonymous())
588             return "";
589         let displayBox = layoutState.displayBox(box);
590         let boxRect = displayBox.rect();
591         return indentation + (box.node().tagName ? (box.node().tagName + " ") : "")  + box.name() + " at (" + Utils.precisionRound(boxRect.left()) + "," + Utils.precisionRound(boxRect.top()) + ") size " + Utils.precisionRound(boxRect.width()) + "x" + Utils.precisionRound(boxRect.height()) + "\n";
592     }
593
594     static _dumpLines(layoutState, root, level) {
595         ASSERT(root.establishesInlineFormattingContext());
596         let inlineFormattingState = layoutState.establishedFormattingState(root);
597         let lines = inlineFormattingState.lines();
598         let content = "";
599         let indentation = " ".repeat(level);
600         lines.forEach(function(line) {
601             let lineRect = line.rect();
602             content += indentation + "RootInlineBox at (" + lineRect.left() + "," + lineRect.top() + ") size " + Utils.precisionRound(lineRect.width()) + "x" + lineRect.height() + "\n";
603             line.lineBoxes().forEach(function(lineBox) {
604                 let indentation = " ".repeat(level + 1);
605                 let inlineBoxName = lineBox.startPosition === undefined ? "InlineBox" : "InlineTextBox";
606                 content += indentation +  inlineBoxName + " at (" + Utils.precisionRound(lineBox.lineBoxRect.left()) + "," + Utils.precisionRound(lineBox.lineBoxRect.top()) + ") size " + Utils.precisionRound(lineBox.lineBoxRect.width()) + "x" + lineBox.lineBoxRect.height() + "\n";
607             });
608         });
609         return content;
610     }
611
612     static _dumpTree(layoutState, root, level) {
613         let content = "";
614         if (root.isBlockContainerBox() && root.establishesInlineFormattingContext())
615             content += this._dumpLines(layoutState, root, level);
616         for (let child = root.firstChild(); child; child = child.nextSibling()) {
617             content += this._dumpBox(layoutState, child, level);
618             if (child.isContainer())
619                 content += this._dumpTree(layoutState, child, level + 1, content);
620         }
621         return content;
622     }
623
624     static precisionRoundWithDecimals(number) {
625         return number.toFixed(2);
626     }
627
628     static precisionRound(number) {
629         let factor = Math.pow(10, 2);
630         return Math.round(number * factor) / factor;
631     }
632 }
633