blob: 4116b2a3fad02e1060bb7b137804753cf97dd7ca [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/views/view.h"
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdlib>
#include <iomanip>
#include <iterator>
#include <memory>
#include <optional>
#include <sstream>
#include <string_view>
#include <utility>
#include "base/auto_reset.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/containers/adapters.h"
#include "base/containers/contains.h"
#include "base/containers/flat_set.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/debug/stack_trace.h"
#include "base/feature_list.h"
#include "base/functional/callback_helpers.h"
#include "base/i18n/rtl.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/ranges/algorithm.h"
#include "base/scoped_observation.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/common/trace_event_common.h"
#include "base/trace_event/trace_event.h"
#include "base/tracing/protos/chrome_track_event.pbzero.h"
#include "build/build_config.h"
#include "cc/paint/paint_flags.h"
#include "third_party/perfetto/include/perfetto/tracing/event_context.h"
#include "third_party/perfetto/include/perfetto/tracing/track_event.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkScalar.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/actions/actions.h"
#include "ui/base/accelerators/accelerator_manager.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/metadata/base_type_conversion.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/color/color_provider.h"
#include "ui/compositor/clip_recorder.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/paint_context.h"
#include "ui/compositor/paint_recorder.h"
#include "ui/compositor/transform_recorder.h"
#include "ui/display/screen.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_target_iterator.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/accessibility/accessibility_paint_checks.h"
#include "ui/views/accessibility/ax_event_manager.h"
#include "ui/views/accessibility/ax_virtual_view.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/actions/action_view_interface.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/buildflags.h"
#include "ui/views/context_menu_controller.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/drag_controller.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/interaction/element_tracker_views.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/view_observer.h"
#include "ui/views/view_tracker.h"
#include "ui/views/view_utils.h"
#include "ui/views/views_features.h"
#include "ui/views/views_switches.h"
#include "ui/views/widget/tooltip_manager.h"
#include "ui/views/widget/widget.h"
#if BUILDFLAG(IS_WIN)
#include "base/win/scoped_gdi_object.h"
#include "ui/native_theme/native_theme_win.h"
#endif
namespace ui {
class ClipboardFormatType;
enum class PropertyChangeReason;
} // namespace ui
namespace views {
namespace {
#if BUILDFLAG(IS_WIN)
constexpr bool kContextMenuOnMousePress = false;
#else
constexpr bool kContextMenuOnMousePress = true;
#endif
// Default horizontal drag threshold in pixels.
// Same as what gtk uses.
constexpr int kDefaultHorizontalDragThreshold = 8;
// Default vertical drag threshold in pixels.
// Same as what gtk uses.
constexpr int kDefaultVerticalDragThreshold = 8;
// The following are used to offset the keys for the callbacks associated with
// the bounds element callbacks.
constexpr int kXChangedKey = sizeof(int) * 0;
constexpr int kYChangedKey = sizeof(int) * 1;
constexpr int kWidthChangedKey = sizeof(int) * 2;
constexpr int kHeightChangedKey = sizeof(int) * 3;
// Returns the top view in |view|'s hierarchy.
const View* GetHierarchyRoot(const View* view) {
const View* root = view;
while (root && root->parent()) {
root = root->parent();
}
return root;
}
} // namespace
namespace internal {
#if DCHECK_IS_ON()
ScopedChildrenLock::ScopedChildrenLock(const View* view)
: reset_(&view->iterating_, true) {}
ScopedChildrenLock::~ScopedChildrenLock() = default;
#else
ScopedChildrenLock::ScopedChildrenLock(const View* view) {}
ScopedChildrenLock::~ScopedChildrenLock() {}
#endif
} // namespace internal
////////////////////////////////////////////////////////////////////////////////
// ViewMaskLayer
// This class is responsible for creating a masking layer for a view that paints
// to a layer. It tracks the size of the layer it is masking.
class VIEWS_EXPORT ViewMaskLayer : public ui::LayerDelegate,
public ViewObserver {
public:
// Note that |observed_view| must outlive the ViewMaskLayer instance.
ViewMaskLayer(const SkPath& path, View* observed_view);
ViewMaskLayer(const ViewMaskLayer& mask_layer) = delete;
ViewMaskLayer& operator=(const ViewMaskLayer& mask_layer) = delete;
~ViewMaskLayer() override;
ui::Layer* layer() { return &layer_; }
private:
// ui::LayerDelegate:
void OnDeviceScaleFactorChanged(float old_device_scale_factor,
float new_device_scale_factor) override;
void OnPaintLayer(const ui::PaintContext& context) override;
// views::ViewObserver:
void OnViewBoundsChanged(View* observed_view) override;
base::ScopedObservation<View, ViewObserver> observed_view_{this};
SkPath path_;
ui::Layer layer_;
};
ViewMaskLayer::ViewMaskLayer(const SkPath& path, View* observed_view)
: path_{path} {
layer_.set_delegate(this);
layer_.SetFillsBoundsOpaquely(false);
layer_.SetName("ViewMaskLayer");
observed_view_.Observe(observed_view);
OnViewBoundsChanged(observed_view);
}
ViewMaskLayer::~ViewMaskLayer() {
layer_.set_delegate(nullptr);
}
void ViewMaskLayer::OnDeviceScaleFactorChanged(float old_device_scale_factor,
float new_device_scale_factor) {}
void ViewMaskLayer::OnPaintLayer(const ui::PaintContext& context) {
cc::PaintFlags flags;
flags.setAlphaf(1.0f);
flags.setStyle(cc::PaintFlags::kFill_Style);
flags.setAntiAlias(true);
ui::PaintRecorder recorder(context, layer()->size());
recorder.canvas()->DrawPath(path_, flags);
}
void ViewMaskLayer::OnViewBoundsChanged(View* observed_view) {
layer_.SetBounds(observed_view->GetLocalBounds());
}
////////////////////////////////////////////////////////////////////////////////
// View, public:
// Creation and lifetime -------------------------------------------------------
View::View() {
SetTargetHandler(this);
if (use_default_fill_layout_) {
SetToDefaultFillLayout();
}
static bool capture_stack_trace =
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kViewStackTraces);
if (capture_stack_trace) {
SetProperty(kViewStackTraceKey,
std::make_unique<base::debug::StackTrace>());
}
ax_node_data_ = std::make_unique<ui::AXNodeData>();
}
View::~View() {
if (layouts_since_last_paint_) {
UMA_HISTOGRAM_COUNTS_100("Views.UnnecessaryLayouts",
layouts_since_last_paint_);
}
for (ViewObserver& observer : observers_) {
observer.OnViewHierarchyWillBeDeleted(this);
}
life_cycle_state_ = LifeCycleState::kDestroying;
if (parent_) {
parent_->RemoveChildView(this);
}
// This view should have been removed from the focus list by now.
DCHECK_EQ(next_focusable_view_, nullptr);
DCHECK_EQ(previous_focusable_view_, nullptr);
// Need to remove layout manager before deleting children because if we do not
// it is possible for layout-related calls (e.g. CalculatePreferredSize()) to
// be called on this view during one of the callbacks below. Since most
// layout managers access child view properties, this would result in a
// use-after-free error.
layout_manager_.reset();
{
internal::ScopedChildrenLock lock(this);
for (views::View* child : children_) {
child->parent_ = nullptr;
// Remove any references to |child| to avoid holding a dangling ptr.
if (child->previous_focusable_view_) {
child->previous_focusable_view_->next_focusable_view_ = nullptr;
}
if (child->next_focusable_view_) {
child->next_focusable_view_->previous_focusable_view_ = nullptr;
}
// Since all children are removed here, it is safe to set
// |child|'s focus list pointers to null and expect any references
// to |child| will be removed subsequently.
child->next_focusable_view_ = nullptr;
child->previous_focusable_view_ = nullptr;
if (!child->owned_by_client_) {
delete child;
}
}
// Clear `children_` to prevent UAFs from observers and properties that may
// end up looking at children(), directly or indirectly, before ~View() goes
// out of scope.
children_.clear();
}
for (ViewObserver& observer : observers_) {
observer.OnViewIsDeleting(this);
}
for (ui::Layer* layer : GetLayersInOrder(ViewLayer::kExclude)) {
layer->RemoveObserver(this);
}
// Clearing properties explicitly here lets us guarantee that properties
// outlive |this| (at least the View part of |this|). This is intentionally
// called at the end so observers can examine properties inside
// OnViewIsDeleting(), for instance.
ClearProperties();
life_cycle_state_ = LifeCycleState::kDestroyed;
}
// Tree operations -------------------------------------------------------------
const Widget* View::GetWidget() const {
// The root view holds a reference to this view hierarchy's Widget.
return parent_ ? parent_->GetWidget() : nullptr;
}
Widget* View::GetWidget() {
return const_cast<Widget*>(const_cast<const View*>(this)->GetWidget());
}
void View::ReorderChildView(View* view, size_t index) {
DCHECK_EQ(view->parent_, this);
const auto i = base::ranges::find(children_, view);
DCHECK(i != children_.end());
// If |view| is already at the desired position, there's nothing to do.
const auto pos =
std::next(children_.begin(),
static_cast<ptrdiff_t>(std::min(index, children_.size() - 1)));
if (i == pos) {
return;
}
// Rotate |view| to be at the desired position.
#if DCHECK_IS_ON()
DCHECK(!iterating_);
#endif
if (pos < i) {
std::rotate(pos, i, std::next(i));
} else {
std::rotate(i, std::next(i), std::next(pos));
}
// Update focus siblings. Unhook |view| from the focus cycle first so
// SetFocusSiblings() won't traverse through it.
view->RemoveFromFocusList();
SetFocusSiblings(view, pos);
for (ViewObserver& observer : observers_) {
observer.OnChildViewReordered(this, view);
}
ReorderLayers();
InvalidateLayout();
}
void View::RemoveChildView(View* view) {
DoRemoveChildView(view, true, false, nullptr);
}
void View::RemoveAllChildViews() {
while (!children_.empty()) {
DoRemoveChildView(children_.front(), false, true, nullptr);
}
UpdateTooltip();
}
void View::RemoveAllChildViewsWithoutDeleting() {
while (!children_.empty()) {
DoRemoveChildView(children_.front(), false, false, nullptr);
}
UpdateTooltip();
}
bool View::Contains(const View* view) const {
for (const View* v = view; v; v = v->parent_) {
if (v == this) {
return true;
}
}
return false;
}
View::Views::const_iterator View::FindChild(const View* view) const {
return base::ranges::find(children_, view);
}
std::optional<size_t> View::GetIndexOf(const View* view) const {
const auto i = FindChild(view);
return i == children_.cend() ? std::nullopt
: std::make_optional(static_cast<size_t>(
std::distance(children_.cbegin(), i)));
}
// Size and disposition --------------------------------------------------------
void View::SetBounds(int x, int y, int width, int height) {
SetBoundsRect(gfx::Rect(x, y, std::max(0, width), std::max(0, height)));
}
void View::SetBoundsRect(const gfx::Rect& bounds) {
if (bounds == bounds_) {
if (needs_layout_) {
needs_layout_ = false;
TRACE_EVENT1("views", "View::Layout(set_bounds)", "class",
GetClassName());
LayoutImmediately();
}
return;
}
bool is_size_changed = bounds_.size() != bounds.size();
// Paint where the view is currently.
SchedulePaintBoundsChanged(is_size_changed);
gfx::Rect prev = bounds_;
bounds_ = bounds;
// Paint the new bounds.
SchedulePaintBoundsChanged(is_size_changed);
if (layer()) {
if (parent_) {
LayerOffsetData offset_data(
parent_->CalculateOffsetToAncestorWithLayer(nullptr));
offset_data += GetMirroredPosition().OffsetFromOrigin();
SetLayerBounds(size(), offset_data);
} else {
SetLayerBounds(bounds_.size(),
LayerOffsetData() + bounds_.OffsetFromOrigin());
}
// In RTL mode, if our width has changed, our children's mirrored bounds
// will have changed. Update the child's layer bounds, or if it is not a
// layer, the bounds of any layers inside the child.
if (GetMirrored() && bounds_.width() != prev.width()) {
for (View* child : children_) {
child->UpdateChildLayerBounds(
LayerOffsetData(layer()->device_scale_factor(),
child->GetMirroredPosition().OffsetFromOrigin()));
}
}
} else {
// If our bounds have changed, then any descendant layer bounds may have
// changed. Update them accordingly.
UpdateChildLayerBounds(CalculateOffsetToAncestorWithLayer(nullptr));
}
OnBoundsChanged(prev);
NotifyAccessibilityEvent(ax::mojom::Event::kLocationChanged, false);
if (needs_layout_ || is_size_changed) {
needs_layout_ = false;
TRACE_EVENT1("views", "View::Layout(bounds_changed)", "class",
GetClassName());
LayoutImmediately();
}
if (GetNeedsNotificationWhenVisibleBoundsChange()) {
OnVisibleBoundsChanged();
}
// Notify interested Views that visible bounds within the root view may have
// changed.
if (descendants_to_notify_) {
for (views::View* i : *descendants_to_notify_) {
i->OnVisibleBoundsChanged();
}
}
for (ViewObserver& observer : observers_) {
observer.OnViewBoundsChanged(this);
}
// The property effects have already been taken into account above. No need to
// redo them here.
if (prev.x() != bounds_.x()) {
OnPropertyChanged(&bounds_ + kXChangedKey, kPropertyEffectsNone);
}
if (prev.y() != bounds_.y()) {
OnPropertyChanged(&bounds_ + kYChangedKey, kPropertyEffectsNone);
}
if (prev.width() != bounds_.width()) {
OnPropertyChanged(&bounds_ + kWidthChangedKey, kPropertyEffectsNone);
}
if (prev.height() != bounds_.height()) {
OnPropertyChanged(&bounds_ + kHeightChangedKey, kPropertyEffectsNone);
}
}
void View::SetSize(const gfx::Size& size) {
SetBounds(x(), y(), size.width(), size.height());
}
void View::SetPosition(const gfx::Point& position) {
SetBounds(position.x(), position.y(), width(), height());
}
void View::SetX(int x) {
SetBounds(x, y(), width(), height());
}
void View::SetY(int y) {
SetBounds(x(), y, width(), height());
}
gfx::Rect View::GetContentsBounds() const {
gfx::Rect contents_bounds(GetLocalBounds());
contents_bounds.Inset(GetInsets());
return contents_bounds;
}
gfx::Rect View::GetLocalBounds() const {
return gfx::Rect(size());
}
gfx::Insets View::GetInsets() const {
return border_ ? border_->GetInsets() : gfx::Insets();
}
gfx::Rect View::GetVisibleBounds() const {
if (!IsDrawn()) {
return gfx::Rect();
}
gfx::Rect vis_bounds(GetLocalBounds());
gfx::Rect ancestor_bounds;
const View* view = this;
gfx::Transform transform;
while (view != nullptr && !vis_bounds.IsEmpty()) {
transform.PostConcat(view->GetTransform());
gfx::Transform translation;
translation.Translate(static_cast<float>(view->GetMirroredX()),
static_cast<float>(view->y()));
transform.PostConcat(translation);
vis_bounds = view->ConvertRectToParent(vis_bounds);
const View* ancestor = view->parent_;
if (ancestor != nullptr) {
ancestor_bounds.SetRect(0, 0, ancestor->width(), ancestor->height());
vis_bounds.Intersect(ancestor_bounds);
} else if (!view->GetWidget()) {
// If the view has no Widget, we're not visible. Return an empty rect.
return gfx::Rect();
}
view = ancestor;
}
if (vis_bounds.IsEmpty()) {
return vis_bounds;
}
// Convert back to this views coordinate system. This mapping returns the
// enclosing rect, which is good because partially visible pixels should
// be considered visible.
return transform.InverseMapRect(vis_bounds).value_or(vis_bounds);
}
gfx::Rect View::GetBoundsInScreen() const {
gfx::Point origin;
View::ConvertPointToScreen(this, &origin);
return gfx::Rect(origin, size());
}
gfx::Rect View::GetAnchorBoundsInScreen() const {
return GetBoundsInScreen();
}
gfx::Size View::GetPreferredSize(const SizeBounds& available_size) const {
if (preferred_size_) {
return *preferred_size_;
}
return CalculatePreferredSize(available_size);
}
int View::GetBaseline() const {
return -1;
}
void View::SetPreferredSize(std::optional<gfx::Size> size) {
if (preferred_size_ == size) {
return;
}
preferred_size_ = std::move(size);
PreferredSizeChanged();
}
void View::SizeToPreferredSize() {
SetSize(GetPreferredSize());
}
gfx::Size View::GetMinimumSize() const {
if (HasLayoutManager()) {
return GetLayoutManager()->GetMinimumSize(this);
}
return GetPreferredSize(SizeBounds(0, 0));
}
gfx::Size View::GetMaximumSize() const {
return gfx::Size();
}
int View::GetHeightForWidth(int w) const {
if (HasLayoutManager()) {
return GetLayoutManager()->GetPreferredHeightForWidth(this, w);
}
return GetPreferredSize(SizeBounds(w, {})).height();
}
SizeBounds View::GetAvailableSize(const View* child) const {
if (HasLayoutManager()) {
return GetLayoutManager()->GetAvailableSize(this, child);
}
return SizeBounds();
}
bool View::GetVisible() const {
return visible_;
}
void View::SetVisible(bool visible) {
const bool was_visible = visible_;
if (was_visible != visible) {
// If the View was visible, schedule paint to refresh parent.
// TODO(beng): not sure we should be doing this if we have a layer.
if (was_visible) {
SchedulePaint();
}
visible_ = visible;
AdvanceFocusIfNecessary();
// Notify the parent.
if (parent_) {
parent_->ChildVisibilityChanged(this);
if (!view_accessibility_ || !view_accessibility_->GetIsIgnored()) {
parent_->NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged,
true);
}
}
// This notifies all sub-views recursively.
PropagateVisibilityNotifications(this, visible_);
UpdateLayerVisibility();
// Notify all other subscriptions of the change.
OnPropertyChanged(&visible_, kPropertyEffectsPaint);
if (was_visible) {
UpdateTooltip();
}
}
if (parent_) {
LayoutManager* const layout_manager = parent_->GetLayoutManager();
if (layout_manager && layout_manager->view_setting_visibility_on_ != this) {
layout_manager->ViewVisibilitySet(parent_, this, was_visible, visible);
}
}
}
base::CallbackListSubscription View::AddVisibleChangedCallback(
PropertyChangedCallback callback) {
return AddPropertyChangedCallback(&visible_, std::move(callback));
}
bool View::IsDrawn() const {
return visible_ && parent_ ? parent_->IsDrawn() : false;
}
bool View::GetIsDrawn() const {
return IsDrawn();
}
bool View::GetEnabled() const {
return enabled_;
}
void View::SetEnabled(bool enabled) {
if (enabled_ == enabled) {
return;
}
enabled_ = enabled;
GetViewAccessibility().SetIsEnabled(enabled);
AdvanceFocusIfNecessary();
OnPropertyChanged(&enabled_, kPropertyEffectsPaint);
}
base::CallbackListSubscription View::AddEnabledChangedCallback(
PropertyChangedCallback callback) {
return AddPropertyChangedCallback(&enabled_, std::move(callback));
}
View::Views View::GetChildrenInZOrder() {
if (HasLayoutManager()) {
const auto result = GetLayoutManager()->GetChildViewsInPaintOrder(this);
DCHECK_EQ(children_.size(), result.size());
return result;
}
return children_;
}
// Transformations -------------------------------------------------------------
gfx::Transform View::GetTransform() const {
if (!layer()) {
return gfx::Transform();
}
gfx::Transform transform = layer()->transform();
gfx::PointF scroll_offset = layer()->CurrentScrollOffset();
// Offsets for layer-based scrolling are never negative, but the horizontal
// scroll direction is reversed in RTL via canvas flipping.
transform.Translate((GetMirrored() ? 1 : -1) * scroll_offset.x(),
-scroll_offset.y());
return transform;
}
void View::SetClipPath(const SkPath& path) {
clip_path_ = path;
if (layer()) {
CreateMaskLayer();
}
}
void View::SetTransform(const gfx::Transform& transform) {
if (transform.IsIdentity()) {
if (layer()) {
layer()->SetTransform(transform);
}
paint_to_layer_for_transform_ = false;
CreateOrDestroyLayer();
} else {
paint_to_layer_for_transform_ = true;
CreateOrDestroyLayer();
DCHECK_NE(layer(), nullptr);
layer()->SetTransform(transform);
layer()->ScheduleDraw();
}
for (ui::Layer* layer : GetLayersInOrder(ViewLayer::kExclude)) {
layer->SetTransform(transform);
}
}
void View::SetPaintToLayer(ui::LayerType layer_type) {
// Avoid re-creating the layer if unnecessary.
if (paint_to_layer_explicitly_set_) {
DCHECK_NE(layer(), nullptr);
if (layer()->type() == layer_type) {
return;
}
}
DestroyLayerImpl(LayerChangeNotifyBehavior::DONT_NOTIFY);
paint_to_layer_explicitly_set_ = true;
// We directly call |CreateLayer()| here to pass |layer_type|. A call to
// |CreateOrDestroyLayer()| is therefore not necessary.
CreateLayer(layer_type);
if (!clip_path_.isEmpty() && !mask_layer_) {
CreateMaskLayer();
}
// Notify the parent chain about the layer change.
NotifyParentsOfLayerChange();
}
void View::DestroyLayer() {
paint_to_layer_explicitly_set_ = false;
CreateOrDestroyLayer();
}
void View::AddLayerToRegion(ui::Layer* new_layer, LayerRegion region) {
AddLayerToRegionImpl(
new_layer, region == LayerRegion::kAbove ? layers_above_ : layers_below_);
}
void View::RemoveLayerFromRegions(ui::Layer* old_layer) {
RemoveLayerFromRegionsKeepInLayerTree(old_layer);
// Note that |old_layer| may have already been removed from its parent.
ui::Layer* parent_layer = layer()->parent();
if (parent_layer && parent_layer == old_layer->parent()) {
parent_layer->Remove(old_layer);
}
CreateOrDestroyLayer();
}
void View::RemoveLayerFromRegionsKeepInLayerTree(ui::Layer* old_layer) {
auto remove_layer =
[old_layer, this](
std::vector<raw_ptr<ui::Layer, VectorExperimental>>& layer_vector) {
auto layer_pos = base::ranges::find(layer_vector, old_layer);
if (layer_pos == layer_vector.end()) {
return false;
}
layer_vector.erase(layer_pos);
old_layer->RemoveObserver(this);
return true;
};
const bool layer_removed =
remove_layer(layers_below_) || remove_layer(layers_above_);
DCHECK(layer_removed) << "Attempted to remove a layer that was never added.";
}
std::vector<ui::Layer*> View::GetLayersInOrder(ViewLayer view_layer) {
// If not painting to a layer, there are no layers immediately related to this
// view.
if (!layer()) {
// If there is no View layer, there should be no layers above or below.
DCHECK(layers_above_.empty() && layers_below_.empty());
return {};
}
std::vector<ui::Layer*> result;
for (ui::Layer* layer_below : layers_below_) {
result.push_back(layer_below);
}
if (view_layer == ViewLayer::kInclude) {
result.push_back(layer());
}
for (ui::Layer* layer_above : layers_above_) {
result.push_back(layer_above);
}
return result;
}
void View::LayerDestroyed(ui::Layer* layer) {
// Only layers added with |AddLayerToRegion()| or |AddLayerAboveView()|
// are observed so |layer| can safely be removed.
RemoveLayerFromRegions(layer);
}
std::unique_ptr<ui::Layer> View::RecreateLayer() {
std::unique_ptr<ui::Layer> old_layer = LayerOwner::RecreateLayer();
Widget* widget = GetWidget();
if (widget) {
widget->LayerTreeChanged();
}
return old_layer;
}
// RTL positioning -------------------------------------------------------------
gfx::Rect View::GetMirroredBounds() const {
gfx::Rect bounds(bounds_);
bounds.set_x(GetMirroredX());
return bounds;
}
gfx::Rect View::GetMirroredContentsBounds() const {
gfx::Rect bounds(bounds_);
bounds.Inset(GetInsets());
bounds.set_x(GetMirroredX());
return bounds;
}
gfx::Point View::GetMirroredPosition() const {
return gfx::Point(GetMirroredX(), y());
}
int View::GetMirroredX() const {
return parent_ ? parent_->GetMirroredXForRect(bounds_) : x();
}
int View::GetMirroredXForRect(const gfx::Rect& rect) const {
return GetMirrored() ? (width() - rect.x() - rect.width()) : rect.x();
}
gfx::Rect View::GetMirroredRect(const gfx::Rect& rect) const {
gfx::Rect mirrored_rect = rect;
mirrored_rect.set_x(GetMirroredXForRect(rect));
return mirrored_rect;
}
int View::GetMirroredXInView(int x) const {
return GetMirrored() ? width() - x : x;
}
int View::GetMirroredXWithWidthInView(int x, int w) const {
return GetMirrored() ? width() - x - w : x;
}
// Layout ----------------------------------------------------------------------
void View::DeprecatedLayoutImmediately() {
LayoutImmediately();
}
void View::Layout(PassKey) {
needs_layout_ = false;
// If we have a layout manager, let it handle the layout for us.
if (HasLayoutManager()) {
GetLayoutManager()->Layout(this);
}
// Make sure to propagate layout to any children that haven't received it yet
// through the layout manager and need to be laid out. This is needed for the
// case when the child requires a layout but its bounds weren't changed by the
// layout manager. If there is no layout manager, we just propagate layout
// down the hierarchy, so whoever receives the call can take appropriate
// action.
internal::ScopedChildrenLock lock(this);
for (views::View* child : children_) {
if (child->needs_layout_ || !HasLayoutManager() ||
child->GetProperty(kViewIgnoredByLayoutKey)) {
TRACE_EVENT1("views", "View::LayoutChildren", "class",
child->GetClassName());
child->needs_layout_ = false;
child->LayoutImmediately();
}
}
}
void View::InvalidateLayout() {
if (invalidating_) {
return;
}
// There is no need to `InvalidateLayout()` when `Widget::IsClosed()`.
if (Widget* widget = GetWidget(); widget && widget->IsClosed()) {
return;
}
base::AutoReset<bool> invalidating(&invalidating_, true);
// We should never need to invalidate during a layout call; this tracks
// how many times that is happening.
++invalidates_during_layout_;
// Always invalidate up. This is needed to handle the case of us already being
// valid, but not our parent.
needs_layout_ = true;
for (ViewObserver& observer : observers_) {
observer.OnViewLayoutInvalidated(this);
}
if (HasLayoutManager()) {
GetLayoutManager()->InvalidateLayout();
}
if (parent_) {
parent_->InvalidateLayout();
} else {
Widget* widget = GetWidget();
if (widget) {
widget->OnRootViewLayoutInvalidated();
}
}
}
LayoutManager* View::GetLayoutManager() const {
return layout_manager_.get();
}
void View::SetLayoutManager(std::nullptr_t) {
SetLayoutManagerImpl(nullptr);
}
bool View::GetUseDefaultFillLayout() const {
return use_default_fill_layout_;
}
void View::SetUseDefaultFillLayout(bool value) {
if (value == use_default_fill_layout_) {
return;
}
use_default_fill_layout_ = value;
if (use_default_fill_layout_) {
SetToDefaultFillLayout();
} else {
SetLayoutManager(nullptr);
}
OnPropertyChanged(&use_default_fill_layout_, kPropertyEffectsLayout);
}
// Attributes ------------------------------------------------------------------
const View* View::GetViewByID(int id) const {
if (id == id_) {
return const_cast<View*>(this);
}
internal::ScopedChildrenLock lock(this);
for (views::View* child : children_) {
const View* view = child->GetViewByID(id);
if (view) {
return view;
}
}
return nullptr;
}
View* View::GetViewByID(int id) {
return const_cast<View*>(const_cast<const View*>(this)->GetViewByID(id));
}
void View::SetID(int id) {
if (id == id_) {
return;
}
id_ = id;
OnPropertyChanged(&id_, kPropertyEffectsNone);
}
base::CallbackListSubscription View::AddIDChangedCallback(
PropertyChangedCallback callback) {
return AddPropertyChangedCallback(&id_, callback);
}
void View::SetGroup(int gid) {
// Don't change the group id once it's set.
DCHECK(group_ == -1 || group_ == gid);
if (group_ != gid) {
group_ = gid;
OnPropertyChanged(&group_, kPropertyEffectsNone);
}
}
int View::GetGroup() const {
return group_;
}
base::CallbackListSubscription View::AddGroupChangedCallback(
PropertyChangedCallback callback) {
return AddPropertyChangedCallback(&group_, callback);
}
bool View::IsGroupFocusTraversable() const {
return true;
}
void View::GetViewsInGroup(int group, Views* views) {
if (group_ == group) {
views->push_back(this);
}
internal::ScopedChildrenLock lock(this);
for (views::View* child : children_) {
child->GetViewsInGroup(group, views);
}
}
View* View::GetSelectedViewForGroup(int group) {
Views views;
GetWidget()->GetRootView()->GetViewsInGroup(group, &views);
return views.empty() ? nullptr : views[0];
}
std::string View::GetObjectName() const {
return GetClassName();
}
// Coordinate conversion -------------------------------------------------------
// static
void View::ConvertPointToTarget(const View* source,
const View* target,
gfx::Point* point) {
DCHECK(source);
DCHECK(target);
if (source == target) {
return;
}
const View* root = GetHierarchyRoot(target);
#if BUILDFLAG(IS_MAC)
// If the root views don't match make sure we are in the same widget tree.
// Full screen in macOS creates a child widget that hosts top chrome.
// TODO(bur): Remove this check when top chrome can be composited into its own
// NSView without the need for a new widget.
if (GetHierarchyRoot(source) != root) {
const Widget* source_top_level_widget =
source->GetWidget()->GetTopLevelWidget();
const Widget* target_top_level_widget =
target->GetWidget()->GetTopLevelWidget();
CHECK_EQ(source_top_level_widget, target_top_level_widget);
}
#else // IS_MAC
CHECK_EQ(GetHierarchyRoot(source), root);
#endif
if (source != root) {
source->ConvertPointForAncestor(root, point);
}
if (target != root) {
target->ConvertPointFromAncestor(root, point);
}
}
// static
gfx::Point View::ConvertPointToTarget(const View* source,
const View* target,
const gfx::Point& point) {
gfx::Point local_point = point;
ConvertPointToTarget(source, target, &local_point);
return local_point;
}
// static
void View::ConvertRectToTarget(const View* source,
const View* target,
gfx::RectF* rect) {
DCHECK(source);
DCHECK(target);
if (source == target) {
return;
}
const View* root = GetHierarchyRoot(target);
CHECK_EQ(GetHierarchyRoot(source), root);
if (source != root) {
source->ConvertRectForAncestor(root, rect);
}
if (target != root) {
target->ConvertRectFromAncestor(root, rect);
}
}
// static
gfx::RectF View::ConvertRectToTarget(const View* source,
const View* target,
const gfx::RectF& rect) {
gfx::RectF local_rect = rect;
ConvertRectToTarget(source, target, &local_rect);
return local_rect;
}
// static
gfx::Rect View::ConvertRectToTarget(const View* source,
const View* target,
const gfx::Rect& rect) {
constexpr float kDefaultAllowedConversionError = 0.00001f;
return gfx::ToEnclosedRectIgnoringError(
ConvertRectToTarget(source, target, gfx::RectF(rect)),
kDefaultAllowedConversionError);
}
// static
void View::ConvertPointToWidget(const View* src, gfx::Point* p) {
DCHECK(src);
DCHECK(p);
src->ConvertPointForAncestor(nullptr, p);
}
// static
void View::ConvertPointFromWidget(const View* dest, gfx::Point* p) {
DCHECK(dest);
DCHECK(p);
dest->ConvertPointFromAncestor(nullptr, p);
}
// static
void View::ConvertPointToScreen(const View* src, gfx::Point* p) {
DCHECK(src);
DCHECK(p);
// If the view is not connected to a tree, there's nothing we can do.
const Widget* widget = src->GetWidget();
if (widget) {
ConvertPointToWidget(src, p);
*p += widget->GetClientAreaBoundsInScreen().OffsetFromOrigin();
}
}
// static
gfx::Point View::ConvertPointToScreen(const View* src, const gfx::Point& p) {
gfx::Point screen_pt = p;
ConvertPointToScreen(src, &screen_pt);
return screen_pt;
}
// static
void View::ConvertPointFromScreen(const View* dst, gfx::Point* p) {
DCHECK(dst);
DCHECK(p);
const views::Widget* widget = dst->GetWidget();
if (!widget) {
return;
}
*p -= widget->GetClientAreaBoundsInScreen().OffsetFromOrigin();
ConvertPointFromWidget(dst, p);
}
// static
gfx::Point View::ConvertPointFromScreen(const View* src, const gfx::Point& p) {
gfx::Point local_pt = p;
ConvertPointFromScreen(src, &local_pt);
return local_pt;
}
// static
void View::ConvertRectToScreen(const View* src, gfx::Rect* rect) {
DCHECK(src);
DCHECK(rect);
gfx::Point new_origin = rect->origin();
views::View::ConvertPointToScreen(src, &new_origin);
rect->set_origin(new_origin);
}
gfx::Rect View::ConvertRectToParent(const gfx::Rect& rect) const {
// This mapping returns the enclosing rect, which is good because pixels that
// partially occupy in the parent should be included.
gfx::Rect x_rect = GetTransform().MapRect(rect);
x_rect.Offset(GetMirroredPosition().OffsetFromOrigin());
return x_rect;
}
gfx::Rect View::ConvertRectToWidget(const gfx::Rect& rect) const {
gfx::Rect x_rect = rect;
for (const View* v = this; v; v = v->parent_) {
x_rect = v->ConvertRectToParent(x_rect);
}
return x_rect;
}
// Painting --------------------------------------------------------------------
void View::SchedulePaint() {
SchedulePaintInRect(GetLocalBounds());
}
void View::SchedulePaintInRect(const gfx::Rect& rect) {
// There is no need to `SchedulePaintInRect()` when `Widget::IsClosed()`.
if (Widget* widget = GetWidget(); widget && widget->IsClosed()) {
return;
}
needs_paint_ = true;
SchedulePaintInRectImpl(rect);
}
// The following is temporary. It is present to help diagnose a potential UaF.
std::string IntToHex(uint32_t value) {
std::stringstream stream;
stream << std::hex << std::setw(8) << std::setfill('0') << value;
return stream.str();
}
void View::Paint(const PaintInfo& parent_paint_info) {
CHECK_EQ(life_cycle_state_, LifeCycleState::kAlive)
// TODO (crbug/323752682): Add additional information to help identify
// where a potential failure lies. Remove once cause is found and fixed.
<< "life_cycle_state_: 0x"
<< IntToHex(static_cast<uint32_t>(life_cycle_state_))
<< " Classname: " << GetClassName();
if (!ShouldPaint()) {
return;
}
if (!has_run_accessibility_paint_checks_) {
RunAccessibilityPaintChecks(this);
has_run_accessibility_paint_checks_ = true;
}
const gfx::Rect& parent_bounds =
!parent() ? GetMirroredBounds() : parent()->GetMirroredBounds();
PaintInfo paint_info = PaintInfo::CreateChildPaintInfo(
parent_paint_info, GetMirroredBounds(), parent_bounds.size(),
GetPaintScaleType(), !!layer(), needs_paint_);
needs_paint_ = false;
const ui::PaintContext& context = paint_info.context();
bool is_invalidated = true;
if (paint_info.context().CanCheckInvalid() ||
base::FeatureList::IsEnabled(features::kEnableViewPaintOptimization)) {
// For View paint optimization, do not default to repainting every View in
// the View hierarchy if the invalidation rect is empty. Repainting does not
// depend on the invalidation rect for View paint optimization.
#if DCHECK_IS_ON()
if (!context.is_pixel_canvas()) {
gfx::Vector2d offset;
context.Visited(this);
View* view = this;
while (view->parent() && !view->layer()) {
DCHECK(view->GetTransform().IsIdentity());
offset += view->GetMirroredPosition().OffsetFromOrigin();
view = view->parent();
}
// The offset in the PaintContext should be the offset up to the paint
// root, which we compute and verify here.
DCHECK_EQ(context.PaintOffset().x(), offset.x());
DCHECK_EQ(context.PaintOffset().y(), offset.y());
// The above loop will stop when |view| is the paint root, which should be
// the root of the current paint walk, as verified by storing the root in
// the PaintContext.
DCHECK_EQ(context.RootVisited(), view);
}
#endif
// If the View wasn't invalidated, don't waste time painting it, the output
// would be culled.
is_invalidated = paint_info.ShouldPaint();
}
TRACE_EVENT1("views", "View::Paint", "class", GetClassName());
if (layouts_since_last_paint_) {
UMA_HISTOGRAM_COUNTS_100("Views.UnnecessaryLayouts",
layouts_since_last_paint_ - 1);
layouts_since_last_paint_ = 0;
}
// If the view is backed by a layer, it should paint with itself as the origin
// rather than relative to its parent.
// TODO(danakj): Rework clip and transform recorder usage here to use
// std::optional once we can do so.
ui::ClipRecorder clip_recorder(parent_paint_info.context());
if (!layer()) {
// Set the clip rect to the bounds of this View, or |clip_path_| if it's
// been set. Note that the X (or left) position we pass to ClipRect takes
// into consideration whether or not the View uses a right-to-left layout so
// that we paint the View in its mirrored position if need be.
if (clip_path_.isEmpty()) {
clip_recorder.ClipRect(gfx::Rect(paint_info.paint_recording_size()) +
paint_info.offset_from_parent());
} else {
SkPath clip_path_in_parent = clip_path_;
// Transform |clip_path_| from local space to parent recording space.
gfx::Transform to_parent_recording_space;
to_parent_recording_space.Translate(paint_info.offset_from_parent());
to_parent_recording_space.Scale(
SkFloatToScalar(paint_info.paint_recording_scale_x()),
SkFloatToScalar(paint_info.paint_recording_scale_y()));
clip_path_in_parent.transform(
gfx::TransformToFlattenedSkMatrix(to_parent_recording_space));
clip_recorder.ClipPathWithAntiAliasing(clip_path_in_parent);
}
}
ui::TransformRecorder transform_recorder(context);
SetUpTransformRecorderForPainting(paint_info.offset_from_parent(),
&transform_recorder);
// Note that the cache is not aware of the offset of the view
// relative to the parent since painting is always done relative to
// the top left of the individual view.
if (is_invalidated ||
!paint_cache_.UseCache(context, paint_info.paint_recording_size())) {
ui::PaintRecorder recorder(context, paint_info.paint_recording_size(),
paint_info.paint_recording_scale_x(),
paint_info.paint_recording_scale_y(),
&paint_cache_);
gfx::Canvas* canvas = recorder.canvas();
gfx::ScopedCanvas scoped_canvas(canvas);
if (flip_canvas_on_paint_for_rtl_ui_) {
scoped_canvas.FlipIfRTL(width());
}
// Delegate painting the contents of the View to the virtual OnPaint method.
OnPaint(canvas);
}
CHECK_EQ(life_cycle_state_, LifeCycleState::kAlive);
// View::Paint() recursion over the subtree.
PaintChildren(paint_info);
}
void View::SetBackground(std::unique_ptr<Background> b) {
background_ = std::move(b);
if (background_ && GetWidget()) {
background_->OnViewThemeChanged(this);
}
SchedulePaint();
}
Background* View::GetBackground() const {
return background_.get();
}
void View::SetBorder(std::unique_ptr<Border> b) {
const gfx::Rect old_contents_bounds = GetContentsBounds();
border_ = std::move(b);
if (border_ && GetWidget()) {
border_->OnViewThemeChanged(this);
}
// Conceptually, this should be PreferredSizeChanged(), but for some view
// hierarchies that triggers synchronous add/remove operations that are unsafe
// in some contexts where SetBorder is called.
//
// InvalidateLayout() still triggers a re-layout of the view, which should
// include re-querying its preferred size so in practice this is both safe and
// has the intended effect.
if (old_contents_bounds != GetContentsBounds()) {
InvalidateLayout();
}
SchedulePaint();
}
Border* View::GetBorder() const {
return border_.get();
}
const ui::ThemeProvider* View::GetThemeProvider() const {
const auto* widget = GetWidget();
return widget ? widget->GetThemeProvider() : nullptr;
}
const LayoutProvider* View::GetLayoutProvider() const {
if (!GetWidget()) {
return nullptr;
}
// TODO(pbos): Ask the widget for a layout provider.
return LayoutProvider::Get();
}
const ui::ColorProvider* View::GetColorProvider() const {
const auto* widget = GetWidget();
return widget ? widget->GetColorProvider() : nullptr;
}
const ui::NativeTheme* View::GetNativeTheme() const {
if (parent()) {
return parent()->GetNativeTheme();
}
const Widget* widget = GetWidget();
if (widget) {
return widget->GetNativeTheme();
}
static bool has_crashed_reported = false;
// Crash on debug builds and dump without crashing on release builds to ensure
// we catch fallthrough to the global NativeTheme instance on all Chromium
// builds (crbug.com/1056756).
if (!has_crashed_reported) {
DCHECK(false);
base::debug::DumpWithoutCrashing();
has_crashed_reported = true;
}
return ui::NativeTheme::GetInstanceForNativeUi();
}
// RTL painting ----------------------------------------------------------------
bool View::GetFlipCanvasOnPaintForRTLUI() const {
return flip_canvas_on_paint_for_rtl_ui_;
}
void View::SetFlipCanvasOnPaintForRTLUI(bool enable) {
if (enable == flip_canvas_on_paint_for_rtl_ui_) {
return;
}
flip_canvas_on_paint_for_rtl_ui_ = enable;
OnPropertyChanged(&flip_canvas_on_paint_for_rtl_ui_, kPropertyEffectsPaint);
}
base::CallbackListSubscription
View::AddFlipCanvasOnPaintForRTLUIChangedCallback(
PropertyChangedCallback callback) {
return AddPropertyChangedCallback(&flip_canvas_on_paint_for_rtl_ui_,
std::move(callback));
}
void View::SetMirrored(bool is_mirrored) {
if (is_mirrored_ && is_mirrored_.value() == is_mirrored) {
return;
}
is_mirrored_ = is_mirrored;
OnPropertyChanged(&is_mirrored_, kPropertyEffectsPaint);
}
bool View::GetMirrored() const {
return is_mirrored_.value_or(base::i18n::IsRTL());
}
// Input -----------------------------------------------------------------------
View* View::GetEventHandlerForPoint(const gfx::Point& point) {
return GetEventHandlerForRect(gfx::Rect(point, gfx::Size(1, 1)));
}
View* View::GetEventHandlerForRect(const gfx::Rect& rect) {
return GetEffectiveViewTargeter()->TargetForRect(this, rect);
}
bool View::GetCanProcessEventsWithinSubtree() const {
return can_process_events_within_subtree_;
}
void View::SetCanProcessEventsWithinSubtree(bool can_process) {
if (can_process_events_within_subtree_ == can_process) {
return;
}
can_process_events_within_subtree_ = can_process;
OnPropertyChanged(&can_process_events_within_subtree_, kPropertyEffectsNone);
}
View* View::GetTooltipHandlerForPoint(const gfx::Point& point) {
// TODO(tdanderson): Move this implementation into ViewTargetDelegate.
if (!HitTestPoint(point) || !GetCanProcessEventsWithinSubtree()) {
return nullptr;
}
// Walk the child Views recursively looking for the View that most
// tightly encloses the specified point.
View::Views children = GetChildrenInZOrder();
DCHECK_EQ(children_.size(), children.size());
for (views::View* child : base::Reversed(children)) {
if (!child->GetVisible()) {
continue;
}
gfx::Point point_in_child_coords(point);
ConvertPointToTarget(this, child, &point_in_child_coords);
View* handler = child->GetTooltipHandlerForPoint(point_in_child_coords);
if (handler) {
return handler;
}
}
return this;
}
ui::Cursor View::GetCursor(const ui::MouseEvent& event) {
return ui::Cursor();
}
bool View::HitTestPoint(const gfx::Point& point) const {
return HitTestRect(gfx::Rect(point, gfx::Size(1, 1)));
}
bool View::HitTestRect(const gfx::Rect& rect) const {
return GetEffectiveViewTargeter()->DoesIntersectRect(this, rect);
}
bool View::IsMouseHovered() const {
// If we haven't yet been placed in an onscreen view hierarchy, we can't be
// hovered.
if (!GetWidget()) {
return false;
}
// If mouse events are disabled, then the mouse cursor is invisible and
// is therefore not hovering over this button.
if (!GetWidget()->IsMouseEventsEnabled()) {
return false;
}
gfx::Point cursor_pos(display::Screen::GetScreen()->GetCursorScreenPoint());
ConvertPointFromScreen(this, &cursor_pos);
return HitTestPoint(cursor_pos);
}
bool View::OnMousePressed(const ui::MouseEvent& event) {
return false;
}
bool View::OnMouseDragged(const ui::MouseEvent& event) {
return false;
}
void View::OnMouseReleased(const ui::MouseEvent& event) {}
void View::OnMouseCaptureLost() {}
void View::OnMouseMoved(const ui::MouseEvent& event) {}
void View::OnMouseEntered(const ui::MouseEvent& event) {}
void View::OnMouseExited(const ui::MouseEvent& event) {}
void View::SetMouseAndGestureHandler(View* new_handler) {
// |new_handler| may be nullptr.
if (parent_) {
parent_->SetMouseAndGestureHandler(new_handler);
}
}
void View::SetMouseHandler(View* new_handler) {
if (parent_) {
parent_->SetMouseHandler(new_handler);
}
}
bool View::OnKeyPressed(const ui::KeyEvent& event) {
return false;
}
bool View::OnKeyReleased(const ui::KeyEvent& event) {
return false;
}
bool View::OnMouseWheel(const ui::MouseWheelEvent& event) {
return false;
}
void View::OnKeyEvent(ui::KeyEvent* event) {
bool consumed = (event->type() == ui::ET_KEY_PRESSED) ? OnKeyPressed(*event)
: OnKeyReleased(*event);
if (consumed) {
event->StopPropagation();
}
}
void View::OnMouseEvent(ui::MouseEvent* event) {
switch (event->type()) {
case ui::ET_MOUSE_PRESSED:
if (ProcessMousePressed(*event)) {
event->SetHandled();
}
return;
case ui::ET_MOUSE_MOVED:
if ((event->flags() &
(ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON |
ui::EF_MIDDLE_MOUSE_BUTTON)) == 0) {
OnMouseMoved(*event);
return;
}
[[fallthrough]];
case ui::ET_MOUSE_DRAGGED:
ProcessMouseDragged(event);
return;
case ui::ET_MOUSE_RELEASED:
ProcessMouseReleased(*event);
return;
case ui::ET_MOUSEWHEEL:
if (OnMouseWheel(*event->AsMouseWheelEvent())) {
event->SetHandled();
}
break;
case ui::ET_MOUSE_ENTERED:
if (event->flags() & ui::EF_TOUCH_ACCESSIBILITY) {
NotifyAccessibilityEvent(ax::mojom::Event::kHover, true);
}
OnMouseEntered(*event);
break;
case ui::ET_MOUSE_EXITED:
OnMouseExited(*event);
break;
default:
return;
}
}
void View::OnScrollEvent(ui::ScrollEvent* event) {}
void View::OnTouchEvent(ui::TouchEvent* event) {
NOTREACHED_NORETURN() << "Views should not receive touch events.";
}
void View::OnGestureEvent(ui::GestureEvent* event) {}
std::string_view View::GetLogContext() const {
return GetClassName();
}
void View::SetNotifyEnterExitOnChild(bool notify) {
notify_enter_exit_on_child_ = notify;
}
bool View::GetNotifyEnterExitOnChild() const {
return notify_enter_exit_on_child_;
}
const ui::InputMethod* View::GetInputMethod() const {
Widget* widget = const_cast<Widget*>(GetWidget());
return widget ? const_cast<const ui::InputMethod*>(widget->GetInputMethod())
: nullptr;
}
std::unique_ptr<ViewTargeter> View::SetEventTargeter(
std::unique_ptr<ViewTargeter> targeter) {
std::unique_ptr<ViewTargeter> old_targeter = std::move(targeter_);
targeter_ = std::move(targeter);
return old_targeter;
}
ViewTargeter* View::GetEffectiveViewTargeter() const {
DCHECK(GetWidget());
ViewTargeter* view_targeter = targeter();
if (!view_targeter) {
view_targeter = GetWidget()->GetRootView()->targeter();
}
CHECK(view_targeter);
return view_targeter;
}
WordLookupClient* View::GetWordLookupClient() {
return nullptr;
}
bool View::CanAcceptEvent(const ui::Event& event) {
return IsDrawn();
}
ui::EventTarget* View::GetParentTarget() {
return parent_;
}
std::unique_ptr<ui::EventTargetIterator> View::GetChildIterator() const {
return std::make_unique<ui::EventTargetIteratorPtrImpl<View>>(children_);
}
ui::EventTargeter* View::GetEventTargeter() {
return targeter_.get();
}
void View::ConvertEventToTarget(const ui::EventTarget* target,
ui::LocatedEvent* event) const {
event->ConvertLocationToTarget(this, static_cast<const View*>(target));
}
gfx::PointF View::GetScreenLocationF(const ui::LocatedEvent& event) const {
DCHECK_EQ(this, event.target());
gfx::Point screen_location(event.location());
ConvertPointToScreen(this, &screen_location);
return gfx::PointF(screen_location);
}
// Accelerators ----------------------------------------------------------------
void View::AddAccelerator(const ui::Accelerator& accelerator) {
if (!accelerators_) {
accelerators_ = std::make_unique<std::vector<ui::Accelerator>>();
}
if (!base::Contains(*accelerators_, accelerator)) {
accelerators_->push_back(accelerator);
}
RegisterPendingAccelerators();
}
void View::RemoveAccelerator(const ui::Accelerator& accelerator) {
CHECK(accelerators_) << "Removing non-existent accelerator";
auto i(base::ranges::find(*accelerators_, accelerator));
CHECK(i != accelerators_->end()) << "Removing non-existent accelerator";
auto index = static_cast<size_t>(i - accelerators_->begin());
accelerators_->erase(i);
if (index >= registered_accelerator_count_) {
// The accelerator is not registered to FocusManager.
return;
}
--registered_accelerator_count_;
// Providing we are attached to a Widget and registered with a focus manager,
// we should de-register from that focus manager now.
if (GetWidget() && accelerator_focus_manager_) {
accelerator_focus_manager_->UnregisterAccelerator(accelerator, this);
}
}
void View::ResetAccelerators() {
if (accelerators_) {
UnregisterAccelerators(false);
}
}
bool View::AcceleratorPressed(const ui::Accelerator& accelerator) {
return false;
}
bool View::CanHandleAccelerators() const {
const Widget* widget = GetWidget();
if (!GetEnabled() || !IsDrawn() || !widget || !widget->IsVisible()) {
return false;
}
#if BUILDFLAG(ENABLE_DESKTOP_AURA)
// Non-ChromeOS aura windows have an associated FocusManagerEventHandler which
// adds currently focused view as an event PreTarget (see
// DesktopNativeWidgetAura::InitNativeWidget). However, the focused view isn't
// always the right view to handle accelerators: It should only handle them
// when active. Only top level widgets can be active, so for child widgets
// check if they are focused instead. ChromeOS also behaves different than
// Linux when an extension popup is about to handle the accelerator.
bool child = widget && widget->GetTopLevelWidget() != widget;
bool focus_in_child = widget && widget->GetRootView()->Contains(
GetFocusManager()->GetFocusedView());
if ((child && !focus_in_child) || (!child && !widget->IsActive())) {
return false;
}
#endif
return true;
}
// Focus -----------------------------------------------------------------------
bool View::HasFocus() const {
const FocusManager* focus_manager = GetFocusManager();
return focus_manager && (focus_manager->GetFocusedView() == this);
}
View* View::GetNextFocusableView() {
return next_focusable_view_;
}
const View* View::GetNextFocusableView() const {
return next_focusable_view_;
}
View* View::GetPreviousFocusableView() {
return previous_focusable_view_;
}
void View::RemoveFromFocusList() {
View* const old_prev = previous_focusable_view_;
View* const old_next = next_focusable_view_;
previous_focusable_view_ = nullptr;
next_focusable_view_ = nullptr;
if (old_prev) {
old_prev->next_focusable_view_ = old_next;
}
if (old_next) {
old_next->previous_focusable_view_ = old_prev;
}
}
void View::InsertBeforeInFocusList(View* view) {
DCHECK(view);
DCHECK_EQ(parent_, view->parent_);
if (view == next_focusable_view_) {
return;
}
RemoveFromFocusList();
next_focusable_view_ = view;
previous_focusable_view_ = view->previous_focusable_view_;
if (previous_focusable_view_) {
previous_focusable_view_->next_focusable_view_ = this;
}
next_focusable_view_->previous_focusable_view_ = this;
}
void View::InsertAfterInFocusList(View* view) {
DCHECK(view);
DCHECK_EQ(parent_, view->parent_);
if (view == previous_focusable_view_) {
return;
}
RemoveFromFocusList();
if (view->next_focusable_view_) {
InsertBeforeInFocusList(view->next_focusable_view_);
return;
}
view->next_focusable_view_ = this;
previous_focusable_view_ = view;
}
View::Views View::GetChildrenFocusList() {
View* starting_focus_view = nullptr;
Views children_views = children();
for (View* child : children_views) {
if (child->GetPreviousFocusableView() == nullptr) {
starting_focus_view = child;
break;
}
}
if (starting_focus_view == nullptr) {
return {};
}
Views result;
// Tracks the views traversed so far. Used to check for cycles.
base::flat_set<View*> seen_views;
View* cur = starting_focus_view;
while (cur != nullptr) {
// Views are not supposed to have focus cycles, but just in case, fail
// gracefully to avoid a crash.
if (seen_views.contains(cur)) {
LOG(ERROR) << "View focus cycle detected.";
return {};
}
seen_views.insert(cur);
result.push_back(cur);
cur = cur->GetNextFocusableView();
}
return result;
}
View::FocusBehavior View::GetFocusBehavior() const {
return focus_behavior_;
}
void View::SetFocusBehavior(FocusBehavior focus_behavior) {
if (GetFocusBehavior() == focus_behavior) {
return;
}
focus_behavior_ = focus_behavior;
AdvanceFocusIfNecessary();
OnPropertyChanged(&focus_behavior_, kPropertyEffectsNone);
}
bool View::IsFocusable() const {
return GetFocusBehavior() == FocusBehavior::ALWAYS && GetEnabled() &&
IsDrawn();
}
FocusManager* View::GetFocusManager() {
Widget* widget = GetWidget();
return widget ? widget->GetFocusManager() : nullptr;
}
const FocusManager* View::GetFocusManager() const {
const Widget* widget = GetWidget();
return widget ? widget->GetFocusManager() : nullptr;
}
void View::RequestFocus() {
FocusManager* focus_manager = GetFocusManager();
if (focus_manager) {
bool focusable = focus_manager->keyboard_accessible()
? GetViewAccessibility().IsAccessibilityFocusable()
: IsFocusable();
if (focusable) {
focus_manager->SetFocusedView(this);
}
}
}
bool View::SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) {
return false;
}
FocusTraversable* View::GetFocusTraversable() {
return nullptr;
}
FocusTraversable* View::GetPaneFocusTraversable() {
return nullptr;
}
// Tooltips --------------------------------------------------------------------
std::u16string View::GetTooltipText(const gfx::Point& p) const {
return std::u16string();
}
// Context menus ---------------------------------------------------------------
void View::set_context_menu_controller(ContextMenuController* menu_controller) {
context_menu_controller_ = menu_controller;
GetViewAccessibility().SetShowContextMenu(menu_controller != nullptr);
}
void View::ShowContextMenu(const gfx::Point& p,
ui::MenuSourceType source_type) {
if (!context_menu_controller_) {
return;
}
context_menu_controller_->ShowContextMenuForView(this, p, source_type);
}
gfx::Point View::GetKeyboardContextMenuLocation() {
gfx::Rect vis_bounds = GetVisibleBounds();
gfx::Point screen_point(vis_bounds.x() + vis_bounds.width() / 2,
vis_bounds.y() + vis_bounds.height() / 2);
ConvertPointToScreen(this, &screen_point);
return screen_point;
}
// Drag and drop ---------------------------------------------------------------
bool View::GetDropFormats(int* formats,
std::set<ui::ClipboardFormatType>* format_types) {
return false;
}
bool View::AreDropTypesRequired() {
return false;
}
bool View::CanDrop(const OSExchangeData& data) {
// TODO(sky): when I finish up migration, this should default to true.
return false;
}
void View::OnDragEntered(const ui::DropTargetEvent& event) {}
int View::OnDragUpdated(const ui::DropTargetEvent& event) {
return ui::DragDropTypes::DRAG_NONE;
}
void View::OnDragExited() {}
void View::OnDragDone() {}
View::DropCallback View::GetDropCallback(const ui::DropTargetEvent& event) {
return base::NullCallback();
}
// static
bool View::ExceededDragThreshold(const gfx::Vector2d& delta) {
return (abs(delta.x()) > GetHorizontalDragThreshold() ||
abs(delta.y()) > GetVerticalDragThreshold());
}
// Accessibility----------------------------------------------------------------
ViewAccessibility& View::GetViewAccessibility() const {
if (!view_accessibility_) {
view_accessibility_ = ViewAccessibility::Create(const_cast<View*>(this));
}
return *view_accessibility_;
}
void View::GetAccessibleNodeData(ui::AXNodeData* node_data) {
// `ViewAccessibility::GetAccessibleNodeData` populates the id and classname
// values prior to asking the View for its data. We don't want to stomp on
// those values.
ax_node_data_->id = node_data->id;
ax_node_data_->AddStringAttribute(
ax::mojom::StringAttribute::kClassName,
node_data->GetStringAttribute(ax::mojom::StringAttribute::kClassName));
// Copy everything set by the property setters.
*node_data = *ax_node_data_;
}
void View::SetAccessibilityProperties(
std::optional<ax::mojom::Role> role,
std::optional<std::u16string> name,
std::optional<std::u16string> description,
std::optional<std::u16string> role_description,
std::optional<ax::mojom::NameFrom> name_from,
std::optional<ax::mojom::DescriptionFrom> description_from) {
base::AutoReset<bool> initializing(&pause_accessibility_events_, true);
if (role.has_value()) {
if (role_description.has_value()) {
SetAccessibleRole(role.value(), role_description.value());
} else {
SetAccessibleRole(role.value());
}
}
// Defining the NameFrom value without specifying the name doesn't make much
// sense. The only exception might be if the NameFrom is setting the name to
// explicitly empty. In order to prevent surprising/confusing behavior, we
// only use the NameFrom value if we have an explicit name. As a result, any
// caller setting the name to explicitly empty must set the name to an empty
// string.
if (name.has_value()) {
if (name_from.has_value()) {
SetAccessibleName(name.value(), name_from.value());
} else {
SetAccessibleName(name.value());
}
}
// See the comment above regarding the NameFrom value.
if (description.has_value()) {
if (description_from.has_value()) {
GetViewAccessibility().SetDescription(description.value(),
description_from.value());
} else {
GetViewAccessibility().SetDescription(description.value());
}
}
}
void View::SetAccessibleName(const std::u16string& name) {
SetAccessibleName(
name, static_cast<ax::mojom::NameFrom>(ax_node_data_->GetIntAttribute(
ax::mojom::IntAttribute::kNameFrom)));
}
void View::SetAccessibleName(std::u16string name,
ax::mojom::NameFrom name_from) {
// Allow subclasses to adjust the name.
AdjustAccessibleName(name, name_from);
// Ensure we have a current `name_from` value. For instance, the name might
// still be an empty string, but a view is now indicating that this is by
// design by setting `NameFrom::kAttributeExplicitlyEmpty`.
ax_node_data_->SetNameFrom(name_from);
if (name == accessible_name_) {
return;
}
if (name.empty()) {
ax_node_data_->RemoveStringAttribute(ax::mojom::StringAttribute::kName);
} else if (ax_node_data_->role != ax::mojom::Role::kUnknown &&
ax_node_data_->role != ax::mojom::Role::kNone) {
// TODO(accessibility): This is to temporarily work around the DCHECK
// in `AXNodeData` that wants to have a role to calculate a name-from.
// If we don't have a role yet, don't add it to the data until we do.
// See `SetAccessibleRole` where we check for and handle this condition.
// Also note that the `SetAccessibilityProperties` function allows view
// authors to set the role and name at once, if all views use it, we can
// remove this workaround.
ax_node_data_->SetName(name);
}
accessible_name_ = name;
OnPropertyChanged(&accessible_name_, kPropertyEffectsNone);
OnAccessibleNameChanged(name);
NotifyAccessibilityEvent(ax::mojom::Event::kTextChanged, true);
}
void View::SetAccessibleName(View* naming_view) {
DCHECK(naming_view);
DCHECK_NE(this, naming_view);
const std::u16string& name = naming_view->GetAccessibleName();
DCHECK(!name.empty());
SetAccessibleName(name, ax::mojom::NameFrom::kRelatedElement);
ax_node_data_->AddIntListAttribute(
ax::mojom::IntListAttribute::kLabelledbyIds,
{naming_view->GetViewAccessibility().GetUniqueId().Get()});
}
const std::u16string& View::GetAccessibleName() const {
return accessible_name_;
}
void View::SetAccessibleRole(const ax::mojom::Role role) {
if (role == accessible_role_) {
return;
}
ax_node_data_->role = role;
if (role != ax::mojom::Role::kUnknown && role != ax::mojom::Role::kNone) {
if (ax_node_data_->GetStringAttribute(ax::mojom::StringAttribute::kName)
.empty() &&
!accessible_name_.empty()) {
// TODO(accessibility): This is to temporarily work around the DCHECK
// that wants to have a role to calculate a name-from. If we have a
// name in our properties but not in our `AXNodeData`, the name was
// set prior to the role. Now that we have a valid role, we can set
// the name. See `SetAccessibleName` for where we delayed setting it.
ax_node_data_->SetName(accessible_name_);
}
}
accessible_role_ = role;
OnPropertyChanged(&accessible_role_, kPropertyEffectsNone);
}
void View::SetAccessibleRole(const ax::mojom::Role role,
const std::u16string& role_description) {
if (!role_description.empty()) {
ax_node_data_->AddStringAttribute(
ax::mojom::StringAttribute::kRoleDescription,
base::UTF16ToUTF8(role_description));
} else {
ax_node_data_->RemoveStringAttribute(
ax::mojom::StringAttribute::kRoleDescription);
}
SetAccessibleRole(role);
}
ax::mojom::Role View::GetAccessibleRole() const {
return accessible_role_;
}
void View::SetAccessibleDescription(const std::u16string& description) {
if (description.empty()) {
ax_node_data_->RemoveStringAttribute(
ax::mojom::StringAttribute::kDescription);
ax_node_data_->RemoveIntAttribute(
ax::mojom::IntAttribute::kDescriptionFrom);
accessible_description_ = description;
return;
}
SetAccessibleDescription(description,
ax::mojom::DescriptionFrom::kAriaDescription);
}
void View::SetAccessibleDescription(
const std::u16string& description,
ax::mojom::DescriptionFrom description_from) {
// Ensure we have a current `description_from` value. For instance, the
// description might still be an empty string, but a view is now indicating
// that this is by design by setting
// `DescriptionFrom::kAttributeExplicitlyEmpty`.
ax_node_data_->SetDescriptionFrom(description_from);
if (description == accessible_description_) {
return;
}
// `AXNodeData::SetDescription` DCHECKs that the description is not empty
// unless it has `DescriptionFrom::kAttributeExplicitlyEmpty`.
if (!description.empty() ||
ax_node_data_->GetDescriptionFrom() ==
ax::mojom::DescriptionFrom::kAttributeExplicitlyEmpty) {
ax_node_data_->SetDescription(description);
}
accessible_description_ = description;
OnPropertyChanged(&accessible_description_, kPropertyEffectsNone);
}
void View::SetAccessibleDescription(View* describing_view) {
DCHECK(describing_view);
DCHECK_NE(this, describing_view);
const std::u16string& name = describing_view->GetAccessibleName();
DCHECK(!name.empty());
SetAccessibleDescription(name, ax::mojom::DescriptionFrom::kRelatedElement);
ax_node_data_->AddIntListAttribute(
ax::mojom::IntListAttribute::kDescribedbyIds,
{describing_view->GetViewAccessibility().GetUniqueId().Get()});
}
const std::u16string& View::GetAccessibleDescription() const {
return accessible_description_;
}
bool View::HandleAccessibleAction(const ui::AXActionData& action_data) {
switch (action_data.action) {
case ax::mojom::Action::kBlur:
if (HasFocus()) {
GetFocusManager()->ClearFocus();
return true;
}
break;
case ax::mojom::Action::kDoDefault: {
const gfx::Point center = GetLocalBounds().CenterPoint();
ui::MouseEvent press(ui::ET_MOUSE_PRESSED, center, center,
ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
ui::EF_LEFT_MOUSE_BUTTON);
OnEvent(&press);
ui::MouseEvent release(ui::ET_MOUSE_RELEASED, center, center,
ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
ui::EF_LEFT_MOUSE_BUTTON);
OnEvent(&release);
return true;
}
case ax::mojom::Action::kFocus:
if (GetViewAccessibility().IsAccessibilityFocusable()) {
RequestFocus();
return true;
}
break;
case ax::mojom::Action::kScrollToMakeVisible:
ScrollRectToVisible(GetLocalBounds());
return true;
case ax::mojom::Action::kShowContextMenu:
ShowContextMenu(GetBoundsInScreen().CenterPoint(),
ui::MENU_SOURCE_KEYBOARD);
return true;
default:
// Some actions are handled by subclasses of View.
break;
}
return false;
}
gfx::NativeViewAccessible View::GetNativeViewAccessible() {
return GetViewAccessibility().GetNativeObject();
}
void View::NotifyAccessibilityEvent(ax::mojom::Event event_type,
bool send_native_event) {
// If it belongs to a widget but its native widget is already destructed, do
// not send such accessibility event as it's unexpected to send such events
// during destruction, and is likely to lead to crashes/problems.
if (GetWidget() && !GetWidget()->GetNativeView()) {
return;
}
// If `pause_accessibility_events_` is true, it means we are initializing
// property values. In this specific case, we do not want to notify platform
// assistive technologies that a property has changed.
if (pause_accessibility_events_) {
return;
}
AXEventManager::Get()->NotifyViewEvent(this, event_type);
if (send_native_event && GetWidget()) {
GetViewAccessibility().NotifyAccessibilityEvent(event_type);
}
OnAccessibilityEvent(event_type);
}
void View::OnAccessibilityEvent(ax::mojom::Event event_type) {}
// Scrolling -------------------------------------------------------------------
void View::ScrollRectToVisible(const gfx::Rect& rect) {
if (parent_) {
parent_->ScrollRectToVisible(rect + bounds().OffsetFromOrigin());
}
}
void View::ScrollViewToVisible() {
ScrollRectToVisible(GetLocalBounds());
}
void View::AddObserver(ViewObserver* observer) {
CHECK(observer);
observers_.AddObserver(observer);
}
void View::RemoveObserver(ViewObserver* observer) {
observers_.RemoveObserver(observer);
}
bool View::HasObserver(const ViewObserver* observer) const {
return observers_.HasObserver(observer);
}
////////////////////////////////////////////////////////////////////////////////
// View, protected:
// Size and disposition --------------------------------------------------------
gfx::Size View::CalculatePreferredSize() const {
if (HasLayoutManager()) {
return GetLayoutManager()->GetPreferredSize(this);
}
return gfx::Size();
}
gfx::Size View::CalculatePreferredSize(const SizeBounds& available_size) const {
return CalculatePreferredSize();
}
void View::PreferredSizeChanged() {
if (parent_) {
parent_->ChildPreferredSizeChanged(this);
}
// Since some layout managers (specifically AnimatingLayoutManager) can react
// to InvalidateLayout() by doing calculations and since the parent can
// potentially change preferred size, etc. as a result of calling
// ChildPreferredSizeChanged(), postpone invalidation until the events have
// run all the way up the hierarchy.
InvalidateLayout();
for (ViewObserver& observer : observers_) {
observer.OnViewPreferredSizeChanged(this);
}
}
bool View::GetNeedsNotificationWhenVisibleBoundsChange() const {
return false;
}
void View::OnVisibleBoundsChanged() {}
// Tree operations -------------------------------------------------------------
void View::ViewHierarchyChanged(const ViewHierarchyChangedDetails& details) {}
void View::VisibilityChanged(View* starting_from, bool is_visible) {}
void View::NativeViewHierarchyChanged() {
FocusManager* focus_manager = GetFocusManager();
if (accelerator_focus_manager_ != focus_manager) {
UnregisterAccelerators(true);
if (focus_manager) {
RegisterPendingAccelerators();
}
}
}
void View::AddedToWidget() {}
void View::RemovedFromWidget() {}
// Painting --------------------------------------------------------------------
void View::OnDidSchedulePaint(const gfx::Rect& rect) {}
void View::PaintChildren(const PaintInfo& paint_info) {
TRACE_EVENT1("views", "View::PaintChildren", "class", GetClassName());
RecursivePaintHelper(&View::Paint, paint_info);
}
void View::OnPaint(gfx::Canvas* canvas) {
TRACE_EVENT1("views", "View::OnPaint", "class", GetClassName());
OnPaintBackground(canvas);
OnPaintBorder(canvas);
}
void View::OnPaintBackground(gfx::Canvas* canvas) {
if (background_) {
TRACE_EVENT0("views", "View::OnPaintBackground");
background_->Paint(canvas, this);
}
}
void View::OnPaintBorder(gfx::Canvas* canvas) {
if (border_) {
TRACE_EVENT0("views", "View::OnPaintBorder");
border_->Paint(*this, canvas);
}
}
// Accelerated Painting --------------------------------------------------------
View::LayerOffsetData View::CalculateOffsetToAncestorWithLayer(
ui::Layer** layer_parent) {
if (layer()) {
if (layer_parent) {
*layer_parent = layer();
}
return LayerOffsetData(layer()->device_scale_factor());
}
if (!parent_) {
return LayerOffsetData();
}
LayerOffsetData offset_data =
parent_->CalculateOffsetToAncestorWithLayer(layer_parent);
return offset_data + GetMirroredPosition().OffsetFromOrigin();
}
void View::UpdateParentLayer() {
if (!layer()) {
return;
}
ui::Layer* parent_layer = nullptr;
if (parent_) {
parent_->CalculateOffsetToAncestorWithLayer(&parent_layer);
}
ReparentLayer(parent_layer);
}
void View::MoveLayerToParent(ui::Layer* parent_layer,
const LayerOffsetData& offset_data) {
LayerOffsetData local_offset_data(offset_data);
if (parent_layer != layer()) {
local_offset_data += GetMirroredPosition().OffsetFromOrigin();
}
if (layer() && parent_layer != layer()) {
SetLayerParent(parent_layer);
SetLayerBounds(size(), local_offset_data);
} else {
internal::ScopedChildrenLock lock(this);
for (views::View* child : GetChildrenInZOrder()) {
child->MoveLayerToParent(parent_layer, local_offset_data);
}
}
}
void View::UpdateLayerVisibility() {
bool visible = visible_;
for (const View* v = parent_; visible && v && !v->layer(); v = v->parent_) {
visible = v->GetVisible();
}
UpdateChildLayerVisibility(visible);
}
void View::UpdateChildLayerVisibility(bool ancestor_visible) {
const bool layers_visible = ancestor_visible && visible_;
if (layer()) {
for (ui::Layer* layer : GetLayersInOrder()) {
layer->SetVisible(layers_visible);
}
}
{
internal::ScopedChildrenLock lock(this);
for (views::View* child : children_) {
child->UpdateChildLayerVisibility(layers_visible);
}
}
}
void View::DestroyLayerImpl(LayerChangeNotifyBehavior notify_parents) {
// Normally, adding layers above or below will trigger painting to a layer.
// It would leave this view in an inconsistent state if its layer were
// destroyed while layers beneath were still present. So, assume this doesn't
// happen.
DCHECK(layers_below_.empty() && layers_above_.empty());
if (!layer()) {
return;
}
// Copy children(), since the loop below will mutate its result.
std::vector<raw_ptr<ui::Layer, VectorExperimental>> children =
layer()->children();
ui::Layer* new_parent = layer()->parent();
for (ui::Layer* child : children) {
layer()->Remove(child);
if (new_parent) {
new_parent->Add(child);
}
}
mask_layer_.reset();
LayerOwner::DestroyLayer();
if (new_parent) {
ReorderLayers();
}
UpdateChildLayerBounds(CalculateOffsetToAncestorWithLayer(nullptr));
SchedulePaint();
// Notify the parent chain about the layer change.
if (notify_parents == LayerChangeNotifyBehavior::NOTIFY) {
NotifyParentsOfLayerChange();
}
Widget* widget = GetWidget();
if (widget) {
widget->LayerTreeChanged();
}
}
void View::NotifyParentsOfLayerChange() {
// Notify the parent chain about the layer change.
View* view_parent = parent();
while (view_parent) {
view_parent->OnChildLayerChanged(this);
view_parent = view_parent->parent();
}
}
void View::UpdateChildLayerBounds(const LayerOffsetData& offset_data) {
if (layer()) {
SetLayerBounds(size(), offset_data);
} else {
internal::ScopedChildrenLock lock(this);
for (views::View* child : children_) {
child->UpdateChildLayerBounds(
offset_data + child->GetMirroredPosition().OffsetFromOrigin());
}
}
}
void View::OnPaintLayer(const ui::PaintContext& context) {
PaintFromPaintRoot(context);
}
void View::OnLayerTransformed(const gfx::Transform& old_transform,
ui::PropertyChangeReason reason) {
NotifyAccessibilityEvent(ax::mojom::Event::kLocationChanged, false);
for (ViewObserver& observer : observers_) {
observer.OnViewLayerTransformed(this);
}
}
void View::OnLayerClipRectChanged(const gfx::Rect& old_rect,
ui::PropertyChangeReason reason) {
for (ViewObserver& observer : observers_) {
observer.OnViewLayerClipRectChanged(this);
}
}
void View::OnDeviceScaleFactorChanged(float old_device_scale_factor,
float new_device_scale_factor) {
snap_layer_to_pixel_boundary_ =
(new_device_scale_factor - std::floor(new_device_scale_factor)) != 0.0f;
if (!layer()) {
return;
}
// There can be no subpixel offset if the layer has no parent.
if (!parent() || !layer()->parent()) {
return;
}
if (layer()->parent() && layer()->GetCompositor() &&
layer()->GetCompositor()->is_pixel_canvas()) {
LayerOffsetData offset_data(
parent()->CalculateOffsetToAncestorWithLayer(nullptr));
offset_data += GetMirroredPosition().OffsetFromOrigin();
SnapLayerToPixelBoundary(offset_data);
} else {
SnapLayerToPixelBoundary(LayerOffsetData());
}
}
void View::CreateOrDestroyLayer() {
if (paint_to_layer_explicitly_set_ || paint_to_layer_for_transform_ ||
!layers_below_.empty() || !layers_above_.empty()) {
// If we need to paint to a layer, make sure we have one.
if (!layer()) {
CreateLayer(ui::LAYER_TEXTURED);
}
} else if (layer()) {
// If we don't, make sure we delete our layer.
DestroyLayerImpl(LayerChangeNotifyBehavior::NOTIFY);
}
}
void View::ReorderLayers() {
View* v = this;
while (v && !v->layer()) {
v = v->parent();
}
Widget* widget = GetWidget();
if (!v) {
if (widget) {
ui::Layer* layer = widget->GetLayer();
if (layer) {
widget->GetRootView()->ReorderChildLayers(layer);
}
}
} else {
v->ReorderChildLayers(v->layer());
}
if (widget) {
// Reorder the widget's child NativeViews in case a child NativeView is
// associated with a view (e.g. via a NativeViewHost). Always do the
// reordering because the associated NativeView's layer (if it has one)
// is parented to the widget's layer regardless of whether the host view has
// an ancestor with a layer.
widget->ReorderNativeViews();
}
}
void View::AddLayerToRegionImpl(
ui::Layer* new_layer,
std::vector<raw_ptr<ui::Layer, VectorExperimental>>& layer_vector) {
DCHECK(new_layer);
DCHECK(!base::Contains(layer_vector, new_layer)) << "Layer already added.";
new_layer->AddObserver(this);
new_layer->SetVisible(GetVisible());
layer_vector.push_back(new_layer);
// If painting to a layer already, ensure |new_layer| gets added and stacked
// correctly. If not, this will happen on layer creation.
if (layer()) {
ui::Layer* const parent_layer = layer()->parent();
// Note that |new_layer| may have already been added to the parent, for
// example when the layer of a LayerOwner is recreated.
if (parent_layer && parent_layer != new_layer->parent()) {
parent_layer->Add(new_layer);
}
new_layer->SetBounds(gfx::Rect(new_layer->size()) +
layer()->bounds().OffsetFromOrigin());
if (parent()) {
parent()->ReorderLayers();
}
}
CreateOrDestroyLayer();
layer()->SetFillsBoundsOpaquely(false);
}
void View::SetLayerParent(ui::Layer* parent_layer) {
// Adding the main layer can trigger a call to |SnapLayerToPixelBoundary()|.
// That method assumes layers beneath have already been added. Therefore
// layers above and below must be added first here. See crbug.com/961212.
for (ui::Layer* extra_layer : GetLayersInOrder(ViewLayer::kExclude)) {
parent_layer->Add(extra_layer);
}
parent_layer->Add(layer());
// After adding the main layer, it's relative position in the stack needs
// to be adjusted. This will ensure the layer is between any of the layers
// above and below the main layer.
if (!layers_below_.empty()) {
parent_layer->StackAbove(layer(), layers_below_.back());
} else if (!layers_above_.empty()) {
parent_layer->StackBelow(layer(), layers_above_.front());
}
}
void View::ReorderChildLayers(ui::Layer* parent_layer) {
if (layer() && layer() != parent_layer) {
DCHECK_EQ(parent_layer, layer()->parent());
for (ui::Layer* layer_above : layers_above_) {
parent_layer->StackAtBottom(layer_above);
}
parent_layer->StackAtBottom(layer());
for (ui::Layer* layer_below : layers_below_) {
parent_layer->StackAtBottom(layer_below);
}
} else {
// Iterate backwards through the children so that a child with a layer
// which is further to the back is stacked above one which is further to
// the front.
View::Views children = GetChildrenInZOrder();
DCHECK_EQ(children_.size(), children.size());
for (views::View* child : base::Reversed(children)) {
child->ReorderChildLayers(parent_layer);
}
}
}
void View::OnChildLayerChanged(View* child) {}
// Input -----------------------------------------------------------------------
View::DragInfo* View::GetDragInfo() {
return parent_ ? parent_->GetDragInfo() : nullptr;
}
// Focus -----------------------------------------------------------------------
void View::OnFocus() {}
void View::OnBlur() {}
void View::Focus() {
OnFocus();
#if !BUILDFLAG(IS_CHROMEOS_LACROS)
// TODO(crbug.com/40285437) - Get this working on Lacros as well.
UpdateTooltipForFocus();
#endif
// TODO(pbos): Investigate if parts of this can run unconditionally.
if (!suppress_default_focus_handling_) {
// Clear the native focus. This ensures that no visible native view has the
// focus and that we still receive keyboard inputs.
FocusManager* focus_manager = GetFocusManager();
if (focus_manager) {
focus_manager->ClearNativeFocus();
}
// Notify assistive technologies of the focus change.
AXVirtualView* const focused_virtual_child =
view_accessibility_ ? view_accessibility_->FocusedVirtualChild()
: nullptr;
if (focused_virtual_child) {
focused_virtual_child->NotifyAccessibilityEvent(ax::mojom::Event::kFocus);
} else {
NotifyAccessibilityEvent(ax::mojom::Event::kFocus, true);
}
}
// If this is the contents root of a |ScrollView|, focus should bring the
// |ScrollView| to visible rather than resetting its content scroll position.
ScrollView* const scroll_view = ScrollView::GetScrollViewForContents(this);
if (scroll_view) {
scroll_view->ScrollViewToVisible();
} else {
ScrollViewToVisible();
}
for (ViewObserver& observer : observers_) {
observer.OnViewFocused(this);
}
}
void View::Blur() {
ViewTracker tracker(this);
OnBlur();
if (tracker.view()) {
for (ViewObserver& observer : observers_) {
observer.OnViewBlurred(this);
}
}
}
// System events ---------------------------------------------------------------
void View::OnThemeChanged() {
#if DCHECK_IS_ON()
on_theme_changed_called_ = true;
#endif
}
// Tooltips --------------------------------------------------------------------
void View::TooltipTextChanged() {
Widget* widget = GetWidget();
// TooltipManager may be null if there is a problem creating it.
if (widget && widget->GetTooltipManager()) {
widget->GetTooltipManager()->TooltipTextChanged(this);
}
}
void View::UpdateTooltipForFocus() {
if (base::FeatureList::IsEnabled(
::views::features::kKeyboardAccessibleTooltipInViews) &&
!kShouldDisableKeyboardTooltipsForTesting) {
Widget* widget = GetWidget();
if (widget && widget->GetTooltipManager()) {
widget->GetTooltipManager()->UpdateTooltipForFocus(this);
}
}
}
// Drag and drop ---------------------------------------------------------------
int View::GetDragOperations(const gfx::Point& press_pt) {
return drag_controller_
? drag_controller_->GetDragOperationsForView(this, press_pt)
: ui::DragDropTypes::DRAG_NONE;
}
void View::WriteDragData(const gfx::Point& press_pt, OSExchangeData* data) {
DCHECK(drag_controller_);
drag_controller_->WriteDragDataForView(this, press_pt, data);
}
bool View::InDrag() const {
const Widget* widget = GetWidget();
return widget ? widget->dragged_view() == this : false;
}
int View::GetHorizontalDragThreshold() {
// TODO(jennyz): This value may need to be adjusted for different platforms
// and for different display density.
return kDefaultHorizontalDragThreshold;
}
int View::GetVerticalDragThreshold() {
// TODO(jennyz): This value may need to be adjusted for different platforms
// and for different display density.
return kDefaultVerticalDragThreshold;
}
PaintInfo::ScaleType View::GetPaintScaleType() const {
return PaintInfo::ScaleType::kScaleWithEdgeSnapping;
}
void View::HandlePropertyChangeEffects(PropertyEffects effects) {
if (effects & kPropertyEffectsPreferredSizeChanged) {
PreferredSizeChanged();
}
if (effects & kPropertyEffectsLayout) {
InvalidateLayout();
}
if (effects & kPropertyEffectsPaint) {
SchedulePaint();
}
}
void View::AfterPropertyChange(const void* key, int64_t old_value) {
if (key == kElementIdentifierKey) {
const ui::ElementIdentifier old_element_id =
ui::ElementIdentifier::FromRawValue(
base::checked_cast<intptr_t>(old_value));
if (old_element_id) {
views::ElementTrackerViews::GetInstance()->UnregisterView(old_element_id,
this);
}
const ui::ElementIdentifier new_element_id =
GetProperty(kElementIdentifierKey);
if (new_element_id) {
views::ElementTrackerViews::GetInstance()->RegisterView(new_element_id,
this);
}
}
for (auto& observer : observers_) {
observer.OnViewPropertyChanged(this, key, old_value);
}
}
void View::OnPropertyChanged(ui::metadata::PropertyKey property,
PropertyEffects property_effects) {
if (property_effects != kPropertyEffectsNone) {
HandlePropertyChangeEffects(property_effects);
}
TriggerChangedCallback(property);
}
int View::GetX() const {
return x();
}
int View::GetY() const {
return y();
}
int View::GetWidth() const {
return width();
}
int View::GetHeight() const {
return height();
}
void View::SetWidth(int width) {
SetBounds(x(), y(), width, height());
}
void View::SetHeight(int height) {
SetBounds(x(), y(), width(), height);
}
std::u16string View::GetTooltip() const {
return GetTooltipText(gfx::Point());
}
////////////////////////////////////////////////////////////////////////////////
// View, private:
// DropInfo --------------------------------------------------------------------
void View::DragInfo::Reset() {
possible_drag = false;
start_pt = gfx::Point();
}
void View::DragInfo::PossibleDrag(const gfx::Point& p) {
possible_drag = true;
start_pt = p;
}
// Painting --------------------------------------------------------------------
void View::SchedulePaintInRectImpl(const gfx::Rect& rect) {
OnDidSchedulePaint(rect);
if (!visible_) {
return;
}
if (layer()) {
layer()->SchedulePaint(rect);
} else if (parent_) {
// Translate the requested paint rect to the parent's coordinate system
// then pass this notification up to the parent.
parent_->SchedulePaintInRectImpl(ConvertRectToParent(rect));
}
}
void View::SchedulePaintBoundsChanged(bool size_changed) {
if (!visible_) {
return;
}
// If we have a layer and the View's size did not change, we do not need to
// schedule any paints since the layer will be redrawn at its new location
// during the next Draw() cycle in the compositor.
if (!layer() || size_changed) {
// Otherwise, if the size changes or we don't have a layer then we need to
// use SchedulePaint to invalidate the area occupied by the View.
SchedulePaint();
} else {
// The compositor doesn't Draw() until something on screen changes, so
// if our position changes but nothing is being animated on screen, then
// tell the compositor to redraw the scene. We know layer() exists due to
// the above if clause.
layer()->ScheduleDraw();
}
}
void View::SchedulePaintOnParent() {
if (parent_) {
// Translate the requested paint rect to the parent's coordinate system
// then pass this notification up to the parent.
parent_->SchedulePaintInRect(ConvertRectToParent(GetLocalBounds()));
}
}
bool View::ShouldPaint() const {
return visible_ && !size().IsEmpty();
}
void View::SetUpTransformRecorderForPainting(
const gfx::Vector2d& offset_from_parent,
ui::TransformRecorder* recorder) const {
// If the view is backed by a layer, it should paint with itself as the origin
// rather than relative to its parent.
if (layer()) {
return;
}
// Translate the graphics such that 0,0 corresponds to where this View is
// located relative to its parent.
gfx::Transform transform_from_parent;
transform_from_parent.Translate(offset_from_parent.x(),
offset_from_parent.y());
recorder->Transform(transform_from_parent);
}
void View::RecursivePaintHelper(void (View::*func)(const PaintInfo&),
const PaintInfo& info) {
View::Views children = GetChildrenInZOrder();
DCHECK_EQ(children_.size(), children.size());
for (views::View* child : children) {
if (!child->layer()) {
(child->*func)(info);
}
}
}
void View::PaintFromPaintRoot(const ui::PaintContext& parent_context) {
PaintInfo paint_info = PaintInfo::CreateRootPaintInfo(
parent_context, layer() ? layer()->size() : size());
Paint(paint_info);
static bool draw_view_bounds_rects =
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDrawViewBoundsRects);
if (draw_view_bounds_rects) {
PaintDebugRects(paint_info);
}
}
void View::PaintDebugRects(const PaintInfo& parent_paint_info) {
if (!ShouldPaint()) {
return;
}
const gfx::Rect& parent_bounds = (layer() || !parent())
? GetMirroredBounds()
: parent()->GetMirroredBounds();
PaintInfo paint_info = PaintInfo::CreateChildPaintInfo(
parent_paint_info, GetMirroredBounds(), parent_bounds.size(),
GetPaintScaleType(), !!layer());
const ui::PaintContext& context = paint_info.context();
ui::TransformRecorder transform_recorder(context);
SetUpTransformRecorderForPainting(paint_info.offset_from_parent(),
&transform_recorder);
RecursivePaintHelper(&View::PaintDebugRects, paint_info);
// Draw outline rects for debugging.
ui::PaintRecorder recorder(context, paint_info.paint_recording_size(),
paint_info.paint_recording_scale_x(),
paint_info.paint_recording_scale_y(),
&paint_cache_);
gfx::Canvas* canvas = recorder.canvas();
const float scale = canvas->UndoDeviceScaleFactor();
gfx::RectF outline_rect(ScaleToEnclosedRect(GetLocalBounds(), scale));
gfx::RectF content_outline_rect(
ScaleToEnclosedRect(GetContentsBounds(), scale));
const auto* color_provider = GetColorProvider();
if (content_outline_rect != outline_rect) {
content_outline_rect.Inset(0.5f);
canvas->DrawRect(content_outline_rect,
color_provider->GetColor(ui::kColorDebugContentOutline));
}
outline_rect.Inset(0.5f);
canvas->DrawRect(outline_rect,
color_provider->GetColor(ui::kColorDebugBoundsOutline));
}
// Tree operations -------------------------------------------------------------
void View::AddChildViewAtImpl(View* view, size_t index) {
CHECK_NE(view, this) << "You cannot add a view as its own child";
DCHECK_LE(index, children_.size());
// TODO(crbug.com/41447071): Should just DCHECK(!view->parent_);.
View* parent = view->parent_;
if (parent == this) {
ReorderChildView(view, index);
return;
}
// Remove |view| from its parent, if any.
Widget* old_widget = view->GetWidget();
ui::NativeTheme* old_theme = old_widget ? view->GetNativeTheme() : nullptr;
if (parent) {
parent->DoRemoveChildView(view, true, false, this);
}
view->parent_ = this;
#if DCHECK_IS_ON()
DCHECK(!iterating_);
#endif
const auto pos = children_.insert(
std::next(children_.cbegin(), static_cast<ptrdiff_t>(index)), view);
view->RemoveFromFocusList();
SetFocusSiblings(view, pos);
// Ensure the layer tree matches the view tree before calling to any client
// code. This way if client code further modifies the view tree we are in a
// sane state.
const bool did_reparent_any_layers = view->UpdateParentLayers();
Widget* widget = GetWidget();
if (did_reparent_any_layers && widget) {
widget->LayerTreeChanged();
}
ReorderLayers();
// Make sure the visibility of the child layers are correct.
// If any of the parent View is hidden, then the layers of the subtree
// rooted at |this| should be hidden. Otherwise, all the child layers should
// inherit the visibility of the owner View.
view->UpdateLayerVisibility();
// Need to notify the layout manager because one of the callbacks below might
// want to know the view's new preferred size, minimum size, etc.
if (HasLayoutManager()) {
GetLayoutManager()->ViewAdded(this, view);
}
if (widget && (view->GetNativeTheme() != old_theme)) {
view->PropagateThemeChanged();
}
ViewHierarchyChangedDetails details(true, this, view, parent);
for (View* v = this; v; v = v->parent_) {
v->ViewHierarchyChangedImpl(details);
}
view->PropagateAddNotifications(details, widget && widget != old_widget);
UpdateTooltip();
if (widget) {
RegisterChildrenForVisibleBoundsNotification(view);
if (view->GetVisible()) {
view->SchedulePaint();
}
}
for (ViewObserver& observer : observers_) {
observer.OnChildViewAdded(this, view);
}
}
void View::DoRemoveChildView(View* view,
bool update_tool_tip,
bool delete_removed_view,
View* new_parent) {
DCHECK(view);
const auto i = FindChild(view);
if (i == children_.cend()) {
return;
}
std::unique_ptr<View> view_to_be_deleted;
view->RemoveFromFocusList();
Widget* widget = GetWidget();
bool is_removed_from_widget = false;
if (widget) {
UnregisterChildrenForVisibleBoundsNotification(view);
if (view->GetVisible()) {
view->SchedulePaint();
}
is_removed_from_widget = !new_parent || new_parent->GetWidget() != widget;
if (is_removed_from_widget) {
widget->NotifyWillRemoveView(view);
}
}
// Make sure the layers belonging to the subtree rooted at |view| get
// removed.
view->OrphanLayers();
if (widget) {
widget->LayerTreeChanged();
}
// Need to notify the layout manager because one of the callbacks below might
// want to know the view's new preferred size, minimum size, etc.
if (HasLayoutManager()) {
GetLayoutManager()->ViewRemoved(this, view);
}
view->PropagateRemoveNotifications(this, new_parent, is_removed_from_widget);
view->parent_ = nullptr;
if (delete_removed_view && !view->owned_by_client_) {
view_to_be_deleted.reset(view);
}
#if DCHECK_IS_ON()
DCHECK(!iterating_);
#endif
children_.erase(i);
if (update_tool_tip) {
UpdateTooltip();
}
for (ViewObserver& observer : observers_) {
observer.OnChildViewRemoved(this, view);
}
}
void View::PropagateRemoveNotifications(View* old_parent,
View* new_parent,
bool is_removed_from_widget) {
{
internal::ScopedChildrenLock lock(this);
for (views::View* child : children_) {
child->PropagateRemoveNotifications(old_parent, new_parent,
is_removed_from_widget);
}
}
// When a view is removed from a hierarchy, UnregisterAccelerators() is called
// for the removed view and all descendant views in post-order.
UnregisterAccelerators(true);
ViewHierarchyChangedDetails details(false, old_parent, this, new_parent);
for (View* v = this; v; v = v->parent_) {
v->ViewHierarchyChangedImpl(details);
}
if (is_removed_from_widget) {
RemovedFromWidget();
for (ViewObserver& observer : observers_) {
observer.OnViewRemovedFromWidget(this);
}
}
}
void View::PropagateAddNotifications(const ViewHierarchyChangedDetails& details,
bool is_added_to_widget) {
// When a view is added to a Widget hierarchy, RegisterPendingAccelerators()
// will be called for the added view and all its descendants in pre-order.
// This means that descendant views will register their accelerators after
// their parents. This allows children to override accelerators registered by
// their parents as accelerators registered later take priority over those
// registered earlier.
if (GetFocusManager()) {
RegisterPendingAccelerators();
}
{
internal::ScopedChildrenLock lock(this);
for (views::View* child : children_) {
child->PropagateAddNotifications(details, is_added_to_widget);
}
}
ViewHierarchyChangedImpl(details);
if (is_added_to_widget) {
AddedToWidget();
for (ViewObserver& observer : observers_) {
observer.OnViewAddedToWidget(this);
}
}
}
void View::PropagateNativeViewHierarchyChanged() {
{
internal::ScopedChildrenLock lock(this);
for (views::View* child : children_) {
child->PropagateNativeViewHierarchyChanged();
}
}
NativeViewHierarchyChanged();
}
void View::ViewHierarchyChangedImpl(
const ViewHierarchyChangedDetails& details) {
ViewHierarchyChanged(details);
for (ViewObserver& observer : observers_) {
observer.OnViewHierarchyChanged(this, details);
}
details.parent->needs_layout_ = true;
}
// Size and disposition --------------------------------------------------------
void View::PropagateVisibilityNotifications(View* start, bool is_visible) {
{
internal::ScopedChildrenLock lock(this);
for (views::View* child : children_) {
child->PropagateVisibilityNotifications(start, is_visible);
}
}
VisibilityChangedImpl(start, is_visible);
}
void View::VisibilityChangedImpl(View* starting_from, bool is_visible) {
VisibilityChanged(starting_from, is_visible);
for (ViewObserver& observer : observers_) {
observer.OnViewVisibilityChanged(this, starting_from);
}
}
void View::SnapLayerToPixelBoundary(const LayerOffsetData& offset_data) {
if (!layer()) {
return;
}
#if DCHECK_IS_ON()
// We rely on our layers above and below being parented correctly at this
// point.
for (ui::Layer* layer_above_below : GetLayersInOrder(ViewLayer::kExclude)) {
DCHECK_EQ(layer()->parent(), layer_above_below->parent());
}
#endif // DCHECK_IS_ON()
if (layer()->GetCompositor() && layer()->GetCompositor()->is_pixel_canvas()) {
gfx::Vector2dF offset = snap_layer_to_pixel_boundary_ && layer()->parent()
? offset_data.GetSubpixelOffset()
: gfx::Vector2dF();
layer()->SetSubpixelPositionOffset(offset);
for (ui::Layer* layer : GetLayersInOrder(ViewLayer::kExclude)) {
layer->SetSubpixelPositionOffset(offset);
}
}
}
// static
void View::RegisterChildrenForVisibleBoundsNotification(View* view) {
if (view->GetNeedsNotificationWhenVisibleBoundsChange()) {
view->RegisterForVisibleBoundsNotification();
}
for (View* child : view->children_) {
RegisterChildrenForVisibleBoundsNotification(child);
}
}
// static
void View::UnregisterChildrenForVisibleBoundsNotification(View* view) {
if (view->GetNeedsNotificationWhenVisibleBoundsChange()) {
view->UnregisterForVisibleBoundsNotification();
}
for (View* child : view->children_) {
UnregisterChildrenForVisibleBoundsNotification(child);
}
}
void View::RegisterForVisibleBoundsNotification() {
if (registered_for_visible_bounds_notification_) {
return;
}
registered_for_visible_bounds_notification_ = true;
for (View* ancestor = parent_; ancestor; ancestor = ancestor->parent_) {
ancestor->AddDescendantToNotify(this);
}
}
void View::UnregisterForVisibleBoundsNotification() {
if (!registered_for_visible_bounds_notification_) {
return;
}
registered_for_visible_bounds_notification_ = false;
for (View* ancestor = parent_; ancestor; ancestor = ancestor->parent_) {
ancestor->RemoveDescendantToNotify(this);
}
}
void View::AddDescendantToNotify(View* view) {
DCHECK(view);
if (!descendants_to_notify_) {
descendants_to_notify_ = std::make_unique<Views>();
}
descendants_to_notify_->push_back(view);
}
void View::RemoveDescendantToNotify(View* view) {
DCHECK(view && descendants_to_notify_);
auto i = base::ranges::find(*descendants_to_notify_, view);
DCHECK(i != descendants_to_notify_->end());
descendants_to_notify_->erase(i);
if (descendants_to_notify_->empty()) {
descendants_to_notify_.reset();
}
}
void View::SetLayoutManagerImpl(std::unique_ptr<LayoutManager> layout_manager) {
// Some code keeps a bare pointer to the layout manager for calling
// derived-class-specific-functions. It's an easy mistake to create a new
// unique_ptr and re-set the layout manager with a new unique_ptr, which
// will cause a crash. Re-setting to nullptr is OK.
CHECK(!layout_manager || layout_manager_ != layout_manager);
layout_manager_ = std::move(layout_manager);
if (layout_manager_) {
layout_manager_->Installed(this);
}
// Restore the default fill layout when the layout manager is cleared.
if (!layout_manager_ && use_default_fill_layout_) {
SetToDefaultFillLayout();
return;
}
has_default_fill_layout_ = false;
}
void View::SetToDefaultFillLayout() {
SetLayoutManager(std::make_unique<FillLayout>())->SetIncludeInsets(false);
has_default_fill_layout_ = true;
}
void View::SetLayerBounds(const gfx::Size& size,
const LayerOffsetData& offset_data) {
const gfx::Rect bounds = gfx::Rect(size) + offset_data.offset();
layer()->SetBounds(bounds);
for (ui::Layer* layer : GetLayersInOrder(ViewLayer::kExclude)) {
layer->SetBounds(gfx::Rect(layer->size()) + bounds.OffsetFromOrigin());
}
SnapLayerToPixelBoundary(offset_data);
// Observers may need to adjust the bounds of layers in regions, so always
// notify observers even if the bounds of `layer()` didn't change.
for (ViewObserver& observer : observers_) {
observer.OnViewLayerBoundsSet(this);
}
}
// Transformations -------------------------------------------------------------
bool View::GetTransformRelativeTo(const View* ancestor,
gfx::Transform* transform) const {
const View* p = this;
while (p && p != ancestor) {
transform->PostConcat(p->GetTransform());
gfx::Transform translation;
translation.Translate(static_cast<float>(p->GetMirroredX()),
static_cast<float>(p->y()));
transform->PostConcat(translation);
p = p->parent_;
}
return p == ancestor;
}
// Coordinate conversion -------------------------------------------------------
bool View::ConvertPointForAncestor(const View* ancestor,
gfx::Point* point) const {
gfx::Transform trans;
// TODO(sad): Have some way of caching the transformation results.
bool result = GetTransformRelativeTo(ancestor, &trans);
*point = gfx::ToFlooredPoint(trans.MapPoint(gfx::PointF(*point)));
return result;
}
bool View::ConvertPointFromAncestor(const View* ancestor,
gfx::Point* point) const {
gfx::Transform trans;
bool result = GetTransformRelativeTo(ancestor, &trans);
if (const std::optional<gfx::PointF> transformed_point =
trans.InverseMapPoint(gfx::PointF(*point))) {
*point = gfx::ToFlooredPoint(transformed_point.value());
}
return result;
}
bool View::ConvertRectForAncestor(const View* ancestor,
gfx::RectF* rect) const {
gfx::Transform trans;
// TODO(sad): Have some way of caching the transformation results.
bool result = GetTransformRelativeTo(ancestor, &trans);
*rect = trans.MapRect(*rect);
return result;
}
bool View::ConvertRectFromAncestor(const View* ancestor,
gfx::RectF* rect) const {
gfx::Transform trans;
bool result = GetTransformRelativeTo(ancestor, &trans);
*rect = trans.InverseMapRect(*rect).value_or(*rect);
return result;
}
// Accelerated painting --------------------------------------------------------
void View::CreateLayer(ui::LayerType layer_type) {
// A new layer is being created for the view. So all the layers of the
// sub-tree can inherit the visibility of the corresponding view.
{
internal::ScopedChildrenLock lock(this);
for (views::View* child : children_) {
child->UpdateChildLayerVisibility(true);
}
}
SetLayer(std::make_unique<ui::Layer>(layer_type));
layer()->set_delegate(this);
layer()->SetName(GetClassName());
UpdateParentLayers();
UpdateLayerVisibility();
// The new layer needs to be ordered in the layer tree according
// to the view tree. Children of this layer were added in order
// in UpdateParentLayers().
if (parent()) {
parent()->ReorderLayers();
}
Widget* widget = GetWidget();
if (widget) {
widget->LayerTreeChanged();
}
// Before having its own Layer, this View may have painted in to a Layer owned
// by an ancestor View. Scheduling a paint on the parent View will erase this
// View's painting effects on the ancestor View's Layer.
// (See crbug.com/551492)
SchedulePaintOnParent();
}
bool View::UpdateParentLayers() {
TRACE_EVENT1("views", "View::UpdateParentLayers", "class", GetClassName());
// Attach all top-level un-parented layers.
if (layer()) {
if (!layer()->parent()) {
UpdateParentLayer();
return true;
}
// The layers of any child views are already in place, so we can stop
// iterating here.
return false;
}
bool result = false;
internal::ScopedChildrenLock lock(this);
for (views::View* child : children_) {
if (child->UpdateParentLayers()) {
result = true;
}
}
return result;
}
void View::OrphanLayers() {
if (layer()) {
if (ui::Layer* parent = layer()->parent()) {
for (ui::Layer* layer : GetLayersInOrder()) {
// TODO(http://b/319941708): Please remove the below crash keys once the
// the crash is fixed. It seems one of the layers returned by
// `GetLayersInOrder()` is not a sibling of this view's `layer()` (i.e.
// the parent is different).
SCOPED_CRASH_KEY_BOOL("OrphanLayers", "layer_valid", !!layer);
SCOPED_CRASH_KEY_BOOL("OrphanLayers", "layer_is_sibling",
layer->parent() == parent);
SCOPED_CRASH_KEY_STRING256("OrphanLayers", "this_layer_name",
this->layer()->name());
SCOPED_CRASH_KEY_STRING256("OrphanLayers", "parent_layer_name",
parent->name());
SCOPED_CRASH_KEY_STRING256("OrphanLayers", "sibling_layer_name",
layer->name());
SCOPED_CRASH_KEY_STRING256("OrphanLayers", "widget_name",
GetWidget() ? GetWidget()->GetName() : "");
SCOPED_CRASH_KEY_STRING256("OrphanLayers", "view_class_name",
GetClassName());
parent->Remove(layer);
}
}
// The layer belonging to this View has already been orphaned. It is not
// necessary to orphan the child layers.
return;
}
internal::ScopedChildrenLock lock(this);
for (views::View* child : children_) {
child->OrphanLayers();
}
}
void View::ReparentLayer(ui::Layer* parent_layer) {
DCHECK_NE(layer(), parent_layer);
if (parent_layer) {
SetLayerParent(parent_layer);
}
// Update the layer bounds; this needs to be called after this layer is added
// to the new parent layer since snapping to pixel boundary will be affected
// by the layer hierarchy.
LayerOffsetData offset =
parent_ ? parent_->CalculateOffsetToAncestorWithLayer(nullptr)
: LayerOffsetData(layer()->device_scale_factor());
SetLayerBounds(size(), offset + GetMirroredBounds().OffsetFromOrigin());
layer()->SchedulePaint(GetLocalBounds());
MoveLayerToParent(layer(), LayerOffsetData(layer()->device_scale_factor()));
}
void View::CreateMaskLayer() {
DCHECK(layer());
mask_layer_ = std::make_unique<views::ViewMaskLayer>(clip_path_, this);
layer()->SetMaskLayer(mask_layer_->layer());
}
// Layout ----------------------------------------------------------------------
bool View::HasLayoutManager() const {
if (has_default_fill_layout_) {
// TODO (kylixrd): Determine whether this is ncessary and remove if not.
return !children_.empty();
}
return !!layout_manager_;
}
void View::LayoutImmediately() {
TRACE_EVENT("ui", "View::LayoutImmediately", [&](perfetto::EventContext ctx) {
auto* event = ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>();
auto* data = event->set_view_class_name();
data->set_name(GetClassName());
});
invalidates_during_layout_ = 0;
++layouts_since_last_paint_;
base::AutoReset allow_layout(&layout_allowed_, true);
++current_layout_call_depth_;
++max_layout_call_depth_;
Layout(PassKey());
UMA_HISTOGRAM_COUNTS_100("Views.InvalidatesDuringLayout",
invalidates_during_layout_);
--current_layout_call_depth_;
if (current_layout_call_depth_ == 0) {
UMA_HISTOGRAM_EXACT_LINEAR("Views.LayoutCallDepth", max_layout_call_depth_,
6);
max_layout_call_depth_ = 0;
}
}
// Input -----------------------------------------------------------------------
bool View::ProcessMousePressed(const ui::MouseEvent& event) {
int drag_operations = (enabled_ && event.IsOnlyLeftMouseButton() &&
HitTestPoint(event.location()))
? GetDragOperations(event.location())
: 0;
ContextMenuController* context_menu_controller =
event.IsRightMouseButton() ? context_menu_controller_.get() : nullptr;
View::DragInfo* drag_info = GetDragInfo();
const bool was_enabled = GetEnabled();
const bool result = OnMousePressed(event);
if (!was_enabled) {
return result;
}
if (event.IsOnlyRightMouseButton() && context_menu_controller &&
kContextMenuOnMousePress) {
// Assume that if there is a context menu controller we won't be deleted
// from mouse pressed.
if (HitTestPoint(event.location())) {
gfx::Point location(event.location());
ConvertPointToScreen(this, &location);
ShowContextMenu(location, ui::MENU_SOURCE_MOUSE);
return true;
}
}
// WARNING: we may have been deleted, don't use any View variables.
if (drag_operations != ui::DragDropTypes::DRAG_NONE) {
drag_info->PossibleDrag(event.location());
return true;
}
return !!context_menu_controller || result;
}
void View::ProcessMouseDragged(ui::MouseEvent* event) {
// Copy the field, that way if we're deleted after drag and drop no harm is
// done.
ContextMenuController* context_menu_controller = context_menu_controller_;
const bool possible_drag = GetDragInfo()->possible_drag;
if (possible_drag &&
ExceededDragThreshold(GetDragInfo()->start_pt - event->location()) &&
(!drag_controller_ ||
drag_controller_->CanStartDragForView(this, GetDragInfo()->start_pt,
event->location()))) {
if (DoDrag(*event, GetDragInfo()->start_pt,
ui::mojom::DragEventSource::kMouse)) {
event->StopPropagation();
return;
}
} else {
if (OnMouseDragged(*event)) {
event->SetHandled();
return;
}
// Fall through to return value based on context menu controller.
}
// WARNING: we may have been deleted.
if ((context_menu_controller != nullptr) || possible_drag) {
event->SetHandled();
}
}
void View::ProcessMouseReleased(const ui::MouseEvent& event) {
if (!kContextMenuOnMousePress && context_menu_controller_ &&
event.IsOnlyRightMouseButton()) {
// Assume that if there is a context menu controller we won't be deleted
// from mouse released.
gfx::Point location(event.location());
OnMouseReleased(event);
if (HitTestPoint(location)) {
ConvertPointToScreen(this, &location);
ShowContextMenu(location, ui::MENU_SOURCE_MOUSE);
}
} else {
OnMouseReleased(event);
}
// WARNING: we may have been deleted.
}
// Accelerators ----------------------------------------------------------------
void View::RegisterPendingAccelerators() {
if (!accelerators_ ||
registered_accelerator_count_ == accelerators_->size()) {
// No accelerators are waiting for registration.
return;
}
if (!GetWidget()) {
// The view is not yet attached to a widget, defer registration until then.
return;
}
accelerator_focus_manager_ = GetFocusManager();
CHECK(accelerator_focus_manager_);
for (std::vector<ui::Accelerator>::const_iterator i =
accelerators_->begin() +
static_cast<ptrdiff_t>(registered_accelerator_count_);
i != accelerators_->end(); ++i) {
accelerator_focus_manager_->RegisterAccelerator(
*i, ui::AcceleratorManager::kNormalPriority, this);
}
registered_accelerator_count_ = accelerators_->size();
}
void View::UnregisterAccelerators(bool leave_data_intact) {
if (!accelerators_) {
return;
}
if (GetWidget()) {
if (accelerator_focus_manager_) {
accelerator_focus_manager_->UnregisterAccelerators(this);
accelerator_focus_manager_ = nullptr;
}
if (!leave_data_intact) {
accelerators_->clear();
accelerators_.reset();
}
registered_accelerator_count_ = 0;
}
}
// Focus -----------------------------------------------------------------------
void View::SetFocusSiblings(View* view, Views::const_iterator pos) {
// |view| was just inserted at |pos|, so all of these conditions must hold.
DCHECK(!children_.empty());
DCHECK(pos != children_.cend());
DCHECK_EQ(view, *pos);
if (children_.size() > 1) {
if (std::next(pos) == children_.cend()) {
// |view| was inserted at the end, but the end of the child list may not
// be the last focusable element. Try to hook in after the last focusable
// child.
View* const old_last = *base::ranges::find_if_not(
children_.cbegin(), pos, &View::next_focusable_view_);
DCHECK_NE(old_last, view);
view->InsertAfterInFocusList(old_last);
} else {
// |view| was inserted somewhere other than the end. Hook in before the
// subsequent child.
view->InsertBeforeInFocusList(*std::next(pos));
}
}
}
void View::AdvanceFocusIfNecessary() {
// Focus should only be advanced if this is the focused view and has become
// unfocusable. If the view is still focusable or is not focused, we can
// return early avoiding further unnecessary checks. Focusability check is
// performed first as it tends to be faster.
if (GetViewAccessibility().IsAccessibilityFocusable() || !HasFocus()) {
return;
}
FocusManager* focus_manager = GetFocusManager();
if (focus_manager) {
focus_manager->AdvanceFocusIfNecessary();
}
}
// System events ---------------------------------------------------------------
void View::PropagateThemeChanged() {
{
internal::ScopedChildrenLock lock(this);
for (views::View* child : base::Reversed(children_)) {
child->PropagateThemeChanged();
}
}
OnThemeChanged();
if (border_) {
border_->OnViewThemeChanged(this);
}
if (background_) {
background_->OnViewThemeChanged(this);
}
#if DCHECK_IS_ON()
DCHECK(on_theme_changed_called_)
<< "views::View::OnThemeChanged() has not been called. This means that "
"some class in the hierarchy is not calling their direct parent's "
"OnThemeChanged(). Please fix this by adding the missing call. Do not "
"call views::View::OnThemeChanged() directly unless views::View is "
"the direct parent class.";
on_theme_changed_called_ = false;
#endif
for (ViewObserver& observer : observers_) {
observer.OnViewThemeChanged(this);
}
}
void View::PropagateDeviceScaleFactorChanged(float old_device_scale_factor,
float new_device_scale_factor) {
{
internal::ScopedChildrenLock lock(this);
for (views::View* child : base::Reversed(children_)) {
child->PropagateDeviceScaleFactorChanged(old_device_scale_factor,
new_device_scale_factor);
}
}
// If the view is drawing to the layer, OnDeviceScaleFactorChanged() is called
// through LayerDelegate callback.
if (!layer()) {
OnDeviceScaleFactorChanged(old_device_scale_factor,
new_device_scale_factor);
}
}
// Tooltips --------------------------------------------------------------------
void View::UpdateTooltip() {
Widget* widget = GetWidget();
// TODO(beng): The TooltipManager nullptr check can be removed when we
// consolidate Init() methods and make views_unittests Init() all
// Widgets that it uses.
if (widget && widget->GetTooltipManager()) {
widget->GetTooltipManager()->UpdateTooltip();
}
}
bool View::kShouldDisableKeyboardTooltipsForTesting = false;
void View::DisableKeyboardTooltipsForTesting() {
View::kShouldDisableKeyboardTooltipsForTesting = true;
}
void View::EnableKeyboardTooltipsForTesting() {
View::kShouldDisableKeyboardTooltipsForTesting = false;
}
// Drag and drop ---------------------------------------------------------------
bool View::DoDrag(const ui::LocatedEvent& event,
const gfx::Point& press_pt,
ui::mojom::DragEventSource source) {
int drag_operations = GetDragOperations(press_pt);
if (drag_operations == ui::DragDropTypes::DRAG_NONE) {
return false;
}
Widget* widget = GetWidget();
// We should only start a drag from an event, implying we have a widget.
DCHECK(widget);
// Don't attempt to start a drag while in the process of dragging. This is
// especially important on X where we can get multiple mouse move events when
// we start the drag.
if (widget->dragged_view()) {
return false;
}
std::unique_ptr<OSExchangeData> data(std::make_unique<OSExchangeData>());
WriteDragData(press_pt, data.get());
// Message the RootView to do the drag and drop. That way if we're removed
// the RootView can detect it and avoid calling us back.
gfx::Point widget_location(event.location());
ConvertPointToWidget(this, &widget_location);
widget->RunShellDrag(this, std::move(data), widget_location, drag_operations,
source);
// WARNING: we may have been deleted.
return true;
}
std::unique_ptr<ActionViewInterface> View::GetActionViewInterface() {
return std::make_unique<BaseActionViewInterface>(this);
}
base::CallbackListSubscription View::RegisterNotifyViewControllerCallback(
base::RepeatingClosureList::CallbackType callback) {
return notify_view_controller_callback_list_.Add(std::move(callback));
}
void View::NotifyViewControllerCallback() {
notify_view_controller_callback_list_.Notify();
}
BaseActionViewInterface::BaseActionViewInterface(View* action_view)
: action_view_(action_view) {}
void BaseActionViewInterface::ActionItemChangedImpl(
actions::ActionItem* action_item) {
action_view_->SetEnabled(action_item->GetEnabled());
action_view_->SetVisible(action_item->GetVisible());
}
// This block requires the existence of METADATA_HEADER_BASE(View) in the class
// declaration for View.
BEGIN_METADATA_BASE(View)
ADD_PROPERTY_METADATA(std::unique_ptr<Background>, Background)
ADD_PROPERTY_METADATA(std::unique_ptr<Border>, Border)
ADD_READONLY_PROPERTY_METADATA(const char*, ClassName)
ADD_PROPERTY_METADATA(bool, Enabled)
ADD_PROPERTY_METADATA(View::FocusBehavior, FocusBehavior)
ADD_PROPERTY_METADATA(bool, FlipCanvasOnPaintForRTLUI)
ADD_PROPERTY_METADATA(int, Group)
ADD_PROPERTY_METADATA(int, Height)
ADD_PROPERTY_METADATA(int, ID)
ADD_READONLY_PROPERTY_METADATA(bool, IsDrawn);
ADD_READONLY_PROPERTY_METADATA(gfx::Size, MaximumSize)
ADD_READONLY_PROPERTY_METADATA(gfx::Size, MinimumSize)
ADD_PROPERTY_METADATA(bool, Mirrored)
ADD_PROPERTY_METADATA(bool, NotifyEnterExitOnChild)
ADD_READONLY_PROPERTY_METADATA(std::string, ObjectName)
ADD_READONLY_PROPERTY_METADATA(std::u16string, Tooltip)
ADD_PROPERTY_METADATA(bool, Visible)
ADD_PROPERTY_METADATA(bool, CanProcessEventsWithinSubtree)
ADD_PROPERTY_METADATA(bool, UseDefaultFillLayout)
ADD_PROPERTY_METADATA(int, Width)
ADD_PROPERTY_METADATA(int, X)
ADD_PROPERTY_METADATA(int, Y)
ADD_CLASS_PROPERTY_METADATA(gfx::Insets, kMarginsKey)
ADD_CLASS_PROPERTY_METADATA(gfx::Insets, kInternalPaddingKey)
ADD_CLASS_PROPERTY_METADATA(LayoutAlignment, kCrossAxisAlignmentKey)
ADD_CLASS_PROPERTY_METADATA(bool, kViewIgnoredByLayoutKey)
END_METADATA
} // namespace views
DEFINE_ENUM_CONVERTERS(views::View::FocusBehavior,
{views::View::FocusBehavior::ACCESSIBLE_ONLY,
u"ACCESSIBLE_ONLY"},
{views::View::FocusBehavior::ALWAYS, u"ALWAYS"},
{views::View::FocusBehavior::NEVER, u"NEVER"})