[LayoutReloaded] Remove all FormattingContext::absolute*Box functions.
[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     isEmpty() {
212         return this.m_size.isEmpty();
213     }
214
215     equal(other) {
216         return this.m_topLeft.equal(other.topLeft()) && this.m_size.equal(other.size());
217     }
218
219     intersects(other) {
220         return !this.isEmpty() && !other.isEmpty()
221         && this.left() < other.right() && other.left() < this.right()
222         && this.top() < other.bottom() && other.top() < this.bottom();
223     }
224
225     contains(other) {
226         return this.left() <= other.left() && this.right() >= other.right()
227         && this.top() <= other.top() && this.bottom() >= other.bottom();
228     }
229
230     clone() {
231         return new LayoutRect(this.topLeft().clone(), this.size().clone());
232     }
233 }
234
235 function ASSERT_NOT_REACHED() {
236     throw Error("Should not reach!");
237 }
238
239 function ASSERT(statement) {
240     if (statement)
241         return;
242     throw Error("Assertion failure");
243 }
244
245 class Utils {
246     static computedValue(strValue, baseValue) {
247         if (strValue.indexOf("px") > -1)
248             return parseFloat(strValue);
249         if (strValue.indexOf("%") > -1)
250             return parseFloat(strValue) * baseValue / 100;
251         return Number.NaN;
252     }
253
254     static propertyIsAuto(propertyName, box) {
255         if (box.isAnonymous())
256             return true;
257         return window.getComputedStyle(box.node()).isPropertyValueInitial(propertyName);
258     }
259
260     static isWidthAuto(box) {
261         return Utils.propertyIsAuto("width", box);
262     }
263
264     static isHeightAuto(box) {
265         return Utils.propertyIsAuto("height", box);
266     }
267
268     static isTopAuto(box) {
269         return Utils.propertyIsAuto("top", box);
270     }
271
272     static isLeftAuto(box) {
273         return Utils.propertyIsAuto("left", box);
274     }
275
276     static isBottomAuto(box) {
277         return Utils.propertyIsAuto("bottom", box);
278     }
279
280     static isRightAuto(box) {
281         return Utils.propertyIsAuto("right", box);
282     }
283
284     static width(box) {
285         ASSERT(!Utils.isWidthAuto(box));
286         return parseFloat(window.getComputedStyle(box.node()).width);
287     }
288
289     static height(box) {
290         ASSERT(!Utils.isHeightAuto(box));
291         return parseFloat(window.getComputedStyle(box.node()).height);
292     }
293
294     static top(box) {
295         return parseFloat(box.node().style.top);
296     }
297
298     static bottom(box) {
299         return parseFloat(box.node().style.bottom);
300     }
301
302     static left(box) {
303         return parseFloat(box.node().style.left);
304     }
305
306     static right(box) {
307         return parseFloat(box.node().style.right);
308     }
309
310     static hasBorderTop(box) {
311         return window.getComputedStyle(box.node()).borderTopWidth != "0px";
312     }
313
314     static hasBorderBottom(box) {
315         return window.getComputedStyle(box.node()).borderBottomWidth != "0px";
316     }
317
318     static hasPaddingTop(box) {
319         return window.getComputedStyle(box.node()).paddingTop != "0px";
320     }
321
322     static hasPaddingBottom(box) {
323         return window.getComputedStyle(box.node()).paddingBottom != "0px";
324     }
325
326     static computedMarginTop(node) {
327         return Utils.computedValue(window.getComputedStyle(node).marginTop);
328     }
329
330     static computedMarginLeft(node) {
331         return Utils.computedValue(window.getComputedStyle(node).marginLeft);
332     }
333
334     static computedMarginBottom(node) {
335         return Utils.computedValue(window.getComputedStyle(node).marginBottom);
336     }
337
338     static computedMarginRight(node) {
339         return Utils.computedValue(window.getComputedStyle(node).marginRight);
340     }
341
342     static computedBorderTopLeft(node) {
343         return new LayoutSize(Utils.computedValue(window.getComputedStyle(node).borderLeftWidth), Utils.computedValue(window.getComputedStyle(node).borderTopWidth));
344     }
345
346     static computedBorderBottomRight(node) {
347         return new LayoutSize(Utils.computedValue(window.getComputedStyle(node).borderRightWidth), Utils.computedValue(window.getComputedStyle(node).borderBottomWidth));
348     }
349
350     static computedPaddingTopLeft(node) {
351         return new LayoutSize(Utils.computedValue(window.getComputedStyle(node).paddingLeft), Utils.computedValue(window.getComputedStyle(node).paddingTop));
352     }
353
354     static computedPaddingBottomRight(node) {
355         return new LayoutSize(Utils.computedValue(window.getComputedStyle(node).paddingRight), Utils.computedValue(window.getComputedStyle(node).paddingBottom));
356     }
357
358     static computedBorderAndPaddingTop(node) {
359         return Utils.computedBorderTopLeft(node).height() + Utils.computedPaddingTopLeft(node).height();
360     }
361
362     static computedBorderAndPaddingLeft(node) {
363         return Utils.computedBorderTopLeft(node).width() + Utils.computedPaddingTopLeft(node).width();
364     }
365
366     static computedBorderAndPaddingTop(node) {
367         return Utils.computedBorderTopLeft(node).height() + Utils.computedPaddingTopLeft(node).height();
368     }
369
370     static computedBorderAndPaddingLeft(node) {
371         return Utils.computedBorderTopLeft(node).width() + Utils.computedPaddingTopLeft(node).width();
372     }
373
374     static computedBorderAndPaddingBottom(node) {
375         return Utils.computedBorderBottomRight(node).height() + Utils.computedPaddingBottomRight(node).height();
376     }
377
378     static computedBorderAndPaddingRight(node) {
379         return Utils.computedBorderBottomRight(node).width() + Utils.computedPaddingBottomRight(node).width();
380     }
381
382     static computedHorizontalBorderAndPadding(node) {
383         return this.computedBorderAndPaddingLeft(node) + this.computedBorderAndPaddingRight(node);
384     }
385
386     static computedVerticalBorderAndPadding(node) {
387         return this.computedBorderAndPaddingTop(node) + this.computedBorderAndPaddingBottom(node);
388     }
389
390     static computedLineHeight(node) {
391         return Utils.computedValue(window.getComputedStyle(node).lineHeight);
392     }
393
394     static hasClear(box) {
395         return Utils.hasClearLeft(box) || Utils.hasClearRight(box) || Utils.hasClearBoth(box);
396     }
397
398     static hasClearLeft(box) {
399         return window.getComputedStyle(box.node()).clear == "left";
400     }
401
402     static hasClearRight(box) {
403         return window.getComputedStyle(box.node()).clear == "right";
404     }
405
406     static hasClearBoth(box) {
407         return window.getComputedStyle(box.node()).clear == "both";
408     }
409
410     static isBlockLevelElement(node) {
411         if (!node)
412             return false;
413         let display = window.getComputedStyle(node).display;
414         return  display == "block" || display == "list-item" || display == "table";
415     }
416
417     static isBlockContainerElement(node) {
418         if (node.nodeType != Node.ELEMENT_NODE)
419             return false;
420         let display = window.getComputedStyle(node).display;
421         return  display == "block" || display == "list-item" || display == "inline-block" || display == "table-cell" || display == "table-caption"; //TODO && !replaced element
422     }
423
424     static isInlineLevelElement(node) {
425         let display = window.getComputedStyle(node).display;
426         return  display == "inline" || display == "inline-block" || display == "inline-table";
427     }
428
429     static isTableElement(node) {
430         let display = window.getComputedStyle(node).display;
431         return  display == "table" || display == "inline-table";
432     }
433
434     static isRelativelyPositioned(box) {
435         if (box.isAnonymous())
436             return false;
437         let node = box.node();
438         return window.getComputedStyle(node).position == "relative";
439     }
440
441     static isAbsolutelyPositioned(box) {
442         if (box.isAnonymous())
443             return false;
444         let node = box.node();
445         return window.getComputedStyle(node).position == "absolute";
446     }
447
448     static isFixedPositioned(box) {
449         if (box.isAnonymous())
450             return false;
451         let node = box.node();
452         return window.getComputedStyle(node).position == "fixed";
453     }
454
455     static isStaticallyPositioned(box) {
456         if (box.isAnonymous())
457             return true;
458         let node = box.node();
459         return (Utils.propertyIsAuto("top", box) && Utils.propertyIsAuto("bottom", box)) || (Utils.propertyIsAuto("left", box) && Utils.propertyIsAuto("right", box));
460     }
461
462     static isOverflowVisible(box) {
463         return window.getComputedStyle(box.node()).overflow == "visible";
464     }
465
466     static isFloatingPositioned(box) {
467         if (box.isAnonymous())
468             return false;
469         let node = box.node();
470         return window.getComputedStyle(node).float != "none";
471     }
472
473     static isFloatingLeft(box) {
474         let node = box.node();
475         return window.getComputedStyle(node).float == "left";
476     }
477
478     static mapPosition(position, box, container) {
479         ASSERT(box instanceof Display.Box);
480         ASSERT(container instanceof Display.Box);
481
482         if (box == container)
483             return position;
484         for (let ascendant = box.parent(); ascendant && ascendant != container; ascendant = ascendant.parent())
485             position.moveBy(ascendant.topLeft());
486         return position;
487     }
488
489     static marginBox(box, container) {
490         let marginBox = box.marginBox();
491         let mappedPosition = Utils.mapPosition(marginBox.topLeft(), box, container);
492         return new LayoutRect(mappedPosition, marginBox.size());
493     }
494
495     static borderBox(box, container) {
496         let borderBox = box.borderBox();
497         let mappedPosition = Utils.mapPosition(box.topLeft(), box, container);
498         mappedPosition.moveBy(borderBox.topLeft());
499         return new LayoutRect(mappedPosition, borderBox.size());
500     }
501
502     static contentBox(box, container) {
503         let contentBox = box.contentBox();
504         let mappedPosition = Utils.mapPosition(box.topLeft(), box, container);
505         mappedPosition.moveBy(contentBox.topLeft());
506         return new LayoutRect(mappedPosition, contentBox.size());
507     }
508
509     static textRuns(text, container) {
510         return window.collectTextRuns(text, container.node());
511     }
512
513     static textRunsForLine(text, availableSpace, container) {
514         return window.collectTextRuns(text, container.node(), availableSpace);
515     }
516
517     static nextBreakingOpportunity(textBox, currentPosition)
518     {
519         return window.nextBreakingOpportunity(textBox.content(), currentPosition);
520     }
521
522     static measureText(texBox, start, end)
523     {
524         return texBox.node().textWidth(start, end);
525     }
526
527     static textHeight(textBox)
528     {
529         return textBox.text().node().textHeight();
530     }
531
532     // "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";
533     static layoutTreeDump(initialContainingBlock, layoutState) {
534         return this._dumpBox(layoutState, initialContainingBlock, 1) + this._dumpTree(layoutState, initialContainingBlock, 2);
535     }
536
537     static _findDisplayBox(layoutState, box) {
538         for (let formattingState of layoutState.formattingStates()) {
539             let displayBox = formattingState[1].displayBoxMap().get(box);
540             if (displayBox)
541                 return displayBox;
542         }
543         ASSERT(!box.parent());
544         return layoutState.initialDisplayBox();
545     }
546
547     static _dumpBox(layoutState, box, level) {
548         // 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
549         // of a block container.
550         let indentation = " ".repeat(level);
551         if (box instanceof Layout.InlineBox) {
552             if (box.text())
553                 return indentation + "#text RenderText\n";
554         }
555         if (box.isAnonymous())
556             return "";
557         let displayBox = Utils._findDisplayBox(layoutState, box);
558         let boxRect = displayBox.rect();
559         return indentation + (box.node().tagName ? (box.node().tagName + " ") : "")  + box.name() + " at (" + boxRect.left() + "," + boxRect.top() + ") size " + boxRect.width() + "x" + boxRect.height() + "\n";
560     }
561
562     static _dumpLines(layoutState, root, level) {
563         ASSERT(root.establishesInlineFormattingContext());
564         let inlineFormattingState = layoutState.formattingState(root);
565         let lines = inlineFormattingState.lines();
566         let content = "";
567         let indentation = " ".repeat(level);
568         lines.forEach(function(line) {
569             let lineRect = line.rect();
570             content += indentation + "RootInlineBox at (" + lineRect.left() + "," + lineRect.top() + ") size " + Utils.precisionRound(lineRect.width(), 2) + "x" + lineRect.height() + "\n";
571             line.lineBoxes().forEach(function(lineBox) {
572                 let indentation = " ".repeat(level + 1);
573                 content += indentation + "InlineTextBox at (" + Utils.precisionRound(lineBox.lineBoxRect.left(), 2) + "," + Utils.precisionRound(lineBox.lineBoxRect.top(), 2) + ") size " + Utils.precisionRound(lineBox.lineBoxRect.width(), 2) + "x" + lineBox.lineBoxRect.height() + "\n";
574             });
575         });
576         return content;
577     }
578
579     static _dumpTree(layoutState, root, level) {
580         let content = "";
581         if (root.isBlockContainerBox() && root.establishesInlineFormattingContext())
582             content += this._dumpLines(layoutState, root, level);
583         for (let child = root.firstChild(); child; child = child.nextSibling()) {
584             content += this._dumpBox(layoutState, child, level);
585             if (child.isContainer())
586                 content += this._dumpTree(layoutState, child, level + 1, content);
587         }
588         return content;
589     }
590
591     static precisionRound(number, precision) {
592         var factor = Math.pow(10, precision);
593         return Math.round(number * factor) / factor;
594     }
595 }
596