blob: 73c4c9b6996741ccb78ea0245e4626a63cb779d8 [file] [log] [blame]
/*
* Copyright (C) 2012 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 COMPUTER, 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 COMPUTER, 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.
*/
#include "third_party/blink/renderer/core/layout/layout_geometry_map.h"
#include "base/auto_reset.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/platform/transforms/transform_state.h"
#define LAYOUT_GEOMETRY_MAP_LOGGING 0
namespace blink {
LayoutGeometryMap::LayoutGeometryMap(MapCoordinatesFlags flags)
: insertion_position_(kNotFound),
non_uniform_steps_count_(0),
transformed_steps_count_(0),
fixed_steps_count_(0),
map_coordinates_flags_(flags) {}
LayoutGeometryMap::~LayoutGeometryMap() = default;
void LayoutGeometryMap::MapToAncestor(
TransformState& transform_state,
const LayoutBoxModelObject* ancestor) const {
// If the mapping includes something like columns, we have to go via
// layoutObjects.
if (HasNonUniformStep()) {
mapping_.back().layout_object_->MapLocalToAncestor(
ancestor, transform_state,
kApplyContainerFlip | map_coordinates_flags_);
transform_state.Flatten();
return;
}
bool in_fixed = false;
#if DCHECK_IS_ON()
bool found_ancestor =
!ancestor || (mapping_.size() && mapping_[0].layout_object_ == ancestor);
#endif
int i = mapping_.size() - 1;
for (; i >= 0; --i) {
const LayoutGeometryMapStep& current_step = mapping_[i];
// If container is the root LayoutView (step 0) we want to apply its fixed
// position offset.
if (i > 0 && current_step.layout_object_ == ancestor) {
#if DCHECK_IS_ON()
found_ancestor = true;
#endif
break;
}
// If this box has a transform, it acts as a fixed position container
// for fixed descendants, which prevents the propagation of 'fixed'
// unless the layer itself is also fixed position.
if (i && current_step.flags_ & kContainsFixedPosition &&
!(current_step.flags_ & kIsFixedPosition))
in_fixed = false;
else if (current_step.flags_ & kIsFixedPosition)
in_fixed = true;
#if DCHECK_IS_ON()
DCHECK_EQ(!i, IsTopmostLayoutView(current_step.layout_object_));
#endif
if (!i) {
// A null container indicates mapping through the root LayoutView, so
// including its transform (the page scale).
if (!ancestor && current_step.transform_)
transform_state.ApplyTransform(*current_step.transform_.get());
} else {
TransformState::TransformAccumulation accumulate =
current_step.flags_ & kAccumulatingTransform
? TransformState::kAccumulateTransform
: TransformState::kFlattenTransform;
if (current_step.transform_)
transform_state.ApplyTransform(*current_step.transform_.get(),
accumulate);
else
transform_state.Move(current_step.offset_.Width(),
current_step.offset_.Height(), accumulate);
}
if (in_fixed && current_step.layout_object_->IsLayoutView()) {
transform_state.Move(current_step.offset_for_fixed_position_);
in_fixed = false;
}
}
if (in_fixed) {
// In case we've not reached top ('ancestor' isn't top level view) either
// assure that 'ancestor' and object both fixed or apply fixed offset of
// the nearest containing view.
for (; i >= 0; --i) {
const LayoutGeometryMapStep& current_step = mapping_[i];
if (current_step.flags_ & (kContainsFixedPosition | kIsFixedPosition))
break;
if (current_step.layout_object_->IsLayoutView()) {
transform_state.Move(current_step.offset_for_fixed_position_);
break;
}
}
}
#if DCHECK_IS_ON()
DCHECK(found_ancestor);
#endif
transform_state.Flatten();
}
#ifndef NDEBUG
// Handy function to call from gdb while debugging mismatched point/rect errors.
void LayoutGeometryMap::DumpSteps() const {
fprintf(stderr, "LayoutGeometryMap::dumpSteps accumulatedOffset=%d,%d\n",
accumulated_offset_.Width().ToInt(),
accumulated_offset_.Height().ToInt());
for (int i = mapping_.size() - 1; i >= 0; --i) {
fprintf(stderr, " [%d] %s: offset=%d,%d", i,
mapping_[i].layout_object_->DebugName().Ascii().data(),
mapping_[i].offset_.Width().ToInt(),
mapping_[i].offset_.Height().ToInt());
if (mapping_[i].flags_ & kContainsFixedPosition)
fprintf(stderr, " containsFixedPosition");
fprintf(stderr, "\n");
}
}
#endif
FloatQuad LayoutGeometryMap::MapToAncestor(
const FloatRect& rect,
const LayoutBoxModelObject* ancestor) const {
FloatQuad result;
if (!HasFixedPositionStep() && !HasTransformStep() && !HasNonUniformStep() &&
(!ancestor ||
(mapping_.size() && ancestor == mapping_[0].layout_object_))) {
result = rect;
result.Move(accumulated_offset_);
} else {
TransformState transform_state(TransformState::kApplyTransformDirection,
rect.Center(), rect);
MapToAncestor(transform_state, ancestor);
result = transform_state.LastPlanarQuad();
}
#if DCHECK_IS_ON()
if (mapping_.size() > 0) {
const LayoutObject* last_layout_object = mapping_.back().layout_object_;
FloatRect layout_object_mapped_result =
last_layout_object
->LocalToAncestorQuad(rect, ancestor, map_coordinates_flags_)
.BoundingBox();
DCHECK(layout_object_mapped_result.EqualWithinEpsilon(result.BoundingBox(),
1.1f) ||
layout_object_mapped_result.MayNotHaveExactIntRectRepresentation() ||
result.BoundingBox().MayNotHaveExactIntRectRepresentation())
<< "Rounded: " << RoundedIntRect(layout_object_mapped_result) << " vs "
<< RoundedIntRect(result.BoundingBox())
<< ". Original: " << layout_object_mapped_result << " vs "
<< result.BoundingBox();
}
#endif
return result;
}
void LayoutGeometryMap::PushMappingsToAncestor(
const LayoutObject* layout_object,
const LayoutBoxModelObject* ancestor_layout_object) {
// We need to push mappings in reverse order here, so do insertions rather
// than appends.
base::AutoReset<size_t> position_change(&insertion_position_,
mapping_.size());
do {
layout_object =
layout_object->PushMappingToContainer(ancestor_layout_object, *this);
} while (layout_object && layout_object != ancestor_layout_object);
#if DCHECK_IS_ON()
DCHECK(mapping_.IsEmpty() || IsTopmostLayoutView(mapping_[0].layout_object_));
#endif
}
static bool CanMapBetweenLayoutObjects(const LayoutObject& layout_object,
const LayoutObject& ancestor) {
for (const LayoutObject* current = &layout_object;;
current = current->Parent()) {
const ComputedStyle& style = current->StyleRef();
if (style.GetPosition() == EPosition::kFixed ||
style.IsFlippedBlocksWritingMode() ||
style.HasTransformRelatedProperty())
return false;
if (current->CanContainFixedPositionObjects() ||
current->IsLayoutFlowThread() || current->IsSVGRoot())
return false;
if (current == &ancestor)
break;
if (current->IsFloatingWithNonContainingBlockParent())
return false;
}
return true;
}
void LayoutGeometryMap::PushMappingsToAncestor(
const PaintLayer* layer,
const PaintLayer* ancestor_layer) {
const LayoutObject& layout_object = layer->GetLayoutObject();
bool cross_document =
ancestor_layer &&
layout_object.GetFrame() != ancestor_layer->GetLayoutObject().GetFrame();
DCHECK(!cross_document ||
map_coordinates_flags_ & kTraverseDocumentBoundaries);
// We have to visit all the layoutObjects to detect flipped blocks. This might
// defeat the gains from mapping via layers.
bool can_convert_in_layer_tree =
(ancestor_layer && !cross_document)
? CanMapBetweenLayoutObjects(layout_object,
ancestor_layer->GetLayoutObject())
: false;
#if LAYOUT_GEOMETRY_MAP_LOGGING
DLOG(INFO) << "LayoutGeometryMap::pushMappingsToAncestor from layer " << layer
<< " to layer " << ancestor_layer
<< ", canConvertInLayerTree=" << can_convert_in_layer_tree;
#endif
if (can_convert_in_layer_tree) {
LayoutPoint layer_offset;
layer->ConvertToLayerCoords(ancestor_layer, layer_offset);
// The LayoutView must be pushed first.
if (!mapping_.size()) {
DCHECK(ancestor_layer->GetLayoutObject().IsLayoutView());
PushMappingsToAncestor(&ancestor_layer->GetLayoutObject(), nullptr);
}
base::AutoReset<size_t> position_change(&insertion_position_,
mapping_.size());
bool accumulating_transform =
layout_object.StyleRef().Preserves3D() ||
ancestor_layer->GetLayoutObject().StyleRef().Preserves3D();
Push(&layout_object, ToLayoutSize(layer_offset),
accumulating_transform ? kAccumulatingTransform : 0);
return;
}
const LayoutBoxModelObject* ancestor_layout_object =
ancestor_layer ? &ancestor_layer->GetLayoutObject() : nullptr;
PushMappingsToAncestor(&layout_object, ancestor_layout_object);
}
void LayoutGeometryMap::Push(const LayoutObject* layout_object,
const LayoutSize& offset_from_container,
GeometryInfoFlags flags,
LayoutSize offset_for_fixed_position) {
#if LAYOUT_GEOMETRY_MAP_LOGGING
DLOG(INFO) << "LayoutGeometryMap::push" << layout_object << " "
<< offset_from_container.Width().ToInt() << ","
<< offset_from_container.Height().ToInt()
<< " isNonUniform=" << kIsNonUniform;
#endif
DCHECK_NE(insertion_position_, kNotFound);
DCHECK(!layout_object->IsLayoutView() || !insertion_position_ ||
map_coordinates_flags_ & kTraverseDocumentBoundaries);
DCHECK(offset_for_fixed_position.IsZero() || layout_object->IsLayoutView());
mapping_.insert(insertion_position_,
LayoutGeometryMapStep(layout_object, flags));
LayoutGeometryMapStep& step = mapping_[insertion_position_];
step.offset_ = offset_from_container;
step.offset_for_fixed_position_ = offset_for_fixed_position;
StepInserted(step);
}
void LayoutGeometryMap::Push(const LayoutObject* layout_object,
const TransformationMatrix& t,
GeometryInfoFlags flags,
LayoutSize offset_for_fixed_position) {
DCHECK_NE(insertion_position_, kNotFound);
DCHECK(!layout_object->IsLayoutView() || !insertion_position_ ||
map_coordinates_flags_ & kTraverseDocumentBoundaries);
DCHECK(offset_for_fixed_position.IsZero() || layout_object->IsLayoutView());
mapping_.insert(insertion_position_,
LayoutGeometryMapStep(layout_object, flags));
LayoutGeometryMapStep& step = mapping_[insertion_position_];
step.offset_for_fixed_position_ = offset_for_fixed_position;
if (!t.IsIntegerTranslation())
step.transform_ = TransformationMatrix::Create(t);
else
step.offset_ = LayoutSize(LayoutUnit(t.E()), LayoutUnit(t.F()));
StepInserted(step);
}
void LayoutGeometryMap::PopMappingsToAncestor(
const LayoutBoxModelObject* ancestor_layout_object) {
DCHECK(mapping_.size());
bool might_be_saturated = false;
while (mapping_.size() &&
mapping_.back().layout_object_ != ancestor_layout_object) {
might_be_saturated =
might_be_saturated || accumulated_offset_.Width().MightBeSaturated();
might_be_saturated =
might_be_saturated || accumulated_offset_.Height().MightBeSaturated();
StepRemoved(mapping_.back());
mapping_.pop_back();
}
if (UNLIKELY(might_be_saturated)) {
accumulated_offset_ = LayoutSize();
for (const auto& step : mapping_)
accumulated_offset_ += step.offset_;
}
}
void LayoutGeometryMap::PopMappingsToAncestor(
const PaintLayer* ancestor_layer) {
const LayoutBoxModelObject* ancestor_layout_object =
ancestor_layer ? &ancestor_layer->GetLayoutObject() : nullptr;
PopMappingsToAncestor(ancestor_layout_object);
}
void LayoutGeometryMap::StepInserted(const LayoutGeometryMapStep& step) {
accumulated_offset_ += step.offset_;
if (step.flags_ & kIsNonUniform)
++non_uniform_steps_count_;
if (step.transform_)
++transformed_steps_count_;
if (step.flags_ & kIsFixedPosition)
++fixed_steps_count_;
}
void LayoutGeometryMap::StepRemoved(const LayoutGeometryMapStep& step) {
accumulated_offset_ -= step.offset_;
if (step.flags_ & kIsNonUniform) {
DCHECK(non_uniform_steps_count_);
--non_uniform_steps_count_;
}
if (step.transform_) {
DCHECK(transformed_steps_count_);
--transformed_steps_count_;
}
if (step.flags_ & kIsFixedPosition) {
DCHECK(fixed_steps_count_);
--fixed_steps_count_;
}
}
#if DCHECK_IS_ON()
bool LayoutGeometryMap::IsTopmostLayoutView(
const LayoutObject* layout_object) const {
if (!layout_object->IsLayoutView())
return false;
// If we're not working with multiple LayoutViews, then any view is considered
// "topmost" (to preserve original behavior).
if (!(map_coordinates_flags_ & kTraverseDocumentBoundaries))
return true;
return layout_object->GetFrame()->IsLocalRoot();
}
#endif
} // namespace blink