| // Copyright 2018 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/accessibility/ax_virtual_view.h" |
| |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <map> |
| #include <utility> |
| |
| #include "base/containers/adapters.h" |
| #include "base/functional/callback.h" |
| #include "base/no_destructor.h" |
| #include "base/notimplemented.h" |
| #include "build/build_config.h" |
| #include "ui/accessibility/ax_action_data.h" |
| #include "ui/accessibility/ax_role_properties.h" |
| #include "ui/accessibility/ax_tree_data.h" |
| #include "ui/accessibility/platform/ax_platform_node.h" |
| #include "ui/base/buildflags.h" |
| #include "ui/base/layout.h" |
| #include "ui/base/mojom/menu_source_type.mojom.h" |
| #include "ui/gfx/geometry/rect_conversions.h" |
| #include "ui/views/accessibility/ax_update_notifier.h" |
| #include "ui/views/accessibility/view_accessibility.h" |
| #include "ui/views/accessibility/view_ax_platform_node_delegate.h" |
| #include "ui/views/view.h" |
| #include "ui/views/widget/widget.h" |
| |
| #if BUILDFLAG(IS_WIN) |
| #include "ui/views/win/hwnd_util.h" |
| #endif |
| |
| namespace views { |
| |
| // Tracks all virtual ax views. |
| std::map<int32_t, AXVirtualView*>& GetIdMap() { |
| static base::NoDestructor<std::map<int32_t, AXVirtualView*>> id_to_obj_map; |
| return *id_to_obj_map; |
| } |
| |
| // static |
| const char AXVirtualView::kViewClassName[] = "AXVirtualView"; |
| |
| // static |
| AXVirtualView* AXVirtualView::GetFromId(int32_t id) { |
| auto& id_map = GetIdMap(); |
| const auto& it = id_map.find(id); |
| return it != id_map.end() ? it->second : nullptr; |
| } |
| |
| AXVirtualView::AXVirtualView() : ViewAccessibility(nullptr) { |
| GetIdMap()[ViewAccessibility::GetUniqueId()] = this; |
| ax_platform_node_ = ui::AXPlatformNode::Create(*this); |
| DCHECK(ax_platform_node_); |
| SetClassName(GetViewClassName()); |
| } |
| |
| AXVirtualView::~AXVirtualView() { |
| GetIdMap().erase(ViewAccessibility::GetUniqueId()); |
| DCHECK(!parent_view_ || !virtual_parent_view_) |
| << "Either |parent_view_| or |virtual_parent_view_| could be set but " |
| "not both."; |
| |
| #if defined(USE_AURA) |
| if (ax_aura_obj_cache_) { |
| ax_aura_obj_cache_->Remove(this); |
| } |
| #endif |
| } |
| |
| void AXVirtualView::AddChildView(std::unique_ptr<AXVirtualView> view) { |
| DCHECK(view); |
| if (view->virtual_parent_view_ == this) { |
| return; // Already a child of this virtual view. |
| } |
| AddChildViewAt(std::move(view), children_.size()); |
| } |
| |
| void AXVirtualView::AddChildViewAt(std::unique_ptr<AXVirtualView> view, |
| size_t index) { |
| DCHECK(view); |
| CHECK_NE(view.get(), this) |
| << "You cannot add an AXVirtualView as its own child."; |
| DCHECK(!view->parent_view_) << "This |view| already has a View " |
| "parent. Call RemoveVirtualChildView first."; |
| DCHECK(!view->virtual_parent_view_) << "This |view| already has an " |
| "AXVirtualView parent. Call " |
| "RemoveChildView first."; |
| DCHECK_LE(index, children_.size()); |
| |
| view->virtual_parent_view_ = this; |
| children_.insert(children_.begin() + static_cast<ptrdiff_t>(index), |
| std::move(view)); |
| views::View* owner_view = GetOwnerView(); |
| |
| AXVirtualView* added_view = children_[index].get(); |
| added_view->OnViewHasNewAncestor( |
| /* ancestor_focusable */ data().HasState(ax::mojom::State::kFocusable) || |
| has_focusable_ancestor()); |
| |
| if (owner_view) { |
| owner_view->NotifyAccessibilityEventDeprecated( |
| ax::mojom::Event::kChildrenChanged, true); |
| } |
| } |
| |
| void AXVirtualView::ReorderChildView(AXVirtualView* view, size_t index) { |
| DCHECK(view); |
| index = std::min(index, children_.size() - 1); |
| |
| DCHECK_EQ(view->virtual_parent_view_, this); |
| if (children_[index].get() == view) { |
| return; |
| } |
| |
| auto cur_index = GetIndexOf(view); |
| if (!cur_index.has_value()) { |
| return; |
| } |
| |
| std::unique_ptr<AXVirtualView> child = |
| std::move(children_[cur_index.value()]); |
| children_.erase(children_.begin() + |
| static_cast<ptrdiff_t>(cur_index.value())); |
| children_.insert(children_.begin() + static_cast<ptrdiff_t>(index), |
| std::move(child)); |
| |
| GetOwnerView()->NotifyAccessibilityEventDeprecated( |
| ax::mojom::Event::kChildrenChanged, true); |
| } |
| |
| std::unique_ptr<AXVirtualView> AXVirtualView::RemoveFromParentView() { |
| if (parent_view_) { |
| return parent_view_->RemoveVirtualChildView(this); |
| } |
| |
| // This virtual view hasn't been added to a parent view yet. |
| CHECK(virtual_parent_view_) |
| << "Cannot remove from parent view if there is no parent."; |
| return virtual_parent_view_->RemoveChildView(this); |
| } |
| |
| std::unique_ptr<AXVirtualView> AXVirtualView::RemoveChildView( |
| AXVirtualView* view) { |
| DCHECK(view); |
| auto cur_index = GetIndexOf(view); |
| if (!cur_index.has_value()) { |
| return {}; |
| } |
| |
| bool focus_changed = false; |
| if (GetOwnerView()) { |
| ViewAccessibility& view_accessibility = |
| GetOwnerView()->GetViewAccessibility(); |
| if (view_accessibility.FocusedVirtualChild() && |
| Contains(view_accessibility.FocusedVirtualChild())) { |
| focus_changed = true; |
| } |
| } |
| |
| std::unique_ptr<AXVirtualView> child = |
| std::move(children_[cur_index.value()]); |
| children_.erase(children_.begin() + |
| static_cast<ptrdiff_t>(cur_index.value())); |
| child->virtual_parent_view_ = nullptr; |
| |
| if (GetOwnerView()) { |
| if (focus_changed) { |
| GetOwnerView()->GetViewAccessibility().OverrideFocus(nullptr); |
| } |
| GetOwnerView()->NotifyAccessibilityEventDeprecated( |
| ax::mojom::Event::kChildrenChanged, true); |
| } |
| |
| return child; |
| } |
| |
| void AXVirtualView::RemoveAllChildViews() { |
| while (!children_.empty()) { |
| RemoveChildView(children_.back().get()); |
| } |
| } |
| |
| bool AXVirtualView::Contains(const AXVirtualView* view) const { |
| DCHECK(view); |
| for (const AXVirtualView* v = view; v; v = v->virtual_parent_view_) { |
| if (v == this) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| std::optional<size_t> AXVirtualView::GetIndexOf( |
| const AXVirtualView* view) const { |
| DCHECK(view); |
| const auto iter = |
| std::ranges::find(children_, view, &std::unique_ptr<AXVirtualView>::get); |
| return iter != children_.end() |
| ? std::make_optional(static_cast<size_t>(iter - children_.begin())) |
| : std::nullopt; |
| } |
| |
| const char* AXVirtualView::GetViewClassName() const { |
| return kViewClassName; |
| } |
| |
| gfx::NativeViewAccessible AXVirtualView::GetNativeObject() const { |
| DCHECK(ax_platform_node_); |
| return ax_platform_node_->GetNativeViewAccessible(); |
| } |
| |
| Widget* AXVirtualView::GetWidget() const { |
| View* owner_view = GetOwnerView(); |
| if (owner_view) { |
| return owner_view->GetWidget(); |
| } |
| return nullptr; |
| } |
| |
| ViewAccessibility* AXVirtualView::GetViewAccessibilityParent() const { |
| if (parent_view_) { |
| return parent_view_; |
| } |
| if (virtual_parent_view_) { |
| return virtual_parent_view_; |
| } |
| // This virtual view hasn't been added to a parent view yet. |
| return nullptr; |
| } |
| |
| std::string AXVirtualView::GetDebugString() const { |
| View* owner_view = GetOwnerView(); |
| if (!owner_view) { |
| return std::string("Virtual view with no owner view"); |
| } |
| return base::StrCat({"Virtual view child of ", owner_view->GetClassName()}); |
| } |
| |
| void AXVirtualView::NotifyEvent(ax::mojom::Event event_type, |
| bool send_native_event) { |
| // If `ready_to_notify_events_` is false, 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 (!ready_to_notify_events_) { |
| return; |
| } |
| |
| DCHECK(ax_platform_node_); |
| if (event_type == ax::mojom::Event::kAlert) { |
| CHECK(ui::IsAlert(GetRole())) |
| << "On some platforms, the alert event does not work correctly unless " |
| "it is fired on an object with an alert role. Role was " |
| << GetRole(); |
| } |
| if (GetOwnerView()) { |
| const ViewAccessibility::AccessibilityEventsCallback& events_callback = |
| GetOwnerView()->GetViewAccessibility().accessibility_events_callback(); |
| if (events_callback) { |
| events_callback.Run(this, event_type); |
| } |
| } |
| |
| // This is used on platforms that have a native accessibility API. |
| ax_platform_node_->NotifyAccessibilityEvent(event_type); |
| |
| // This is used on platforms that don't have a native accessibility API. |
| AXUpdateNotifier::Get()->NotifyVirtualViewEvent(this, event_type); |
| } |
| |
| void AXVirtualView::NotifyDataChanged() { |
| AXUpdateNotifier::Get()->NotifyVirtualViewDataChanged(this); |
| } |
| |
| // ui::AXPlatformNodeDelegate |
| |
| const ui::AXNodeData& AXVirtualView::GetData() const { |
| return data(); |
| } |
| |
| size_t AXVirtualView::GetChildCount() const { |
| size_t count = 0; |
| for (const std::unique_ptr<AXVirtualView>& child : children_) { |
| if (child->IsIgnored()) { |
| count += child->GetChildCount(); |
| } else { |
| ++count; |
| } |
| } |
| return count; |
| } |
| |
| gfx::NativeViewAccessible AXVirtualView::ChildAtIndex(size_t index) const { |
| DCHECK_LT(index, GetChildCount()) |
| << "|index| should be less than the child count."; |
| |
| for (const std::unique_ptr<AXVirtualView>& child : children_) { |
| if (child->IsIgnored()) { |
| size_t child_count = child->GetChildCount(); |
| if (index < child_count) { |
| return child->ChildAtIndex(index); |
| } |
| index -= child_count; |
| } else { |
| if (index == 0) { |
| return child->GetNativeObject(); |
| } |
| --index; |
| } |
| } |
| |
| NOTREACHED() << "|index| should be less than the child count."; |
| } |
| |
| #if !BUILDFLAG(IS_MAC) |
| gfx::NativeViewAccessible AXVirtualView::GetNSWindow() { |
| NOTREACHED(); |
| } |
| #endif |
| |
| gfx::NativeViewAccessible AXVirtualView::GetNativeViewAccessible() { |
| return GetNativeObject(); |
| } |
| |
| gfx::NativeViewAccessible AXVirtualView::GetParent() const { |
| if (parent_view_) { |
| if (!parent_view_->GetIsIgnored()) { |
| return parent_view_->GetNativeObject(); |
| } |
| return GetDelegate()->GetParent(); |
| } |
| |
| if (virtual_parent_view_) { |
| if (virtual_parent_view_->IsIgnored()) { |
| return virtual_parent_view_->GetParent(); |
| } |
| return virtual_parent_view_->GetNativeObject(); |
| } |
| |
| // This virtual view hasn't been added to a parent view yet. |
| return gfx::NativeViewAccessible(); |
| } |
| |
| gfx::Rect AXVirtualView::GetBoundsRect( |
| const ui::AXCoordinateSystem coordinate_system, |
| const ui::AXClippingBehavior clipping_behavior, |
| ui::AXOffscreenResult* offscreen_result) const { |
| // We could optionally add clipping here if ever needed. |
| // TODO(nektar): Implement bounds that are relative to the parent. |
| gfx::Rect bounds = gfx::ToEnclosingRect(GetData().relative_bounds.bounds); |
| View* owner_view = GetOwnerView(); |
| if (owner_view && owner_view->GetWidget()) { |
| View::ConvertRectToScreen(owner_view, &bounds); |
| } |
| switch (coordinate_system) { |
| case ui::AXCoordinateSystem::kScreenDIPs: |
| return bounds; |
| case ui::AXCoordinateSystem::kScreenPhysicalPixels: { |
| float scale_factor = 1.0; |
| if (auto* widget = GetWidget()) { |
| gfx::NativeView native_view = widget->GetNativeView(); |
| if (native_view) { |
| scale_factor = ui::GetScaleFactorForNativeView(native_view); |
| } |
| } |
| return gfx::ScaleToEnclosingRect(bounds, scale_factor); |
| } |
| case ui::AXCoordinateSystem::kRootFrame: |
| case ui::AXCoordinateSystem::kFrame: |
| NOTIMPLEMENTED(); |
| return gfx::Rect(); |
| } |
| } |
| |
| gfx::NativeViewAccessible AXVirtualView::HitTestSync( |
| int screen_physical_pixel_x, |
| int screen_physical_pixel_y) const { |
| if (GetData().IsInvisible()) { |
| return gfx::NativeViewAccessible(); |
| } |
| |
| // Check if the point is within any of the virtual children of this view. |
| // AXVirtualView's HitTestSync is a recursive function that will return the |
| // deepest child, since it does not support relative bounds. |
| // Search the greater indices first, since they're on top in the z-order. |
| for (const std::unique_ptr<AXVirtualView>& child : |
| base::Reversed(children_)) { |
| gfx::NativeViewAccessible result = |
| child->HitTestSync(screen_physical_pixel_x, screen_physical_pixel_y); |
| if (result) { |
| return result; |
| } |
| } |
| |
| // If it's not inside any of our virtual children, and it's inside the bounds |
| // of this virtual view, then it's inside this virtual view. |
| gfx::Rect bounds_in_screen_physical_pixels = |
| GetBoundsRect(ui::AXCoordinateSystem::kScreenPhysicalPixels, |
| ui::AXClippingBehavior::kUnclipped); |
| if (bounds_in_screen_physical_pixels.Contains( |
| static_cast<float>(screen_physical_pixel_x), |
| static_cast<float>(screen_physical_pixel_y)) && |
| !IsIgnored()) { |
| return GetNativeObject(); |
| } |
| |
| return gfx::NativeViewAccessible(); |
| } |
| |
| gfx::NativeViewAccessible AXVirtualView::GetFocus() const { |
| View* owner_view = GetOwnerView(); |
| if (owner_view) { |
| if (!(owner_view->HasFocus())) { |
| return gfx::NativeViewAccessible(); |
| } |
| return owner_view->GetViewAccessibility().GetFocusedDescendant(); |
| } |
| |
| // This virtual view hasn't been added to a parent view yet. |
| return gfx::NativeViewAccessible(); |
| } |
| |
| ui::AXPlatformNode* AXVirtualView::GetFromNodeID(int32_t id) { |
| AXVirtualView* virtual_view = GetFromId(id); |
| if (virtual_view) { |
| return virtual_view->ax_platform_node(); |
| } |
| return nullptr; |
| } |
| |
| bool AXVirtualView::AccessibilityPerformAction(const ui::AXActionData& data) { |
| bool result = false; |
| if (ViewAccessibility::data().HasAction(data.action)) { |
| result = HandleAccessibleAction(data); |
| } |
| if (!result && GetOwnerView()) { |
| return HandleAccessibleActionInOwnerView(data); |
| } |
| return result; |
| } |
| |
| bool AXVirtualView::ShouldIgnoreHoveredStateForTesting() { |
| // TODO(nektar): Implement. |
| return false; |
| } |
| |
| bool AXVirtualView::IsOffscreen() const { |
| // TODO(nektar): Implement. |
| return false; |
| } |
| |
| ui::AXPlatformNodeId AXVirtualView::GetUniqueId() const { |
| // The unique ID is held in the `ViewAccessibility`. |
| return ViewAccessibility::GetUniqueId(); |
| } |
| |
| // Virtual views need to implement this function in order for accessibility |
| // events to be routed correctly. |
| gfx::AcceleratedWidget AXVirtualView::GetTargetForNativeAccessibilityEvent() { |
| #if BUILDFLAG(IS_WIN) |
| if (GetOwnerView()) { |
| return HWNDForView(GetOwnerView()); |
| } |
| #endif |
| return gfx::kNullAcceleratedWidget; |
| } |
| |
| std::vector<int32_t> AXVirtualView::GetColHeaderNodeIds() const { |
| return GetDelegate()->GetColHeaderNodeIds(); |
| } |
| |
| std::vector<int32_t> AXVirtualView::GetColHeaderNodeIds(int col_index) const { |
| return GetDelegate()->GetColHeaderNodeIds(col_index); |
| } |
| |
| std::optional<int32_t> AXVirtualView::GetCellId(int row_index, |
| int col_index) const { |
| return GetDelegate()->GetCellId(row_index, col_index); |
| } |
| |
| bool AXVirtualView::HandleAccessibleAction( |
| const ui::AXActionData& action_data) { |
| if (!GetOwnerView()) { |
| return false; |
| } |
| |
| switch (action_data.action) { |
| case ax::mojom::Action::kShowContextMenu: { |
| const gfx::Rect screen_bounds = GetBoundsRect( |
| ui::AXCoordinateSystem::kScreenDIPs, ui::AXClippingBehavior::kClipped, |
| nullptr /* offscreen_result */); |
| if (!screen_bounds.IsEmpty()) { |
| GetOwnerView()->ShowContextMenu(screen_bounds.CenterPoint(), |
| ui::mojom::MenuSourceType::kKeyboard); |
| return true; |
| } |
| break; |
| } |
| |
| default: |
| break; |
| } |
| |
| return HandleAccessibleActionInOwnerView(action_data); |
| } |
| |
| bool AXVirtualView::HandleAccessibleActionInOwnerView( |
| const ui::AXActionData& action_data) { |
| DCHECK(GetOwnerView()); |
| // Save the node id so that the owner view can determine which virtual view |
| // is being targeted for action. |
| ui::AXActionData forwarded_action_data = action_data; |
| forwarded_action_data.target_node_id = GetData().id; |
| return GetOwnerView()->HandleAccessibleAction(forwarded_action_data); |
| } |
| |
| void AXVirtualView::set_cache(AXAuraObjCache* cache) { |
| #if defined(USE_AURA) |
| if (ax_aura_obj_cache_ && cache) { |
| ax_aura_obj_cache_->Remove(this); |
| } |
| #endif |
| |
| ax_aura_obj_cache_ = cache; |
| } |
| |
| View* AXVirtualView::GetOwnerView() const { |
| if (parent_view_) { |
| return parent_view_->view(); |
| } |
| |
| if (virtual_parent_view_) { |
| return virtual_parent_view_->GetOwnerView(); |
| } |
| |
| // This virtual view hasn't been added to a parent view yet. |
| return nullptr; |
| } |
| |
| ViewAXPlatformNodeDelegate* AXVirtualView::GetDelegate() const { |
| DCHECK(GetOwnerView()); |
| #if BUILDFLAG(HAS_NATIVE_ACCESSIBILITY) |
| return static_cast<ViewAXPlatformNodeDelegate*>( |
| &GetOwnerView()->GetViewAccessibility()); |
| #else |
| return nullptr; |
| #endif |
| } |
| |
| AXVirtualViewWrapper* AXVirtualView::GetOrCreateWrapper( |
| views::AXAuraObjCache* cache) { |
| #if defined(USE_AURA) |
| return static_cast<AXVirtualViewWrapper*>(cache->GetOrCreate(this)); |
| #else |
| return nullptr; |
| #endif |
| } |
| |
| void AXVirtualView::PruneVirtualSubtree() { |
| pruned_ = true; |
| UpdateIgnoredState(); |
| for (auto& child : children()) { |
| child->PruneVirtualSubtree(); |
| } |
| } |
| |
| void AXVirtualView::UnpruneVirtualSubtree() { |
| pruned_ = false; |
| UpdateIgnoredState(); |
| for (auto& child : children()) { |
| child->UnpruneVirtualSubtree(); |
| } |
| } |
| |
| void AXVirtualView::ForceSetIsFocusable(bool focusable) { |
| should_be_focusable_ = focusable; |
| |
| // To align with previous behavior, if a virtual view is set to explicitly |
| // focusable, we must make sure it is not ignored. |
| if (focusable) { |
| data_.RemoveState(ax::mojom::State::kIgnored); |
| } else { |
| if (should_be_ignored_) { |
| data_.AddState(ax::mojom::State::kIgnored); |
| } |
| } |
| |
| UpdateFocusableState(); |
| } |
| |
| void AXVirtualView::ResetIsFocusable() { |
| should_be_focusable_ = std::nullopt; |
| UpdateFocusableState(); |
| } |
| |
| void AXVirtualView::OnViewHasNewAncestor(bool ancestor_focusable) { |
| // We need to make sure that we are propagating the right values down the |
| // recursive calls. For the invisible state, this means we look at the direct |
| // parent, rather than the new ancestor, which in subsequent recursive calls |
| // could be a root of an entire tree that is getting reparented. This is |
| // because if at some point during the recursion, the parent is invisible, it |
| // should affect its descendants, even if the new ancestor is not. For |
| // example, if we have a tree like this: A (visible) |
| // B (invisible) |
| // and then a separate tree: |
| // C (invisible) |
| // D (invisible by inheritance of C) |
| // and then we reparent C to be a child of A: |
| // A (visible) |
| // B (invisible) |
| // C (invisible) |
| // D (invisible by inheritance of C) |
| // Even though `A` is visible ( A would be the new ancestor), we need to make |
| // sure that during the recursion, we don't mark `D` as visible, since it's |
| // parent is invisible. |
| bool parent_invisible = false; |
| if (parent_view()) { |
| CHECK(parent_view()->view()); |
| parent_invisible = parent_view()->is_invisible_by_inheritance() || |
| !parent_view()->view()->GetVisible(); |
| } else { |
| CHECK(virtual_parent_view()); |
| // We only need to check if the parent view is drawn, because the accessible |
| // invisible state does not get propagated down the hierarchy. |
| parent_invisible = !virtual_parent_view()->parent_view_is_drawn(); |
| } |
| |
| parent_view_is_drawn_ = !parent_invisible; |
| |
| UpdateInvisibleState(); |
| |
| // We only want to propagate the `ancestor_focusable` value if it's true. This |
| // is because if this view is unfocusable, and it gets added to a tree with a |
| // focusable ancestor, it should now be marked as ignored. However, being |
| // added to a tree with an unfocusable ancestor doesn't affect the ignored |
| // state of this view or its descendants. |
| if (ancestor_focusable) { |
| SetHasFocusableAncestor(ancestor_focusable); |
| } |
| |
| UpdateReadyToNotifyEvents(); |
| for (auto& child : children_) { |
| child->OnViewHasNewAncestor(ancestor_focusable); |
| } |
| } |
| |
| void AXVirtualView::UpdateFocusableState() { |
| bool is_focusable = |
| (GetIsEnabled() && !data().HasState(ax::mojom::State::kInvisible) && |
| !ViewAccessibility::GetIsIgnored()); |
| |
| if (should_be_focusable_.has_value()) { |
| is_focusable = should_be_focusable_.value(); |
| } |
| |
| SetState(ax::mojom::State::kFocusable, is_focusable); |
| } |
| |
| void AXVirtualView::UpdateInvisibleState() { |
| bool is_invisible = !parent_view_is_drawn_ || should_be_invisible_; |
| SetState(ax::mojom::State::kInvisible, is_invisible); |
| UpdateFocusableState(); |
| } |
| |
| void AXVirtualView::OnWidgetClosing(Widget* widget) { |
| // The RootView's ViewAccessibility should be the only registered |
| // WidgetObserver. |
| CHECK_EQ(GetOwnerView(), widget->GetRootView()); |
| SetWidgetClosedRecursive(widget, true); |
| } |
| |
| void AXVirtualView::OnWidgetDestroyed(Widget* widget) { |
| // The RootView's ViewAccessibility should be the only registered |
| // WidgetObserver. |
| CHECK(widget->GetRootView()); |
| CHECK_EQ(GetOwnerView(), widget->GetRootView()); |
| SetWidgetClosedRecursive(widget, true); |
| } |
| |
| void AXVirtualView::OnWidgetUpdated(Widget* widget, Widget* old_widget) { |
| CHECK(widget); |
| DCHECK_EQ(widget, GetWidget()); |
| if (widget == old_widget) { |
| return; |
| } |
| |
| // There's a chance we are reparenting a view that was previously a root |
| // view in another widget, if so we need to remove it as an observer of the |
| // old widget. |
| if (old_widget && old_widget != widget) { |
| old_widget->RemoveObserver(this); |
| } |
| |
| // If we have already marked `is_widget_closed_` as true, then there's a |
| // chance that the view was reparented to a non-closed widget. If so, we must |
| // update `is_widget_closed_` in case the new widget is not closed. |
| SetWidgetClosedRecursive(widget, widget->IsClosed()); |
| } |
| |
| void AXVirtualView::UpdateIgnoredState() { |
| // TODO(crbug.com/371237539): In ChromeOS, its not an expectation that being |
| // a view unfocusable descendant of a focusable ancestor will make the view |
| // ignored. |
| #if !BUILDFLAG(IS_CHROMEOS) |
| bool is_ignored = |
| should_be_ignored_ || pruned_ || |
| GetCachedRole() == ax::mojom::Role::kNone || |
| (has_focusable_ancestor_ && |
| (should_be_focusable_.has_value() && !should_be_focusable_.value())); |
| |
| if (should_be_focusable_.has_value()) { |
| is_ignored = is_ignored && !should_be_focusable_.value(); |
| } |
| #else |
| bool is_ignored = |
| should_be_ignored_ || pruned_ || data().role == ax::mojom::Role::kNone; |
| #endif // !BUILDFLAG(IS_CHROMEOS) |
| SetState(ax::mojom::State::kIgnored, is_ignored); |
| UpdateFocusableState(); |
| } |
| |
| void AXVirtualView::UpdateReadyToNotifyEvents() { |
| auto* parent = parent_view() ? parent_view() : virtual_parent_view(); |
| if (parent && parent->IsReadyToNotifyEvents()) { |
| SetReadyToNotifyEvents(); |
| } |
| } |
| |
| void AXVirtualView::UpdateParentViewIsDrawnRecursive( |
| const views::View* initial_view, |
| bool parent_view_is_drawn) { |
| parent_view_is_drawn_ = parent_view_is_drawn; |
| UpdateInvisibleState(); |
| |
| // Now we do the same for any virtual children. |
| for (auto& child : children_) { |
| child->UpdateParentViewIsDrawnRecursive(initial_view, parent_view_is_drawn); |
| } |
| } |
| |
| void AXVirtualView::SetIsEnabled(bool enabled) { |
| if (enabled == GetIsEnabled()) { |
| return; |
| } |
| |
| if (!enabled) { |
| data_.SetRestriction(ax::mojom::Restriction::kDisabled); |
| } else if (data_.GetRestriction() == ax::mojom::Restriction::kDisabled) { |
| // Take into account the possibility that the View is marked as readonly |
| // but enabled. In other words, we can't just remove all restrictions, |
| // unless the View is explicitly marked as disabled. Note that readonly is |
| // another restriction state in addition to enabled and disabled, (see |
| // `ax::mojom::Restriction`). |
| data_.SetRestriction(ax::mojom::Restriction::kNone); |
| } |
| } |
| |
| void AXVirtualView::SetShowContextMenu(bool show_context_menu) { |
| if (show_context_menu) { |
| data_.AddAction(ax::mojom::Action::kShowContextMenu); |
| } else { |
| data_.RemoveAction(ax::mojom::Action::kShowContextMenu); |
| } |
| } |
| |
| void AXVirtualView::SetIsEnabledRecursive(bool enabled) { |
| SetIsEnabled(enabled); |
| for (auto& child : children_) { |
| child->SetIsEnabledRecursive(enabled); |
| } |
| } |
| |
| void AXVirtualView::SetShowContextMenuRecursive(bool show_context_menu) { |
| SetShowContextMenu(show_context_menu); |
| for (auto& child : children_) { |
| child->SetShowContextMenuRecursive(show_context_menu); |
| } |
| } |
| } // namespace views |