blob: aa1673f7fd935c407ee7f4e0124279a638dfbc88 [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 BlockFormattingContext : public FormattingContext {
public:
void layout() override;
void computeWidth(const Layout::Box&) override;
void computeHeight(const Layout::Box&) override;
void marginTop(const Layout::Box&) override;
void marginBottom(const Layout::Box&) override;
private:
void computeStaticPosition(const Layout::Box&);
void computeInFlowHeight(const Layout::Box&);
void horizontalConstraint(const Layout::Box&);
void contentHeight(const Layout::Box&);
};
*/
class BlockFormattingContext extends FormattingContext {
constructor(blockFormattingState) {
super(blockFormattingState);
}
layout() {
// 9.4.1 Block formatting contexts
// In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a containing block.
// The vertical distance between two sibling boxes is determined by the 'margin' properties.
// Vertical margins between adjacent block-level boxes in a block formatting context collapse.
// This is a post-order tree traversal layout.
// The root container layout is done in the formatting context it lives in, not that one it creates, so let's start with the first child.
this._addToLayoutQueue(this._firstInFlowChildWithNeedsLayout(this.formattingRoot()));
// 1. Go all the way down to the leaf node
// 2. Compute static position and width as we travers down
// 3. As we climb back on the tree, compute height and finialize position
// (Any subtrees with new formatting contexts need to layout synchronously)
while (this._descendantNeedsLayout()) {
// Travers down on the descendants until we find a leaf node.
while (true) {
let layoutBox = this._nextInLayoutQueue();
this.computeWidth(layoutBox);
this._computeStaticPosition(layoutBox);
if (layoutBox.establishesFormattingContext()) {
this.layoutState().formattingContext(layoutBox).layout();
break;
}
let childToLayout = this._firstInFlowChildWithNeedsLayout(layoutBox);
if (!childToLayout)
break;
this._addToLayoutQueue(childToLayout);
}
// Climb back on the ancestors and compute height/final position.
while (this._descendantNeedsLayout()) {
// All inflow descendants (if there are any) are laid out by now. Let's compute the box's height.
let layoutBox = this._nextInLayoutQueue();
this.computeHeight(layoutBox);
// Adjust position now that we have all the previous floats placed in this context -if needed.
this.floatingContext().computePosition(layoutBox);
// Move in-flow positioned children to their final position.
this._placeInFlowPositionedChildren(layoutBox);
// We are done with laying out this box.
this._removeFromLayoutQueue(layoutBox);
this.formattingState().clearNeedsLayout(layoutBox);
let nextSiblingToLayout = this._nextInFlowSiblingWithNeedsLayout(layoutBox);
if (nextSiblingToLayout) {
this._addToLayoutQueue(nextSiblingToLayout);
break;
}
}
}
// Place the inflow positioned children.
this._placeInFlowPositionedChildren(this.formattingRoot());
// And take care of out-of-flow boxes as the final step.
this._layoutOutOfFlowDescendants();
ASSERT(!this.formattingState().layoutNeeded());
}
computeWidth(layoutBox) {
if (layoutBox.isOutOfFlowPositioned())
return this._computeOutOfFlowWidth(layoutBox);
if (layoutBox.isFloatingPositioned())
return this._computeFloatingWidth(layoutBox);
return this._computeInFlowWidth(layoutBox);
}
computeHeight(layoutBox) {
if (layoutBox.isOutOfFlowPositioned())
return this._computeOutOfFlowHeight(layoutBox);
if (layoutBox.isFloatingPositioned())
return this._computeFloatingHeight(layoutBox);
return this._computeInFlowHeight(layoutBox);
}
marginTop(layoutBox) {
return BlockMarginCollapse.marginTop(layoutBox);
}
marginBottom(layoutBox) {
return BlockMarginCollapse.marginBottom(layoutBox);
}
_computeStaticPosition(layoutBox) {
// In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a containing block.
// The vertical distance between two sibling boxes is determined by the 'margin' properties.
// Vertical margins between adjacent block-level boxes in a block formatting context collapse.
let containingBlockContentBox = this.displayBox(layoutBox.containingBlock()).contentBox();
// Start from the top of the container's content box.
let previousInFlowSibling = layoutBox.previousInFlowSibling();
let contentBottom = containingBlockContentBox.top()
if (previousInFlowSibling)
contentBottom = this.displayBox(previousInFlowSibling).bottom() + this.marginBottom(previousInFlowSibling);
let position = new LayoutPoint(contentBottom, containingBlockContentBox.left());
position.moveBy(new LayoutSize(this.marginLeft(layoutBox), this.marginTop(layoutBox)));
this.displayBox(layoutBox).setTopLeft(position);
}
_computeInFlowHeight(layoutBox) {
if (Utils.isHeightAuto(layoutBox)) {
// Only children in the normal flow are taken into account (i.e., floating boxes and absolutely positioned boxes are ignored,
// and relatively positioned boxes are considered without their offset). Note that the child box may be an anonymous block box.
// The element's height is the distance from its top content edge to the first applicable of the following:
// 1. the bottom edge of the last line box, if the box establishes a inline formatting context with one or more lines
return this.displayBox(layoutBox).setHeight(this._contentHeight(layoutBox) + Utils.computedVerticalBorderAndPadding(layoutBox.node()));
}
return this.displayBox(layoutBox).setHeight(Utils.height(layoutBox) + Utils.computedVerticalBorderAndPadding(layoutBox.node()));
}
_horizontalConstraint(layoutBox) {
let horizontalConstraint = this.displayBox(layoutBox.containingBlock()).contentBox().width();
horizontalConstraint -= this.marginLeft(layoutBox) + this.marginRight(layoutBox);
return horizontalConstraint;
}
_contentHeight(layoutBox) {
// 10.6.3 Block-level non-replaced elements in normal flow when 'overflow' computes to 'visible'
// The element's height is the distance from its top content edge to the first applicable of the following:
// 1. the bottom edge of the last line box, if the box establishes a inline formatting context with one or more lines
// 2. the bottom edge of the bottom (possibly collapsed) margin of its last in-flow child, if the child's bottom margin does not collapse
// with the element's bottom margin
// 3. the bottom border edge of the last in-flow child whose top margin doesn't collapse with the element's bottom margin
// 4. zero, otherwise
// Only children in the normal flow are taken into account.
if (!layoutBox.isContainer() || !layoutBox.hasInFlowChild())
return 0;
if (layoutBox.establishesInlineFormattingContext()) {
let lines = this.layoutState().establishedFormattingState(layoutBox).lines();
if (!lines.length)
return 0;
let lastLine = lines[lines.length - 1];
return lastLine.rect().bottom();
}
let top = this.displayBox(layoutBox).contentBox().top();
let bottom = this._adjustBottomWithFIXME(layoutBox);
return bottom - top;
}
_adjustBottomWithFIXME(layoutBox) {
// FIXME: This function is a big FIXME.
let lastInFlowChild = layoutBox.lastInFlowChild();
let lastInFlowDisplayBox = this.displayBox(lastInFlowChild);
let bottom = lastInFlowDisplayBox.bottom() + this.marginBottom(lastInFlowChild);
// FIXME: margin for body
if (lastInFlowChild.name() == "RenderBody" && Utils.isHeightAuto(lastInFlowChild) && !this.displayBox(lastInFlowChild).contentBox().height())
bottom -= this.marginBottom(lastInFlowChild);
// FIXME: figure out why floatings part of the initial block formatting context get propagated to HTML
if (layoutBox.node().tagName == "HTML") {
let floatingBottom = this.floatingContext().bottom();
if (!Number.isNaN(floatingBottom))
bottom = Math.max(floatingBottom, bottom);
}
return bottom;
}
}