blob: f61784001238e9da03665c98d0937a788654d4d2 [file] [log] [blame]
// Copyright 2015 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/layout/layout_boxes.h"
#include "cobalt/cssom/computed_style_utils.h"
#include "cobalt/cssom/keyword_value.h"
#include "cobalt/layout/anonymous_block_box.h"
#include "cobalt/layout/container_box.h"
#include "cobalt/layout/rect_layout_unit.h"
#include "cobalt/layout/size_layout_unit.h"
#include "cobalt/layout/used_style.h"
namespace cobalt {
namespace layout {
LayoutBoxes::Type LayoutBoxes::type() const { return kLayoutLayoutBoxes; }
// Algorithm for GetClientRects:
// https://www.w3.org/TR/2013/WD-cssom-view-20131217/#dom-element-getclientrects
scoped_refptr<dom::DOMRectList> LayoutBoxes::GetClientRects() const {
// 1. If the element on which it was invoked does not have an associated
// layout box return an empty DOMRectList object and stop this algorithm.
// 2. If the element has an associated SVG layout box return a DOMRectList
// object containing a single DOMRect object that describes the bounding box
// of the element as defined by the SVG specification, applying the transforms
// that apply to the element and its ancestors.
// 3. Return a DOMRectList object containing a list of DOMRect objects in
// content order describing the bounding border boxes (including those with a
// height or width of zero) with the following constraints:
// . Apply the transforms that apply to the element and its ancestors.
// . If the element on which the method was invoked has a computed value for
// the 'display' property of 'table' or 'inline-table' include both the
// table box and the caption box, if any, but not the anonymous container
// box.
// . Replace each anonymous block box with its child box(es) and repeat this
// until no anonymous block boxes are left in the final list.
Boxes client_rect_boxes;
GetClientRectBoxes(boxes_, &client_rect_boxes);
scoped_refptr<dom::DOMRectList> dom_rect_list(new dom::DOMRectList());
for (Boxes::const_iterator box_iterator = client_rect_boxes.begin();
box_iterator != client_rect_boxes.end(); ++box_iterator) {
RectLayoutUnit transformed_border_box(
(*box_iterator)
->GetTransformedBoxFromRootWithScroll(
(*box_iterator)->GetBorderBoxFromMarginBox()));
dom_rect_list->AppendDOMRect(
new dom::DOMRect(transformed_border_box.x().toFloat(),
transformed_border_box.y().toFloat(),
transformed_border_box.width().toFloat(),
transformed_border_box.height().toFloat()));
}
return dom_rect_list;
}
bool LayoutBoxes::IsInline() const {
DCHECK(!boxes_.empty());
return boxes_.front()->computed_style()->display() ==
cssom::KeywordValue::GetInline();
}
float LayoutBoxes::GetBorderEdgeLeft() const {
return GetBoundingBorderRectangle().x();
}
float LayoutBoxes::GetBorderEdgeTop() const {
return GetBoundingBorderRectangle().y();
}
float LayoutBoxes::GetBorderEdgeWidth() const {
return GetBoundingBorderRectangle().width();
}
float LayoutBoxes::GetBorderEdgeHeight() const {
return GetBoundingBorderRectangle().height();
}
float LayoutBoxes::GetBorderLeftWidth() const {
DCHECK(!boxes_.empty());
return boxes_.front()->border_left_width().toFloat();
}
float LayoutBoxes::GetBorderTopWidth() const {
DCHECK(!boxes_.empty());
return boxes_.front()->border_top_width().toFloat();
}
float LayoutBoxes::GetMarginEdgeWidth() const {
DCHECK(!boxes_.empty());
return boxes_.front()->GetMarginBoxWidth().toFloat();
}
float LayoutBoxes::GetMarginEdgeHeight() const {
DCHECK(!boxes_.empty());
return boxes_.front()->GetMarginBoxHeight().toFloat();
}
math::Vector2dF LayoutBoxes::GetPaddingEdgeOffset() const {
DCHECK(!boxes_.empty());
return boxes_.front()->GetPaddingBoxOffsetFromRoot(
false /*transform_forms_root*/);
}
float LayoutBoxes::GetPaddingEdgeWidth() const {
DCHECK(!boxes_.empty());
return boxes_.front()->GetPaddingBoxWidth().toFloat();
}
float LayoutBoxes::GetPaddingEdgeHeight() const {
DCHECK(!boxes_.empty());
return boxes_.front()->GetPaddingBoxHeight().toFloat();
}
math::RectF LayoutBoxes::GetScrollArea(dom::Directionality dir) const {
// https://www.w3.org/TR/cssom-view-1/#scrolling-area
// For rightward and downward:
// Top edge: The element's top padding edge.
// Right edge: The right-most edge of the element's right padding edge and
// the right margin edge of all of the element's descendants' boxes,
// excluding boxes that have an ancestor of the element as their
// containing block.
// Bottom edge: The bottom-most edge of the element's bottom padding edge
// and the bottom margin edge of all of the element's descendants' boxes,
// excluding boxes that have an ancestor of the element as their
// containing block.
// Left edge: The element's left padding edge.
// See also https://www.w3.org/TR/css-overflow-3/#scrollable
if (boxes_.size() == 0) {
return math::RectF();
}
// Return the cached results if applicable.
if (scroll_area_cache_ && scroll_area_cache_->first == dir) {
return scroll_area_cache_->second;
}
// Calculate the scroll area. It should be relative to these layout boxes --
// not to the root.
math::RectF padding_area;
math::RectF scroll_area;
for (scoped_refptr<Box> layout_box : boxes_) {
// Include the box's own content and padding areas.
SizeLayoutUnit padding_size = layout_box->GetClampedPaddingBoxSize();
padding_area.Union(math::RectF(0, 0, padding_size.width().toFloat(),
padding_size.height().toFloat()));
const ContainerBox* container_box = layout_box->AsContainerBox();
if (!container_box) {
continue;
}
std::vector<const Boxes*> child_boxes_list;
child_boxes_list.push_back(&container_box->child_boxes());
while (!child_boxes_list.empty()) {
// Process the next set of child boxes.
const Boxes* child_boxes = child_boxes_list.back();
child_boxes_list.pop_back();
for (const scoped_refptr<Box>& box : *child_boxes) {
// Exclude boxes that have an ancestor of |container_box| as their
// containing block.
for (const ContainerBox* container = box->GetContainingBlock();
container; container = container->GetContainingBlock()) {
if (container == container_box) {
// Add this box's border box to the scroll area.
RectLayoutUnit border_box =
box->GetTransformedBoxFromContainingBlock(
container_box, box->GetBorderBoxFromMarginBox());
scroll_area.Union(math::RectF(
border_box.x().toFloat(), border_box.y().toFloat(),
border_box.width().toFloat(), border_box.height().toFloat()));
// Include the scrollable overflow regions of the contents provided
// they are visible (i.e. container has overflow: visible).
if (box->AsContainerBox() &&
!IsOverflowCropped(box->computed_style())) {
child_boxes_list.push_back(&box->AsContainerBox()->child_boxes());
}
break;
}
}
}
}
}
scroll_area.Union(padding_area);
// Clip the scroll area according to directionality.
float left = scroll_area.x();
float right = scroll_area.right();
float top = padding_area.y();
float bottom = scroll_area.bottom();
switch (dir) {
case dom::kLeftToRightDirectionality:
left = padding_area.x();
break;
case dom::kRightToLeftDirectionality:
right = padding_area.right();
break;
}
// Cache the results to speed up future queries.
scroll_area_cache_.emplace(
dir, math::RectF(left, top, right - left, bottom - top));
return scroll_area_cache_->second;
}
void LayoutBoxes::InvalidateSizes() {
for (Boxes::const_iterator box_iterator = boxes_.begin();
box_iterator != boxes_.end(); ++box_iterator) {
Box* box = *box_iterator;
do {
box->InvalidateUpdateSizeInputsOfBoxAndAncestors();
box = box->GetSplitSibling();
} while (box != NULL);
}
scroll_area_cache_.reset();
}
void LayoutBoxes::InvalidateCrossReferences() {
for (Boxes::const_iterator box_iterator = boxes_.begin();
box_iterator != boxes_.end(); ++box_iterator) {
Box* box = *box_iterator;
do {
box->InvalidateCrossReferencesOfBoxAndAncestors();
box = box->GetSplitSibling();
} while (box != NULL);
}
scroll_area_cache_.reset();
}
void LayoutBoxes::InvalidateRenderTreeNodes() {
for (Boxes::const_iterator box_iterator = boxes_.begin();
box_iterator != boxes_.end(); ++box_iterator) {
Box* box = *box_iterator;
do {
box->InvalidateRenderTreeNodesOfBoxAndAncestors();
box = box->GetSplitSibling();
} while (box != NULL);
}
}
void LayoutBoxes::SetUiNavItem(
const scoped_refptr<ui_navigation::NavItem>& item) {
for (Boxes::const_iterator box_iterator = boxes_.begin();
box_iterator != boxes_.end(); ++box_iterator) {
Box* box = *box_iterator;
box->SetUiNavItem(item);
}
}
math::RectF LayoutBoxes::GetBoundingBorderRectangle() const {
// In the CSSOM View extensions to the HTMLElement interface, at
// https://www.w3.org/TR/2013/WD-cssom-view-20131217/#extensions-to-the-htmlelement-interface,
// the standard mentions the 'first CSS layout box associated with the
// element' and links to a definition 'The term CSS layout box refers to the
// same term in CSS', which is followed by a note 'ISSUE 2' that mentions 'The
// terms CSS layout box and SVG layout box are not currently defined by CSS or
// SVG', at https://www.w3.org/TR/2013/WD-cssom-view-20131217/#css-layout-box.
// This function calculates the bounding box of the border boxes of the layout
// boxes, mirroring behavior of most other browsers for the 'first CSS layout
// box associated with the element'.
RectLayoutUnit bounding_rectangle;
for (Boxes::const_iterator box_iterator = boxes_.begin();
box_iterator != boxes_.end(); ++box_iterator) {
Box* box = *box_iterator;
do {
bounding_rectangle.Union(
box->GetBorderBoxFromRoot(false /*transform_forms_root*/));
box = box->GetSplitSibling();
} while (box != NULL);
}
return math::RectF(bounding_rectangle.x().toFloat(),
bounding_rectangle.y().toFloat(),
bounding_rectangle.width().toFloat(),
bounding_rectangle.height().toFloat());
}
void LayoutBoxes::GetClientRectBoxes(const Boxes& boxes,
Boxes* client_rect_boxes) const {
for (Boxes::const_iterator box_iterator = boxes.begin();
box_iterator != boxes.end(); ++box_iterator) {
Box* box = *box_iterator;
do {
// Replace each anonymous block box with its child box(es) and repeat this
// until no anonymous block boxes are left in the final list.
const AnonymousBlockBox* anonymous_block_box = box->AsAnonymousBlockBox();
if (anonymous_block_box) {
GetClientRectBoxes(anonymous_block_box->child_boxes(),
client_rect_boxes);
} else if (!box->AsTextBox()) {
// Only add the box if it isn't a text box. Text boxes are anonymous
// inline boxes and shouldn't be included.
client_rect_boxes->push_back(box);
}
box = box->GetSplitSibling();
} while (box != NULL);
}
}
} // namespace layout
} // namespace cobalt