Commit 49b9e53e authored by Dana Fried's avatar Dana Fried Committed by Commit Bot

Prevent layout manager from being invalidated during layout.

Fixes potential hard crash when arbitrary View subclass calls
InvalidateLayout() during OnBoundsChanged(), etc.

(We saw this in at least one example in the wild.)

Change-Id: I639fcaf0198fa899989b36a6264d1caa74ef8404
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1900253
Commit-Queue: Caroline Rising <corising@chromium.org>
Reviewed-by: default avatarCaroline Rising <corising@chromium.org>
Auto-Submit: Dana Fried <dfried@chromium.org>
Cr-Commit-Position: refs/heads/master@{#713100}
parent fe84cac8
...@@ -334,29 +334,6 @@ int AnimatingLayoutManager::GetPreferredHeightForWidth(const View* host, ...@@ -334,29 +334,6 @@ int AnimatingLayoutManager::GetPreferredHeightForWidth(const View* host,
: target_layout_manager()->GetPreferredHeightForWidth(host, width); : target_layout_manager()->GetPreferredHeightForWidth(host, width);
} }
void AnimatingLayoutManager::Layout(View* host) {
DCHECK_EQ(host_view(), host);
// Changing the size of a view directly will lead to a layout call rather
// than an invalidation. This should reset the layout (but see the note in
// RecalculateTarget() below).
if (should_animate_bounds_) {
const int host_main = GetMainAxis(orientation(), host->size());
const int desired_main =
GetMainAxis(orientation(), current_layout_.host_size);
if (desired_main > host_main)
ResetLayoutToSize(host->size());
} else if (!cached_layout_size() || host->size() != *cached_layout_size()) {
ResetLayout();
}
ApplyLayout(current_layout_);
// Send animating stopped events on layout so the current layout during the
// event represents the final state instead of an intermediate state.
if (is_animating_ && current_offset_ == 1.0)
OnAnimationEnded();
}
std::vector<View*> AnimatingLayoutManager::GetChildViewsInPaintOrder( std::vector<View*> AnimatingLayoutManager::GetChildViewsInPaintOrder(
const View* host) const { const View* host) const {
DCHECK_EQ(host_view(), host); DCHECK_EQ(host_view(), host);
...@@ -426,6 +403,29 @@ void AnimatingLayoutManager::OnLayoutChanged() { ...@@ -426,6 +403,29 @@ void AnimatingLayoutManager::OnLayoutChanged() {
RecalculateTarget(); RecalculateTarget();
} }
void AnimatingLayoutManager::LayoutImpl() {
// Changing the size of a view directly will lead to a layout call rather
// than an invalidation. This should reset the layout (but see the note in
// RecalculateTarget() below).
const gfx::Size host_size = host_view()->size();
if (should_animate_bounds_) {
const int host_main = GetMainAxis(orientation(), host_size);
const int desired_main =
GetMainAxis(orientation(), current_layout_.host_size);
if (desired_main > host_main)
ResetLayoutToSize(host_size);
} else if (!cached_layout_size() || host_size != *cached_layout_size()) {
ResetLayout();
}
ApplyLayout(current_layout_);
// Send animating stopped events on layout so the current layout during the
// event represents the final state instead of an intermediate state.
if (is_animating_ && current_offset_ == 1.0)
OnAnimationEnded();
}
bool AnimatingLayoutManager::RecalculateTarget() { bool AnimatingLayoutManager::RecalculateTarget() {
constexpr double kResetAnimationThreshold = 0.8; constexpr double kResetAnimationThreshold = 0.8;
......
...@@ -159,7 +159,6 @@ class VIEWS_EXPORT AnimatingLayoutManager : public LayoutManagerBase { ...@@ -159,7 +159,6 @@ class VIEWS_EXPORT AnimatingLayoutManager : public LayoutManagerBase {
gfx::Size GetPreferredSize(const View* host) const override; gfx::Size GetPreferredSize(const View* host) const override;
gfx::Size GetMinimumSize(const View* host) const override; gfx::Size GetMinimumSize(const View* host) const override;
int GetPreferredHeightForWidth(const View* host, int width) const override; int GetPreferredHeightForWidth(const View* host, int width) const override;
void Layout(View* host) override;
std::vector<View*> GetChildViewsInPaintOrder(const View* host) const override; std::vector<View*> GetChildViewsInPaintOrder(const View* host) const override;
// Queues an action to take place after the current animation completes. // Queues an action to take place after the current animation completes.
...@@ -188,6 +187,7 @@ class VIEWS_EXPORT AnimatingLayoutManager : public LayoutManagerBase { ...@@ -188,6 +187,7 @@ class VIEWS_EXPORT AnimatingLayoutManager : public LayoutManagerBase {
const SizeBounds& size_bounds) const override; const SizeBounds& size_bounds) const override;
void OnInstalled(View* host) override; void OnInstalled(View* host) override;
void OnLayoutChanged() override; void OnLayoutChanged() override;
void LayoutImpl() override;
private: private:
struct LayoutFadeInfo; struct LayoutFadeInfo;
......
...@@ -41,8 +41,12 @@ int LayoutManagerBase::GetPreferredHeightForWidth(const View* host, ...@@ -41,8 +41,12 @@ int LayoutManagerBase::GetPreferredHeightForWidth(const View* host,
void LayoutManagerBase::Layout(View* host) { void LayoutManagerBase::Layout(View* host) {
DCHECK_EQ(host_view_, host); DCHECK_EQ(host_view_, host);
const gfx::Size size = host->size(); // A handful of views will cause invalidations while they are being
ApplyLayout(GetProposedLayout(size)); // positioned, which can result in loops or loss of layout data during layout
// application. Therefore we protect the layout manager from spurious
// invalidations during the layout process.
base::AutoReset<bool> setter(&suppress_invalidate_, true);
LayoutImpl();
} }
std::vector<View*> LayoutManagerBase::GetChildViewsInPaintOrder( std::vector<View*> LayoutManagerBase::GetChildViewsInPaintOrder(
...@@ -94,6 +98,10 @@ bool LayoutManagerBase::IsChildIncludedInLayout(const View* child, ...@@ -94,6 +98,10 @@ bool LayoutManagerBase::IsChildIncludedInLayout(const View* child,
return !it->second.ignored && (include_hidden || it->second.can_be_visible); return !it->second.ignored && (include_hidden || it->second.can_be_visible);
} }
void LayoutManagerBase::LayoutImpl() {
ApplyLayout(GetProposedLayout(host_view_->size()));
}
void LayoutManagerBase::ApplyLayout(const ProposedLayout& layout) { void LayoutManagerBase::ApplyLayout(const ProposedLayout& layout) {
for (auto& child_layout : layout.child_layouts) { for (auto& child_layout : layout.child_layouts) {
DCHECK_EQ(host_view_, child_layout.child_view->parent()); DCHECK_EQ(host_view_, child_layout.child_view->parent());
......
...@@ -49,7 +49,7 @@ class VIEWS_EXPORT LayoutManagerBase : public LayoutManager { ...@@ -49,7 +49,7 @@ class VIEWS_EXPORT LayoutManagerBase : public LayoutManager {
gfx::Size GetPreferredSize(const View* host) const override; gfx::Size GetPreferredSize(const View* host) const override;
gfx::Size GetMinimumSize(const View* host) const override; gfx::Size GetMinimumSize(const View* host) const override;
int GetPreferredHeightForWidth(const View* host, int width) const override; int GetPreferredHeightForWidth(const View* host, int width) const override;
void Layout(View* host) override; void Layout(View* host) final;
protected: protected:
LayoutManagerBase(); LayoutManagerBase();
...@@ -103,6 +103,11 @@ class VIEWS_EXPORT LayoutManagerBase : public LayoutManager { ...@@ -103,6 +103,11 @@ class VIEWS_EXPORT LayoutManagerBase : public LayoutManager {
virtual ProposedLayout CalculateProposedLayout( virtual ProposedLayout CalculateProposedLayout(
const SizeBounds& size_bounds) const = 0; const SizeBounds& size_bounds) const = 0;
// Does the actual work of laying out the host view and its children.
// Default implementation is just getting the proposed layout for the host
// size and then applying it.
virtual void LayoutImpl();
// Applies |layout| to the children of the host view. // Applies |layout| to the children of the host view.
void ApplyLayout(const ProposedLayout& layout); void ApplyLayout(const ProposedLayout& layout);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment