| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/views/focus/focus_manager.h" |
| |
| #include <algorithm> |
| #include <vector> |
| |
| #include "base/auto_reset.h" |
| #include "base/i18n/rtl.h" |
| #include "base/logging.h" |
| #include "ui/base/accelerators/accelerator.h" |
| #include "ui/base/ime/input_method.h" |
| #include "ui/base/ime/text_input_client.h" |
| #include "ui/events/event.h" |
| #include "ui/events/keycodes/keyboard_codes.h" |
| #include "ui/views/focus/focus_manager_delegate.h" |
| #include "ui/views/focus/focus_search.h" |
| #include "ui/views/focus/widget_focus_manager.h" |
| #include "ui/views/view.h" |
| #include "ui/views/view_properties.h" |
| #include "ui/views/view_tracker.h" |
| #include "ui/views/widget/root_view.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/views/widget/widget_delegate.h" |
| |
| namespace views { |
| |
| bool FocusManager::arrow_key_traversal_enabled_ = false; |
| |
| FocusManager::FocusManager(Widget* widget, |
| std::unique_ptr<FocusManagerDelegate> delegate) |
| : widget_(widget), |
| delegate_(std::move(delegate)), |
| view_tracker_for_stored_view_(std::make_unique<ViewTracker>()) { |
| DCHECK(widget_); |
| } |
| |
| FocusManager::~FocusManager() { |
| if (focused_view_) |
| focused_view_->RemoveObserver(this); |
| } |
| |
| bool FocusManager::OnKeyEvent(const ui::KeyEvent& event) { |
| const int key_code = event.key_code(); |
| |
| if (event.type() != ui::ET_KEY_PRESSED && event.type() != ui::ET_KEY_RELEASED) |
| return false; |
| |
| if (shortcut_handling_suspended()) |
| return true; |
| |
| ui::Accelerator accelerator(event); |
| |
| if (event.type() == ui::ET_KEY_PRESSED) { |
| // If the focused view wants to process the key event as is, let it be. |
| if (focused_view_ && focused_view_->SkipDefaultKeyEventProcessing(event) && |
| !accelerator_manager_.HasPriorityHandler(accelerator)) |
| return true; |
| |
| // Intercept Tab related messages for focus traversal. |
| // Note that we don't do focus traversal if the root window is not part of |
| // the active window hierarchy as this would mean we have no focused view |
| // and would focus the first focusable view. |
| if (IsTabTraversalKeyEvent(event)) { |
| AdvanceFocus(event.IsShiftDown()); |
| return false; |
| } |
| |
| if (arrow_key_traversal_enabled_ && ProcessArrowKeyTraversal(event)) |
| return false; |
| |
| // Intercept arrow key messages to switch between grouped views. |
| bool is_left = key_code == ui::VKEY_LEFT || key_code == ui::VKEY_UP; |
| bool is_right = key_code == ui::VKEY_RIGHT || key_code == ui::VKEY_DOWN; |
| if (focused_view_ && focused_view_->GetGroup() != -1 && |
| (is_left || is_right)) { |
| bool next = is_right; |
| View::Views views; |
| focused_view_->parent()->GetViewsInGroup(focused_view_->GetGroup(), |
| &views); |
| View::Views::const_iterator i( |
| std::find(views.begin(), views.end(), focused_view_)); |
| DCHECK(i != views.end()); |
| size_t index = i - views.begin(); |
| if (next && index == views.size() - 1) |
| index = 0; |
| else if (!next && index == 0) |
| index = views.size() - 1; |
| else |
| index += next ? 1 : -1; |
| SetFocusedViewWithReason(views[index], kReasonFocusTraversal); |
| return false; |
| } |
| } |
| |
| // Process keyboard accelerators. |
| // If the key combination matches an accelerator, the accelerator is |
| // triggered, otherwise the key event is processed as usual. |
| if (ProcessAccelerator(accelerator)) { |
| // If a shortcut was activated for this keydown message, do not propagate |
| // the event further. |
| return false; |
| } |
| return true; |
| } |
| |
| // Tests whether a view is valid, whether it still belongs to the window |
| // hierarchy of the FocusManager. |
| bool FocusManager::ContainsView(View* view) { |
| Widget* widget = view->GetWidget(); |
| return widget && widget->GetFocusManager() == this; |
| } |
| |
| void FocusManager::AdvanceFocus(bool reverse) { |
| View* v = GetNextFocusableView(focused_view_, nullptr, reverse, false); |
| // Note: Do not skip this next block when v == focused_view_. If the user |
| // tabs past the last focusable element in a webpage, we'll get here, and if |
| // the TabContentsContainerView is the only focusable view (possible in |
| // fullscreen mode), we need to run this block in order to cycle around to the |
| // first element on the page. |
| if (v) { |
| views::View* focused_view = focused_view_; |
| v->AboutToRequestFocusFromTabTraversal(reverse); |
| // AboutToRequestFocusFromTabTraversal() may have changed focus. If it did, |
| // don't change focus again. |
| if (focused_view != focused_view_) |
| return; |
| |
| // Note that GetNextFocusableView may have returned a View in a different |
| // FocusManager. |
| DCHECK(v->GetWidget()); |
| v->GetWidget()->GetFocusManager()->SetFocusedViewWithReason( |
| v, kReasonFocusTraversal); |
| |
| // When moving focus from a child widget to a top-level widget, |
| // the top-level widget may report IsActive()==true because it's |
| // active even though it isn't focused. Explicitly activate the |
| // widget to ensure that case is handled. |
| if (v->GetWidget()->GetFocusManager() != this) |
| v->GetWidget()->Activate(); |
| } |
| } |
| |
| void FocusManager::ClearNativeFocus() { |
| // Keep the top root window focused so we get keyboard events. |
| widget_->ClearNativeFocus(); |
| } |
| |
| bool FocusManager::RotatePaneFocus(Direction direction, |
| FocusCycleWrappingBehavior wrap) { |
| // Get the list of all accessible panes. |
| std::vector<View*> panes; |
| widget_->widget_delegate()->GetAccessiblePanes(&panes); |
| |
| // Count the number of panes and set the default index if no pane |
| // is initially focused. |
| int count = static_cast<int>(panes.size()); |
| if (count == 0) |
| return false; |
| |
| // Initialize |index| to an appropriate starting index if nothing is |
| // focused initially. |
| int index = direction == kBackward ? 0 : count - 1; |
| |
| // Check to see if a pane already has focus and update the index accordingly. |
| const views::View* focused_view = GetFocusedView(); |
| if (focused_view) { |
| for (int i = 0; i < count; i++) { |
| if (panes[i] && panes[i]->Contains(focused_view)) { |
| index = i; |
| break; |
| } |
| } |
| } |
| |
| // Rotate focus. |
| int start_index = index; |
| for (;;) { |
| if (direction == kBackward) |
| index--; |
| else |
| index++; |
| |
| if (wrap == kNoWrap && (index >= count || index < 0)) |
| return false; |
| index = (index + count) % count; |
| |
| // Ensure that we don't loop more than once. |
| if (index == start_index) |
| break; |
| |
| views::View* pane = panes[index]; |
| DCHECK(pane); |
| |
| if (!pane->visible()) |
| continue; |
| |
| pane->RequestFocus(); |
| focused_view = GetFocusedView(); |
| if (pane == focused_view || pane->Contains(focused_view)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| View* FocusManager::GetNextFocusableView(View* original_starting_view, |
| Widget* starting_widget, |
| bool reverse, |
| bool dont_loop) { |
| DCHECK(!focused_view_ || ContainsView(focused_view_)) |
| << " focus_view=" << focused_view_; |
| |
| FocusTraversable* focus_traversable = nullptr; |
| |
| View* starting_view = nullptr; |
| if (original_starting_view) { |
| // Search up the containment hierarchy to see if a view is acting as |
| // a pane, and wants to implement its own focus traversable to keep |
| // the focus trapped within that pane. |
| View* pane_search = original_starting_view; |
| while (pane_search) { |
| focus_traversable = pane_search->GetPaneFocusTraversable(); |
| if (focus_traversable) { |
| starting_view = original_starting_view; |
| break; |
| } |
| pane_search = pane_search->parent(); |
| } |
| |
| if (!focus_traversable) { |
| if (!reverse) { |
| // If the starting view has a focus traversable, use it. |
| // This is the case with NativeWidgetWins for example. |
| focus_traversable = original_starting_view->GetFocusTraversable(); |
| |
| // Otherwise default to the root view. |
| if (!focus_traversable) { |
| focus_traversable = |
| original_starting_view->GetWidget()->GetFocusTraversable(); |
| starting_view = original_starting_view; |
| } |
| } else { |
| // When you are going back, starting view's FocusTraversable |
| // should not be used. |
| focus_traversable = |
| original_starting_view->GetWidget()->GetFocusTraversable(); |
| starting_view = original_starting_view; |
| } |
| } |
| } else { |
| Widget* widget = starting_widget ? starting_widget : widget_; |
| focus_traversable = widget->GetFocusTraversable(); |
| } |
| |
| // Traverse the FocusTraversable tree down to find the focusable view. |
| View* v = FindFocusableView(focus_traversable, starting_view, reverse); |
| if (v) |
| return v; |
| |
| // Let's go up in the FocusTraversable tree. |
| FocusTraversable* parent_focus_traversable = |
| focus_traversable->GetFocusTraversableParent(); |
| starting_view = focus_traversable->GetFocusTraversableParentView(); |
| while (parent_focus_traversable) { |
| FocusTraversable* new_focus_traversable = nullptr; |
| View* new_starting_view = nullptr; |
| // When we are going backward, the parent view might gain the next focus. |
| auto check_starting_view = |
| reverse ? FocusSearch::StartingViewPolicy::kCheckStartingView |
| : FocusSearch::StartingViewPolicy::kSkipStartingView; |
| v = parent_focus_traversable->GetFocusSearch()->FindNextFocusableView( |
| starting_view, |
| reverse ? FocusSearch::SearchDirection::kBackwards |
| : FocusSearch::SearchDirection::kForwards, |
| FocusSearch::TraversalDirection::kUp, check_starting_view, |
| FocusSearch::AnchoredDialogPolicy::kSkipAnchoredDialog, |
| &new_focus_traversable, &new_starting_view); |
| |
| if (new_focus_traversable) { |
| DCHECK(!v); |
| |
| // There is a FocusTraversable, traverse it down. |
| v = FindFocusableView(new_focus_traversable, nullptr, reverse); |
| } |
| |
| if (v) |
| return v; |
| |
| starting_view = focus_traversable->GetFocusTraversableParentView(); |
| parent_focus_traversable = |
| parent_focus_traversable->GetFocusTraversableParent(); |
| } |
| |
| // If we get here, we have reached the end of the focus hierarchy, let's |
| // loop. Make sure there was at least a view to start with, to prevent |
| // infinitely looping in empty windows. |
| if (dont_loop || !original_starting_view) |
| return nullptr; |
| |
| // Easy, just clear the selection and press tab again. |
| // By calling with nullptr as the starting view, we'll start from either |
| // the starting views widget or |widget_|. |
| Widget* widget = starting_view ? starting_view->GetWidget() |
| : original_starting_view->GetWidget(); |
| if (widget->widget_delegate()->ShouldAdvanceFocusToTopLevelWidget()) |
| widget = widget_; |
| return GetNextFocusableView(nullptr, widget, reverse, true); |
| } |
| |
| void FocusManager::SetKeyboardAccessible(bool keyboard_accessible) { |
| if (keyboard_accessible == keyboard_accessible_) |
| return; |
| |
| keyboard_accessible_ = keyboard_accessible; |
| // Disabling keyboard accessibility may cause the focused view to become not |
| // focusable. Hence advance focus if necessary. |
| AdvanceFocusIfNecessary(); |
| } |
| |
| void FocusManager::SetFocusedViewWithReason(View* view, |
| FocusChangeReason reason) { |
| if (focused_view_ == view) |
| return; |
| |
| // TODO(oshima|achuith): This is to diagnose crbug.com/687232. |
| // Change this to DCHECK once it's resolved. |
| CHECK(!view || ContainsView(view)); |
| |
| #if !defined(OS_MACOSX) |
| // TODO(warx): There are some AccessiblePaneViewTest failed on macosx. |
| // crbug.com/650859. Remove !defined(OS_MACOSX) once that is fixed. |
| // |
| // If the widget isn't active store the focused view and then attempt to |
| // activate the widget. If activation succeeds |view| will be focused. |
| // If activation fails |view| will be focused the next time the widget is |
| // made active. |
| if (view && !widget_->IsActive()) { |
| SetStoredFocusView(view); |
| widget_->Activate(); |
| return; |
| } |
| #endif |
| |
| // Update the reason for the focus change (since this is checked by |
| // some listeners), then notify all listeners. |
| focus_change_reason_ = reason; |
| for (FocusChangeListener& observer : focus_change_listeners_) |
| observer.OnWillChangeFocus(focused_view_, view); |
| |
| View* old_focused_view = focused_view_; |
| focused_view_ = view; |
| if (old_focused_view) { |
| old_focused_view->RemoveObserver(this); |
| old_focused_view->Blur(); |
| } |
| // Also make |focused_view_| the stored focus view. This way the stored focus |
| // view is remembered if focus changes are requested prior to a show or while |
| // hidden. |
| SetStoredFocusView(focused_view_); |
| if (focused_view_) { |
| focused_view_->AddObserver(this); |
| focused_view_->Focus(); |
| } |
| |
| for (FocusChangeListener& observer : focus_change_listeners_) |
| observer.OnDidChangeFocus(old_focused_view, focused_view_); |
| |
| if (delegate_) |
| delegate_->OnDidChangeFocus(old_focused_view, focused_view_); |
| } |
| |
| void FocusManager::ClearFocus() { |
| // SetFocusedView(nullptr) is going to clear out the stored view to. We need |
| // to persist it in this case. |
| views::View* focused_view = GetStoredFocusView(); |
| SetFocusedView(nullptr); |
| ClearNativeFocus(); |
| SetStoredFocusView(focused_view); |
| } |
| |
| void FocusManager::AdvanceFocusIfNecessary() { |
| // If widget is inactive, there is no focused view to check. The stored view |
| // will also be checked for focusability when it is being restored. |
| if (!widget_->IsActive()) |
| return; |
| |
| // If widget is active and focused view is not focusable, advance focus or, |
| // if not possible, clear focus. |
| if (focused_view_ && !IsFocusable(focused_view_)) { |
| AdvanceFocus(false); |
| if (focused_view_ && !IsFocusable(focused_view_)) |
| ClearFocus(); |
| } |
| } |
| |
| void FocusManager::StoreFocusedView(bool clear_native_focus) { |
| View* focused_view = focused_view_; |
| // Don't do anything if no focused view. Storing the view (which is nullptr), |
| // in this case, would clobber the view that was previously saved. |
| if (!focused_view_) |
| return; |
| |
| View* v = focused_view_; |
| |
| if (clear_native_focus) { |
| // Temporarily disable notification. ClearFocus() will set the focus to the |
| // main browser window. This extra focus bounce which happens during |
| // deactivation can confuse registered WidgetFocusListeners, as the focus |
| // is not changing due to a user-initiated event. |
| AutoNativeNotificationDisabler local_notification_disabler; |
| // ClearFocus() also stores the focused view. |
| ClearFocus(); |
| } else { |
| SetFocusedView(nullptr); |
| SetStoredFocusView(focused_view); |
| } |
| |
| if (v) |
| v->SchedulePaint(); // Remove focus border. |
| } |
| |
| bool FocusManager::RestoreFocusedView() { |
| View* view = GetStoredFocusView(); |
| if (view) { |
| if (ContainsView(view)) { |
| if (!view->IsFocusable() && view->IsAccessibilityFocusable()) { |
| // RequestFocus would fail, but we want to restore focus to controls |
| // that had focus in accessibility mode. |
| SetFocusedViewWithReason(view, kReasonFocusRestore); |
| } else { |
| // This usually just sets the focus if this view is focusable, but |
| // let the view override RequestFocus if necessary. |
| view->RequestFocus(); |
| |
| // If it succeeded, the reason would be incorrect; set it to |
| // focus restore. |
| if (focused_view_ == view) |
| focus_change_reason_ = kReasonFocusRestore; |
| } |
| } |
| // The |keyboard_accessible_| mode may have changed while the widget was |
| // inactive. |
| AdvanceFocusIfNecessary(); |
| } |
| return view && view == focused_view_; |
| } |
| |
| void FocusManager::SetStoredFocusView(View* focus_view) { |
| view_tracker_for_stored_view_->SetView(focus_view); |
| } |
| |
| View* FocusManager::GetStoredFocusView() { |
| return view_tracker_for_stored_view_->view(); |
| } |
| |
| // Find the next (previous if reverse is true) focusable view for the specified |
| // FocusTraversable, starting at the specified view, traversing down the |
| // FocusTraversable hierarchy. |
| View* FocusManager::FindFocusableView(FocusTraversable* focus_traversable, |
| View* starting_view, |
| bool reverse) { |
| FocusTraversable* new_focus_traversable = nullptr; |
| View* new_starting_view = nullptr; |
| auto can_go_into_anchored_dialog = |
| FocusSearch::AnchoredDialogPolicy::kCanGoIntoAnchoredDialog; |
| View* v = focus_traversable->GetFocusSearch()->FindNextFocusableView( |
| starting_view, |
| reverse ? FocusSearch::SearchDirection::kBackwards |
| : FocusSearch::SearchDirection::kForwards, |
| FocusSearch::TraversalDirection::kDown, |
| FocusSearch::StartingViewPolicy::kSkipStartingView, |
| can_go_into_anchored_dialog, &new_focus_traversable, &new_starting_view); |
| |
| // Let's go down the FocusTraversable tree as much as we can. |
| while (new_focus_traversable) { |
| DCHECK(!v); |
| focus_traversable = new_focus_traversable; |
| new_focus_traversable = nullptr; |
| starting_view = nullptr; |
| v = focus_traversable->GetFocusSearch()->FindNextFocusableView( |
| starting_view, |
| reverse ? FocusSearch::SearchDirection::kBackwards |
| : FocusSearch::SearchDirection::kForwards, |
| FocusSearch::TraversalDirection::kDown, |
| FocusSearch::StartingViewPolicy::kSkipStartingView, |
| can_go_into_anchored_dialog, &new_focus_traversable, |
| &new_starting_view); |
| } |
| return v; |
| } |
| |
| void FocusManager::RegisterAccelerator( |
| const ui::Accelerator& accelerator, |
| ui::AcceleratorManager::HandlerPriority priority, |
| ui::AcceleratorTarget* target) { |
| accelerator_manager_.Register({accelerator}, priority, target); |
| } |
| |
| void FocusManager::UnregisterAccelerator(const ui::Accelerator& accelerator, |
| ui::AcceleratorTarget* target) { |
| accelerator_manager_.Unregister(accelerator, target); |
| } |
| |
| void FocusManager::UnregisterAccelerators(ui::AcceleratorTarget* target) { |
| accelerator_manager_.UnregisterAll(target); |
| } |
| |
| bool FocusManager::ProcessAccelerator(const ui::Accelerator& accelerator) { |
| if (accelerator_manager_.Process(accelerator)) |
| return true; |
| return delegate_ && delegate_->ProcessAccelerator(accelerator); |
| } |
| |
| bool FocusManager::HasPriorityHandler( |
| const ui::Accelerator& accelerator) const { |
| return accelerator_manager_.HasPriorityHandler(accelerator); |
| } |
| |
| // static |
| bool FocusManager::IsTabTraversalKeyEvent(const ui::KeyEvent& key_event) { |
| return key_event.key_code() == ui::VKEY_TAB && |
| (!key_event.IsControlDown() && !key_event.IsAltDown()); |
| } |
| |
| void FocusManager::ViewRemoved(View* removed) { |
| // If the view being removed contains (or is) the focused view, |
| // clear the focus. However, it's not safe to call ClearFocus() |
| // (and in turn ClearNativeFocus()) here because ViewRemoved() can |
| // be called while the top level widget is being destroyed. |
| DCHECK(removed); |
| if (removed->Contains(focused_view_)) |
| SetFocusedView(nullptr); |
| } |
| |
| void FocusManager::AddFocusChangeListener(FocusChangeListener* listener) { |
| focus_change_listeners_.AddObserver(listener); |
| } |
| |
| void FocusManager::RemoveFocusChangeListener(FocusChangeListener* listener) { |
| focus_change_listeners_.RemoveObserver(listener); |
| } |
| |
| bool FocusManager::ProcessArrowKeyTraversal(const ui::KeyEvent& event) { |
| if (event.IsShiftDown() || event.IsControlDown() || event.IsAltDown()) |
| return false; |
| |
| const ui::KeyboardCode key = event.key_code(); |
| if (key != ui::VKEY_UP && key != ui::VKEY_DOWN && key != ui::VKEY_LEFT && |
| key != ui::VKEY_RIGHT) { |
| return false; |
| } |
| |
| const ui::KeyboardCode reverse = |
| base::i18n::IsRTL() ? ui::VKEY_RIGHT : ui::VKEY_LEFT; |
| AdvanceFocus(key == reverse || key == ui::VKEY_UP); |
| return true; |
| } |
| |
| bool FocusManager::IsFocusable(View* view) const { |
| DCHECK(view); |
| |
| // |keyboard_accessible_| is only used on Mac. |
| #if defined(OS_MACOSX) |
| return keyboard_accessible_ ? view->IsAccessibilityFocusable() |
| : view->IsFocusable(); |
| #else |
| return view->IsAccessibilityFocusable(); |
| #endif |
| } |
| |
| void FocusManager::OnViewIsDeleting(View* view) { |
| // Typically ViewRemoved() is called and all the cleanup happens there. With |
| // child widgets it's possible to change the parent out from under the Widget |
| // such that ViewRemoved() is never called. |
| CHECK_EQ(view, focused_view_); |
| SetFocusedView(nullptr); |
| } |
| |
| } // namespace views |