blob: d30f3454b0b4d66d70be5d0f2f2350c993aec885 [file] [log] [blame]
/*
* Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
* Copyright (C) 2008 Nuanti Ltd.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/page/focus_controller.h"
#include <limits>
#include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
#include "third_party/blink/renderer/core/dom/container_node.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/element_traversal.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/dom/range.h"
#include "third_party/blink/renderer/core/dom/shadow_root.h"
#include "third_party/blink/renderer/core/editing/editing_utilities.h" // For firstPositionInOrBeforeNode
#include "third_party/blink/renderer/core/editing/frame_selection.h"
#include "third_party/blink/renderer/core/editing/ime/input_method_controller.h"
#include "third_party/blink/renderer/core/frame/frame_client.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/remote_frame.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
#include "third_party/blink/renderer/core/html/forms/text_control_element.h"
#include "third_party/blink/renderer/core/html/html_plugin_element.h"
#include "third_party/blink/renderer/core/html/html_slot_element.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/input/event_handler.h"
#include "third_party/blink/renderer/core/layout/hit_test_result.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/focus_changed_observer.h"
#include "third_party/blink/renderer/core/page/frame_tree.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/page/slot_scoped_traversal.h"
#include "third_party/blink/renderer/core/page/spatial_navigation.h"
namespace blink {
namespace {
// This class defines the navigation order.
class FocusNavigation : public GarbageCollected<FocusNavigation> {
public:
FocusNavigation(ContainerNode& root, FocusController::OwnerMap& owner_map)
: root_(&root), owner_map_(owner_map) {}
FocusNavigation(ContainerNode& root,
HTMLSlotElement& slot,
FocusController::OwnerMap& owner_map)
: root_(&root), slot_(&slot), owner_map_(owner_map) {}
const Element* Next(const Element& current) {
Element* next = ElementTraversal::Next(current, root_);
while (next && !IsOwnedByRoot(*next))
next = ElementTraversal::Next(*next, root_);
return next;
}
const Element* Previous(const Element& current) {
Element* previous = ElementTraversal::Previous(current, root_);
if (previous == root_)
return nullptr;
while (previous && !IsOwnedByRoot(*previous))
previous = ElementTraversal::Previous(*previous, root_);
return previous;
}
const Element* First() {
Element* first = ElementTraversal::FirstChild(*root_);
while (first && !IsOwnedByRoot(*first))
first = ElementTraversal::Next(*first, root_);
return first;
}
const Element* Last() {
Element* last = ElementTraversal::LastWithin(*root_);
while (last && !IsOwnedByRoot(*last))
last = ElementTraversal::Previous(*last, root_);
return last;
}
Element* Owner() {
if (slot_)
return slot_;
return FindOwner(*root_);
}
void Trace(blink::Visitor* visitor) {
visitor->Trace(root_);
visitor->Trace(slot_);
}
private:
Element* TreeOwner(ContainerNode* node) {
if (ShadowRoot* shadow_root = ToShadowRootOrNull(node))
return &shadow_root->host();
// FIXME: Figure out the right thing for OOPI here.
if (Frame* frame = node->GetDocument().GetFrame())
return frame->DeprecatedLocalOwner();
return nullptr;
}
// Owner is the slot node for slot scope and slot fallback contents scope, the
// shadow host for shadow tree scope and the iframe node for frame scope.
Element* FindOwner(ContainerNode& node) {
auto result = owner_map_.find(&node);
if (result != owner_map_.end())
return result->value;
// Fallback contents owner is set to the nearest ancestor slot node even if
// the slot node have assigned nodes.
Element* owner = nullptr;
if (node.AssignedSlot())
owner = node.AssignedSlot();
else if (IsHTMLSlotElement(node.parentNode()))
owner = node.ParentOrShadowHostElement();
else if (&node == node.ContainingTreeScope().RootNode())
owner = TreeOwner(&node);
else if (node.parentNode())
owner = FindOwner(*node.parentNode());
owner_map_.insert(&node, owner);
return owner;
}
bool IsOwnedByRoot(ContainerNode& node) { return FindOwner(node) == Owner(); }
Member<ContainerNode> root_;
Member<HTMLSlotElement> slot_;
FocusController::OwnerMap& owner_map_;
};
class ScopedFocusNavigation {
STACK_ALLOCATED();
public:
// Searches through the given tree scope, starting from start element, for
// the next/previous selectable element that comes after/before start element.
// The order followed is as specified in the HTML spec[1], which is elements
// with tab indexes first (from lowest to highest), and then elements without
// tab indexes (in document order). The search algorithm also conforms the
// Shadow DOM spec[2], which inserts sequence in a shadow tree into its host.
//
// @param start The element from which to start searching. The element after
// this will be focused. May be null.
// @return The focus element that comes after/before start element.
//
// [1]
// https://html.spec.whatwg.org/multipage/interaction.html#sequential-focus-navigation
// [2] https://w3c.github.io/webcomponents/spec/shadow/#focus-navigation
Element* FindFocusableElement(WebFocusType type) {
return (type == kWebFocusTypeForward) ? NextFocusableElement()
: PreviousFocusableElement();
}
Element* CurrentElement() const {
return const_cast<Element*>(current_.Get());
}
Element* Owner() const;
static ScopedFocusNavigation CreateFor(const Element&,
FocusController::OwnerMap&);
static ScopedFocusNavigation CreateForDocument(Document&,
FocusController::OwnerMap&);
static ScopedFocusNavigation OwnedByNonFocusableFocusScopeOwner(
Element&,
FocusController::OwnerMap&);
static ScopedFocusNavigation OwnedByShadowHost(const Element&,
FocusController::OwnerMap&);
static ScopedFocusNavigation OwnedByHTMLSlotElement(
const HTMLSlotElement&,
FocusController::OwnerMap&);
static ScopedFocusNavigation OwnedByIFrame(const HTMLFrameOwnerElement&,
FocusController::OwnerMap&);
static HTMLSlotElement* FindFallbackScopeOwnerSlot(const Element&);
static bool IsSlotFallbackScoped(const Element&);
static bool IsSlotFallbackScopedForThisSlot(const HTMLSlotElement&,
const Element&);
private:
ScopedFocusNavigation(ContainerNode& scoping_root_node,
const Element* current,
FocusController::OwnerMap&);
Element* FindElementWithExactTabIndex(int tab_index, WebFocusType);
Element* NextElementWithGreaterTabIndex(int tab_index);
Element* PreviousElementWithLowerTabIndex(int tab_index);
Element* NextFocusableElement();
Element* PreviousFocusableElement();
void SetCurrentElement(const Element* element) { current_ = element; }
void MoveToNext();
void MoveToPrevious();
void MoveToFirst();
void MoveToLast();
Member<const Element> current_;
Member<FocusNavigation> navigation_;
};
ScopedFocusNavigation::ScopedFocusNavigation(
ContainerNode& scoping_root_node,
const Element* current,
FocusController::OwnerMap& owner_map)
: current_(current) {
if (HTMLSlotElement* slot = ToHTMLSlotElementOrNull(scoping_root_node)) {
if (slot->AssignedNodes().IsEmpty()) {
navigation_ = new FocusNavigation(scoping_root_node, *slot, owner_map);
} else {
// Here, slot->AssignedNodes() are non null, so the slot must be inside
// the shadow tree.
DCHECK(scoping_root_node.ContainingShadowRoot());
navigation_ =
new FocusNavigation(scoping_root_node.ContainingShadowRoot()->host(),
ToHTMLSlotElement(scoping_root_node), owner_map);
}
} else {
navigation_ = new FocusNavigation(scoping_root_node, owner_map);
}
DCHECK(navigation_);
}
void ScopedFocusNavigation::MoveToNext() {
DCHECK(CurrentElement());
SetCurrentElement(navigation_->Next(*CurrentElement()));
}
void ScopedFocusNavigation::MoveToPrevious() {
DCHECK(CurrentElement());
SetCurrentElement(navigation_->Previous(*CurrentElement()));
}
void ScopedFocusNavigation::MoveToFirst() {
SetCurrentElement(navigation_->First());
}
void ScopedFocusNavigation::MoveToLast() {
SetCurrentElement(navigation_->Last());
}
Element* ScopedFocusNavigation::Owner() const {
return navigation_->Owner();
}
ScopedFocusNavigation ScopedFocusNavigation::CreateFor(
const Element& current,
FocusController::OwnerMap& owner_map) {
if (HTMLSlotElement* slot = SlotScopedTraversal::FindScopeOwnerSlot(current))
return ScopedFocusNavigation(*slot, &current, owner_map);
if (HTMLSlotElement* slot =
ScopedFocusNavigation::FindFallbackScopeOwnerSlot(current))
return ScopedFocusNavigation(*slot, &current, owner_map);
return ScopedFocusNavigation(current.ContainingTreeScope().RootNode(),
&current, owner_map);
}
ScopedFocusNavigation ScopedFocusNavigation::CreateForDocument(
Document& document,
FocusController::OwnerMap& owner_map) {
return ScopedFocusNavigation(document, nullptr, owner_map);
}
ScopedFocusNavigation ScopedFocusNavigation::OwnedByNonFocusableFocusScopeOwner(
Element& element,
FocusController::OwnerMap& owner_map) {
if (IsShadowHost(element))
return ScopedFocusNavigation::OwnedByShadowHost(element, owner_map);
return ScopedFocusNavigation::OwnedByHTMLSlotElement(
ToHTMLSlotElement(element), owner_map);
}
ScopedFocusNavigation ScopedFocusNavigation::OwnedByShadowHost(
const Element& element,
FocusController::OwnerMap& owner_map) {
DCHECK(IsShadowHost(element));
return ScopedFocusNavigation(*element.GetShadowRoot(), nullptr, owner_map);
}
ScopedFocusNavigation ScopedFocusNavigation::OwnedByIFrame(
const HTMLFrameOwnerElement& frame,
FocusController::OwnerMap& owner_map) {
DCHECK(frame.ContentFrame());
DCHECK(frame.ContentFrame()->IsLocalFrame());
ToLocalFrame(frame.ContentFrame())
->GetDocument()
->UpdateDistributionForLegacyDistributedNodes();
return ScopedFocusNavigation(
*ToLocalFrame(frame.ContentFrame())->GetDocument(), nullptr, owner_map);
}
ScopedFocusNavigation ScopedFocusNavigation::OwnedByHTMLSlotElement(
const HTMLSlotElement& element,
FocusController::OwnerMap& owner_map) {
HTMLSlotElement& slot = const_cast<HTMLSlotElement&>(element);
return ScopedFocusNavigation(slot, nullptr, owner_map);
}
HTMLSlotElement* ScopedFocusNavigation::FindFallbackScopeOwnerSlot(
const Element& element) {
Element* parent = const_cast<Element*>(element.parentElement());
while (parent) {
if (auto* slot = ToHTMLSlotElementOrNull(parent))
return slot->AssignedNodes().IsEmpty() ? slot : nullptr;
parent = parent->parentElement();
}
return nullptr;
}
bool ScopedFocusNavigation::IsSlotFallbackScoped(const Element& element) {
return ScopedFocusNavigation::FindFallbackScopeOwnerSlot(element);
}
bool ScopedFocusNavigation::IsSlotFallbackScopedForThisSlot(
const HTMLSlotElement& slot,
const Element& current) {
Element* parent = current.parentElement();
while (parent) {
if (IsHTMLSlotElement(parent) &&
ToHTMLSlotElement(parent)->AssignedNodes().IsEmpty()) {
return !SlotScopedTraversal::IsSlotScoped(current) &&
ToHTMLSlotElement(parent) == slot;
}
parent = parent->parentElement();
}
return false;
}
inline void DispatchBlurEvent(const Document& document,
Element& focused_element) {
focused_element.DispatchBlurEvent(nullptr, kWebFocusTypePage);
if (focused_element == document.FocusedElement()) {
focused_element.DispatchFocusOutEvent(event_type_names::kFocusout, nullptr);
if (focused_element == document.FocusedElement())
focused_element.DispatchFocusOutEvent(event_type_names::kDOMFocusOut,
nullptr);
}
}
inline void DispatchFocusEvent(const Document& document,
Element& focused_element) {
focused_element.DispatchFocusEvent(nullptr, kWebFocusTypePage);
if (focused_element == document.FocusedElement()) {
focused_element.DispatchFocusInEvent(event_type_names::kFocusin, nullptr,
kWebFocusTypePage);
if (focused_element == document.FocusedElement()) {
focused_element.DispatchFocusInEvent(event_type_names::kDOMFocusIn,
nullptr, kWebFocusTypePage);
}
}
}
inline void DispatchEventsOnWindowAndFocusedElement(Document* document,
bool focused) {
DCHECK(document);
// If we have a focused element we should dispatch blur on it before we blur
// the window. If we have a focused element we should dispatch focus on it
// after we focus the window. https://bugs.webkit.org/show_bug.cgi?id=27105
// Do not fire events while modal dialogs are up. See
// https://bugs.webkit.org/show_bug.cgi?id=33962
if (Page* page = document->GetPage()) {
if (page->Paused())
return;
}
if (!focused && document->FocusedElement()) {
Element* focused_element = document->FocusedElement();
// Use focus_type kWebFocusTypePage, same as used in DispatchBlurEvent.
focused_element->SetFocused(false, kWebFocusTypePage);
focused_element->SetHasFocusWithinUpToAncestor(false, nullptr);
DispatchBlurEvent(*document, *focused_element);
}
if (LocalDOMWindow* window = document->domWindow()) {
window->DispatchEvent(*Event::Create(focused ? event_type_names::kFocus
: event_type_names::kBlur));
}
if (focused && document->FocusedElement()) {
Element* focused_element(document->FocusedElement());
// Use focus_type kWebFocusTypePage, same as used in DispatchFocusEvent.
focused_element->SetFocused(true, kWebFocusTypePage);
focused_element->SetHasFocusWithinUpToAncestor(true, nullptr);
DispatchFocusEvent(*document, *focused_element);
}
}
inline bool HasCustomFocusLogic(const Element& element) {
return element.IsHTMLElement() &&
ToHTMLElement(element).HasCustomFocusLogic();
}
inline bool IsShadowHostWithoutCustomFocusLogic(const Element& element) {
return IsShadowHost(element) && !HasCustomFocusLogic(element);
}
inline bool IsNonKeyboardFocusableShadowHost(const Element& element) {
return IsShadowHostWithoutCustomFocusLogic(element) &&
!(element.ShadowRootIfV1() ? element.IsFocusable()
: element.IsKeyboardFocusable());
}
inline bool IsKeyboardFocusableShadowHost(const Element& element) {
return IsShadowHostWithoutCustomFocusLogic(element) &&
element.IsKeyboardFocusable();
}
inline bool IsNonFocusableFocusScopeOwner(Element& element) {
return IsNonKeyboardFocusableShadowHost(element) ||
IsHTMLSlotElement(element);
}
inline bool IsShadowHostDelegatesFocus(const Element& element) {
return element.AuthorShadowRoot() &&
element.AuthorShadowRoot()->delegatesFocus();
}
inline int AdjustedTabIndex(Element& element) {
return IsNonKeyboardFocusableShadowHost(element) ? 0 : element.tabIndex();
}
inline bool ShouldVisit(Element& element) {
return element.IsKeyboardFocusable() ||
IsNonFocusableFocusScopeOwner(element);
}
Element* ScopedFocusNavigation::FindElementWithExactTabIndex(
int tab_index,
WebFocusType type) {
// Search is inclusive of start
for (; CurrentElement();
type == kWebFocusTypeForward ? MoveToNext() : MoveToPrevious()) {
Element* current = CurrentElement();
if (ShouldVisit(*current) && AdjustedTabIndex(*current) == tab_index)
return current;
}
return nullptr;
}
Element* ScopedFocusNavigation::NextElementWithGreaterTabIndex(int tab_index) {
// Search is inclusive of start
int winning_tab_index = std::numeric_limits<int>::max();
Element* winner = nullptr;
for (; CurrentElement(); MoveToNext()) {
Element* current = CurrentElement();
int current_tab_index = AdjustedTabIndex(*current);
if (ShouldVisit(*current) && current_tab_index > tab_index) {
if (!winner || current_tab_index < winning_tab_index) {
winner = current;
winning_tab_index = current_tab_index;
}
}
}
SetCurrentElement(winner);
return winner;
}
Element* ScopedFocusNavigation::PreviousElementWithLowerTabIndex(
int tab_index) {
// Search is inclusive of start
int winning_tab_index = 0;
Element* winner = nullptr;
for (; CurrentElement(); MoveToPrevious()) {
Element* current = CurrentElement();
int current_tab_index = AdjustedTabIndex(*current);
if (ShouldVisit(*current) && current_tab_index < tab_index &&
current_tab_index > winning_tab_index) {
winner = current;
winning_tab_index = current_tab_index;
}
}
SetCurrentElement(winner);
return winner;
}
Element* ScopedFocusNavigation::NextFocusableElement() {
Element* current = CurrentElement();
if (current) {
int tab_index = AdjustedTabIndex(*current);
// If an element is excluded from the normal tabbing cycle, the next
// focusable element is determined by tree order.
if (tab_index < 0) {
for (MoveToNext(); CurrentElement(); MoveToNext()) {
current = CurrentElement();
if (ShouldVisit(*current) && AdjustedTabIndex(*current) >= 0)
return current;
}
} else {
// First try to find an element with the same tabindex as start that comes
// after start in the scope.
MoveToNext();
if (Element* winner =
FindElementWithExactTabIndex(tab_index, kWebFocusTypeForward))
return winner;
}
if (!tab_index) {
// We've reached the last element in the document with a tabindex of 0.
// This is the end of the tabbing order.
return nullptr;
}
}
// Look for the first element in the scope that:
// 1) has the lowest tabindex that is higher than start's tabindex (or 0, if
// start is null), and
// 2) comes first in the scope, if there's a tie.
MoveToFirst();
if (Element* winner = NextElementWithGreaterTabIndex(
current ? AdjustedTabIndex(*current) : 0)) {
return winner;
}
// There are no elements with a tabindex greater than start's tabindex,
// so find the first element with a tabindex of 0.
MoveToFirst();
return FindElementWithExactTabIndex(0, kWebFocusTypeForward);
}
Element* ScopedFocusNavigation::PreviousFocusableElement() {
// First try to find the last element in the scope that comes before start and
// has the same tabindex as start. If start is null, find the last element in
// the scope with a tabindex of 0.
int tab_index;
Element* current = CurrentElement();
if (current) {
MoveToPrevious();
tab_index = AdjustedTabIndex(*current);
} else {
MoveToLast();
tab_index = 0;
}
// However, if an element is excluded from the normal tabbing cycle, the
// previous focusable element is determined by tree order
if (tab_index < 0) {
for (; CurrentElement(); MoveToPrevious()) {
current = CurrentElement();
if (ShouldVisit(*current) && AdjustedTabIndex(*current) >= 0)
return current;
}
} else {
if (Element* winner =
FindElementWithExactTabIndex(tab_index, kWebFocusTypeBackward))
return winner;
}
// There are no elements before start with the same tabindex as start, so look
// for an element that:
// 1) has the highest non-zero tabindex (that is less than start's tabindex),
// and
// 2) comes last in the scope, if there's a tie.
tab_index =
(current && tab_index) ? tab_index : std::numeric_limits<int>::max();
MoveToLast();
return PreviousElementWithLowerTabIndex(tab_index);
}
Element* FindFocusableElementRecursivelyForward(
ScopedFocusNavigation& scope,
FocusController::OwnerMap& owner_map) {
// Starting element is exclusive.
while (Element* found = scope.FindFocusableElement(kWebFocusTypeForward)) {
if (IsShadowHostDelegatesFocus(*found)) {
// If tabindex is positive, find focusable element inside its shadow tree.
if (found->tabIndex() >= 0 &&
IsShadowHostWithoutCustomFocusLogic(*found)) {
ScopedFocusNavigation inner_scope =
ScopedFocusNavigation::OwnedByShadowHost(*found, owner_map);
if (Element* found_in_inner_focus_scope =
FindFocusableElementRecursivelyForward(inner_scope, owner_map))
return found_in_inner_focus_scope;
}
// Skip to the next element in the same scope.
continue;
}
if (!IsNonFocusableFocusScopeOwner(*found))
return found;
// Now |found| is on a non focusable scope owner (either shadow host or
// <shadow> or slot) Find inside the inward scope and return it if found.
// Otherwise continue searching in the same scope.
ScopedFocusNavigation inner_scope =
ScopedFocusNavigation::OwnedByNonFocusableFocusScopeOwner(*found,
owner_map);
if (Element* found_in_inner_focus_scope =
FindFocusableElementRecursivelyForward(inner_scope, owner_map))
return found_in_inner_focus_scope;
}
return nullptr;
}
Element* FindFocusableElementRecursivelyBackward(
ScopedFocusNavigation& scope,
FocusController::OwnerMap& owner_map) {
// Starting element is exclusive.
while (Element* found = scope.FindFocusableElement(kWebFocusTypeBackward)) {
// Now |found| is on a focusable shadow host.
// Find inside shadow backwards. If any focusable element is found, return
// it, otherwise return the host itself.
if (IsKeyboardFocusableShadowHost(*found)) {
ScopedFocusNavigation inner_scope =
ScopedFocusNavigation::OwnedByShadowHost(*found, owner_map);
Element* found_in_inner_focus_scope =
FindFocusableElementRecursivelyBackward(inner_scope, owner_map);
if (found_in_inner_focus_scope)
return found_in_inner_focus_scope;
if (IsShadowHostDelegatesFocus(*found))
continue;
return found;
}
// If delegatesFocus is true and tabindex is negative, skip the whole shadow
// tree under the shadow host.
if (IsShadowHostDelegatesFocus(*found) && found->tabIndex() < 0)
continue;
// Now |found| is on a non focusable scope owner (a shadow host, a <shadow>
// or a slot). Find focusable element in descendant scope. If not found,
// find the next focusable element within the current scope.
if (IsNonFocusableFocusScopeOwner(*found)) {
ScopedFocusNavigation inner_scope =
ScopedFocusNavigation::OwnedByNonFocusableFocusScopeOwner(*found,
owner_map);
if (Element* found_in_inner_focus_scope =
FindFocusableElementRecursivelyBackward(inner_scope, owner_map))
return found_in_inner_focus_scope;
continue;
}
if (!IsShadowHostDelegatesFocus(*found))
return found;
}
return nullptr;
}
Element* FindFocusableElementRecursively(WebFocusType type,
ScopedFocusNavigation& scope,
FocusController::OwnerMap& owner_map) {
return (type == kWebFocusTypeForward)
? FindFocusableElementRecursivelyForward(scope, owner_map)
: FindFocusableElementRecursivelyBackward(scope, owner_map);
}
Element* FindFocusableElementDescendingDownIntoFrameDocument(
WebFocusType type,
Element* element,
FocusController::OwnerMap& owner_map) {
// The element we found might be a HTMLFrameOwnerElement, so descend down the
// tree until we find either:
// 1) a focusable element, or
// 2) the deepest-nested HTMLFrameOwnerElement.
while (element && element->IsFrameOwnerElement()) {
HTMLFrameOwnerElement& owner = ToHTMLFrameOwnerElement(*element);
if (!owner.ContentFrame() || !owner.ContentFrame()->IsLocalFrame())
break;
ToLocalFrame(owner.ContentFrame())
->GetDocument()
->UpdateStyleAndLayoutIgnorePendingStylesheets();
ScopedFocusNavigation scope =
ScopedFocusNavigation::OwnedByIFrame(owner, owner_map);
Element* found_element =
FindFocusableElementRecursively(type, scope, owner_map);
if (!found_element)
break;
DCHECK_NE(element, found_element);
element = found_element;
}
return element;
}
Element* FindFocusableElementAcrossFocusScopesForward(
ScopedFocusNavigation& scope,
FocusController::OwnerMap& owner_map) {
const Element* current = scope.CurrentElement();
Element* found;
if (current && IsShadowHostWithoutCustomFocusLogic(*current)) {
ScopedFocusNavigation inner_scope =
ScopedFocusNavigation::OwnedByShadowHost(*current, owner_map);
Element* found_in_inner_focus_scope =
FindFocusableElementRecursivelyForward(inner_scope, owner_map);
found = found_in_inner_focus_scope
? found_in_inner_focus_scope
: FindFocusableElementRecursivelyForward(scope, owner_map);
} else {
found = FindFocusableElementRecursivelyForward(scope, owner_map);
}
// If there's no focusable element to advance to, move up the focus scopes
// until we find one.
ScopedFocusNavigation current_scope = scope;
while (!found) {
Element* owner = current_scope.Owner();
if (!owner)
break;
current_scope = ScopedFocusNavigation::CreateFor(*owner, owner_map);
found = FindFocusableElementRecursivelyForward(current_scope, owner_map);
}
return FindFocusableElementDescendingDownIntoFrameDocument(
kWebFocusTypeForward, found, owner_map);
}
Element* FindFocusableElementAcrossFocusScopesBackward(
ScopedFocusNavigation& scope,
FocusController::OwnerMap& owner_map) {
Element* found = FindFocusableElementRecursivelyBackward(scope, owner_map);
// If there's no focusable element to advance to, move up the focus scopes
// until we find one.
ScopedFocusNavigation current_scope = scope;
while (!found) {
Element* owner = current_scope.Owner();
if (!owner)
break;
current_scope = ScopedFocusNavigation::CreateFor(*owner, owner_map);
if (IsKeyboardFocusableShadowHost(*owner) &&
!IsShadowHostDelegatesFocus(*owner)) {
found = owner;
break;
}
found = FindFocusableElementRecursivelyBackward(current_scope, owner_map);
}
return FindFocusableElementDescendingDownIntoFrameDocument(
kWebFocusTypeBackward, found, owner_map);
}
Element* FindFocusableElementAcrossFocusScopes(
WebFocusType type,
ScopedFocusNavigation& scope,
FocusController::OwnerMap& owner_map) {
return (type == kWebFocusTypeForward)
? FindFocusableElementAcrossFocusScopesForward(scope, owner_map)
: FindFocusableElementAcrossFocusScopesBackward(scope, owner_map);
}
} // anonymous namespace
FocusController::FocusController(Page* page)
: page_(page),
is_active_(false),
is_focused_(false),
is_changing_focused_frame_(false),
is_emulating_focus_(false) {}
FocusController* FocusController::Create(Page* page) {
return new FocusController(page);
}
void FocusController::SetFocusedFrame(Frame* frame, bool notify_embedder) {
DCHECK(!frame || frame->GetPage() == page_);
if (focused_frame_ == frame || (is_changing_focused_frame_ && frame))
return;
is_changing_focused_frame_ = true;
LocalFrame* old_frame = (focused_frame_ && focused_frame_->IsLocalFrame())
? ToLocalFrame(focused_frame_.Get())
: nullptr;
LocalFrame* new_frame =
(frame && frame->IsLocalFrame()) ? ToLocalFrame(frame) : nullptr;
focused_frame_ = frame;
// Now that the frame is updated, fire events and update the selection focused
// states of both frames.
if (old_frame && old_frame->View()) {
old_frame->Selection().SetFrameIsFocused(false);
old_frame->DomWindow()->DispatchEvent(
*Event::Create(event_type_names::kBlur));
}
if (new_frame && new_frame->View() && IsFocused()) {
new_frame->Selection().SetFrameIsFocused(true);
new_frame->DomWindow()->DispatchEvent(
*Event::Create(event_type_names::kFocus));
}
is_changing_focused_frame_ = false;
// Checking client() is necessary, as the frame might have been detached as
// part of dispatching the focus event above. See https://crbug.com/570874.
if (focused_frame_ && focused_frame_->Client() && notify_embedder)
focused_frame_->Client()->FrameFocused();
NotifyFocusChangedObservers();
}
void FocusController::FocusDocumentView(Frame* frame, bool notify_embedder) {
DCHECK(!frame || frame->GetPage() == page_);
if (focused_frame_ == frame)
return;
LocalFrame* focused_frame = (focused_frame_ && focused_frame_->IsLocalFrame())
? ToLocalFrame(focused_frame_.Get())
: nullptr;
if (focused_frame && focused_frame->View()) {
Document* document = focused_frame->GetDocument();
Element* focused_element = document ? document->FocusedElement() : nullptr;
if (focused_element)
document->ClearFocusedElement();
}
LocalFrame* new_focused_frame =
(frame && frame->IsLocalFrame()) ? ToLocalFrame(frame) : nullptr;
if (new_focused_frame && new_focused_frame->View()) {
Document* document = new_focused_frame->GetDocument();
Element* focused_element = document ? document->FocusedElement() : nullptr;
if (focused_element)
DispatchFocusEvent(*document, *focused_element);
}
// dispatchBlurEvent/dispatchFocusEvent could have changed the focused frame,
// or detached the frame.
if (new_focused_frame && !new_focused_frame->View())
return;
SetFocusedFrame(frame, notify_embedder);
}
LocalFrame* FocusController::FocusedFrame() const {
// All callsites only care about *local* focused frames.
if (focused_frame_ && focused_frame_->IsRemoteFrame())
return nullptr;
return ToLocalFrame(focused_frame_.Get());
}
Frame* FocusController::FocusedOrMainFrame() const {
if (LocalFrame* frame = FocusedFrame())
return frame;
// TODO(dcheng, alexmos): https://crbug.com/820786: This is a temporary hack
// to ensure that we return a LocalFrame, even when the mainFrame is remote.
// FocusController needs to be refactored to deal with RemoteFrames
// cross-process focus transfers.
for (Frame* frame = &page_->MainFrame()->Tree().Top(); frame;
frame = frame->Tree().TraverseNext()) {
if (frame->IsLocalFrame() && ToLocalFrame(frame)->IsLocalRoot())
return frame;
}
return page_->MainFrame();
}
void FocusController::FrameDetached(Frame* detached_frame) {
if (detached_frame == focused_frame_)
SetFocusedFrame(nullptr);
}
HTMLFrameOwnerElement* FocusController::FocusedFrameOwnerElement(
LocalFrame& current_frame) const {
Frame* focused_frame = focused_frame_.Get();
for (; focused_frame; focused_frame = focused_frame->Tree().Parent()) {
if (focused_frame->Tree().Parent() == &current_frame) {
DCHECK(focused_frame->Owner()->IsLocal());
return focused_frame->DeprecatedLocalOwner();
}
}
return nullptr;
}
bool FocusController::IsDocumentFocused(const Document& document) const {
if (!IsActive() || !IsFocused())
return false;
return focused_frame_ &&
focused_frame_->Tree().IsDescendantOf(document.GetFrame());
}
void FocusController::FocusHasChanged() {
bool focused = IsFocused();
if (!focused && FocusedOrMainFrame()->IsLocalFrame())
ToLocalFrame(FocusedOrMainFrame())->GetEventHandler().StopAutoscroll();
// Do not set a focused frame when being unfocused. This might reset
// m_isFocused to true.
if (!focused_frame_ && focused)
SetFocusedFrame(page_->MainFrame());
// setFocusedFrame above might reject to update m_focusedFrame, or
// m_focusedFrame might be changed by blur/focus event handlers.
if (focused_frame_ && focused_frame_->IsLocalFrame() &&
ToLocalFrame(focused_frame_.Get())->View()) {
ToLocalFrame(focused_frame_.Get())->Selection().SetFrameIsFocused(focused);
DispatchEventsOnWindowAndFocusedElement(
ToLocalFrame(focused_frame_.Get())->GetDocument(), focused);
}
NotifyFocusChangedObservers();
}
void FocusController::SetFocused(bool focused) {
if (is_focused_ == focused)
return;
is_focused_ = focused;
if (!is_emulating_focus_)
FocusHasChanged();
}
void FocusController::SetFocusEmulationEnabled(bool emulate_focus) {
if (emulate_focus == is_emulating_focus_)
return;
bool active = IsActive();
bool focused = IsFocused();
is_emulating_focus_ = emulate_focus;
if (active != IsActive())
ActiveHasChanged();
if (focused != IsFocused())
FocusHasChanged();
}
bool FocusController::SetInitialFocus(WebFocusType type) {
bool did_advance_focus = AdvanceFocus(type, true);
// If focus is being set initially, accessibility needs to be informed that
// system focus has moved into the web area again, even if focus did not
// change within WebCore. PostNotification is called instead of
// handleFocusedUIElementChanged, because this will send the notification even
// if the element is the same.
if (FocusedOrMainFrame()->IsLocalFrame()) {
Document* document = ToLocalFrame(FocusedOrMainFrame())->GetDocument();
if (AXObjectCache* cache = document->ExistingAXObjectCache())
cache->HandleInitialFocus();
}
return did_advance_focus;
}
bool FocusController::AdvanceFocus(
WebFocusType type,
bool initial_focus,
InputDeviceCapabilities* source_capabilities) {
switch (type) {
case kWebFocusTypeForward:
case kWebFocusTypeBackward: {
// We should never hit this when a RemoteFrame is focused, since the key
// event that initiated focus advancement should've been routed to that
// frame's process from the beginning.
LocalFrame* starting_frame = ToLocalFrame(FocusedOrMainFrame());
return AdvanceFocusInDocumentOrder(starting_frame, nullptr, type,
initial_focus, source_capabilities);
}
case kWebFocusTypeLeft:
case kWebFocusTypeRight:
case kWebFocusTypeUp:
case kWebFocusTypeDown:
return AdvanceFocusDirectionally(type);
default:
NOTREACHED();
}
return false;
}
bool FocusController::AdvanceFocusAcrossFrames(
WebFocusType type,
RemoteFrame* from,
LocalFrame* to,
InputDeviceCapabilities* source_capabilities) {
// If we are shifting focus from a child frame to its parent, the
// child frame has no more focusable elements, and we should continue
// looking for focusable elements in the parent, starting from the <iframe>
// element of the child frame.
Element* start = nullptr;
if (from->Tree().Parent() == to) {
DCHECK(from->Owner()->IsLocal());
start = ToHTMLFrameOwnerElement(from->Owner());
}
// If we're coming from a parent frame, we need to restart from the first or
// last focusable element.
bool initial_focus = to->Tree().Parent() == from;
return AdvanceFocusInDocumentOrder(to, start, type, initial_focus,
source_capabilities);
}
#if DCHECK_IS_ON()
inline bool IsNonFocusableShadowHost(const Element& element) {
return IsShadowHostWithoutCustomFocusLogic(element) && !element.IsFocusable();
}
#endif
bool FocusController::AdvanceFocusInDocumentOrder(
LocalFrame* frame,
Element* start,
WebFocusType type,
bool initial_focus,
InputDeviceCapabilities* source_capabilities) {
DCHECK(frame);
Document* document = frame->GetDocument();
document->UpdateDistributionForLegacyDistributedNodes();
OwnerMap owner_map;
Element* current = start;
#if DCHECK_IS_ON()
DCHECK(!current || !IsNonFocusableShadowHost(*current));
#endif
if (!current && !initial_focus)
current = document->SequentialFocusNavigationStartingPoint(type);
document->UpdateStyleAndLayoutIgnorePendingStylesheets();
ScopedFocusNavigation scope =
current ? ScopedFocusNavigation::CreateFor(*current, owner_map)
: ScopedFocusNavigation::CreateForDocument(*document, owner_map);
Element* element =
FindFocusableElementAcrossFocusScopes(type, scope, owner_map);
if (!element) {
// If there's a RemoteFrame on the ancestor chain, we need to continue
// searching for focusable elements there.
if (frame->LocalFrameRoot() != frame->Tree().Top()) {
document->ClearFocusedElement();
document->SetSequentialFocusNavigationStartingPoint(nullptr);
SetFocusedFrame(nullptr);
ToRemoteFrame(frame->LocalFrameRoot().Tree().Parent())
->AdvanceFocus(type, &frame->LocalFrameRoot());
return true;
}
// We didn't find an element to focus, so we should try to pass focus to
// Chrome.
if (!initial_focus && page_->GetChromeClient().CanTakeFocus(type)) {
document->ClearFocusedElement();
document->SetSequentialFocusNavigationStartingPoint(nullptr);
SetFocusedFrame(nullptr);
page_->GetChromeClient().TakeFocus(type);
return true;
}
// Chrome doesn't want focus, so we should wrap focus.
ScopedFocusNavigation scope = ScopedFocusNavigation::CreateForDocument(
*ToLocalFrame(page_->MainFrame())->GetDocument(), owner_map);
element = FindFocusableElementRecursively(type, scope, owner_map);
element = FindFocusableElementDescendingDownIntoFrameDocument(type, element,
owner_map);
if (!element)
return false;
}
if (element == document->FocusedElement()) {
// Focus is either coming from a remote frame or has wrapped around.
if (FocusedFrame() != document->GetFrame()) {
SetFocusedFrame(document->GetFrame());
DispatchFocusEvent(*document, *element);
}
return true;
}
if (element->IsFrameOwnerElement() &&
(!IsHTMLPlugInElement(*element) || !element->IsKeyboardFocusable())) {
// We focus frames rather than frame owners.
// FIXME: We should not focus frames that have no scrollbars, as focusing
// them isn't useful to the user.
HTMLFrameOwnerElement* owner = ToHTMLFrameOwnerElement(element);
if (!owner->ContentFrame())
return false;
document->ClearFocusedElement();
// If ContentFrame is remote, continue the search for focusable elements in
// that frame's process. The target ContentFrame's process will grab focus
// from inside AdvanceFocusInDocumentOrder().
//
// ClearFocusedElement() fires events that might detach the contentFrame,
// hence the need to null-check it again.
if (owner->ContentFrame() && owner->ContentFrame()->IsRemoteFrame())
ToRemoteFrame(owner->ContentFrame())->AdvanceFocus(type, frame);
else
SetFocusedFrame(owner->ContentFrame());
return true;
}
DCHECK(element->IsFocusable());
// FIXME: It would be nice to just be able to call setFocusedElement(element)
// here, but we can't do that because some elements (e.g. HTMLInputElement
// and HTMLTextAreaElement) do extra work in their focus() methods.
Document& new_document = element->GetDocument();
if (&new_document != document) {
// Focus is going away from this document, so clear the focused element.
document->ClearFocusedElement();
document->SetSequentialFocusNavigationStartingPoint(nullptr);
}
SetFocusedFrame(new_document.GetFrame());
element->focus(
FocusParams(SelectionBehaviorOnFocus::kReset, type, source_capabilities));
return true;
}
Element* FocusController::FindFocusableElement(WebFocusType type,
Element& element,
OwnerMap& owner_map) {
// FIXME: No spacial navigation code yet.
DCHECK(type == kWebFocusTypeForward || type == kWebFocusTypeBackward);
ScopedFocusNavigation scope =
ScopedFocusNavigation::CreateFor(element, owner_map);
return FindFocusableElementAcrossFocusScopes(type, scope, owner_map);
}
Element* FocusController::NextFocusableElementInForm(Element* element,
WebFocusType focus_type) {
// TODO(ajith.v) Due to crbug.com/781026 when next/previous element is far
// from current element in terms of tabindex, then it's signalling CPU load.
// Will nvestigate further for a proper solution later.
static const int kFocusTraversalThreshold = 50;
element->GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets();
if (!element->IsHTMLElement())
return nullptr;
if (!element->IsFormControlElement() &&
!ToHTMLElement(element)->isContentEditableForBinding())
return nullptr;
HTMLFormElement* form_owner = nullptr;
if (ToHTMLElement(element)->isContentEditableForBinding())
form_owner = Traversal<HTMLFormElement>::FirstAncestor(*element);
else
form_owner = ToHTMLFormControlElement(element)->formOwner();
if (!form_owner)
return nullptr;
OwnerMap owner_map;
Element* next_element = FindFocusableElement(focus_type, *element, owner_map);
int traversal = 0;
for (; next_element && traversal < kFocusTraversalThreshold;
next_element =
FindFocusableElement(focus_type, *next_element, owner_map),
++traversal) {
if (!next_element->IsHTMLElement())
continue;
if (ToHTMLElement(next_element)->isContentEditableForBinding() &&
next_element->IsDescendantOf(form_owner))
return next_element;
if (!next_element->IsFormControlElement())
continue;
HTMLFormControlElement* form_element =
ToHTMLFormControlElement(next_element);
if (form_element->formOwner() != form_owner ||
form_element->IsDisabledOrReadOnly())
continue;
LayoutObject* layout = next_element->GetLayoutObject();
if (layout && layout->IsTextControl()) {
// TODO(ajith.v) Extend it for select elements, radio buttons and check
// boxes
return next_element;
}
}
return nullptr;
}
Element* FocusController::FindFocusableElementInShadowHost(
const Element& shadow_host) {
DCHECK(shadow_host.AuthorShadowRoot());
OwnerMap owner_map;
ScopedFocusNavigation scope =
ScopedFocusNavigation::OwnedByShadowHost(shadow_host, owner_map);
return FindFocusableElementAcrossFocusScopes(kWebFocusTypeForward, scope,
owner_map);
}
static bool RelinquishesEditingFocus(const Element& element) {
DCHECK(HasEditableStyle(element));
return element.GetDocument().GetFrame() && RootEditableElement(element);
}
bool FocusController::SetFocusedElement(Element* element,
Frame* new_focused_frame) {
return SetFocusedElement(
element, new_focused_frame,
FocusParams(SelectionBehaviorOnFocus::kNone, kWebFocusTypeNone, nullptr));
}
bool FocusController::SetFocusedElement(Element* element,
Frame* new_focused_frame,
const FocusParams& params) {
LocalFrame* old_focused_frame = FocusedFrame();
Document* old_document =
old_focused_frame ? old_focused_frame->GetDocument() : nullptr;
Element* old_focused_element =
old_document ? old_document->FocusedElement() : nullptr;
if (element && old_focused_element == element)
return true;
if (old_focused_element && IsRootEditableElement(*old_focused_element) &&
!RelinquishesEditingFocus(*old_focused_element))
return false;
if (old_focused_frame)
old_focused_frame->GetInputMethodController().WillChangeFocus();
Document* new_document = nullptr;
if (element)
new_document = &element->GetDocument();
else if (new_focused_frame && new_focused_frame->IsLocalFrame())
new_document = ToLocalFrame(new_focused_frame)->GetDocument();
if (new_document && old_document == new_document &&
new_document->FocusedElement() == element)
return true;
if (old_document && old_document != new_document)
old_document->ClearFocusedElement();
if (new_focused_frame && !new_focused_frame->GetPage()) {
SetFocusedFrame(nullptr);
return false;
}
SetFocusedFrame(new_focused_frame);
if (new_document) {
bool successfully_focused =
new_document->SetFocusedElement(element, params);
if (!successfully_focused)
return false;
}
return true;
}
void FocusController::ActiveHasChanged() {
Frame* frame = FocusedOrMainFrame();
if (frame->IsLocalFrame()) {
Document* const document =
ToLocalFrame(frame)->LocalFrameRoot().GetDocument();
DCHECK(document);
if (!document->IsActive())
return;
// Invalidate all custom scrollbars because they support the CSS
// window-active attribute. This should be applied to the entire page so
// we invalidate from the root LocalFrameView instead of just the focused.
if (LocalFrameView* view = document->View())
view->InvalidateAllCustomScrollbarsOnActiveChanged();
ToLocalFrame(frame)->Selection().PageActivationChanged();
}
}
void FocusController::SetActive(bool active) {
if (is_active_ == active)
return;
is_active_ = active;
if (!is_emulating_focus_)
ActiveHasChanged();
}
static void UpdateFocusCandidateIfNeeded(WebFocusType direction,
const FocusCandidate& current,
FocusCandidate& candidate,
FocusCandidate& closest) {
DCHECK(candidate.visible_node->IsElementNode());
DCHECK(candidate.visible_node->GetLayoutObject());
// Ignore iframes that don't have a src attribute
if (FrameOwnerElement(candidate) &&
(!FrameOwnerElement(candidate)->ContentFrame() ||
candidate.rect_in_root_frame.IsEmpty()))
return;
// Ignore off-screen focusables that are not exposed after one "scroll step"
// in the direction.
if (candidate.is_offscreen && candidate.is_offscreen_after_scrolling)
return;
DistanceDataForNode(direction, current, candidate);
if (candidate.distance == MaxDistance())
return;
if (closest.IsNull()) {
closest = candidate;
return;
}
LayoutRect intersection_rect =
Intersection(candidate.rect_in_root_frame, closest.rect_in_root_frame);
if (!intersection_rect.IsEmpty() &&
!AreElementsOnSameLine(closest, candidate) &&
intersection_rect == candidate.rect_in_root_frame) {
// If 2 nodes are intersecting, do hit test to find which node in on top.
LayoutUnit x = intersection_rect.X() + intersection_rect.Width() / 2;
LayoutUnit y = intersection_rect.Y() + intersection_rect.Height() / 2;
if (!candidate.visible_node->GetDocument()
.GetPage()
->MainFrame()
->IsLocalFrame())
return;
HitTestLocation location(IntPoint(x.ToInt(), y.ToInt()));
HitTestResult result =
candidate.visible_node->GetDocument()
.GetPage()
->DeprecatedLocalMainFrame()
->GetEventHandler()
.HitTestResultAtLocation(
location, HitTestRequest::kReadOnly | HitTestRequest::kActive |
HitTestRequest::kIgnoreClipping);
if (candidate.visible_node->contains(result.InnerNode())) {
closest = candidate;
return;
}
if (closest.visible_node->contains(result.InnerNode()))
return;
}
if (candidate.distance < closest.distance)
closest = candidate;
}
void FocusController::FindFocusCandidateInContainer(
Node& container,
const LayoutRect& starting_rect,
WebFocusType direction,
FocusCandidate& closest,
const SkipList& already_checked) {
if (already_checked.Contains(&container))
return;
Element* focused_element =
(FocusedFrame() && FocusedFrame()->GetDocument())
? FocusedFrame()->GetDocument()->FocusedElement()
: nullptr;
Element* element = ElementTraversal::FirstWithin(container);
FocusCandidate current;
current.rect_in_root_frame = starting_rect;
current.focusable_node = focused_element;
current.visible_node = focused_element;
for (; element;
element =
(IsNavigableContainer(element, direction))
? ElementTraversal::NextSkippingChildren(*element, &container)
: ElementTraversal::Next(*element, &container)) {
if (element == focused_element)
continue;
if (!element->IsKeyboardFocusable() &&
!IsNavigableContainer(element, direction))
continue;
if (HasRemoteFrame(element))
continue;
if (already_checked.Contains(element))
continue;
FocusCandidate candidate = FocusCandidate(element, direction);
if (candidate.IsNull())
continue;
candidate.enclosing_scrollable_box = &container;
UpdateFocusCandidateIfNeeded(direction, current, candidate, closest);
}
}
bool FocusController::AdvanceFocusDirectionallyInContainer(
Node* start_container,
const LayoutRect& starting_rect,
WebFocusType direction,
Node* pruned_sub_tree_root) {
if (!start_container)
return false;
HeapVector<Member<Node>> stack;
stack.push_back(start_container);
SkipList already_checked;
if (pruned_sub_tree_root)
already_checked.insert(pruned_sub_tree_root);
while (!stack.IsEmpty()) {
Node* container = stack.back();
FocusCandidate candidate;
FindFocusCandidateInContainer(*container, starting_rect, direction,
candidate, already_checked);
if (candidate.IsNull()) {
// Nothing to focus in this container, scroll if possible.
// NOTE: If no scrolling is performed (i.e. ScrollInDirection returns
// false), the spatial navigation algorithm will skip this container.
if (ScrollInDirection(container, direction))
return true;
stack.pop_back();
continue;
}
if (!IsNavigableContainer(candidate.visible_node, direction)) {
// We found a new focus node, navigate to it.
Element* element = ToElement(candidate.focusable_node);
DCHECK(element);
element->focus(
FocusParams(SelectionBehaviorOnFocus::kReset, direction, nullptr));
return true;
}
// We now dig into a navigable container.
HTMLFrameOwnerElement* frame_element = FrameOwnerElement(candidate);
if (frame_element && frame_element->ContentFrame()->IsLocalFrame()) {
// Navigate into a discovered frame.
ToLocalFrame(frame_element->ContentFrame())
->GetDocument()
->UpdateStyleAndLayoutIgnorePendingStylesheets();
// Mark this |already_checked| so we can skip this subtree in case
// FindFocusCandidateInContainer() returns it again.
already_checked.insert(candidate.visible_node);
stack.push_back(
ToLocalFrame(frame_element->ContentFrame())->GetDocument());
continue;
}
// Search sub-container.
stack.push_back(candidate.visible_node);
}
return ScrollInDirection(start_container, direction);
}
bool FocusController::AdvanceFocusDirectionally(WebFocusType direction) {
// FIXME: Directional focus changes don't yet work with RemoteFrames.
if (!FocusedOrMainFrame()->IsLocalFrame())
return false;
const LocalFrame* current_frame = ToLocalFrame(FocusedOrMainFrame());
DCHECK(current_frame);
Document* focused_document = current_frame->GetDocument();
if (!focused_document)
return false;
Element* focused_element = focused_document->FocusedElement();
Node* container = focused_document;
if (auto* document = DynamicTo<Document>(container))
document->UpdateStyleAndLayoutIgnorePendingStylesheets();
if (focused_element)
container = ScrollableAreaOrDocumentOf(focused_element);
const LayoutRect visible_rect = RootViewport(current_frame);
const LayoutRect start_box =
SearchOrigin(visible_rect, focused_element, direction);
bool consumed = false;
Node* pruned_sub_tree_root = nullptr;
while (container) {
consumed = AdvanceFocusDirectionallyInContainer(
container, start_box, direction, pruned_sub_tree_root);
if (consumed)
break;
// Nothing found in |container| so search the parent container.
pruned_sub_tree_root = container;
container = ScrollableAreaOrDocumentOf(container);
if (auto* document = DynamicTo<Document>(container))
document->UpdateStyleAndLayoutIgnorePendingStylesheets();
}
return consumed;
}
void FocusController::RegisterFocusChangedObserver(
FocusChangedObserver* observer) {
DCHECK(observer);
DCHECK(!focus_changed_observers_.Contains(observer));
focus_changed_observers_.insert(observer);
}
void FocusController::NotifyFocusChangedObservers() const {
for (const auto& it : focus_changed_observers_)
it->FocusedFrameChanged();
}
void FocusController::Trace(blink::Visitor* visitor) {
visitor->Trace(page_);
visitor->Trace(focused_frame_);
visitor->Trace(focus_changed_observers_);
}
} // namespace blink