| /* |
| * 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; |
| } |
| } |
| |