summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/page/spatial_navigation.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/blink/renderer/core/page/spatial_navigation.cc')
-rw-r--r--chromium/third_party/blink/renderer/core/page/spatial_navigation.cc178
1 files changed, 111 insertions, 67 deletions
diff --git a/chromium/third_party/blink/renderer/core/page/spatial_navigation.cc b/chromium/third_party/blink/renderer/core/page/spatial_navigation.cc
index 08bde9ae003..75878ac795a 100644
--- a/chromium/third_party/blink/renderer/core/page/spatial_navigation.cc
+++ b/chromium/third_party/blink/renderer/core/page/spatial_navigation.cc
@@ -56,7 +56,7 @@ constexpr double kMinDistance = std::numeric_limits<int>::lowest();
constexpr int kFudgeFactor = 2;
-static void DeflateIfOverlapped(LayoutRect&, LayoutRect&);
+static void DeflateIfOverlapped(PhysicalRect&, PhysicalRect&);
FocusCandidate::FocusCandidate(Node* node, SpatialNavigationDirection direction)
: visible_node(nullptr), focusable_node(nullptr), is_offscreen(true) {
@@ -88,15 +88,15 @@ bool IsSpatialNavigationEnabled(const LocalFrame* frame) {
}
static bool RectsIntersectOnOrthogonalAxis(SpatialNavigationDirection direction,
- const LayoutRect& a,
- const LayoutRect& b) {
+ const PhysicalRect& a,
+ const PhysicalRect& b) {
switch (direction) {
case SpatialNavigationDirection::kLeft:
case SpatialNavigationDirection::kRight:
- return a.MaxY() > b.Y() && a.Y() < b.MaxY();
+ return a.Bottom() > b.Y() && a.Y() < b.Bottom();
case SpatialNavigationDirection::kUp:
case SpatialNavigationDirection::kDown:
- return a.MaxX() > b.X() && a.X() < b.MaxX();
+ return a.Right() > b.X() && a.X() < b.Right();
default:
NOTREACHED();
return false;
@@ -106,22 +106,22 @@ static bool RectsIntersectOnOrthogonalAxis(SpatialNavigationDirection direction,
// 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());
+static inline bool Below(const PhysicalRect& a, const PhysicalRect& b) {
+ return a.Y() >= b.Bottom() || (a.Y() >= b.Y() && a.Bottom() > b.Bottom() &&
+ a.X() < b.Right() && a.Right() > 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 inline bool RightOf(const PhysicalRect& a, const PhysicalRect& b) {
+ return a.X() >= b.Right() || (a.X() >= b.X() && a.Right() > b.Right() &&
+ a.Y() < b.Bottom() && a.Bottom() > b.Y());
}
static bool IsRectInDirection(SpatialNavigationDirection direction,
- const LayoutRect& cur_rect,
- const LayoutRect& target_rect) {
+ const PhysicalRect& cur_rect,
+ const PhysicalRect& target_rect) {
switch (direction) {
case SpatialNavigationDirection::kLeft:
return RightOf(cur_rect, target_rect);
@@ -148,7 +148,7 @@ FloatRect RectInViewport(const Node& node) {
if (!object)
return FloatRect();
- LayoutRect rect_in_root_frame = NodeRectInRootFrame(&node);
+ PhysicalRect rect_in_root_frame = NodeRectInRootFrame(&node);
// Convert to the visual viewport which will account for pinch zoom.
VisualViewport& visual_viewport =
@@ -207,9 +207,9 @@ bool IsUnobscured(const FocusCandidate& candidate) {
if (candidate.visible_node->IsMediaElement())
return true;
- LayoutRect viewport_rect = LayoutRect(
+ PhysicalRect viewport_rect(
local_main_frame->GetPage()->GetVisualViewport().VisibleContentRect());
- LayoutRect interesting_rect =
+ PhysicalRect interesting_rect =
Intersection(candidate.rect_in_root_frame, viewport_rect);
if (interesting_rect.IsEmpty())
@@ -296,7 +296,7 @@ bool ScrollInDirection(Node* container, SpatialNavigationDirection direction) {
return true;
}
-static void DeflateIfOverlapped(LayoutRect& a, LayoutRect& b) {
+static void DeflateIfOverlapped(PhysicalRect& a, PhysicalRect& b) {
if (!a.Intersects(b) || a.Contains(b) || b.Contains(a))
return;
@@ -407,7 +407,7 @@ bool CanScrollInDirection(const LocalFrame* frame,
ScrollableArea* scrollable_area = frame->View()->GetScrollableArea();
LayoutSize size(scrollable_area->ContentsSize());
LayoutSize offset(scrollable_area->ScrollOffsetInt());
- LayoutRect rect(scrollable_area->VisibleContentRect(kIncludeScrollbars));
+ PhysicalRect rect(scrollable_area->VisibleContentRect(kIncludeScrollbars));
switch (direction) {
case SpatialNavigationDirection::kLeft:
@@ -424,7 +424,7 @@ bool CanScrollInDirection(const LocalFrame* frame,
}
}
-LayoutRect NodeRectInRootFrame(const Node* node) {
+PhysicalRect NodeRectInRootFrame(const Node* node) {
DCHECK(node);
DCHECK(node->GetLayoutObject());
DCHECK(!node->GetDocument().View()->NeedsLayout());
@@ -443,7 +443,7 @@ LayoutRect NodeRectInRootFrame(const Node* node) {
LayoutUnit(object->StyleRef().BorderLeftWidth()));
object->MapToVisualRectInAncestorSpace(/*ancestor=*/nullptr, rect);
- return rect.ToLayoutRect();
+ return rect;
}
// This method calculates the exitPoint from the startingRect and the entryPoint
@@ -451,38 +451,38 @@ LayoutRect NodeRectInRootFrame(const Node* node) {
// distance between the 2 rects. Takes care of overlapping rects, defining
// points so that the distance between them is zero where necessary.
void EntryAndExitPointsForDirection(SpatialNavigationDirection direction,
- const LayoutRect& starting_rect,
- const LayoutRect& potential_rect,
+ const PhysicalRect& starting_rect,
+ const PhysicalRect& potential_rect,
LayoutPoint& exit_point,
LayoutPoint& entry_point) {
switch (direction) {
case SpatialNavigationDirection::kLeft:
exit_point.SetX(starting_rect.X());
- if (potential_rect.MaxX() < starting_rect.X())
- entry_point.SetX(potential_rect.MaxX());
+ if (potential_rect.Right() < starting_rect.X())
+ entry_point.SetX(potential_rect.Right());
else
entry_point.SetX(starting_rect.X());
break;
case SpatialNavigationDirection::kUp:
exit_point.SetY(starting_rect.Y());
- if (potential_rect.MaxY() < starting_rect.Y())
- entry_point.SetY(potential_rect.MaxY());
+ if (potential_rect.Bottom() < starting_rect.Y())
+ entry_point.SetY(potential_rect.Bottom());
else
entry_point.SetY(starting_rect.Y());
break;
case SpatialNavigationDirection::kRight:
- exit_point.SetX(starting_rect.MaxX());
- if (potential_rect.X() > starting_rect.MaxX())
+ exit_point.SetX(starting_rect.Right());
+ if (potential_rect.X() > starting_rect.Right())
entry_point.SetX(potential_rect.X());
else
- entry_point.SetX(starting_rect.MaxX());
+ entry_point.SetX(starting_rect.Right());
break;
case SpatialNavigationDirection::kDown:
- exit_point.SetY(starting_rect.MaxY());
- if (potential_rect.Y() > starting_rect.MaxY())
+ exit_point.SetY(starting_rect.Bottom());
+ if (potential_rect.Y() > starting_rect.Bottom())
entry_point.SetY(potential_rect.Y());
else
- entry_point.SetY(starting_rect.MaxY());
+ entry_point.SetY(starting_rect.Bottom());
break;
default:
NOTREACHED();
@@ -493,16 +493,16 @@ void EntryAndExitPointsForDirection(SpatialNavigationDirection direction,
case SpatialNavigationDirection::kRight:
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());
+ if (potential_rect.Bottom() < starting_rect.Y())
+ entry_point.SetY(potential_rect.Bottom());
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())
+ exit_point.SetY(starting_rect.Bottom());
+ if (potential_rect.Y() > starting_rect.Bottom())
entry_point.SetY(potential_rect.Y());
else
- entry_point.SetY(starting_rect.MaxY());
+ entry_point.SetY(starting_rect.Bottom());
} else {
exit_point.SetY(max(starting_rect.Y(), potential_rect.Y()));
entry_point.SetY(exit_point.Y());
@@ -512,16 +512,16 @@ void EntryAndExitPointsForDirection(SpatialNavigationDirection direction,
case SpatialNavigationDirection::kDown:
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());
+ if (potential_rect.Right() < starting_rect.X())
+ entry_point.SetX(potential_rect.Right());
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())
+ exit_point.SetX(starting_rect.Right());
+ if (potential_rect.X() > starting_rect.Right())
entry_point.SetX(potential_rect.X());
else
- entry_point.SetX(starting_rect.MaxX());
+ entry_point.SetX(starting_rect.Right());
} else {
exit_point.SetX(max(starting_rect.X(), potential_rect.X()));
entry_point.SetX(exit_point.X());
@@ -532,13 +532,56 @@ void EntryAndExitPointsForDirection(SpatialNavigationDirection direction,
}
}
+double ProjectedOverlap(SpatialNavigationDirection direction,
+ PhysicalRect current,
+ PhysicalRect candidate) {
+ switch (direction) {
+ case SpatialNavigationDirection::kLeft:
+ case SpatialNavigationDirection::kRight:
+ current.SetWidth(LayoutUnit(1));
+ candidate.SetX(current.X());
+ current.Intersect(candidate);
+ return current.Height();
+ case SpatialNavigationDirection::kUp:
+ case SpatialNavigationDirection::kDown:
+ current.SetHeight(LayoutUnit(1));
+ candidate.SetY(current.Y());
+ current.Intersect(candidate);
+ return current.Width();
+ default:
+ NOTREACHED();
+ return kMaxDistance;
+ }
+}
+
+double Alignment(SpatialNavigationDirection direction,
+ PhysicalRect current,
+ PhysicalRect candidate) {
+ // The formula and constants for "alignment" are experimental and
+ // come from https://drafts.csswg.org/css-nav-1/#heuristics.
+ const int kAlignWeight = 5;
+
+ double projected_overlap = ProjectedOverlap(direction, current, candidate);
+ switch (direction) {
+ case SpatialNavigationDirection::kLeft:
+ case SpatialNavigationDirection::kRight:
+ return (kAlignWeight * projected_overlap) / current.Height();
+ case SpatialNavigationDirection::kUp:
+ case SpatialNavigationDirection::kDown:
+ return (kAlignWeight * projected_overlap) / current.Width();
+ default:
+ NOTREACHED();
+ return kMaxDistance;
+ }
+}
+
double ComputeDistanceDataForNode(SpatialNavigationDirection direction,
const FocusCandidate& current_interest,
const FocusCandidate& candidate) {
double distance = 0.0;
double overlap = 0.0;
- LayoutRect node_rect = candidate.rect_in_root_frame;
- LayoutRect current_rect = current_interest.rect_in_root_frame;
+ PhysicalRect node_rect = candidate.rect_in_root_frame;
+ PhysicalRect current_rect = current_interest.rect_in_root_frame;
if (node_rect.Contains(current_rect)) {
// When leaving an "insider", don't focus its underlaying container box.
// Go directly to the outside world. This avoids focus from being trapped
@@ -564,7 +607,7 @@ double ComputeDistanceDataForNode(SpatialNavigationDirection direction,
return kMaxDistance;
} else {
DeflateIfOverlapped(current_rect, node_rect);
- LayoutRect intersection_rect = Intersection(current_rect, node_rect);
+ PhysicalRect intersection_rect = Intersection(current_rect, node_rect);
overlap =
(intersection_rect.Width() * intersection_rect.Height()).ToDouble();
}
@@ -616,36 +659,37 @@ double ComputeDistanceDataForNode(SpatialNavigationDirection direction,
return kMaxDistance;
}
- // Distance calculation is based on http://www.w3.org/TR/WICD/#focus-handling
- return distance + navigation_axis_distance +
+ // Distance calculation is based on https://drafts.csswg.org/css-nav-1/.
+ return distance + navigation_axis_distance -
+ Alignment(direction, current_rect, node_rect) +
weighted_orthogonal_axis_distance - sqrt(overlap);
}
// Returns a thin rectangle that represents one of |box|'s edges.
// To not intersect elements that are positioned inside |box|, we add one
// LayoutUnit of margin that puts the returned slice "just outside" |box|.
-LayoutRect OppositeEdge(SpatialNavigationDirection side,
- const LayoutRect& box,
- LayoutUnit thickness) {
- LayoutRect thin_rect = box;
+PhysicalRect OppositeEdge(SpatialNavigationDirection side,
+ const PhysicalRect& box,
+ LayoutUnit thickness) {
+ PhysicalRect thin_rect = box;
switch (side) {
case SpatialNavigationDirection::kLeft:
- thin_rect.SetX(thin_rect.MaxX() - thickness);
+ thin_rect.SetX(thin_rect.Right() - thickness);
thin_rect.SetWidth(thickness);
- thin_rect.Move(1, 0);
+ thin_rect.offset.left += 1;
break;
case SpatialNavigationDirection::kRight:
thin_rect.SetWidth(thickness);
- thin_rect.Move(-1, 0);
+ thin_rect.offset.left -= 1;
break;
case SpatialNavigationDirection::kDown:
thin_rect.SetHeight(thickness);
- thin_rect.Move(0, -1);
+ thin_rect.offset.top -= 1;
break;
case SpatialNavigationDirection::kUp:
- thin_rect.SetY(thin_rect.MaxY() - thickness);
+ thin_rect.SetY(thin_rect.Bottom() - thickness);
thin_rect.SetHeight(thickness);
- thin_rect.Move(0, 1);
+ thin_rect.offset.top += 1;
break;
default:
NOTREACHED();
@@ -654,13 +698,13 @@ LayoutRect OppositeEdge(SpatialNavigationDirection side,
return thin_rect;
}
-LayoutRect StartEdgeForAreaElement(const HTMLAreaElement& area,
- SpatialNavigationDirection direction) {
+PhysicalRect StartEdgeForAreaElement(const HTMLAreaElement& area,
+ SpatialNavigationDirection 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 = OppositeEdge(
+ PhysicalRect rect = OppositeEdge(
direction,
area.GetDocument().GetFrame()->View()->ConvertToRootFrame(
area.ComputeAbsoluteRect(area.ImageElement()->GetLayoutObject())),
@@ -673,8 +717,8 @@ HTMLFrameOwnerElement* FrameOwnerElement(const FocusCandidate& candidate) {
}
// The visual viewport's rect (given in the root frame's coordinate space).
-LayoutRect RootViewport(const LocalFrame* current_frame) {
- return LayoutRect(
+PhysicalRect RootViewport(const LocalFrame* current_frame) {
+ return PhysicalRect::EnclosingRect(
current_frame->GetPage()->GetVisualViewport().VisibleRect());
}
@@ -685,9 +729,9 @@ LayoutRect RootViewport(const LocalFrame* current_frame) {
// scroller’s scroller ... all the way up until the root frame's document.
// The root frame's document is a good base case because it's, per definition,
// a visible scrollable area.
-LayoutRect SearchOrigin(const LayoutRect viewport_rect_of_root_frame,
- Node* focus_node,
- const SpatialNavigationDirection direction) {
+PhysicalRect SearchOrigin(const PhysicalRect& viewport_rect_of_root_frame,
+ Node* focus_node,
+ const SpatialNavigationDirection direction) {
if (!focus_node) {
// Search from one of the visual viewport's edges towards the navigated
// direction. For example, UP makes spatnav search upwards, starting at the
@@ -703,7 +747,7 @@ LayoutRect SearchOrigin(const LayoutRect viewport_rect_of_root_frame,
if (area_element)
return StartEdgeForAreaElement(*area_element, direction);
- LayoutRect box_in_root_frame = NodeRectInRootFrame(focus_node);
+ PhysicalRect box_in_root_frame = NodeRectInRootFrame(focus_node);
return Intersection(box_in_root_frame, viewport_rect_of_root_frame);
}
@@ -711,7 +755,7 @@ LayoutRect SearchOrigin(const LayoutRect viewport_rect_of_root_frame,
while (container) {
if (!IsOffscreen(container)) {
// The first scroller that encloses focus and is [partially] visible.
- LayoutRect box_in_root_frame = NodeRectInRootFrame(container);
+ PhysicalRect box_in_root_frame = NodeRectInRootFrame(container);
return OppositeEdge(direction, Intersection(box_in_root_frame,
viewport_rect_of_root_frame));
}