blob: 3c21f9701e835bc088a8a0204bb63839c4aa25b3 [file] [log] [blame]
/*
* Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
* Copyright (C) 2009 Antonio Gomes <tonikitoo@webkit.org>
*
* All rights reserved.
*
* 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/spatial_navigation.h"
#include "third_party/blink/renderer/core/dom/node_traversal.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/settings.h"
#include "third_party/blink/renderer/core/html/html_area_element.h"
#include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
#include "third_party/blink/renderer/core/html/html_image_element.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/layout/layout_view.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/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/platform/geometry/int_rect.h"
namespace blink {
using namespace HTMLNames;
static void DeflateIfOverlapped(LayoutRect&, LayoutRect&);
static bool IsScrollableNode(const Node*);
FocusCandidate::FocusCandidate(Node* node, WebFocusType direction)
: visible_node(nullptr),
focusable_node(nullptr),
enclosing_scrollable_box(nullptr),
distance(MaxDistance()),
is_offscreen(true),
is_offscreen_after_scrolling(true) {
DCHECK(node);
DCHECK(node->IsElementNode());
if (auto* area = ToHTMLAreaElementOrNull(*node)) {
HTMLImageElement* image = area->ImageElement();
if (!image || !image->GetLayoutObject())
return;
visible_node = image;
rect_in_root_frame =
VirtualRectForAreaElementAndDirection(*area, direction);
} else {
if (!node->GetLayoutObject())
return;
visible_node = node;
rect_in_root_frame = NodeRectInRootFrame(node, true /* ignore border */);
}
focusable_node = node;
is_offscreen = HasOffscreenRect(visible_node);
is_offscreen_after_scrolling = HasOffscreenRect(visible_node, direction);
}
bool IsSpatialNavigationEnabled(const LocalFrame* frame) {
return (frame && frame->GetSettings() &&
frame->GetSettings()->GetSpatialNavigationEnabled());
}
bool SpatialNavigationIgnoresEventHandlers(const LocalFrame* frame) {
return (frame && frame->GetSettings() &&
frame->GetSettings()->GetDeviceSupportsTouch());
}
static bool RectsIntersectOnOrthogonalAxis(WebFocusType direction,
const LayoutRect& a,
const LayoutRect& b) {
switch (direction) {
case kWebFocusTypeLeft:
case kWebFocusTypeRight:
return a.MaxY() > b.Y() && a.Y() < b.MaxY();
case kWebFocusTypeUp:
case kWebFocusTypeDown:
return a.MaxX() > b.X() && a.X() < b.MaxX();
default:
NOTREACHED();
return false;
}
}
// Return true if rect |a| is below |b|. False otherwise.
// For overlapping rects, |a| is considered to be below |b|
// if both edges of |a| are below the respective ones of |b|
static inline bool Below(const LayoutRect& a, const LayoutRect& b) {
return a.Y() >= b.MaxY() || (a.Y() >= b.Y() && a.MaxY() > b.MaxY() &&
a.X() < b.MaxX() && a.MaxX() > b.X());
}
// Return true if rect |a| is on the right of |b|. False otherwise.
// For overlapping rects, |a| is considered to be on the right of |b|
// if both edges of |a| are on the right of the respective ones of |b|
static inline bool RightOf(const LayoutRect& a, const LayoutRect& b) {
return a.X() >= b.MaxX() || (a.X() >= b.X() && a.MaxX() > b.MaxX() &&
a.Y() < b.MaxY() && a.MaxY() > b.Y());
}
static bool IsRectInDirection(WebFocusType direction,
const LayoutRect& cur_rect,
const LayoutRect& target_rect) {
switch (direction) {
case kWebFocusTypeLeft:
return RightOf(cur_rect, target_rect);
case kWebFocusTypeRight:
return RightOf(target_rect, cur_rect);
case kWebFocusTypeUp:
return Below(cur_rect, target_rect);
case kWebFocusTypeDown:
return Below(target_rect, cur_rect);
default:
NOTREACHED();
return false;
}
}
// Checks if |node| is offscreen the visible area (viewport) of its container
// document. In case it is, one can scroll in direction or take any different
// desired action later on.
bool HasOffscreenRect(const Node* node, WebFocusType direction) {
// Get the LocalFrameView in which |node| is (which means the current viewport
// if |node| is not in an inner document), so we can check if its content rect
// is visible before we actually move the focus to it.
LocalFrameView* frame_view = node->GetDocument().View();
if (!frame_view)
return true;
DCHECK(!frame_view->NeedsLayout());
LayoutRect container_viewport_rect(
frame_view->LayoutViewport()->VisibleContentRect());
// We want to select a node if it is currently off screen, but will be
// exposed after we scroll. Adjust the viewport to post-scrolling position.
// If the container has overflow:hidden, we cannot scroll, so we do not pass
// direction and we do not adjust for scrolling.
int pixels_per_line_step =
ScrollableArea::PixelsPerLineStep(frame_view->GetChromeClient());
switch (direction) {
case kWebFocusTypeLeft:
container_viewport_rect.SetX(container_viewport_rect.X() -
pixels_per_line_step);
container_viewport_rect.SetWidth(container_viewport_rect.Width() +
pixels_per_line_step);
break;
case kWebFocusTypeRight:
container_viewport_rect.SetWidth(container_viewport_rect.Width() +
pixels_per_line_step);
break;
case kWebFocusTypeUp:
container_viewport_rect.SetY(container_viewport_rect.Y() -
pixels_per_line_step);
container_viewport_rect.SetHeight(container_viewport_rect.Height() +
pixels_per_line_step);
break;
case kWebFocusTypeDown:
container_viewport_rect.SetHeight(container_viewport_rect.Height() +
pixels_per_line_step);
break;
default:
break;
}
LayoutObject* layout_object = node->GetLayoutObject();
if (!layout_object)
return true;
LayoutRect rect(layout_object->AbsoluteVisualRect());
if (rect.IsEmpty())
return true;
return !container_viewport_rect.Intersects(rect);
}
bool ScrollInDirection(LocalFrame* frame, WebFocusType direction) {
DCHECK(frame);
if (frame && CanScrollInDirection(frame->GetDocument(), direction)) {
int dx = 0;
int dy = 0;
int pixels_per_line_step =
ScrollableArea::PixelsPerLineStep(frame->View()->GetChromeClient());
switch (direction) {
case kWebFocusTypeLeft:
dx = -pixels_per_line_step;
break;
case kWebFocusTypeRight:
dx = pixels_per_line_step;
break;
case kWebFocusTypeUp:
dy = -pixels_per_line_step;
break;
case kWebFocusTypeDown:
dy = pixels_per_line_step;
break;
default:
NOTREACHED();
return false;
}
frame->View()->LayoutViewport()->ScrollBy(ScrollOffset(dx, dy),
kUserScroll);
return true;
}
return false;
}
bool ScrollInDirection(Node* container, WebFocusType direction) {
DCHECK(container);
if (container->IsDocumentNode())
return ScrollInDirection(ToDocument(container)->GetFrame(), direction);
if (!container->GetLayoutBox())
return false;
if (CanScrollInDirection(container, direction)) {
int dx = 0;
int dy = 0;
// TODO(leviw): Why are these values truncated (toInt) instead of rounding?
LocalFrameView* frame_view = container->GetDocument().View();
int pixels_per_line_step = ScrollableArea::PixelsPerLineStep(
frame_view ? frame_view->GetChromeClient() : nullptr);
switch (direction) {
case kWebFocusTypeLeft:
dx = -pixels_per_line_step;
break;
case kWebFocusTypeRight:
DCHECK_GT(container->GetLayoutBox()->ScrollWidth(),
(container->GetLayoutBox()->ScrollLeft() +
container->GetLayoutBox()->ClientWidth()));
dx = pixels_per_line_step;
break;
case kWebFocusTypeUp:
dy = -pixels_per_line_step;
break;
case kWebFocusTypeDown:
DCHECK(container->GetLayoutBox()->ScrollHeight() -
(container->GetLayoutBox()->ScrollTop() +
container->GetLayoutBox()->ClientHeight()));
dy = pixels_per_line_step;
break;
default:
NOTREACHED();
return false;
}
container->GetLayoutBox()->ScrollByRecursively(ScrollOffset(dx, dy));
return true;
}
return false;
}
static void DeflateIfOverlapped(LayoutRect& a, LayoutRect& b) {
if (!a.Intersects(b) || a.Contains(b) || b.Contains(a))
return;
LayoutUnit deflate_factor = LayoutUnit(-FudgeFactor());
// Avoid negative width or height values.
if ((a.Width() + 2 * deflate_factor > 0) &&
(a.Height() + 2 * deflate_factor > 0))
a.Inflate(deflate_factor);
if ((b.Width() + 2 * deflate_factor > 0) &&
(b.Height() + 2 * deflate_factor > 0))
b.Inflate(deflate_factor);
}
bool IsScrollableNode(const Node* node) {
DCHECK(!node->IsDocumentNode());
if (!node)
return false;
if (LayoutObject* layout_object = node->GetLayoutObject())
return layout_object->IsBox() &&
ToLayoutBox(layout_object)->CanBeScrolledAndHasScrollableArea() &&
node->hasChildren();
return false;
}
Node* ScrollableAreaOrDocumentOf(Node* node) {
DCHECK(node);
Node* parent = node;
do {
// FIXME: Spatial navigation is broken for OOPI.
if (parent->IsDocumentNode())
parent = ToDocument(parent)->GetFrame()->DeprecatedLocalOwner();
else
parent = parent->ParentOrShadowHostNode();
} while (parent && !IsScrollableAreaOrDocument(parent));
return parent;
}
bool IsScrollableAreaOrDocument(const Node* node) {
if (!node)
return false;
return node->IsDocumentNode() ||
(node->IsFrameOwnerElement() &&
ToHTMLFrameOwnerElement(node)->ContentFrame()) ||
IsScrollableNode(node);
}
bool IsNavigableContainer(const Node* node, WebFocusType direction) {
if (!node)
return false;
return node->IsDocumentNode() ||
(node->IsFrameOwnerElement() &&
ToHTMLFrameOwnerElement(node)->ContentFrame()) ||
CanScrollInDirection(node, direction);
}
bool CanScrollInDirection(const Node* container, WebFocusType direction) {
DCHECK(container);
if (container->IsDocumentNode())
return CanScrollInDirection(ToDocument(container)->GetFrame(), direction);
if (!IsScrollableNode(container))
return false;
switch (direction) {
case kWebFocusTypeLeft:
return (container->GetLayoutObject()->Style()->OverflowX() !=
EOverflow::kHidden &&
container->GetLayoutBox()->ScrollLeft() > 0);
case kWebFocusTypeUp:
return (container->GetLayoutObject()->Style()->OverflowY() !=
EOverflow::kHidden &&
container->GetLayoutBox()->ScrollTop() > 0);
case kWebFocusTypeRight:
return (container->GetLayoutObject()->Style()->OverflowX() !=
EOverflow::kHidden &&
container->GetLayoutBox()->ScrollLeft() +
container->GetLayoutBox()->ClientWidth() <
container->GetLayoutBox()->ScrollWidth());
case kWebFocusTypeDown:
return (container->GetLayoutObject()->Style()->OverflowY() !=
EOverflow::kHidden &&
container->GetLayoutBox()->ScrollTop() +
container->GetLayoutBox()->ClientHeight() <
container->GetLayoutBox()->ScrollHeight());
default:
NOTREACHED();
return false;
}
}
bool CanScrollInDirection(const LocalFrame* frame, WebFocusType direction) {
if (!frame->View())
return false;
LayoutView* layoutView = frame->ContentLayoutObject();
if (!layoutView)
return false;
ScrollbarMode vertical_mode;
ScrollbarMode horizontal_mode;
layoutView->CalculateScrollbarModes(horizontal_mode, vertical_mode);
if ((direction == kWebFocusTypeLeft || direction == kWebFocusTypeRight) &&
kScrollbarAlwaysOff == horizontal_mode)
return false;
if ((direction == kWebFocusTypeUp || direction == kWebFocusTypeDown) &&
kScrollbarAlwaysOff == vertical_mode)
return false;
ScrollableArea* scrollable_area = frame->View()->LayoutViewport();
LayoutSize size(scrollable_area->ContentsSize());
LayoutSize offset(scrollable_area->ScrollOffsetInt());
LayoutRect rect(scrollable_area->VisibleContentRect(kIncludeScrollbars));
switch (direction) {
case kWebFocusTypeLeft:
return offset.Width() > 0;
case kWebFocusTypeUp:
return offset.Height() > 0;
case kWebFocusTypeRight:
return rect.Width() + offset.Width() < size.Width();
case kWebFocusTypeDown:
return rect.Height() + offset.Height() < size.Height();
default:
NOTREACHED();
return false;
}
}
LayoutRect NodeRectInRootFrame(const Node* node, bool ignore_border) {
DCHECK(node);
DCHECK(node->GetLayoutObject());
DCHECK(!node->GetDocument().View()->NeedsLayout());
LayoutRect rect = node->GetDocument().GetFrame()->View()->ConvertToRootFrame(
node->BoundingBox());
// For authors that use border instead of outline in their CSS, we compensate
// by ignoring the border when calculating the rect of the focused element.
if (ignore_border) {
rect.Move(node->GetLayoutObject()->Style()->BorderLeftWidth(),
node->GetLayoutObject()->Style()->BorderTopWidth());
rect.SetWidth(LayoutUnit(
rect.Width() - node->GetLayoutObject()->Style()->BorderLeftWidth() -
node->GetLayoutObject()->Style()->BorderRightWidth()));
rect.SetHeight(LayoutUnit(
rect.Height() - node->GetLayoutObject()->Style()->BorderTopWidth() -
node->GetLayoutObject()->Style()->BorderBottomWidth()));
}
return rect;
}
// This method calculates the exitPoint from the startingRect and the entryPoint
// into the candidate rect. The line between those 2 points is the closest
// distance between the 2 rects. Takes care of overlapping rects, defining
// points so that the distance between them is zero where necessary
void EntryAndExitPointsForDirection(WebFocusType direction,
const LayoutRect& starting_rect,
const LayoutRect& potential_rect,
LayoutPoint& exit_point,
LayoutPoint& entry_point) {
switch (direction) {
case kWebFocusTypeLeft:
exit_point.SetX(starting_rect.X());
if (potential_rect.MaxX() < starting_rect.X())
entry_point.SetX(potential_rect.MaxX());
else
entry_point.SetX(starting_rect.X());
break;
case kWebFocusTypeUp:
exit_point.SetY(starting_rect.Y());
if (potential_rect.MaxY() < starting_rect.Y())
entry_point.SetY(potential_rect.MaxY());
else
entry_point.SetY(starting_rect.Y());
break;
case kWebFocusTypeRight:
exit_point.SetX(starting_rect.MaxX());
if (potential_rect.X() > starting_rect.MaxX())
entry_point.SetX(potential_rect.X());
else
entry_point.SetX(starting_rect.MaxX());
break;
case kWebFocusTypeDown:
exit_point.SetY(starting_rect.MaxY());
if (potential_rect.Y() > starting_rect.MaxY())
entry_point.SetY(potential_rect.Y());
else
entry_point.SetY(starting_rect.MaxY());
break;
default:
NOTREACHED();
}
switch (direction) {
case kWebFocusTypeLeft:
case kWebFocusTypeRight:
if (Below(starting_rect, potential_rect)) {
exit_point.SetY(starting_rect.Y());
if (potential_rect.MaxY() < starting_rect.Y())
entry_point.SetY(potential_rect.MaxY());
else
entry_point.SetY(starting_rect.Y());
} else if (Below(potential_rect, starting_rect)) {
exit_point.SetY(starting_rect.MaxY());
if (potential_rect.Y() > starting_rect.MaxY())
entry_point.SetY(potential_rect.Y());
else
entry_point.SetY(starting_rect.MaxY());
} else {
exit_point.SetY(max(starting_rect.Y(), potential_rect.Y()));
entry_point.SetY(exit_point.Y());
}
break;
case kWebFocusTypeUp:
case kWebFocusTypeDown:
if (RightOf(starting_rect, potential_rect)) {
exit_point.SetX(starting_rect.X());
if (potential_rect.MaxX() < starting_rect.X())
entry_point.SetX(potential_rect.MaxX());
else
entry_point.SetX(starting_rect.X());
} else if (RightOf(potential_rect, starting_rect)) {
exit_point.SetX(starting_rect.MaxX());
if (potential_rect.X() > starting_rect.MaxX())
entry_point.SetX(potential_rect.X());
else
entry_point.SetX(starting_rect.MaxX());
} else {
exit_point.SetX(max(starting_rect.X(), potential_rect.X()));
entry_point.SetX(exit_point.X());
}
break;
default:
NOTREACHED();
}
}
bool AreElementsOnSameLine(const FocusCandidate& first_candidate,
const FocusCandidate& second_candidate) {
if (first_candidate.IsNull() || second_candidate.IsNull())
return false;
if (!first_candidate.visible_node->GetLayoutObject() ||
!second_candidate.visible_node->GetLayoutObject())
return false;
if (!first_candidate.rect_in_root_frame.Intersects(
second_candidate.rect_in_root_frame))
return false;
if (IsHTMLAreaElement(*first_candidate.focusable_node) ||
IsHTMLAreaElement(*second_candidate.focusable_node))
return false;
if (!first_candidate.visible_node->GetLayoutObject()->IsLayoutInline() ||
!second_candidate.visible_node->GetLayoutObject()->IsLayoutInline())
return false;
if (first_candidate.visible_node->GetLayoutObject()->ContainingBlock() !=
second_candidate.visible_node->GetLayoutObject()->ContainingBlock())
return false;
return true;
}
void DistanceDataForNode(WebFocusType direction,
const FocusCandidate& current,
FocusCandidate& candidate) {
if (!IsRectInDirection(direction, current.rect_in_root_frame,
candidate.rect_in_root_frame))
return;
if (AreElementsOnSameLine(current, candidate)) {
if ((direction == kWebFocusTypeUp &&
current.rect_in_root_frame.Y() > candidate.rect_in_root_frame.Y()) ||
(direction == kWebFocusTypeDown &&
candidate.rect_in_root_frame.Y() > current.rect_in_root_frame.Y())) {
candidate.distance = 0;
return;
}
}
LayoutRect node_rect = candidate.rect_in_root_frame;
LayoutRect current_rect = current.rect_in_root_frame;
DeflateIfOverlapped(current_rect, node_rect);
LayoutPoint exit_point;
LayoutPoint entry_point;
EntryAndExitPointsForDirection(direction, current_rect, node_rect, exit_point,
entry_point);
LayoutUnit x_axis = (exit_point.X() - entry_point.X()).Abs();
LayoutUnit y_axis = (exit_point.Y() - entry_point.Y()).Abs();
LayoutUnit navigation_axis_distance;
LayoutUnit weighted_orthogonal_axis_distance;
// Bias and weights are put to the orthogonal axis distance calculation
// so aligned candidates would have advantage over partially-aligned ones
// and then over not-aligned candidates. The bias is given to not-aligned
// candidates with respect to size of the current rect. The weight for
// left/right direction is given a higher value to allow navigation on
// common horizonally-aligned elements. The hardcoded values are based on
// tests and experiments.
const int kOrthogonalWeightForLeftRight = 30;
const int kOrthogonalWeightForUpDown = 2;
int orthogonal_bias = 0;
switch (direction) {
case kWebFocusTypeLeft:
case kWebFocusTypeRight:
navigation_axis_distance = x_axis;
if (!RectsIntersectOnOrthogonalAxis(direction, current_rect, node_rect))
orthogonal_bias = (current_rect.Height() / 2).ToInt();
weighted_orthogonal_axis_distance =
(y_axis + orthogonal_bias) * kOrthogonalWeightForLeftRight;
break;
case kWebFocusTypeUp:
case kWebFocusTypeDown:
navigation_axis_distance = y_axis;
if (!RectsIntersectOnOrthogonalAxis(direction, current_rect, node_rect))
orthogonal_bias = (current_rect.Width() / 2).ToInt();
weighted_orthogonal_axis_distance =
(x_axis + orthogonal_bias) * kOrthogonalWeightForUpDown;
break;
default:
NOTREACHED();
return;
}
double euclidian_distance_pow2 =
(x_axis * x_axis + y_axis * y_axis).ToDouble();
LayoutRect intersection_rect = Intersection(current_rect, node_rect);
double overlap =
(intersection_rect.Width() * intersection_rect.Height()).ToDouble();
// Distance calculation is based on http://www.w3.org/TR/WICD/#focus-handling
candidate.distance = sqrt(euclidian_distance_pow2) +
navigation_axis_distance +
weighted_orthogonal_axis_distance - sqrt(overlap);
}
bool CanBeScrolledIntoView(WebFocusType direction,
const FocusCandidate& candidate) {
DCHECK(candidate.visible_node);
DCHECK(candidate.is_offscreen);
LayoutRect candidate_rect = candidate.rect_in_root_frame;
// TODO(ecobos@igalia.com): Investigate interaction with Shadow DOM.
for (Node& parent_node :
NodeTraversal::AncestorsOf(*candidate.visible_node)) {
if (UNLIKELY(!parent_node.GetLayoutObject())) {
DCHECK(parent_node.IsElementNode() &&
ToElement(parent_node).HasDisplayContentsStyle());
continue;
}
LayoutRect parent_rect = NodeRectInRootFrame(&parent_node);
if (!candidate_rect.Intersects(parent_rect)) {
if (((direction == kWebFocusTypeLeft ||
direction == kWebFocusTypeRight) &&
parent_node.GetLayoutObject()->Style()->OverflowX() ==
EOverflow::kHidden) ||
((direction == kWebFocusTypeUp || direction == kWebFocusTypeDown) &&
parent_node.GetLayoutObject()->Style()->OverflowY() ==
EOverflow::kHidden))
return false;
}
if (parent_node == candidate.enclosing_scrollable_box)
return CanScrollInDirection(&parent_node, direction);
}
return true;
}
// The starting rect is the rect of the focused node, in document coordinates.
// Compose a virtual starting rect if there is no focused node or if it is off
// screen. The virtual rect is the edge of the container or frame. We select
// which edge depending on the direction of the navigation.
LayoutRect VirtualRectForDirection(WebFocusType direction,
const LayoutRect& starting_rect,
LayoutUnit width) {
LayoutRect virtual_starting_rect = starting_rect;
switch (direction) {
case kWebFocusTypeLeft:
virtual_starting_rect.SetX(virtual_starting_rect.MaxX() - width);
virtual_starting_rect.SetWidth(width);
break;
case kWebFocusTypeUp:
virtual_starting_rect.SetY(virtual_starting_rect.MaxY() - width);
virtual_starting_rect.SetHeight(width);
break;
case kWebFocusTypeRight:
virtual_starting_rect.SetWidth(width);
break;
case kWebFocusTypeDown:
virtual_starting_rect.SetHeight(width);
break;
default:
NOTREACHED();
}
return virtual_starting_rect;
}
LayoutRect VirtualRectForAreaElementAndDirection(const HTMLAreaElement& area,
WebFocusType direction) {
DCHECK(area.ImageElement());
// Area elements tend to overlap more than other focusable elements. We
// flatten the rect of the area elements to minimize the effect of overlapping
// areas.
LayoutRect rect = VirtualRectForDirection(
direction,
area.GetDocument().GetFrame()->View()->ConvertToRootFrame(
area.ComputeAbsoluteRect(area.ImageElement()->GetLayoutObject())),
LayoutUnit(1));
return rect;
}
HTMLFrameOwnerElement* FrameOwnerElement(FocusCandidate& candidate) {
return candidate.IsFrameOwnerElement()
? ToHTMLFrameOwnerElement(candidate.visible_node)
: nullptr;
};
LayoutRect FindSearchStartPoint(const LocalFrame* frame,
WebFocusType direction) {
LayoutRect starting_rect = VirtualRectForDirection(
direction,
frame->View()->ConvertToRootFrame(frame->View()->DocumentToFrame(
LayoutRect(frame->View()->LayoutViewport()->VisibleContentRect()))));
const Element* focused_element = frame->GetDocument()->FocusedElement();
if (focused_element) {
auto* area_element = ToHTMLAreaElementOrNull(focused_element);
if (area_element)
focused_element = area_element->ImageElement();
if (!HasOffscreenRect(focused_element)) {
starting_rect = area_element ? VirtualRectForAreaElementAndDirection(
*area_element, direction)
: NodeRectInRootFrame(focused_element, true);
}
}
return starting_rect;
}
} // namespace blink