blob: b491924e2a0ee01c6e4235ee1da62261e785dcdd [file] [log] [blame]
/*
* Copyright (C) 2018 Apple Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
class LayoutPoint {
constructor(top, left) {
this.m_top = top;
this.m_left = left;
}
setLeft(left) {
this.m_left = left;
}
setTop(top) {
this.m_top = top;
}
left() {
return this.m_left;
}
top() {
return this.m_top;
}
shiftLeft(distance) {
this.m_left += distance;
}
shiftTop(distance) {
this.m_top += distance;
}
moveBy(distance) {
if (distance.top && distance.left) {
this.m_top += distance.top();
this.m_left += distance.left();
}
else if (distance.width && distance.height) {
this.m_top += distance.height();
this.m_left += distance.width();
}
}
equal(other) {
return this.top() == other.top() && this.left() == other.left();
}
clone() {
return new LayoutPoint(this.top(), this.left());
}
}
class LayoutSize {
constructor(width, height) {
this.m_width = width;
this.m_height = height;
}
setWidth(width) {
this.m_width = width;
}
setHeight(height) {
this.m_height = height;
}
width() {
return this.m_width;
}
height() {
return this.m_height;
}
growBy(distance) {
this.m_width += distance.width();
this.m_height += distance.height();
}
shrinkBy(distance) {
this.m_width -= distance.width();
this.m_height -= distance.height();
}
isEmpty() {
return this.m_width <= 0 || this.m_height <= 0;
}
equal(other) {
return this.width() == other.width() && this.height() == other.height();
}
clone() {
return new LayoutSize(this.width(), this.height());
}
}
class LayoutRect {
constructor(topLeft, size) {
this.m_topLeft = topLeft.clone();
this.m_size = size.clone();
}
setTop(top) {
this.m_topLeft.setTop(top);
}
setLeft(left) {
this.m_topLeft.setLeft(left);
}
setBottom(bottom) {
this.m_size.setHeight(bottom - this.m_topLeft.top());
}
setRight(right) {
this.m_size.setWidth(right - this.m_topLeft.left());
}
left() {
return this.m_topLeft.left();
}
top() {
return this.m_topLeft.top();
}
bottom() {
return this.m_topLeft.top() + this.m_size.height();
}
right() {
return this.m_topLeft.left() + this.m_size.width();
}
setTopLeft(topLeft) {
this.m_topLeft = topLeft.clone();
}
topLeft() {
return this.m_topLeft.clone();
}
topRight() {
return new LayoutPoint(this.top(), this.right());
}
bottomRight() {
return new LayoutPoint(this.bottom(), this.right());
}
setWidth(width) {
this.m_size.setWidth(width);
}
setHeight(height) {
this.m_size.setHeight(height);
}
setSize(newSize) {
this.m_size = newSize.clone();
}
size() {
return this.m_size.clone();
}
width() {
return this.m_size.width();
}
height() {
return this.m_size.height();
}
growBy(distance) {
this.m_size.growBy(distance);
}
shrinkBy(distance) {
this.m_size.shrinkBy(distance);
}
moveBy(distance) {
this.m_topLeft.moveBy(distance);
}
growHorizontally(distance) {
this.m_size.setWidth(this.m_size.width() + distance);
}
moveHorizontally(distance) {
this.m_topLeft.shiftLeft(distance);
}
moveVertically(distance) {
this.m_topLeft.shiftTop(distance);
}
isEmpty() {
return this.m_size.isEmpty();
}
equal(other) {
return this.m_topLeft.equal(other.topLeft()) && this.m_size.equal(other.size());
}
intersects(other) {
return !this.isEmpty() && !other.isEmpty()
&& this.left() < other.right() && other.left() < this.right()
&& this.top() < other.bottom() && other.top() < this.bottom();
}
contains(other) {
return this.left() <= other.left() && this.right() >= other.right()
&& this.top() <= other.top() && this.bottom() >= other.bottom();
}
clone() {
return new LayoutRect(this.topLeft().clone(), this.size().clone());
}
}
function ASSERT_NOT_REACHED() {
throw Error("Should not reach!");
}
function ASSERT(statement) {
if (statement)
return;
throw Error("Assertion failure");
}
class Utils {
static computedValue(strValue, baseValue) {
if (strValue.indexOf("px") > -1)
return parseFloat(strValue);
if (strValue.indexOf("%") > -1)
return parseFloat(strValue) * baseValue / 100;
return Number.NaN;
}
static propertyIsAuto(propertyName, box) {
if (box.isAnonymous())
return true;
return window.getComputedStyle(box.node()).isPropertyValueInitial(propertyName);
}
static isWidthAuto(box) {
return Utils.propertyIsAuto("width", box);
}
static isHeightAuto(box) {
return Utils.propertyIsAuto("height", box);
}
static isTopAuto(box) {
return Utils.propertyIsAuto("top", box);
}
static isLeftAuto(box) {
return Utils.propertyIsAuto("left", box);
}
static isBottomAuto(box) {
return Utils.propertyIsAuto("bottom", box);
}
static isRightAuto(box) {
return Utils.propertyIsAuto("right", box);
}
static width(box) {
ASSERT(!Utils.isWidthAuto(box));
return parseFloat(window.getComputedStyle(box.node()).width);
}
static height(box) {
ASSERT(!Utils.isHeightAuto(box));
return parseFloat(window.getComputedStyle(box.node()).height);
}
static top(box) {
return parseFloat(box.node().style.top);
}
static bottom(box) {
return parseFloat(box.node().style.bottom);
}
static left(box) {
return parseFloat(box.node().style.left);
}
static right(box) {
return parseFloat(box.node().style.right);
}
static hasBorderTop(box) {
return window.getComputedStyle(box.node()).borderTopWidth != "0px";
}
static hasBorderBottom(box) {
return window.getComputedStyle(box.node()).borderBottomWidth != "0px";
}
static hasPaddingTop(box) {
return window.getComputedStyle(box.node()).paddingTop != "0px";
}
static hasPaddingBottom(box) {
return window.getComputedStyle(box.node()).paddingBottom != "0px";
}
static computedMarginTop(node) {
return Utils.computedValue(window.getComputedStyle(node).marginTop);
}
static computedMarginLeft(node) {
return Utils.computedValue(window.getComputedStyle(node).marginLeft);
}
static computedMarginBottom(node) {
return Utils.computedValue(window.getComputedStyle(node).marginBottom);
}
static computedMarginRight(node) {
return Utils.computedValue(window.getComputedStyle(node).marginRight);
}
static computedBorderTopLeft(node) {
return new LayoutSize(Utils.computedValue(window.getComputedStyle(node).borderLeftWidth), Utils.computedValue(window.getComputedStyle(node).borderTopWidth));
}
static computedBorderBottomRight(node) {
return new LayoutSize(Utils.computedValue(window.getComputedStyle(node).borderRightWidth), Utils.computedValue(window.getComputedStyle(node).borderBottomWidth));
}
static computedPaddingTopLeft(node) {
return new LayoutSize(Utils.computedValue(window.getComputedStyle(node).paddingLeft), Utils.computedValue(window.getComputedStyle(node).paddingTop));
}
static computedPaddingBottomRight(node) {
return new LayoutSize(Utils.computedValue(window.getComputedStyle(node).paddingRight), Utils.computedValue(window.getComputedStyle(node).paddingBottom));
}
static computedBorderAndPaddingTop(node) {
return Utils.computedBorderTopLeft(node).height() + Utils.computedPaddingTopLeft(node).height();
}
static computedBorderAndPaddingLeft(node) {
return Utils.computedBorderTopLeft(node).width() + Utils.computedPaddingTopLeft(node).width();
}
static computedBorderAndPaddingTop(node) {
return Utils.computedBorderTopLeft(node).height() + Utils.computedPaddingTopLeft(node).height();
}
static computedBorderAndPaddingLeft(node) {
return Utils.computedBorderTopLeft(node).width() + Utils.computedPaddingTopLeft(node).width();
}
static computedBorderAndPaddingBottom(node) {
return Utils.computedBorderBottomRight(node).height() + Utils.computedPaddingBottomRight(node).height();
}
static computedBorderAndPaddingRight(node) {
return Utils.computedBorderBottomRight(node).width() + Utils.computedPaddingBottomRight(node).width();
}
static computedHorizontalBorderAndPadding(node) {
return this.computedBorderAndPaddingLeft(node) + this.computedBorderAndPaddingRight(node);
}
static computedVerticalBorderAndPadding(node) {
return this.computedBorderAndPaddingTop(node) + this.computedBorderAndPaddingBottom(node);
}
static computedLineHeight(node) {
return Utils.computedValue(window.getComputedStyle(node).lineHeight);
}
static hasClear(box) {
return Utils.hasClearLeft(box) || Utils.hasClearRight(box) || Utils.hasClearBoth(box);
}
static hasClearLeft(box) {
return window.getComputedStyle(box.node()).clear == "left";
}
static hasClearRight(box) {
return window.getComputedStyle(box.node()).clear == "right";
}
static hasClearBoth(box) {
return window.getComputedStyle(box.node()).clear == "both";
}
static isBlockLevelElement(node) {
if (!node)
return false;
let display = window.getComputedStyle(node).display;
return display == "block" || display == "list-item" || display == "table";
}
static isBlockContainerElement(node) {
if (!node || node.nodeType != Node.ELEMENT_NODE)
return false;
let display = window.getComputedStyle(node).display;
return display == "block" || display == "list-item" || display == "inline-block" || display == "table-cell" || display == "table-caption"; //TODO && !replaced element
}
static isInlineLevelElement(node) {
let display = window.getComputedStyle(node).display;
return display == "inline" || display == "inline-block" || display == "inline-table";
}
static isTableElement(node) {
let display = window.getComputedStyle(node).display;
return display == "table" || display == "inline-table";
}
static isInlineBlockElement(node) {
if (!node || node.nodeType != Node.ELEMENT_NODE)
return false;
let display = window.getComputedStyle(node).display;
return display == "inline-block";
}
static isRelativelyPositioned(box) {
if (box.isAnonymous())
return false;
let node = box.node();
return window.getComputedStyle(node).position == "relative";
}
static isAbsolutelyPositioned(box) {
if (box.isAnonymous())
return false;
let node = box.node();
return window.getComputedStyle(node).position == "absolute";
}
static isFixedPositioned(box) {
if (box.isAnonymous())
return false;
let node = box.node();
return window.getComputedStyle(node).position == "fixed";
}
static isStaticallyPositioned(box) {
if (box.isAnonymous())
return true;
let node = box.node();
return (Utils.propertyIsAuto("top", box) && Utils.propertyIsAuto("bottom", box)) || (Utils.propertyIsAuto("left", box) && Utils.propertyIsAuto("right", box));
}
static isOverflowVisible(box) {
return window.getComputedStyle(box.node()).overflow == "visible";
}
static isFloatingPositioned(box) {
if (box.isAnonymous())
return false;
let node = box.node();
return window.getComputedStyle(node).float != "none";
}
static isFloatingLeft(box) {
let node = box.node();
return window.getComputedStyle(node).float == "left";
}
static mapPosition(position, box, container) {
ASSERT(box instanceof Display.Box);
ASSERT(container instanceof Display.Box);
if (box == container)
return position;
for (let ascendant = box.parent(); ascendant && ascendant != container; ascendant = ascendant.parent())
position.moveBy(ascendant.topLeft());
return position;
}
static marginBox(box, container) {
let marginBox = box.marginBox();
let mappedPosition = Utils.mapPosition(marginBox.topLeft(), box, container);
return new LayoutRect(mappedPosition, marginBox.size());
}
static borderBox(box, container) {
let borderBox = box.borderBox();
let mappedPosition = Utils.mapPosition(box.topLeft(), box, container);
mappedPosition.moveBy(borderBox.topLeft());
return new LayoutRect(mappedPosition, borderBox.size());
}
static contentBox(box, container) {
let contentBox = box.contentBox();
let mappedPosition = Utils.mapPosition(box.topLeft(), box, container);
mappedPosition.moveBy(contentBox.topLeft());
return new LayoutRect(mappedPosition, contentBox.size());
}
static textRuns(text, container) {
return window.collectTextRuns(text, container.node());
}
static textRunsForLine(text, availableSpace, container) {
return window.collectTextRuns(text, container.node(), availableSpace);
}
static nextBreakingOpportunity(textBox, currentPosition)
{
return window.nextBreakingOpportunity(textBox.content(), currentPosition);
}
static measureText(texBox, start, end)
{
return texBox.node().textWidth(start, end);
}
static textHeight(textBox)
{
return textBox.text().node().textHeight();
}
static layoutBoxById(layoutBoxId, box) {
if (box.id() == layoutBoxId)
return box;
if (!box.isContainer())
return null;
// Super inefficient but this is all temporary anyway.
for (let child = box.firstChild(); child; child = child.nextSibling()) {
if (child.id() == layoutBoxId)
return child;
let foundIt = Utils.layoutBoxById(layoutBoxId, child);
if (foundIt)
return foundIt;
}
return null;
}
// "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";
static layoutTreeDump(layoutState) {
return this._dumpBox(layoutState, layoutState.rootContainer(), 1) + this._dumpTree(layoutState, layoutState.rootContainer(), 2);
}
static _dumpBox(layoutState, box, level) {
// 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
// of a block container.
let indentation = " ".repeat(level);
if (box.isInlineBox()) {
if (box.text())
return indentation + "#text RenderText\n";
}
if (box.name() == "RenderInline") {
if (box.isInFlowPositioned()) {
let displayBox = layoutState.displayBox(box);
let boxRect = displayBox.rect();
return indentation + box.node().tagName + " " + box.name() + " (" + Utils.precisionRoundWithDecimals(boxRect.left()) + ", " + Utils.precisionRoundWithDecimals(boxRect.top()) + ")\n";
}
return indentation + box.node().tagName + " " + box.name() + "\n";
}
if (box.isAnonymous())
return "";
let displayBox = layoutState.displayBox(box);
let boxRect = displayBox.rect();
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";
}
static _dumpLines(layoutState, root, level) {
ASSERT(root.establishesInlineFormattingContext());
let inlineFormattingState = layoutState.establishedFormattingState(root);
let lines = inlineFormattingState.lines();
let content = "";
let indentation = " ".repeat(level);
lines.forEach(function(line) {
let lineRect = line.rect();
content += indentation + "RootInlineBox at (" + lineRect.left() + "," + lineRect.top() + ") size " + Utils.precisionRound(lineRect.width()) + "x" + lineRect.height() + "\n";
line.lineBoxes().forEach(function(lineBox) {
let indentation = " ".repeat(level + 1);
let inlineBoxName = lineBox.startPosition === undefined ? "InlineBox" : "InlineTextBox";
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";
});
});
return content;
}
static _dumpTree(layoutState, root, level) {
let content = "";
if (root.isBlockContainerBox() && root.establishesInlineFormattingContext())
content += this._dumpLines(layoutState, root, level);
for (let child = root.firstChild(); child; child = child.nextSibling()) {
content += this._dumpBox(layoutState, child, level);
if (child.isContainer())
content += this._dumpTree(layoutState, child, level + 1, content);
}
return content;
}
static precisionRoundWithDecimals(number) {
return number.toFixed(2);
}
static precisionRound(number) {
let factor = Math.pow(10, 2);
return Math.round(number * factor) / factor;
}
}