blob: 02ff2ad3b3b17afc7b7a665b7fc1da0501753da3 [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 "core/page/FocusController.h"
#include "core/HTMLNames.h"
#include "core/dom/AXObjectCache.h"
#include "core/dom/ContainerNode.h"
#include "core/dom/Document.h"
#include "core/dom/Element.h"
#include "core/dom/ElementTraversal.h"
#include "core/dom/Range.h"
#include "core/dom/shadow/ElementShadow.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/dom/shadow/SlotScopedTraversal.h"
#include "core/editing/EditingUtilities.h" // For firstPositionInOrBeforeNode
#include "core/editing/FrameSelection.h"
#include "core/editing/InputMethodController.h"
#include "core/events/Event.h"
#include "core/frame/FrameClient.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalDOMWindow.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/RemoteFrame.h"
#include "core/frame/Settings.h"
#include "core/html/HTMLAreaElement.h"
#include "core/html/HTMLImageElement.h"
#include "core/html/HTMLPlugInElement.h"
#include "core/html/HTMLShadowElement.h"
#include "core/html/HTMLSlotElement.h"
#include "core/html/TextControlElement.h"
#include "core/input/EventHandler.h"
#include "core/layout/HitTestResult.h"
#include "core/page/ChromeClient.h"
#include "core/page/FocusChangedObserver.h"
#include "core/page/FrameTree.h"
#include "core/page/Page.h"
#include "core/page/SpatialNavigation.h"
#include <limits>
namespace blink {
using namespace HTMLNames;
namespace {
inline bool IsShadowInsertionPointFocusScopeOwner(Element& element) {
return IsActiveShadowInsertionPoint(element) &&
toHTMLShadowElement(element).OlderShadowRoot();
}
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 current_; }
Element* Owner() const;
static ScopedFocusNavigation CreateFor(const Element&);
static ScopedFocusNavigation CreateForDocument(Document&);
static ScopedFocusNavigation OwnedByNonFocusableFocusScopeOwner(Element&);
static ScopedFocusNavigation OwnedByShadowHost(const Element&);
static ScopedFocusNavigation OwnedByShadowInsertionPoint(HTMLShadowElement&);
static ScopedFocusNavigation OwnedByHTMLSlotElement(const HTMLSlotElement&);
static ScopedFocusNavigation OwnedByIFrame(const HTMLFrameOwnerElement&);
static HTMLSlotElement* FindFallbackScopeOwnerSlot(const Element&);
static bool IsSlotFallbackScoped(const Element&);
static bool IsSlotFallbackScopedForThisSlot(const HTMLSlotElement&,
const Element&);
private:
ScopedFocusNavigation(TreeScope&, const Element*);
ScopedFocusNavigation(HTMLSlotElement&, const Element*);
Element* FindElementWithExactTabIndex(int tab_index, WebFocusType);
Element* NextElementWithGreaterTabIndex(int tab_index);
Element* PreviousElementWithLowerTabIndex(int tab_index);
Element* NextFocusableElement();
Element* PreviousFocusableElement();
void SetCurrentElement(Element* element) { current_ = element; }
void MoveToNext();
void MoveToPrevious();
void MoveToFirst();
void MoveToLast();
Member<ContainerNode> root_node_;
Member<HTMLSlotElement> root_slot_;
Member<Element> current_;
bool slot_fallback_traversal_;
};
ScopedFocusNavigation::ScopedFocusNavigation(TreeScope& tree_scope,
const Element* current)
: root_node_(tree_scope.RootNode()),
root_slot_(nullptr),
current_(const_cast<Element*>(current)) {}
ScopedFocusNavigation::ScopedFocusNavigation(HTMLSlotElement& slot,
const Element* current)
: root_node_(nullptr),
root_slot_(&slot),
current_(const_cast<Element*>(current)),
slot_fallback_traversal_(slot.AssignedNodes().IsEmpty()) {}
void ScopedFocusNavigation::MoveToNext() {
DCHECK(current_);
if (root_slot_) {
if (slot_fallback_traversal_) {
current_ = ElementTraversal::Next(*current_, root_slot_);
while (current_ &&
!ScopedFocusNavigation::IsSlotFallbackScopedForThisSlot(
*root_slot_, *current_))
current_ = ElementTraversal::Next(*current_, root_slot_);
} else {
current_ = SlotScopedTraversal::Next(*current_);
}
} else {
current_ = ElementTraversal::Next(*current_);
while (current_ && (SlotScopedTraversal::IsSlotScoped(*current_) ||
ScopedFocusNavigation::IsSlotFallbackScoped(*current_)))
current_ = ElementTraversal::Next(*current_);
}
}
void ScopedFocusNavigation::MoveToPrevious() {
DCHECK(current_);
if (root_slot_) {
if (slot_fallback_traversal_) {
current_ = ElementTraversal::Previous(*current_, root_slot_);
if (current_ == root_slot_)
current_ = nullptr;
while (current_ &&
!ScopedFocusNavigation::IsSlotFallbackScopedForThisSlot(
*root_slot_, *current_))
current_ = ElementTraversal::Previous(*current_);
} else {
current_ = SlotScopedTraversal::Previous(*current_);
}
} else {
current_ = ElementTraversal::Previous(*current_);
while (current_ && (SlotScopedTraversal::IsSlotScoped(*current_) ||
ScopedFocusNavigation::IsSlotFallbackScoped(*current_)))
current_ = ElementTraversal::Previous(*current_);
}
}
void ScopedFocusNavigation::MoveToFirst() {
if (root_slot_) {
if (!slot_fallback_traversal_) {
current_ = SlotScopedTraversal::FirstAssignedToSlot(*root_slot_);
} else {
Element* first = ElementTraversal::FirstChild(*root_slot_);
while (first && !ScopedFocusNavigation::IsSlotFallbackScopedForThisSlot(
*root_slot_, *first))
first = ElementTraversal::Next(*first, root_slot_);
current_ = first;
}
} else {
Element* first = root_node_->IsElementNode()
? &ToElement(*root_node_)
: ElementTraversal::Next(*root_node_);
while (first && (SlotScopedTraversal::IsSlotScoped(*first) ||
ScopedFocusNavigation::IsSlotFallbackScoped(*first)))
first = ElementTraversal::Next(*first, root_node_);
current_ = first;
}
}
void ScopedFocusNavigation::MoveToLast() {
if (root_slot_) {
if (!slot_fallback_traversal_) {
current_ = SlotScopedTraversal::LastAssignedToSlot(*root_slot_);
} else {
Element* last = ElementTraversal::LastWithin(*root_slot_);
while (last && !ScopedFocusNavigation::IsSlotFallbackScopedForThisSlot(
*root_slot_, *last))
last = ElementTraversal::Previous(*last, root_slot_);
current_ = last;
}
} else {
Element* last = ElementTraversal::LastWithin(*root_node_);
while (last && (SlotScopedTraversal::IsSlotScoped(*last) ||
ScopedFocusNavigation::IsSlotFallbackScoped(*last)))
last = ElementTraversal::Previous(*last, root_node_);
current_ = last;
}
}
Element* ScopedFocusNavigation::Owner() const {
if (root_slot_)
return root_slot_;
DCHECK(root_node_);
if (root_node_->IsShadowRoot()) {
ShadowRoot& shadow_root = ToShadowRoot(*root_node_);
return shadow_root.IsYoungest()
? &shadow_root.host()
: shadow_root.ShadowInsertionPointOfYoungerShadowRoot();
}
// FIXME: Figure out the right thing for OOPI here.
if (Frame* frame = root_node_->GetDocument().GetFrame())
return frame->DeprecatedLocalOwner();
return nullptr;
}
ScopedFocusNavigation ScopedFocusNavigation::CreateFor(const Element& current) {
if (HTMLSlotElement* slot = SlotScopedTraversal::FindScopeOwnerSlot(current))
return ScopedFocusNavigation(*slot, &current);
if (HTMLSlotElement* slot =
ScopedFocusNavigation::FindFallbackScopeOwnerSlot(current))
return ScopedFocusNavigation(*slot, &current);
return ScopedFocusNavigation(current.ContainingTreeScope(), &current);
}
ScopedFocusNavigation ScopedFocusNavigation::CreateForDocument(
Document& document) {
return ScopedFocusNavigation(document, nullptr);
}
ScopedFocusNavigation ScopedFocusNavigation::OwnedByNonFocusableFocusScopeOwner(
Element& element) {
if (IsShadowHost(element))
return ScopedFocusNavigation::OwnedByShadowHost(element);
if (IsShadowInsertionPointFocusScopeOwner(element))
return ScopedFocusNavigation::OwnedByShadowInsertionPoint(
toHTMLShadowElement(element));
DCHECK(isHTMLSlotElement(element));
return ScopedFocusNavigation::OwnedByHTMLSlotElement(
toHTMLSlotElement(element));
}
ScopedFocusNavigation ScopedFocusNavigation::OwnedByShadowHost(
const Element& element) {
DCHECK(IsShadowHost(element));
return ScopedFocusNavigation(*&element.Shadow()->YoungestShadowRoot(),
nullptr);
}
ScopedFocusNavigation ScopedFocusNavigation::OwnedByIFrame(
const HTMLFrameOwnerElement& frame) {
DCHECK(frame.ContentFrame());
DCHECK(frame.ContentFrame()->IsLocalFrame());
ToLocalFrame(frame.ContentFrame())->GetDocument()->UpdateDistribution();
return ScopedFocusNavigation(
*ToLocalFrame(frame.ContentFrame())->GetDocument(), nullptr);
}
ScopedFocusNavigation ScopedFocusNavigation::OwnedByShadowInsertionPoint(
HTMLShadowElement& shadow_insertion_point) {
DCHECK(IsShadowInsertionPointFocusScopeOwner(shadow_insertion_point));
return ScopedFocusNavigation(*shadow_insertion_point.OlderShadowRoot(),
nullptr);
}
ScopedFocusNavigation ScopedFocusNavigation::OwnedByHTMLSlotElement(
const HTMLSlotElement& element) {
return ScopedFocusNavigation(const_cast<HTMLSlotElement&>(element), nullptr);
}
HTMLSlotElement* ScopedFocusNavigation::FindFallbackScopeOwnerSlot(
const Element& element) {
Element* parent = const_cast<Element*>(element.parentElement());
while (parent) {
if (isHTMLSlotElement(parent))
return toHTMLSlotElement(parent)->AssignedNodes().IsEmpty()
? toHTMLSlotElement(parent)
: 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(EventTypeNames::focusout, nullptr);
if (focused_element == document.FocusedElement())
focused_element.DispatchFocusOutEvent(EventTypeNames::DOMFocusOut,
nullptr);
}
}
inline void DispatchFocusEvent(const Document& document,
Element& focused_element) {
focused_element.DispatchFocusEvent(0, kWebFocusTypePage);
if (focused_element == document.FocusedElement()) {
focused_element.DispatchFocusInEvent(EventTypeNames::focusin, nullptr,
kWebFocusTypePage);
if (focused_element == document.FocusedElement())
focused_element.DispatchFocusInEvent(EventTypeNames::DOMFocusIn, 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->Suspended())
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 ? EventTypeNames::focus : EventTypeNames::blur));
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) ||
IsShadowInsertionPointFocusScopeOwner(element) ||
isHTMLSlotElement(element);
}
inline bool IsShadowHostDelegatesFocus(const Element& element) {
return element.AuthorShadowRoot() &&
element.AuthorShadowRoot()->delegatesFocus();
}
inline int AdjustedTabIndex(Element& element) {
return (IsNonKeyboardFocusableShadowHost(element) ||
IsShadowInsertionPointFocusScopeOwner(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) {
// 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);
if (Element* found_in_inner_focus_scope =
FindFocusableElementRecursivelyForward(inner_scope))
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);
if (Element* found_in_inner_focus_scope =
FindFocusableElementRecursivelyForward(inner_scope))
return found_in_inner_focus_scope;
}
return nullptr;
}
Element* FindFocusableElementRecursivelyBackward(ScopedFocusNavigation& scope) {
// 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);
Element* found_in_inner_focus_scope =
FindFocusableElementRecursivelyBackward(inner_scope);
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);
if (Element* found_in_inner_focus_scope =
FindFocusableElementRecursivelyBackward(inner_scope))
return found_in_inner_focus_scope;
continue;
}
if (!IsShadowHostDelegatesFocus(*found))
return found;
}
return nullptr;
}
Element* FindFocusableElementRecursively(WebFocusType type,
ScopedFocusNavigation& scope) {
return (type == kWebFocusTypeForward)
? FindFocusableElementRecursivelyForward(scope)
: FindFocusableElementRecursivelyBackward(scope);
}
Element* FindFocusableElementDescendingDownIntoFrameDocument(WebFocusType type,
Element* element) {
// 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);
Element* found_element = FindFocusableElementRecursively(type, scope);
if (!found_element)
break;
DCHECK_NE(element, found_element);
element = found_element;
}
return element;
}
Element* FindFocusableElementAcrossFocusScopesForward(
ScopedFocusNavigation& scope) {
Element* current = scope.CurrentElement();
Element* found;
if (current && IsShadowHostWithoutCustomFocusLogic(*current)) {
ScopedFocusNavigation inner_scope =
ScopedFocusNavigation::OwnedByShadowHost(*current);
Element* found_in_inner_focus_scope =
FindFocusableElementRecursivelyForward(inner_scope);
found = found_in_inner_focus_scope
? found_in_inner_focus_scope
: FindFocusableElementRecursivelyForward(scope);
} else {
found = FindFocusableElementRecursivelyForward(scope);
}
// 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);
found = FindFocusableElementRecursivelyForward(current_scope);
}
return FindFocusableElementDescendingDownIntoFrameDocument(
kWebFocusTypeForward, found);
}
Element* FindFocusableElementAcrossFocusScopesBackward(
ScopedFocusNavigation& scope) {
Element* found = FindFocusableElementRecursivelyBackward(scope);
// 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);
if (IsKeyboardFocusableShadowHost(*owner) &&
!IsShadowHostDelegatesFocus(*owner)) {
found = owner;
break;
}
found = FindFocusableElementRecursivelyBackward(current_scope);
}
return FindFocusableElementDescendingDownIntoFrameDocument(
kWebFocusTypeBackward, found);
}
Element* FindFocusableElementAcrossFocusScopes(WebFocusType type,
ScopedFocusNavigation& scope) {
return (type == kWebFocusTypeForward)
? FindFocusableElementAcrossFocusScopesForward(scope)
: FindFocusableElementAcrossFocusScopesBackward(scope);
}
} // anonymous namespace
FocusController::FocusController(Page* page)
: page_(page),
is_active_(false),
is_focused_(false),
is_changing_focused_frame_(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().SetFocused(false);
old_frame->DomWindow()->DispatchEvent(Event::Create(EventTypeNames::blur));
}
if (new_frame && new_frame->View() && IsFocused()) {
new_frame->Selection().SetFocused(true);
new_frame->DomWindow()->DispatchEvent(Event::Create(EventTypeNames::focus));
}
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)
DispatchBlurEvent(*document, *focused_element);
}
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 {
// TODO(alexmos): Strengthen this to DCHECK that whoever called this really
// expected a LocalFrame. Refactor call sites so that the rare cases that
// need to know about focused RemoteFrames use a separate accessor (to be
// added).
if (focused_frame_ && focused_frame_->IsRemoteFrame())
return nullptr;
return ToLocalFrame(focused_frame_.Get());
}
Frame* FocusController::FocusedOrMainFrame() const {
if (LocalFrame* frame = FocusedFrame())
return frame;
// FIXME: 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->IsLocalRoot())
return frame;
}
return page_->MainFrame();
}
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::SetFocused(bool focused) {
if (IsFocused() == focused)
return;
is_focused_ = focused;
if (!is_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_ && is_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().SetFocused(focused);
DispatchEventsOnWindowAndFocusedElement(
ToLocalFrame(focused_frame_.Get())->GetDocument(), focused);
}
NotifyFocusChangedObservers();
}
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->UpdateDistribution();
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)
: ScopedFocusNavigation::CreateForDocument(*document);
Element* element = FindFocusableElementAcrossFocusScopes(type, scope);
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);
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());
element = FindFocusableElementRecursively(type, scope);
element =
FindFocusableElementDescendingDownIntoFrameDocument(type, element);
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) {
// FIXME: No spacial navigation code yet.
DCHECK(type == kWebFocusTypeForward || type == kWebFocusTypeBackward);
ScopedFocusNavigation scope = ScopedFocusNavigation::CreateFor(element);
return FindFocusableElementAcrossFocusScopes(type, scope);
}
Element* FocusController::FindFocusableElementInShadowHost(
const Element& shadow_host) {
DCHECK(shadow_host.AuthorShadowRoot());
ScopedFocusNavigation scope =
ScopedFocusNavigation::OwnedByShadowHost(shadow_host);
return FindFocusableElementAcrossFocusScopes(kWebFocusTypeForward, scope);
}
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::SetActive(bool active) {
if (is_active_ == active)
return;
is_active_ = active;
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 FrameView instead of just the focused.
if (FrameView* view = document->View())
view->InvalidateAllCustomScrollbarsOnActiveChanged();
ToLocalFrame(frame)->Selection().PageActivationChanged();
}
}
static void UpdateFocusCandidateIfNeeded(WebFocusType type,
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.IsEmpty()))
return;
// Ignore off screen child nodes of containers that do not scroll
// (overflow:hidden)
if (candidate.is_offscreen && !CanBeScrolledIntoView(type, candidate))
return;
DistanceDataForNode(type, current, candidate);
if (candidate.distance == MaxDistance())
return;
if (candidate.is_offscreen_after_scrolling)
return;
if (closest.IsNull()) {
closest = candidate;
return;
}
LayoutRect intersection_rect = Intersection(candidate.rect, closest.rect);
if (!intersection_rect.IsEmpty() &&
!AreElementsOnSameLine(closest, candidate) &&
intersection_rect == candidate.rect) {
// 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;
HitTestResult result =
candidate.visible_node->GetDocument()
.GetPage()
->DeprecatedLocalMainFrame()
->GetEventHandler()
.HitTestResultAtPoint(IntPoint(x.ToInt(), y.ToInt()),
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 type,
FocusCandidate& closest) {
Element* focused_element =
(FocusedFrame() && FocusedFrame()->GetDocument())
? FocusedFrame()->GetDocument()->FocusedElement()
: nullptr;
Element* element = ElementTraversal::FirstWithin(container);
FocusCandidate current;
current.rect = starting_rect;
current.focusable_node = focused_element;
current.visible_node = focused_element;
for (; element;
element =
(element->IsFrameOwnerElement() ||
CanScrollInDirection(element, type))
? ElementTraversal::NextSkippingChildren(*element, &container)
: ElementTraversal::Next(*element, &container)) {
if (element == focused_element)
continue;
if (!element->IsKeyboardFocusable() && !element->IsFrameOwnerElement() &&
!CanScrollInDirection(element, type))
continue;
FocusCandidate candidate = FocusCandidate(element, type);
if (candidate.IsNull())
continue;
candidate.enclosing_scrollable_box = &container;
UpdateFocusCandidateIfNeeded(type, current, candidate, closest);
}
}
bool FocusController::AdvanceFocusDirectionallyInContainer(
Node* container,
const LayoutRect& starting_rect,
WebFocusType type) {
if (!container)
return false;
LayoutRect new_starting_rect = starting_rect;
if (starting_rect.IsEmpty())
new_starting_rect =
VirtualRectForDirection(type, NodeRectInAbsoluteCoordinates(container));
// Find the closest node within current container in the direction of the
// navigation.
FocusCandidate focus_candidate;
FindFocusCandidateInContainer(*container, new_starting_rect, type,
focus_candidate);
if (focus_candidate.IsNull()) {
// Nothing to focus, scroll if possible.
// NOTE: If no scrolling is performed (i.e. scrollInDirection returns
// false), the spatial navigation algorithm will skip this container.
return ScrollInDirection(container, type);
}
HTMLFrameOwnerElement* frame_element = FrameOwnerElement(focus_candidate);
// If we have an iframe without the src attribute, it will not have a
// contentFrame(). We DCHECK here to make sure that
// updateFocusCandidateIfNeeded() will never consider such an iframe as a
// candidate.
DCHECK(!frame_element || frame_element->ContentFrame());
if (frame_element && frame_element->ContentFrame()->IsLocalFrame()) {
if (focus_candidate.is_offscreen_after_scrolling) {
ScrollInDirection(&focus_candidate.visible_node->GetDocument(), type);
return true;
}
// Navigate into a new frame.
LayoutRect rect;
Element* focused_element =
ToLocalFrame(FocusedOrMainFrame())->GetDocument()->FocusedElement();
if (focused_element && !HasOffscreenRect(focused_element))
rect = NodeRectInAbsoluteCoordinates(focused_element,
true /* ignore border */);
ToLocalFrame(frame_element->ContentFrame())
->GetDocument()
->UpdateStyleAndLayoutIgnorePendingStylesheets();
if (!AdvanceFocusDirectionallyInContainer(
ToLocalFrame(frame_element->ContentFrame())->GetDocument(), rect,
type)) {
// The new frame had nothing interesting, need to find another candidate.
return AdvanceFocusDirectionallyInContainer(
container,
NodeRectInAbsoluteCoordinates(focus_candidate.visible_node, true),
type);
}
return true;
}
if (CanScrollInDirection(focus_candidate.visible_node, type)) {
if (focus_candidate.is_offscreen_after_scrolling) {
ScrollInDirection(focus_candidate.visible_node, type);
return true;
}
// Navigate into a new scrollable container.
LayoutRect starting_rect;
Element* focused_element =
ToLocalFrame(FocusedOrMainFrame())->GetDocument()->FocusedElement();
if (focused_element && !HasOffscreenRect(focused_element))
starting_rect = NodeRectInAbsoluteCoordinates(focused_element, true);
return AdvanceFocusDirectionallyInContainer(focus_candidate.visible_node,
starting_rect, type);
}
if (focus_candidate.is_offscreen_after_scrolling) {
Node* container = focus_candidate.enclosing_scrollable_box;
ScrollInDirection(container, type);
return true;
}
// We found a new focus node, navigate to it.
Element* element = ToElement(focus_candidate.focusable_node);
DCHECK(element);
if (!element->IsTextControl() && !HasEditableStyle(*element->ToNode())) {
// To fulfill the expectation of spatial-navigation/snav-input.html
// we clear selection when spatnav moves focus away from a text-field.
FocusedFrame()->Selection().Clear();
}
element->focus(FocusParams(SelectionBehaviorOnFocus::kReset, type, nullptr));
return true;
}
bool FocusController::AdvanceFocusDirectionally(WebFocusType type) {
// FIXME: Directional focus changes don't yet work with RemoteFrames.
if (!FocusedOrMainFrame()->IsLocalFrame())
return false;
LocalFrame* cur_frame = ToLocalFrame(FocusedOrMainFrame());
DCHECK(cur_frame);
Document* focused_document = cur_frame->GetDocument();
if (!focused_document)
return false;
Element* focused_element = focused_document->FocusedElement();
Node* container = focused_document;
if (container->IsDocumentNode())
ToDocument(container)->UpdateStyleAndLayoutIgnorePendingStylesheets();
// Figure out the starting rect.
LayoutRect starting_rect;
if (focused_element) {
if (!HasOffscreenRect(focused_element)) {
container = ScrollableEnclosingBoxOrParentFrameForNodeInDirection(
type, focused_element);
starting_rect = NodeRectInAbsoluteCoordinates(focused_element,
true /* ignore border */);
} else if (isHTMLAreaElement(*focused_element)) {
HTMLAreaElement& area = toHTMLAreaElement(*focused_element);
container = ScrollableEnclosingBoxOrParentFrameForNodeInDirection(
type, area.ImageElement());
starting_rect = VirtualRectForAreaElementAndDirection(area, type);
}
}
bool consumed = false;
do {
consumed =
AdvanceFocusDirectionallyInContainer(container, starting_rect, type);
starting_rect =
NodeRectInAbsoluteCoordinates(container, true /* ignore border */);
container =
ScrollableEnclosingBoxOrParentFrameForNodeInDirection(type, container);
if (container && container->IsDocumentNode())
ToDocument(container)->UpdateStyleAndLayoutIgnorePendingStylesheets();
} while (!consumed && container);
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();
}
DEFINE_TRACE(FocusController) {
visitor->Trace(page_);
visitor->Trace(focused_frame_);
visitor->Trace(focus_changed_observers_);
}
} // namespace blink